Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 47 additions & 0 deletions .github/scripts/test-runtime-image.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
#!/usr/bin/env bash
set -euo pipefail

image_ref="${1:?usage: test-runtime-image.sh IMAGE BINARY}"
binary="${2:?usage: test-runtime-image.sh IMAGE BINARY}"

# Use the image's default user and isolated temporary files, never a node volume.
docker run --rm --network none --entrypoint /bin/bash "$image_ref" -euxc '
[[ "$(id -u)" == 999 && "$(id -g)" == 999 ]]
for command in bash sh cat touch tail rm mkdir mktemp; do
command -v "$command"
done
test -s /etc/ssl/certs/ca-certificates.crt
test_dir=$(mktemp -d)

# The init script must delete only the discovery key, including on a rerun.
mkdir "$test_dir/execution"
touch "$test_dir/execution/discovery-secret" "$test_dir/execution/keep"
for attempt in 1 2; do
bash -euc '\''rm -rf -- "$1/execution/discovery-secret"'\'' bash "$test_dir"
test ! -e "$test_dir/execution/discovery-secret"
test -f "$test_dir/execution/keep"
done

printf "normal\n" > "$test_dir/runmode"
runmode=$(cat "$test_dir/runmode")
[[ X${runmode} == Xnormal ]]
printf "prune\n" > "$test_dir/runmode"
[[ "$(tail -n 1 "$test_dir/runmode")" == prune ]]
runmode=$(cat "$test_dir/runmode")
if [[ X${runmode} != Xnormal ]]; then
if [[ X${runmode} == Xprune ]]; then
touch "$test_dir/prune.done"
fi
touch "$test_dir/done.done"
tail -f /dev/null &
child=$!
kill -TERM "$child"
status=0
wait "$child" || status=$?
[[ "$status" == 143 ]]
fi
test -f "$test_dir/prune.done"
test -f "$test_dir/done.done"
/bin/sh -eu -c '\''test "$(cat "$1/runmode")" = prune'\'' sh "$test_dir"
exec "/usr/local/bin/$1" --version
' bash "$binary"
52 changes: 38 additions & 14 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,24 +18,31 @@ permissions:
env:
AWS_REGION: us-east-1
REGISTRY: public.ecr.aws/b2h7a5c4/chaintable
IMAGE_NAME: arc-node
CHAINS: "arc"
DOCKERFILE: deployments/Dockerfile.execution
BAKE_TARGET: arc-execution

jobs:
build-artifacts:
name: Build ${{ matrix.arch }}
name: Build ${{ matrix.component }} ${{ matrix.arch }}
# Same-repository PRs publish review images. External forks cannot receive publishing credentials.
if: github.repository == 'Chaintable/arc-node' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository)
strategy:
fail-fast: false
matrix:
arch: [amd64, arm64]
component: [execution, consensus]
include:
- arch: amd64
runner: ubuntu-latest
- arch: arm64
runner: ubuntu-24.04-arm
- component: execution
image_name: arc-node
- component: consensus
image_name: arc-consensus
env:
IMAGE_NAME: ${{ matrix.image_name }}
DOCKERFILE: deployments/Dockerfile.${{ matrix.component }}
BAKE_TARGET: arc-${{ matrix.component }}
runs-on: ${{ matrix.runner }}
timeout-minutes: 180
permissions:
Expand Down Expand Up @@ -117,8 +124,8 @@ jobs:
${{ env.BAKE_TARGET }}.platform=linux/${{ matrix.arch }}
${{ env.BAKE_TARGET }}.tags=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.vars.outputs.revision }}-${{ matrix.arch }}
${{ env.BAKE_TARGET }}.output=type=registry
${{ env.BAKE_TARGET }}.cache-from=type=gha,scope=arc-execution-${{ matrix.arch }}
${{ env.BAKE_TARGET }}.cache-to=type=gha,mode=max,scope=arc-execution-${{ matrix.arch }}
${{ env.BAKE_TARGET }}.cache-from=type=gha,scope=arc-${{ matrix.component }}-${{ matrix.arch }}
${{ env.BAKE_TARGET }}.cache-to=type=gha,mode=max,scope=arc-${{ matrix.component }}-${{ matrix.arch }}
env:
BUILDX_NO_DEFAULT_ATTESTATIONS: 1
CI: "true"
Expand All @@ -140,10 +147,25 @@ jobs:
exit 1
fi

