refactor: Consolidate and optimize Docker image architecture (#233)

## Overview

This PR consolidates and optimizes the Docker build system, reducing
redundancy and improving CI/CD performance. The changes eliminate
duplicate Dockerfiles, introduce a flexible build template, and optimize
release builds to reuse CI artifacts.

## Changes Summary

### 🐳 Docker Images Restructured

**Before:** 5 Dockerfiles with significant overlap
**After:** 4 focused images + 1 utility

#### Final Structure:

1. **`operator/Dockerfile`**  Updated
   - **Standard operator image** for CI and release builds
   - Minimal node image (accepts pre-built binaries)
   - GHCR: `ghcr.io/datahaven-xyz/datahaven/datahaven` (CI)
   - DockerHub: `datahavenxyz/datahaven` (releases)

2. **`docker/datahaven-build.Dockerfile`** (moved from
`operator/Dockerfile`)
   - Full source-to-binary build for manual releases
   - DockerHub: `datahavenxyz/datahaven:{label}`
   - Supports custom RUSTFLAGS and fast-runtime feature
   - Only used for manual workflow_dispatch builds

3. **`docker/datahaven-production.Dockerfile`** (kept)
   - Binary builder for CPU-specific releases
   - Used by build-prod-binary workflow template
   - Supports custom target-cpu flags

4. **`docker/datahaven-dev.Dockerfile`**  NEW (local dev only)
   - **FOR LOCAL DEVELOPMENT/TROUBLESHOOTING ONLY**
   - Includes debug tools: gdb, strace, vim, sudo
   - Extra dependencies: librocksdb-dev, curl
   - RUST_BACKTRACE enabled by default
   - **DO NOT USE for CI or production builds**

5. **`test/docker/crossbuild-mac-libpq.dockerfile`** (kept)
   - Utility for macOS → Linux cross-compilation

#### Removed (Redundant):
-  `docker/datahaven.Dockerfile` → replaced by operator/Dockerfile
-  `test/docker/datahaven-node-local.dockerfile` → replaced by
datahaven-dev.Dockerfile

---

### 🔄 Workflow Improvements

#### Enhanced `publish-docker` Template
- Supports both GHCR and DockerHub registries
- Flexible inputs: dockerfile, context, build-args, cache scope
- Auto-generates OCI-compliant labels
- Reduces code duplication (~70 lines → ~15 per workflow)

#### Refactored CI Pipeline
- **`docker-build-ci`**: Builds `operator/Dockerfile` → GHCR for CI/E2E
testing
- **`docker-build-release`**: Builds `operator/Dockerfile` → DockerHub
(main branch only)
- Both CI and release workflows now use the same minimal operator image
- Release builds **reuse CI binaries** instead of rebuilding from source

#### Optimized Release Workflow
The `task-docker-release` workflow now has dual modes:

**Mode 1: `workflow_call` (CI - main pushes)**
-  Reuses binary from CI's build-operator task
-  Uses lightweight `operator/Dockerfile`
-  Tags: `latest`, `sha-{short}`
-  **Fast**: ~5 minutes (vs ~30 min previously)

**Mode 2: `workflow_dispatch` (Manual)**
-  Full source build with `datahaven-build.Dockerfile`
-  Custom branch and label selection
-  Optional fast-runtime feature
-  Tags: `PROD-{label}` or user-defined

---

### 🔧 Additional Optimizations

- Copy libpq5 from builder stage instead of reinstalling (smaller,
faster)
- Remove redundant protobuf-compiler package (use protoc v21.12
directly)
- Standardize user UID to 1000 across all runtime images
- Consistent OCI labeling and metadata

---------

Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
Steve Degosserie 2025-10-15 01:33:20 +02:00 committed by GitHub
parent 8874a99100
commit 9a5404de82
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
13 changed files with 528 additions and 387 deletions

View file

@ -1,69 +1,125 @@
name: Publish docker image
description: |
Publish docker image tags to dockerhub
Publish docker image tags to container registry
inputs:
dockerhub_username:
description: "Dockerhub username"
dockerfile:
description: "Path to Dockerfile"
required: true
dockerhub_password:
description: "Dockerhub password"
context:
description: "Build context path"
required: false
default: "."
registry:
description: "Container registry (ghcr.io or docker.io)"
required: false
default: "docker.io"
registry_username:
description: "Registry username"
required: true
registry_password:
description: "Registry password"
required: true
image_tags:
description: "Image tags"
description: "Image tags (newline or comma-separated)"
required: true
image_title:
description: "Image title"
required: true
required: false
default: "DataHaven Node"
image_description:
description: "Image description"
required: true
required: false
default: "DataHaven blockchain node"
image_url:
description: "Image url"
required: true
required: false
default: "https://github.com/datahaven-xyz/datahaven"
image_source:
description: "Image source"
required: true
required: false
default: "https://github.com/datahaven-xyz/datahaven"
image_created:
description: "Image creation timestamp"
required: true
required: false
default: ""
image_revision:
description: "Image revision"
required: true
description: "Image revision (git sha)"
required: false
default: ""
image_licenses:
description: "Image licenses"
required: true
required: false
default: "Apache-2.0"
cache_scope:
description: "Cache scope for GitHub Actions cache"
required: false
default: "docker-build"
build_args:
description: "Build arguments (newline or comma-separated)"
required: false
default: ""
platforms:
description: "Target platforms"
required: false
default: "linux/amd64"
runs:
using: "composite"
steps:
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3.8.0
uses: docker/setup-buildx-action@v3
with:
version: latest
driver-opts: |
image=moby/buildkit:master
- name: Login to DockerHub
- name: Login to Container Registry
uses: docker/login-action@v3
with:
username: ${{ inputs.dockerhub_username }}
password: ${{ inputs.dockerhub_password }}
- name: Build and push datahaven
registry: ${{ inputs.registry }}
username: ${{ inputs.registry_username }}
password: ${{ inputs.registry_password }}
- name: Prepare labels
id: labels
shell: bash
run: |
CREATED="${{ inputs.image_created }}"
if [ -z "$CREATED" ]; then
CREATED=$(date -u +'%Y-%m-%dT%H:%M:%SZ')
fi
REVISION="${{ inputs.image_revision }}"
if [ -z "$REVISION" ]; then
REVISION="${{ github.sha }}"
fi
echo "created=$CREATED" >> $GITHUB_OUTPUT
echo "revision=$REVISION" >> $GITHUB_OUTPUT
- name: Build and push image
id: docker_build
uses: docker/build-push-action@v6
with:
context: .
file: ./docker/datahaven.Dockerfile
platforms: linux/amd64
context: ${{ inputs.context }}
file: ${{ inputs.dockerfile }}
platforms: ${{ inputs.platforms }}
push: true
tags: ${{ inputs.image_tags }}
build-args: ${{ inputs.build_args }}
cache-from: type=gha,scope=${{ inputs.cache_scope }}
cache-to: type=gha,mode=max,scope=${{ inputs.cache_scope }}
provenance: mode=max
sbom: true
labels: |
org.opencontainers.image.title=${{ inputs.image_title }}
org.opencontainers.image.description=${{ inputs.image_title }}
org.opencontainers.image.description=${{ inputs.image_description }}
org.opencontainers.image.url=${{ inputs.image_url }}
org.opencontainers.image.source=${{ inputs.image_source }}
org.opencontainers.image.created=${{ inputs.image_created }}
org.opencontainers.image.revision=${{ inputs.image_revision }}
org.opencontainers.image.created=${{ steps.labels.outputs.created }}
org.opencontainers.image.revision=${{ steps.labels.outputs.revision }}
org.opencontainers.image.licenses=${{ inputs.image_licenses }}

View file

@ -41,10 +41,18 @@ jobs:
uses: ./.github/workflows/task-check-metadata.yml
with:
binary-hash: ${{ needs.build-operator.outputs.binary-hash }}
docker-build:
docker-build-ci:
needs: [build-operator]
uses: ./.github/workflows/task-docker.yml
uses: ./.github/workflows/task-docker-ci.yml
secrets: inherit
with:
binary-hash: ${{ needs.build-operator.outputs.binary-hash }}
docker-build-release:
needs: [build-operator]
if: github.ref == 'refs/heads/main'
uses: ./.github/workflows/task-docker-release.yml
secrets: inherit
with:
binary-hash: ${{ needs.build-operator.outputs.binary-hash }}
@ -57,8 +65,8 @@ jobs:
# Third Tier - E2E tests depend on docker build
e2e-tests:
needs: [docker-build]
needs: [docker-build-ci]
uses: ./.github/workflows/task-e2e.yml
secrets: inherit
with:
image-tag: ${{ needs.docker-build.outputs.image-tag }}
image-tag: ${{ needs.docker-build-ci.outputs.image-tag }}

View file

@ -1,164 +0,0 @@
name: Docker Build & Publish
on:
workflow_dispatch:
inputs:
label:
description: "Label for the Docker image"
required: true
type: string
branch:
description: "Branch to checkout and build"
required: true
type: string
fast_runtime:
description: "Enable fast runtime features"
required: false
type: boolean
default: false
push:
branches:
- main
jobs:
build-test-push:
runs-on: ubuntu-latest
outputs:
image-tag: ${{ steps.last_tag_extractor.outputs.last_tag_value }}
defaults:
run:
working-directory: operator
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
ref: ${{ github.event.inputs.branch || github.ref }}
- uses: ./.github/workflows/actions/cleanup-runner
- name: Docker meta (dispatch)
if: github.event_name == 'workflow_dispatch'
id: meta-dispatch
uses: docker/metadata-action@v5
with:
images: datahavenxyz/datahaven
flavor: |
latest=false
tags: |
type=raw,value=PROD-${{ github.event.inputs.label }}
- name: Docker meta (main push)
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
id: meta-main
uses: docker/metadata-action@v5
with:
images: datahavenxyz/datahaven
flavor: |
latest=true
tags: |
type=raw,value=latest
- name: Extract last tag for job output
id: last_tag_extractor
run: |
if [ "${{ github.event_name }}" == "workflow_dispatch" ]; then
echo "last_tag_value=$(echo '${{ steps.meta-dispatch.outputs.json }}' | jq -r '.tags[-1]')" >> $GITHUB_OUTPUT
else
echo "last_tag_value=$(echo '${{ steps.meta-main.outputs.json }}' | jq -r '.tags[-1]')" >> $GITHUB_OUTPUT
fi
- name: Log Docker Metadata
run: |
if [ "${{ github.event_name }}" == "workflow_dispatch" ]; then
echo "Generated tags: ${{ steps.meta-dispatch.outputs.tags }}"
echo "Generated labels: ${{ steps.meta-dispatch.outputs.labels }}"
echo "Generated JSON: ${{ steps.meta-dispatch.outputs.json }}"
else
echo "Generated tags: ${{ steps.meta-main.outputs.tags }}"
echo "Generated labels: ${{ steps.meta-main.outputs.labels }}"
echo "Generated JSON: ${{ steps.meta-main.outputs.json }}"
fi
- uses: docker/setup-qemu-action@v3
- uses: docker/setup-buildx-action@v3
- uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Cache Mount blobs
uses: actions/cache@v4
id: cache
with:
path: |
**/cargo-registry
**/cargo-git
key: cache-mount-${{ hashFiles('./operator/Dockerfile') }}-${{ hashFiles('./operator/Cargo.lock') }}-${{hashFiles('./operator/runtime/**/*.rs','./operator/pallets/**/*.rs', './operator/node/**/*.rs')}}
restore-keys: |
cache-mount-${{ hashFiles('./operator/Dockerfile') }}-${{ hashFiles('./operator/Cargo.lock') }}
cache-mount-${{ hashFiles('./operator/Dockerfile') }}
cache-mount-
- name: Inject cache into docker
uses: reproducible-containers/buildkit-cache-dance@v3.1.0
with:
cache-map: |
{
"cargo-registry": { "target": "/usr/local/cargo/registry" },
"cargo-git": { "target": "/usr/local/cargo/git" }
}
skip-extraction: ${{ steps.cache.outputs.cache-hit }}
- name: Build and push Docker image
id: build
uses: docker/build-push-action@v5
timeout-minutes: 240 # 4 hours
with:
context: ./operator
file: ./operator/Dockerfile
push: true
tags: ${{ github.event_name == 'workflow_dispatch' && steps.meta-dispatch.outputs.tags || steps.meta-main.outputs.tags }}
labels: ${{ github.event_name == 'workflow_dispatch' && steps.meta-dispatch.outputs.labels || steps.meta-main.outputs.labels }}
platforms: linux/amd64
build-args: |
FAST_RUNTIME=${{ github.event_name == 'workflow_dispatch' && github.event.inputs.fast_runtime == 'true' && 'TRUE' || 'FALSE' }}
cache-from: type=gha,scope=datahaven-build
cache-to: type=gha,mode=max,scope=datahaven-build
provenance: mode=max
sbom: true
- name: Log build cache statistics
run: |
echo "Build cache statistics:"
docker buildx du --verbose
# --- Smoke tests ---
- name: Pull and test node --help
run: |
docker pull ${{ steps.last_tag_extractor.outputs.last_tag_value }}
docker run --rm ${{ steps.last_tag_extractor.outputs.last_tag_value }} --help
- name: Integration test (dev chain starts)
run: |
docker run --rm -d -p 9944:9944 --name local-dh-node \
${{ steps.last_tag_extractor.outputs.last_tag_value }} --dev --unsafe-rpc-external
- name: Wait for node to be healthy and test
run: |
echo "Waiting for node to start..."
for i in {1..30}; do # Retry for 30 * 5s = 150 seconds
if curl --fail --location 'http://127.0.0.1:9944' \
--header 'Content-Type: application/json' \
--data '{"jsonrpc":"2.0","id":1,"method":"system_chain","params":[]}' ; then
echo "Node is healthy!"
docker logs local-dh-node --tail 100
exit 0
fi
echo "Attempt $i: Node not ready yet, sleeping 5s..."
sleep 5
done
echo "Node failed to start or respond in time."
docker logs local-dh-node --tail 100
exit 1
- name: Cleanup integration test container
if: always()
run: docker rm -f local-dh-node

View file

@ -49,9 +49,9 @@ jobs:
- name: Prepare binary
run: |
mkdir -p ./target/ci
mkdir -p ./target/x86_64-unknown-linux-gnu/release
mkdir -p ../build
cp ./target/release/datahaven-node ./target/ci/datahaven-node
cp ./target/release/datahaven-node ./target/x86_64-unknown-linux-gnu/release/datahaven-node
cp ./target/release/datahaven-node ../build/datahaven-node
- name: Hash binary
id: hash-binary
@ -66,7 +66,7 @@ jobs:
uses: actions/upload-artifact@v4
with:
name: datahaven-node-${{ steps.hash-binary.outputs.datahaven-node-hash }}
path: operator/target/x86_64-unknown-linux-gnu/release/datahaven-node
path: build/datahaven-node
retention-days: 1
- name: Upload WASM to workflow

View file

@ -1,4 +1,4 @@
name: Docker Build & Publish
name: Docker Build & Publish (CI)
on:
workflow_dispatch:
@ -23,28 +23,29 @@ permissions:
packages: write
concurrency:
group: docker-build-${{ github.ref }}
group: docker-build-ci-${{ github.ref }}
cancel-in-progress: true
jobs:
build-test-push:
runs-on: ubuntu-latest
outputs:
image-tag: ${{ steps.last_tag_extractor.outputs.image-tag }}
image-tag: ${{ steps.extract_tag.outputs.image-tag }}
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Download binary artifact
uses: actions/download-artifact@v4
with:
name: datahaven-node-${{ inputs.binary-hash }}
path: ./operator/target/x86_64-unknown-linux-gnu/release/
path: ./build/
- name: Prepare binary
run: |
chmod +x ./operator/target/x86_64-unknown-linux-gnu/release/datahaven-node
ls -la ./operator/target/x86_64-unknown-linux-gnu/release/
chmod +x ./build/datahaven-node
ls -la ./build/
- name: Docker meta
id: meta
@ -61,67 +62,37 @@ jobs:
type=ref,event=pr
- name: Extract tag for job output
id: last_tag_extractor
id: extract_tag
run: |
FULL_TAG=$(echo '${{ steps.meta.outputs.json }}' | jq -r '.tags[-1]')
TAG_ONLY=$(echo "$FULL_TAG" | sed 's|.*:||')
echo "image-tag=$TAG_ONLY" >> $GITHUB_OUTPUT
echo "image-name=ghcr.io/datahaven-xyz/datahaven/datahaven:$TAG_ONLY" >> $GITHUB_OUTPUT
- name: Log Docker Metadata
run: |
echo "Generated tags: ${{ steps.meta.outputs.tags }}"
echo "Generated labels: ${{ steps.meta.outputs.labels }}"
echo "Generated JSON: ${{ steps.meta.outputs.json }}"
- uses: docker/setup-qemu-action@v3
- uses: docker/setup-buildx-action@v3
with:
driver-opts: |
image=moby/buildkit:master
network=host
buildkitd-flags: |
--allow-insecure-entitlement network.host
--allow-insecure-entitlement security.insecure
- name: Log in to GHCR
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build and push Docker image
id: build
uses: docker/build-push-action@v5
uses: ./.github/workflow-templates/publish-docker
with:
dockerfile: ./operator/Dockerfile
context: .
file: ./test/docker/datahaven-node-local.dockerfile
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
platforms: linux/amd64
cache-from: type=gha,scope=datahaven-local-build
cache-to: type=gha,mode=max,scope=datahaven-local-build
provenance: mode=max
sbom: true
- name: Log build cache statistics
run: |
echo "Build cache statistics:"
docker buildx du --verbose
registry: ghcr.io
registry_username: ${{ github.actor }}
registry_password: ${{ secrets.GITHUB_TOKEN }}
image_tags: ${{ steps.meta.outputs.tags }}
image_title: "DataHaven Node - CI"
image_description: "CI build of DataHaven operator node"
cache_scope: datahaven-ci-build
# --- Smoke tests ---
- name: Pull and test node --help
run: |
docker pull ${{ steps.last_tag_extractor.outputs.image-name }}
docker run --rm ${{ steps.last_tag_extractor.outputs.image-name }} --help
docker pull ${{ steps.extract_tag.outputs.image-name }}
docker run --rm ${{ steps.extract_tag.outputs.image-name }} --help
- name: Integration test (dev chain starts)
run: |
docker run --rm -d -p 9944:9944 --name local-dh-node \
${{ steps.last_tag_extractor.outputs.image-name }} --dev --unsafe-rpc-external
${{ steps.extract_tag.outputs.image-name }} --dev --unsafe-rpc-external
- name: Wait for node to be healthy and test
run: |
@ -143,4 +114,4 @@ jobs:
- name: Cleanup integration test container
if: always()
run: docker rm -f local-dh-node
run: docker rm -f local-dh-node

View file

@ -0,0 +1,190 @@
name: Docker Build & Publish (Release)
on:
workflow_dispatch:
inputs:
label:
description: "Label for the Docker image"
required: true
type: string
branch:
description: "Branch to checkout and build"
required: true
type: string
fast_runtime:
description: "Enable fast runtime features"
required: false
type: boolean
default: false
workflow_call:
inputs:
binary-hash:
description: "The hash of the operator binary (for CI builds)"
required: true
type: string
outputs:
image-tag:
description: "The tag portion of the docker image (without registry)"
value: "${{ jobs.build-test-push.outputs.image-tag }}"
permissions:
contents: read
packages: write
concurrency:
group: docker-build-release-${{ github.ref }}
cancel-in-progress: true
jobs:
build-test-push:
runs-on: ubuntu-latest
outputs:
image-tag: ${{ steps.extract_tag.outputs.image-tag }}
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
ref: ${{ github.event.inputs.branch || github.ref }}
- uses: ./.github/workflows/actions/cleanup-runner
if: github.event_name == 'workflow_dispatch'
# --- Conditional: Download binary for CI builds ---
- name: Download binary artifact (CI build)
if: github.event_name == 'workflow_call'
uses: actions/download-artifact@v4
with:
name: datahaven-node-${{ inputs.binary-hash }}
path: ./build/
- name: Prepare binary (CI build)
if: github.event_name == 'workflow_call'
run: |
chmod +x ./build/datahaven-node
ls -la ./build/
# --- Docker metadata ---
- name: Docker meta (dispatch)
if: github.event_name == 'workflow_dispatch'
id: meta-dispatch
uses: docker/metadata-action@v5
with:
images: datahavenxyz/datahaven
flavor: |
latest=false
tags: |
type=raw,value=${{ github.event.inputs.label }}
- name: Docker meta (CI - main push)
if: github.event_name == 'workflow_call'
id: meta-ci
uses: docker/metadata-action@v5
with:
images: datahavenxyz/datahaven
flavor: |
latest=true
tags: |
type=raw,value=latest
type=sha,format=short,prefix=sha-
- name: Extract tag for job output
id: extract_tag
run: |
if [ "${{ github.event_name }}" == "workflow_dispatch" ]; then
TAG=$(echo '${{ steps.meta-dispatch.outputs.json }}' | jq -r '.tags[-1]')
else
TAG=$(echo '${{ steps.meta-ci.outputs.json }}' | jq -r '.tags[-1]')
fi
echo "image-tag=$TAG" >> $GITHUB_OUTPUT
# --- Conditional: Cargo cache for full builds ---
- name: Set up cargo cache (full build)
if: github.event_name == 'workflow_dispatch'
uses: actions/cache@v4
id: cache
with:
path: |
**/cargo-registry
**/cargo-git
key: cache-mount-${{ hashFiles('./docker/datahaven-build.Dockerfile') }}-${{ hashFiles('./operator/Cargo.lock') }}-${{hashFiles('./operator/runtime/**/*.rs','./operator/pallets/**/*.rs', './operator/node/**/*.rs')}}
restore-keys: |
cache-mount-${{ hashFiles('./docker/datahaven-build.Dockerfile') }}-${{ hashFiles('./operator/Cargo.lock') }}
cache-mount-${{ hashFiles('./docker/datahaven-build.Dockerfile') }}
cache-mount-
- name: Inject cache into docker (full build)
if: github.event_name == 'workflow_dispatch'
uses: reproducible-containers/buildkit-cache-dance@v3.1.0
with:
cache-map: |
{
"cargo-registry": { "target": "/usr/local/cargo/registry" },
"cargo-git": { "target": "/usr/local/cargo/git" }
}
skip-extraction: ${{ steps.cache.outputs.cache-hit }}
# --- Build and push: Full build (workflow_dispatch) ---
- name: Build and push Docker image (full build)
if: github.event_name == 'workflow_dispatch'
uses: ./.github/workflow-templates/publish-docker
with:
dockerfile: ./docker/datahaven-build.Dockerfile
context: ./operator
registry: docker.io
registry_username: ${{ secrets.DOCKERHUB_USERNAME }}
registry_password: ${{ secrets.DOCKERHUB_TOKEN }}
image_tags: ${{ steps.meta-dispatch.outputs.tags }}
image_title: "DataHaven Node - Release"
image_description: "Release build of DataHaven blockchain node"
cache_scope: datahaven-release-build
build_args: |
FAST_RUNTIME=${{ github.event.inputs.fast_runtime == 'true' && 'TRUE' || 'FALSE' }}
# --- Build and push: CI binary reuse (workflow_call) ---
- name: Build and push Docker image (CI binary)
if: github.event_name == 'workflow_call'
uses: ./.github/workflow-templates/publish-docker
with:
dockerfile: ./operator/Dockerfile
context: .
registry: docker.io
registry_username: ${{ secrets.DOCKERHUB_USERNAME }}
registry_password: ${{ secrets.DOCKERHUB_TOKEN }}
image_tags: ${{ steps.meta-ci.outputs.tags }}
image_title: "DataHaven Node - Release"
image_description: "Release build of DataHaven operator node"
cache_scope: datahaven-release-ci
# --- Smoke tests ---
- name: Pull and test node --help
run: |
docker pull ${{ steps.extract_tag.outputs.image-tag }}
docker run --rm ${{ steps.extract_tag.outputs.image-tag }} --help
- name: Integration test (dev chain starts)
run: |
docker run --rm -d -p 9944:9944 --name local-dh-node \
${{ steps.extract_tag.outputs.image-tag }} --dev --unsafe-rpc-external
- name: Wait for node to be healthy and test
run: |
echo "Waiting for node to start..."
for i in {1..30}; do # Retry for 30 * 5s = 150 seconds
if curl --fail --location 'http://127.0.0.1:9944' \
--header 'Content-Type: application/json' \
--data '{"jsonrpc":"2.0","id":1,"method":"system_chain","params":[]}' ; then
echo "Node is healthy!"
docker logs local-dh-node --tail 100
exit 0
fi
echo "Attempt $i: Node not ready yet, sleeping 5s..."
sleep 5
done
echo "Node failed to start or respond in time."
docker logs local-dh-node --tail 100
exit 1
- name: Cleanup integration test container
if: always()
run: docker rm -f local-dh-node

View file

@ -0,0 +1,75 @@
# --- Setup Build Environment ---
FROM docker.io/paritytech/ci-unified:bullseye-1.88.0 AS base
ARG MOLD_VERSION=2.40.4
ARG PROTOC_VER=21.12
ARG SCCACHE_VERSION=0.10.0
ARG FAST_RUNTIME=FALSE
RUN apt-get update && apt-get install -y --no-install-recommends \
curl \
xz-utils \
clang \
libpq-dev \
&& echo "Installing mold v${MOLD_VERSION}..." \
&& curl -Lo mold.tar.gz "https://github.com/rui314/mold/releases/download/v${MOLD_VERSION}/mold-${MOLD_VERSION}-x86_64-linux.tar.gz" \
&& tar -xf mold.tar.gz --strip-components=1 -C /usr/local \
&& rm mold.tar.gz \
&& apt-get clean \
&& rm -rf /var/lib/apt/lists/* \
&& echo "Installing protoc v${PROTOC_VER}..." \
&& curl -Lo protoc.zip "https://github.com/protocolbuffers/protobuf/releases/download/v${PROTOC_VER}/protoc-${PROTOC_VER}-linux-x86_64.zip" \
&& unzip -q protoc.zip -d /usr/local/ \
&& rm protoc.zip \
&& echo "Installing sccache v${SCCACHE_VERSION}..." \
&& curl -Lo sccache.tar.gz "https://github.com/mozilla/sccache/releases/download/v${SCCACHE_VERSION}/sccache-v${SCCACHE_VERSION}-x86_64-unknown-linux-musl.tar.gz" \
&& tar -xf sccache.tar.gz --strip-components=1 -C /usr/local/bin sccache-v${SCCACHE_VERSION}-x86_64-unknown-linux-musl/sccache \
&& rm sccache.tar.gz
RUN cargo install cargo-chef --version 0.1.72 --locked
ENV RUSTC_WRAPPER=sccache \
SCCACHE_DIR=/usr/local/sccache \
SCCACHE_CACHE_SIZE=25G \
RUSTFLAGS="-Clinker=clang -Clink-arg=-fuse-ld=/usr/local/bin/mold"
# --- Prepare build plan with cargo-chef ---
FROM base AS planner
WORKDIR /datahaven
COPY . .
RUN cargo chef prepare --recipe-path recipe.json
# --- Build dependencies using cargo-chef ---
FROM base AS builder
WORKDIR /datahaven
COPY --from=planner /datahaven/recipe.json recipe.json
RUN --mount=type=cache,target=/usr/local/cargo/registry \
--mount=type=cache,target=/usr/local/cargo/git \
cargo chef cook --recipe-path recipe.json --release
COPY . .
RUN --mount=type=cache,target=/usr/local/cargo/registry \
--mount=type=cache,target=/usr/local/cargo/git \
if [ "$FAST_RUNTIME" = "TRUE" ]; then \
cargo build --locked --release --features fast-runtime; \
else \
cargo build --locked --release; \
fi
# --- Create final lightweight runtime image ---
FROM docker.io/parity/base-bin:latest
COPY --from=builder /usr/lib/x86_64-linux-gnu/libpq.so* /usr/lib/x86_64-linux-gnu/
COPY --from=builder /datahaven/target/release/datahaven-node /usr/local/bin
USER root
RUN useradd -m -u 1001 -U -s /bin/sh -d /datahaven datahaven && \
mkdir -p /data /datahaven/.local/share && \
chown -R datahaven:datahaven /data && \
ln -s /data /datahaven/.local/share/datahaven && \
/usr/local/bin/datahaven-node --version
USER datahaven
EXPOSE 30333 9933 9944 9615
VOLUME ["/data"]
ENTRYPOINT ["/usr/local/bin/datahaven-node"]

View file

@ -0,0 +1,78 @@
# DataHaven Development/Troubleshooting Image
#
# This image is ONLY for local development and troubleshooting purposes.
# It includes additional debugging tools and dependencies not needed in production.
#
# DO NOT USE for CI or production builds - use operator/Dockerfile instead.
#
# Build Args:
# DEBUG_MODE - Set to "true" to include debugging tools (default: false)
#
# Expected Binary Location:
# ./operator/target/x86_64-unknown-linux-gnu/release/datahaven-node
#
# Features:
# - Ubuntu base with additional system tools
# - librocksdb-dev for local development
# - Optional gdb, strace, vim for debugging
# - RUST_BACKTRACE enabled by default
# - Additional directories (/specs, /storage) for testing
FROM ubuntu:noble
LABEL version="0.3.0"
LABEL description="DataHaven Node - Development/CI/E2E Testing Build"
LABEL maintainer="steve@moonsonglabs.com"
ARG DEBUG_MODE=false
# Install runtime dependencies
RUN apt-get update && \
DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
ca-certificates \
curl \
libpq-dev \
librocksdb-dev && \
# Optionally install debug tools
if [ "$DEBUG_MODE" = "true" ]; then \
apt-get install -y --no-install-recommends \
sudo \
gdb \
strace \
vim; \
fi && \
apt-get autoremove -y && \
apt-get clean && \
find /var/lib/apt/lists/ -type f -not -name lock -delete
# Create datahaven user and directories
RUN useradd -m -u 1000 -U -s /bin/sh -d /datahaven datahaven && \
mkdir -p /data /datahaven/.local/share /specs /storage && \
chown -R datahaven:datahaven /data /storage && \
ln -s /data /datahaven/.local/share/datahaven-node
# Grant sudo access if debug mode is enabled
RUN if [ "$DEBUG_MODE" = "true" ]; then \
echo "datahaven ALL=(ALL) NOPASSWD:ALL" >> /etc/sudoers && \
chmod -R 777 /storage /data; \
fi
USER datahaven
# Copy pre-built binary
COPY --chown=datahaven:datahaven ./operator/target/x86_64-unknown-linux-gnu/release/datahaven-node /usr/local/bin/datahaven-node
RUN chmod uog+x /usr/local/bin/datahaven-node
# Enable Rust backtraces for better debugging
ENV RUST_BACKTRACE=1
# Expose ports
# 30333: p2p networking
# 9944: WebSocket/RPC
# 9615: Prometheus metrics
EXPOSE 30333 9944 9615
VOLUME ["/data"]
ENTRYPOINT ["datahaven-node"]
CMD ["--tmp"]

View file

@ -1,4 +1,4 @@
# Production Node for DataHaven
# Production Image for DataHaven
#
# Requires to run from repository root and to copy the binary in the build folder (part of the release workflow)
@ -15,7 +15,7 @@ WORKDIR /
RUN echo "*** Installing Basic dependencies ***"
RUN apt-get update && apt-get install -y ca-certificates && update-ca-certificates
RUN apt install --assume-yes git clang curl libpq-dev libssl-dev llvm libudev-dev make protobuf-compiler pkg-config unzip
RUN apt install --assume-yes git clang curl libpq-dev libssl-dev llvm libudev-dev make pkg-config unzip
RUN echo "*** Installing protoc v${PROTOC_VER} ***"
RUN curl -Lo /tmp/protoc.zip "https://github.com/protocolbuffers/protobuf/releases/download/v${PROTOC_VER}/protoc-${PROTOC_VER}-linux-x86_64.zip" \

View file

@ -1,34 +0,0 @@
# DataHaven Binary
#
# Requires to run from repository root and to copy the binary in the build folder (part of the release workflow)
FROM debian:stable AS builder
RUN apt-get update && apt-get install -y libpq5 ca-certificates && update-ca-certificates
FROM debian:stable-slim
LABEL maintainer="steve@moonsonglabs.com"
LABEL description="DataHaven Binary"
RUN useradd -m -u 1000 -U -s /bin/sh -d /datahaven datahaven && \
mkdir -p /datahaven/.local/share && \
mkdir /data && \
chown -R datahaven:datahaven /data && \
ln -s /data /datahaven/.local/share/datahaven && \
rm -rf /usr/sbin
COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt
USER datahaven
COPY --chown=datahaven build/* /datahaven
RUN chmod uog+x /datahaven/datahaven*
# 30333 for parachain p2p
# 9944 for Websocket & RPC call
# 9615 for Prometheus (metrics)
EXPOSE 30333 9944 9615
VOLUME ["/data"]
ENTRYPOINT ["/datahaven/datahaven-node"]

View file

@ -1,76 +1,77 @@
# --- Setup Build Environment ---
FROM docker.io/paritytech/ci-unified:bullseye-1.88.0 AS base
# DataHaven Operator Image
#
# This is the standard operator image used for CI and release builds.
# It's a minimal image that accepts a pre-built binary.
#
# Usage:
# - CI builds: Binary from build-operator workflow artifact
# - Release builds: Binary from build-operator workflow artifact
# - Local builds: Binary from local cargo build
#
# Expected Binary Location:
# build/datahaven-node
#
# Registries:
# - GHCR: ghcr.io/datahaven-xyz/datahaven/datahaven (CI)
# - DockerHub: datahavenxyz/datahaven (releases)
ARG MOLD_VERSION=2.40.4
ARG PROTOC_VER=21.12
ARG SCCACHE_VERSION=0.10.0
ARG FAST_RUNTIME=FALSE
RUN apt-get update && apt-get install -y --no-install-recommends \
curl \
xz-utils \
clang \
libpq-dev \
&& echo "Installing mold v${MOLD_VERSION}..." \
&& curl -Lo mold.tar.gz "https://github.com/rui314/mold/releases/download/v${MOLD_VERSION}/mold-${MOLD_VERSION}-x86_64-linux.tar.gz" \
&& tar -xf mold.tar.gz --strip-components=1 -C /usr/local \
&& rm mold.tar.gz \
&& apt-get clean \
&& rm -rf /var/lib/apt/lists/* \
&& echo "Installing protoc v${PROTOC_VER}..." \
&& curl -Lo protoc.zip "https://github.com/protocolbuffers/protobuf/releases/download/v${PROTOC_VER}/protoc-${PROTOC_VER}-linux-x86_64.zip" \
&& unzip -q protoc.zip -d /usr/local/ \
&& rm protoc.zip \
&& echo "Installing sccache v${SCCACHE_VERSION}..." \
&& curl -Lo sccache.tar.gz "https://github.com/mozilla/sccache/releases/download/v${SCCACHE_VERSION}/sccache-v${SCCACHE_VERSION}-x86_64-unknown-linux-musl.tar.gz" \
&& tar -xf sccache.tar.gz --strip-components=1 -C /usr/local/bin sccache-v${SCCACHE_VERSION}-x86_64-unknown-linux-musl/sccache \
&& rm sccache.tar.gz
FROM debian:stable AS builder
RUN cargo install cargo-chef --version 0.1.72 --locked
# Install CA certificates and libpq5 for the release build
RUN apt-get update && \
apt-get install -y --no-install-recommends \
libpq5 \
ca-certificates && \
update-ca-certificates && \
apt-get clean && \
rm -rf /var/lib/apt/lists/*
ENV RUSTC_WRAPPER=sccache \
SCCACHE_DIR=/usr/local/sccache \
SCCACHE_CACHE_SIZE=25G \
RUSTFLAGS="-Clinker=clang -Clink-arg=-fuse-ld=/usr/local/bin/mold"
FROM debian:stable-slim
# --- Prepare build plan with cargo-chef ---
FROM base AS planner
WORKDIR /datahaven
COPY . .
RUN cargo chef prepare --recipe-path recipe.json
LABEL version="0.3.0"
LABEL description="DataHaven Node - Release Build"
LABEL maintainer="steve@moonsonglabs.com"
# --- Build dependencies using cargo-chef ---
FROM base AS builder
WORKDIR /datahaven
COPY --from=planner /datahaven/recipe.json recipe.json
RUN --mount=type=cache,target=/usr/local/cargo/registry \
--mount=type=cache,target=/usr/local/cargo/git \
cargo chef cook --recipe-path recipe.json --release
COPY . .
RUN --mount=type=cache,target=/usr/local/cargo/registry \
--mount=type=cache,target=/usr/local/cargo/git \
if [ "$FAST_RUNTIME" = "TRUE" ]; then \
cargo build --locked --release --features fast-runtime; \
else \
cargo build --locked --release; \
fi
# Copy CA certificates and shared libraries from builder
COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt
COPY --from=builder \
/lib/x86_64-linux-gnu/libpq.so.5 \
/lib/x86_64-linux-gnu/libssl.so.3 \
/lib/x86_64-linux-gnu/libcrypto.so.3 \
/lib/x86_64-linux-gnu/libgssapi_krb5.so.2 \
/lib/x86_64-linux-gnu/libldap.so.2 \
/lib/x86_64-linux-gnu/libz.so.1 \
/lib/x86_64-linux-gnu/libzstd.so.1 \
/lib/x86_64-linux-gnu/libkrb5.so.3 \
/lib/x86_64-linux-gnu/libk5crypto.so.3 \
/lib/x86_64-linux-gnu/libcom_err.so.2 \
/lib/x86_64-linux-gnu/libkrb5support.so.0 \
/lib/x86_64-linux-gnu/liblber.so.2 \
/lib/x86_64-linux-gnu/libsasl2.so.2 \
/lib/x86_64-linux-gnu/libkeyutils.so.1 \
/lib/x86_64-linux-gnu/
# --- Create final lightweight runtime image ---
FROM docker.io/parity/base-bin:latest
RUN apt-get update && apt-get install -y libpq5
COPY --from=builder /datahaven/target/release/datahaven-node /usr/local/bin
USER root
RUN useradd -m -u 1001 -U -s /bin/sh -d /datahaven datahaven && \
mkdir -p /data /datahaven/.local/share && \
# Create datahaven user and directories
RUN useradd -m -u 1000 -U -s /bin/sh -d /datahaven datahaven && \
mkdir -p /datahaven/.local/share /data && \
chown -R datahaven:datahaven /data && \
ln -s /data /datahaven/.local/share/datahaven && \
/usr/local/bin/datahaven-node --version
rm -rf /usr/sbin
USER datahaven
EXPOSE 30333 9933 9944 9615
# Copy pre-built binary
COPY --chown=datahaven:datahaven build/* /datahaven
# Make binary executable
RUN chmod uog+x /datahaven/datahaven*
# Expose ports
# 30333: p2p networking
# 9944: WebSocket/RPC
# 9615: Prometheus metrics
EXPOSE 30333 9944 9615
VOLUME ["/data"]
ENTRYPOINT ["/usr/local/bin/datahaven-node"]
ENTRYPOINT ["/datahaven/datahaven-node"]

View file

@ -1,40 +0,0 @@
# DATAHAVEN_NODE DOCKERFILE
#
# This Dockerfile expects to have the binary already built.
# So it just copies the binary into the image and runs it.
#
# This is done to speed up iterating while running the E2E CLI.
#
# Requires to run from /test folder and to copy the binary in the build folder
FROM ubuntu:noble
LABEL version="0.1.0"
LABEL description="DataHaven Node Local Build"
ENV RUST_BACKTRACE=1
RUN apt-get update && \
DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
ca-certificates curl sudo librocksdb-dev libpq-dev && \
apt-get autoremove -y && \
apt-get clean && \
find /var/lib/apt/lists/ -type f -not -name lock -delete && \
useradd -m -u 1337 -U -s /bin/sh -d /datahaven datahaven && \
mkdir -p /data /datahaven/.local/share /specs /storage && \
chown -R datahaven:datahaven /data && \
ln -s /data /datahaven/.local/share/datahaven-node && \
echo "datahaven ALL=(ALL) NOPASSWD:ALL" >> /etc/sudoers && \
chmod -R 777 /storage /data
USER datahaven
COPY --chown=datahaven:datahaven ./operator/target/x86_64-unknown-linux-gnu/release/datahaven-node /usr/local/bin/datahaven-node
RUN chmod uog+x /usr/local/bin/datahaven-node
EXPOSE 9333 9944 30333 30334 9615
VOLUME ["/data"]
ENTRYPOINT ["datahaven-node"]
CMD ["--tmp"]

View file

@ -7,7 +7,7 @@
"cli": "bun run cli/index.ts",
"fmt": "biome check .",
"fmt:fix": "biome check --write .",
"build:docker:operator": "docker build --no-cache --platform linux/amd64 -t datahavenxyz/datahaven:local -f ./docker/datahaven-node-local.dockerfile ../.",
"build:docker:operator": "docker build --no-cache --platform linux/amd64 -t datahavenxyz/datahaven:local -f ../docker/datahaven-dev.Dockerfile ../.",
"generate:wagmi": "wagmi generate",
"generate:snowbridge-cfgs": "bun -e \"import {generateSnowbridgeConfigs} from './scripts/gen-snowbridge-cfgs.ts'; await generateSnowbridgeConfigs()\"",
"generate:types": "(cd ../operator && cargo build --release) && bun x papi add --wasm \"../operator/target/release/wbuild/datahaven-stagenet-runtime/datahaven_stagenet_runtime.wasm\" datahaven",