- name: Verify runtime shell and utilities
env:
REVISION: ${{ steps.vars.outputs.revision }}
COMPONENT: ${{ matrix.component }}
ARCH: ${{ matrix.arch }}
run: |
bash .github/scripts/test-runtime-image.sh \
"${REGISTRY}/${IMAGE_NAME}:${REVISION}-${ARCH}" "arc-node-${COMPONENT}"

merge-docker-manifest:
name: Merge multi-arch manifest
name: Merge ${{ matrix.image_name }} multi-arch manifest
runs-on: ubuntu-latest
needs: build-artifacts
strategy:
fail-fast: false
matrix:
image_name: [arc-node, arc-consensus]
env:
IMAGE_NAME: ${{ matrix.image_name }}
permissions:
contents: read
id-token: write
Expand Down Expand Up @@ -204,10 +226,12 @@ jobs:
"${REGISTRY}/${IMAGE_NAME}:${REVISION}-amd64" \
"${REGISTRY}/${IMAGE_NAME}:${REVISION}-arm64"
docker buildx imagetools inspect "${REGISTRY}/${IMAGE_NAME}:${REVISION}"
# Writer aliases reference the same multi-architecture manifest, without rebuilding.
for chain in ${CHAINS}; do
docker buildx imagetools create \
--tag "${REGISTRY}/${chain}-writer:${REVISION}" \
"${REGISTRY}/${IMAGE_NAME}:${REVISION}"
docker buildx imagetools inspect "${REGISTRY}/${chain}-writer:${REVISION}"
done
# Only the execution image has Writer aliases; never alias consensus as Writer.
if [[ "$IMAGE_NAME" == "arc-node" ]]; then
for chain in ${CHAINS}; do
docker buildx imagetools create \
--tag "${REGISTRY}/${chain}-writer:${REVISION}" \
"${REGISTRY}/${IMAGE_NAME}:${REVISION}"
docker buildx imagetools inspect "${REGISTRY}/${chain}-writer:${REVISION}"
done
fi
52 changes: 38 additions & 14 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,24 +15,31 @@ permissions:
env:
AWS_REGION: us-east-1
REGISTRY: public.ecr.aws/b2h7a5c4/chaintable
IMAGE_NAME: arc-node
CHAINS: "arc"
DOCKERFILE: deployments/Dockerfile.execution
BAKE_TARGET: arc-execution

jobs:
build-artifacts:
name: Build ${{ matrix.arch }}
name: Build ${{ matrix.component }} ${{ matrix.arch }}
# Same-repository PRs publish review images. External forks cannot receive publishing credentials.
if: github.repository == 'Chaintable/arc-node' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository)
strategy:
fail-fast: false
matrix:
arch: [amd64, arm64]
component: [execution, consensus]
include:
- arch: amd64
runner: ubuntu-latest
- arch: arm64
runner: ubuntu-24.04-arm
- component: execution
image_name: arc-node
- component: consensus
image_name: arc-consensus
env:
IMAGE_NAME: ${{ matrix.image_name }}
DOCKERFILE: deployments/Dockerfile.${{ matrix.component }}
BAKE_TARGET: arc-${{ matrix.component }}
runs-on: ${{ matrix.runner }}
timeout-minutes: 180
permissions:
Expand Down Expand Up @@ -114,8 +121,8 @@ jobs:
${{ env.BAKE_TARGET }}.platform=linux/${{ matrix.arch }}
${{ env.BAKE_TARGET }}.tags=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.vars.outputs.revision }}-${{ matrix.arch }}
${{ env.BAKE_TARGET }}.output=type=registry
${{ env.BAKE_TARGET }}.cache-from=type=gha,scope=arc-execution-${{ matrix.arch }}
${{ env.BAKE_TARGET }}.cache-to=type=gha,mode=max,scope=arc-execution-${{ matrix.arch }}
${{ env.BAKE_TARGET }}.cache-from=type=gha,scope=arc-${{ matrix.component }}-${{ matrix.arch }}
${{ env.BAKE_TARGET }}.cache-to=type=gha,mode=max,scope=arc-${{ matrix.component }}-${{ matrix.arch }}
env:
BUILDX_NO_DEFAULT_ATTESTATIONS: 1
CI: "true"
Expand All @@ -137,10 +144,25 @@ jobs:
exit 1
fi

- name: Verify runtime shell and utilities
env:
REVISION: ${{ steps.vars.outputs.revision }}
COMPONENT: ${{ matrix.component }}
ARCH: ${{ matrix.arch }}
run: |
bash .github/scripts/test-runtime-image.sh \
"${REGISTRY}/${IMAGE_NAME}:${REVISION}-${ARCH}" "arc-node-${COMPONENT}"

merge-docker-manifest:
name: Merge multi-arch manifest
name: Merge ${{ matrix.image_name }} multi-arch manifest
runs-on: ubuntu-latest
needs: build-artifacts
strategy:
fail-fast: false
matrix:
image_name: [arc-node, arc-consensus]
env:
IMAGE_NAME: ${{ matrix.image_name }}
permissions:
contents: read
id-token: write
Expand Down Expand Up @@ -201,10 +223,12 @@ jobs:
"${REGISTRY}/${IMAGE_NAME}:${REVISION}-amd64" \
"${REGISTRY}/${IMAGE_NAME}:${REVISION}-arm64"
docker buildx imagetools inspect "${REGISTRY}/${IMAGE_NAME}:${REVISION}"
# Writer aliases reference the same multi-architecture manifest, without rebuilding.
for chain in ${CHAINS}; do
docker buildx imagetools create \
--tag "${REGISTRY}/${chain}-writer:${REVISION}" \
"${REGISTRY}/${IMAGE_NAME}:${REVISION}"
docker buildx imagetools inspect "${REGISTRY}/${chain}-writer:${REVISION}"
done
# Only the execution image has Writer aliases; never alias consensus as Writer.
if [[ "$IMAGE_NAME" == "arc-node" ]]; then
for chain in ${CHAINS}; do
docker buildx imagetools create \
--tag "${REGISTRY}/${chain}-writer:${REVISION}" \
"${REGISTRY}/${IMAGE_NAME}:${REVISION}"
docker buildx imagetools inspect "${REGISTRY}/${chain}-writer:${REVISION}"
done
fi
15 changes: 11 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,19 +20,23 @@ background-tracer / ETL

## Chaintable images

The public image workflows build the execution node from this repository for
The public image workflows build the execution and consensus nodes from this repository for
`linux/amd64` and `linux/arm64`:

- Base image: `public.ecr.aws/b2h7a5c4/chaintable/arc-node`
- Writer alias: `public.ecr.aws/b2h7a5c4/chaintable/arc-writer` (the same manifest)
- Consensus image: `public.ecr.aws/b2h7a5c4/chaintable/arc-consensus`

Same-repository PRs, default-branch pushes, and manual builds use an eight-character
commit tag; per-architecture base tags append `-amd64` or `-arm64`. GitHub Releases
use `v<base-version>-ct.N` tags. Use a fixed tag after its build and manifest jobs
succeed; provisioning both public ECR repositories is a maintainer prerequisite.
succeed; provisioning all three public ECR repositories is a maintainer prerequisite.

These images contain `arc-node-execution` and `arc-snapshots`, not the consensus
node. The consensus client and external ETL are separate services. The Arc
The execution images contain `arc-node-execution` and `arc-snapshots`; the consensus
image contains `arc-node-consensus` and `arc-snapshots`. Both use Debian 12 with Bash,
`sh`, and coreutils for Kubernetes init/runmode scripts, retaining user `arc`
(UID/GID 999) and their direct binary entrypoints. The consensus client and external
ETL remain separate services. The Arc
documentation below describes the underlying client, not the Chaintable image
publication process.

Expand All @@ -47,6 +51,9 @@ GIT_VERSION="$(git describe --tags --always --dirty)" \
docker buildx bake arc-execution --set arc-execution.tags=arc-writer:local
```

For consensus, use target `arc-consensus` and tag override
`--set arc-consensus.tags=arc-consensus:local` with the same build metadata.

See [CONTRIBUTING.md](CONTRIBUTING.md) and [SECURITY.md](SECURITY.md) for the
Chaintable contribution and vulnerability-reporting processes.

Expand Down
17 changes: 14 additions & 3 deletions deployments/Dockerfile.consensus
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ RUN cargo chef prepare --recipe-path arc-node-consensus-recipe.json
###############
FROM chef AS builder

# Create a non-root user 'arc' with UID/GID 999. This method is used because distroless images lack user management tools like 'useradd'.
# Create the runtime user 'arc' with UID/GID 999 without adding user-management tools to the final image.
# The user's home directory is set to /data and shell to /bin/false for security.
RUN printf 'arc:x:999:999::/data:/bin/false\n' > /tmp/passwd && \
printf 'arc:x:999:\n' > /tmp/group
Expand Down Expand Up @@ -88,11 +88,22 @@ RUN --mount=type=cache,id=malachite-cargo-registry,sharing=locked,target=/usr/lo
#######################
# release runtime image
#######################
FROM gcr.io/distroless/cc-debian12:nonroot AS release-runtime
FROM public.ecr.aws/docker/library/debian:bookworm-slim AS runtime-base

# Copy CA certificates from the chef stage into the runtime image.
# Bootstrap the HTTPS package sources with the existing certificate bundle.
COPY --from=chef /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt

# Kubernetes init/runmode scripts require Bash and coreutils; keep Debian 12
# compatibility with the builder and the previous distroless runtime.
COPY assets/apt/apt-retry.conf /etc/apt/apt.conf.d/apt-retry.conf
COPY assets/apt/debian.sources /etc/apt/sources.list.d/debian.sources
RUN rm -f /etc/apt/sources.list && \
apt-get update && \
apt-get install -y --no-install-recommends bash coreutils libstdc++6 && \
rm -rf /var/lib/apt/lists/*

FROM runtime-base AS release-runtime

# Copy user information from the builder stage into the runtime image.
COPY --from=builder /tmp/passwd /etc/passwd
COPY --from=builder /tmp/group /etc/group
Expand Down
15 changes: 13 additions & 2 deletions deployments/Dockerfile.execution
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ RUN cargo chef prepare --recipe-path arc-node-execution-recipe.json
###############
FROM chef AS builder

# Create a non-root user 'arc' with UID/GID 999. This method is used because distroless images lack user management tools like 'useradd'.
# Create the runtime user 'arc' with UID/GID 999 without adding user-management tools to the final image.
# The user's home directory is set to /data and shell to /bin/false for security.
RUN printf 'arc:x:999:999::/data:/bin/false\n' > /tmp/passwd && \
printf 'arc:x:999:\n' > /tmp/group
Expand Down Expand Up @@ -88,10 +88,21 @@ RUN --mount=type=cache,id=reth-cargo-registry,sharing=locked,target=/usr/local/c
#######################
# release runtime image
#######################
FROM gcr.io/distroless/cc-debian12:nonroot AS release-runtime
FROM public.ecr.aws/docker/library/debian:bookworm-slim AS runtime-base

COPY --from=chef /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt

# Kubernetes init/runmode scripts require Bash and coreutils; keep Debian 12
# compatibility with the builder and the previous distroless runtime.
COPY assets/apt/apt-retry.conf /etc/apt/apt.conf.d/apt-retry.conf
COPY assets/apt/debian.sources /etc/apt/sources.list.d/debian.sources
RUN rm -f /etc/apt/sources.list && \
apt-get update && \
apt-get install -y --no-install-recommends bash coreutils libstdc++6 && \
rm -rf /var/lib/apt/lists/*

FROM runtime-base AS release-runtime

# Copy user information from the builder stage into the runtime image.
COPY --from=builder /tmp/passwd /etc/passwd
COPY --from=builder /tmp/group /etc/group
Expand Down