From 7293e1c4ba720f34e690b8754f506cb8ae251a07 Mon Sep 17 00:00:00 2001 From: Nando Vieira Date: Thu, 7 May 2026 12:21:36 -0700 Subject: [PATCH 1/7] Make the Docker rust base image configurable. --- .github/workflows/docker.yml | 33 ++++++++++++++++++++++++++++----- Dockerfile | 3 ++- 2 files changed, 30 insertions(+), 6 deletions(-) diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 2f88e15c84..74516a33c2 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -9,6 +9,11 @@ on: type: string required: true default: main + rust_image: + description: "The rust base image tag (e.g. 1.90.0-slim-bookworm). Defaults to latest." + type: string + required: false + default: latest release: types: [published] @@ -75,22 +80,38 @@ jobs: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} - # Compute Docker tags from the ref. - # - Version tag (e.g. v1.2.3): push versioned + latest tags. - # - Any other ref: push a tag for the resolved commit SHA. + - name: Resolve rust image + run: | + rust_image="${{ github.event_name == 'workflow_dispatch' && inputs.rust_image || 'latest' }}" + echo "RUST_IMAGE=${rust_image}" >> "$GITHUB_ENV" + + # Compute Docker tags from the ref and rust image. + # - Version tag (e.g. v1.2.3) + default rust: push versioned + latest tags. + # - Version tag + custom rust: push versioned + -rust- suffix only (do not update :latest). + # - Any other ref: push a tag for the resolved commit SHA, with the rust suffix when custom. - name: Compute tags run: | ref="${{ github.event_name == 'workflow_dispatch' && inputs.ref || github.ref_name }}" + suffix="" + if [[ "${RUST_IMAGE}" != "latest" ]]; then + sanitized="${RUST_IMAGE//[^A-Za-z0-9._-]/-}" + suffix="-rust-${sanitized}" + fi + if [[ "$ref" =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]]; then version="${ref#v}" - echo "DOCKER_TAGS=stellar/stellar-cli:${version},stellar/stellar-cli:latest" >> $GITHUB_ENV + if [[ -n "$suffix" ]]; then + echo "DOCKER_TAGS=stellar/stellar-cli:${version}${suffix}" >> $GITHUB_ENV + else + echo "DOCKER_TAGS=stellar/stellar-cli:${version},stellar/stellar-cli:latest" >> $GITHUB_ENV + fi elif [[ "${{ github.event_name }}" == "release" ]]; then echo "::error::Release tag '${ref}' is not a valid version tag (expected vX.Y.Z)." exit 1 else commit="$(git rev-parse HEAD)" - echo "DOCKER_TAGS=stellar/stellar-cli:${commit}" >> $GITHUB_ENV + echo "DOCKER_TAGS=stellar/stellar-cli:${commit}${suffix}" >> $GITHUB_ENV fi - name: Build and push @@ -100,6 +121,8 @@ jobs: platforms: linux/amd64,linux/arm64 push: true tags: ${{ env.DOCKER_TAGS }} + build-args: | + RUST_IMAGE=${{ env.RUST_IMAGE }} - name: Update Docker Hub description run: | diff --git a/Dockerfile b/Dockerfile index f4d34ce8c1..381fc73bfc 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,5 @@ -FROM rust:latest +ARG RUST_IMAGE=latest +FROM rust:${RUST_IMAGE} RUN rustup target add wasm32v1-none From a0d1d97ff6b8b91e5e64fea1fd382c343793680e Mon Sep 17 00:00:00 2001 From: Nando Vieira Date: Thu, 7 May 2026 12:58:42 -0700 Subject: [PATCH 2/7] Decouple the Dockerfile from the version being built. --- .github/workflows/docker.yml | 36 +++++++++++++++++++++++++++++++----- 1 file changed, 31 insertions(+), 5 deletions(-) diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 74516a33c2..7fa899e011 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -57,10 +57,20 @@ jobs: permissions: contents: read steps: + # Check out the workflow's own ref so we use the Dockerfile + entrypoint + # from the branch/PR running this workflow, not from the (potentially + # older) ref being built. If that ref doesn't carry a Dockerfile (e.g. + # an older release tag), fall back to a known-good branch. - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - with: - ref: ${{ github.event_name == 'workflow_dispatch' && inputs.ref || github.ref }} - fetch-depth: 0 + + # NOTE: switch fallback ref to `main` once this branch lands. + - name: Fall back to docker-custom-image Dockerfile if missing + run: | + if [[ ! -f Dockerfile ]]; then + echo "::notice::Dockerfile not present at ${GITHUB_REF}; falling back to docker-custom-image" + git fetch --depth=1 origin docker-custom-image + git checkout FETCH_HEAD -- Dockerfile entrypoint.sh docker/ + fi - name: Download binaries uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 @@ -85,6 +95,23 @@ jobs: rust_image="${{ github.event_name == 'workflow_dispatch' && inputs.rust_image || 'latest' }}" echo "RUST_IMAGE=${rust_image}" >> "$GITHUB_ENV" + # The docker job's checkout points at the workflow ref (for the Dockerfile), + # not at the binary's ref, so HEAD here doesn't represent what was built. + # Resolve the SHA of the binary ref via ls-remote for the SHA-tag path. + - name: Resolve build commit SHA + run: | + ref="${{ github.event_name == 'workflow_dispatch' && inputs.ref || github.ref_name }}" + if [[ "$ref" =~ ^[0-9a-f]{40}$ ]]; then + sha="$ref" + else + sha="$(git ls-remote https://github.com/${{ github.repository }}.git "$ref" | awk 'END{print $1}')" + fi + if [[ -z "$sha" ]]; then + echo "::error::Could not resolve SHA for ref: $ref" + exit 1 + fi + echo "BUILD_SHA=${sha}" >> "$GITHUB_ENV" + # Compute Docker tags from the ref and rust image. # - Version tag (e.g. v1.2.3) + default rust: push versioned + latest tags. # - Version tag + custom rust: push versioned + -rust- suffix only (do not update :latest). @@ -110,8 +137,7 @@ jobs: echo "::error::Release tag '${ref}' is not a valid version tag (expected vX.Y.Z)." exit 1 else - commit="$(git rev-parse HEAD)" - echo "DOCKER_TAGS=stellar/stellar-cli:${commit}${suffix}" >> $GITHUB_ENV + echo "DOCKER_TAGS=stellar/stellar-cli:${BUILD_SHA}${suffix}" >> $GITHUB_ENV fi - name: Build and push From bec1f8510188ae2db66a5eb15d0dd432e3ba4a67 Mon Sep 17 00:00:00 2001 From: Nando Vieira Date: Thu, 7 May 2026 13:19:52 -0700 Subject: [PATCH 3/7] Install libdbus-1-3 so the binary loads on slim images. --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 381fc73bfc..f2a557c4af 100644 --- a/Dockerfile +++ b/Dockerfile @@ -4,7 +4,7 @@ FROM rust:${RUST_IMAGE} RUN rustup target add wasm32v1-none RUN apt-get update && \ - apt-get install -y --no-install-recommends libudev1 libssl3 && \ + apt-get install -y --no-install-recommends libudev1 libssl3 libdbus-1-3 && \ rm -rf /var/lib/apt/lists/* ARG TARGETARCH From 7f37b6f69916647e366ad9e1a8cbaa59e8d2268f Mon Sep 17 00:00:00 2001 From: Nando Vieira Date: Thu, 7 May 2026 15:05:15 -0700 Subject: [PATCH 4/7] Build the binary inside the chosen base image. --- .github/workflows/docker.yml | 28 +++++++++++++++++++++++++--- 1 file changed, 25 insertions(+), 3 deletions(-) diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 7fa899e011..4576a6b1fd 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -38,11 +38,33 @@ jobs: with: ref: ${{ github.event_name == 'workflow_dispatch' && inputs.ref || github.ref }} - - name: Install build dependencies - run: sudo apt-get update && sudo apt-get install -y --no-install-recommends libudev-dev libdbus-1-dev + - name: Resolve rust image + run: | + rust_image="${{ github.event_name == 'workflow_dispatch' && inputs.rust_image || 'latest' }}" + echo "RUST_IMAGE=${rust_image}" >> "$GITHUB_ENV" + # Build inside the rust_image container so the binary's glibc and linked + # libraries match the runtime base image. Each matrix runner is native + # arch, so docker pulls the matching variant from the multi-arch manifest + # — no QEMU emulation. - name: Build binary - run: cargo build --package stellar-cli --release + run: | + # Compute the revision on the host so we don't need git inside the + # container. soroban-cli's source reads GIT_REVISION via env!() at + # compile time; setting it here lets cargo pass it through to rustc. + git_revision="$(git rev-parse HEAD)" + docker run --rm \ + -v "$(pwd):/src" \ + -w /src \ + -e "GIT_REVISION=${git_revision}" \ + "rust:${RUST_IMAGE}" \ + sh -c " + apt-get update && \ + apt-get install -y --no-install-recommends \ + build-essential cmake pkg-config \ + libssl-dev libudev-dev libdbus-1-dev && \ + cargo build --package stellar-cli --release + " - name: Upload binary uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 From fbb16360e254981ef329f2e461628e4ccc1f8c75 Mon Sep 17 00:00:00 2001 From: Nando Vieira Date: Thu, 7 May 2026 16:24:55 -0700 Subject: [PATCH 5/7] Use main as the Dockerfile fallback. --- .github/workflows/docker.yml | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 4576a6b1fd..32ccbb4986 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -85,12 +85,11 @@ jobs: # an older release tag), fall back to a known-good branch. - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - # NOTE: switch fallback ref to `main` once this branch lands. - - name: Fall back to docker-custom-image Dockerfile if missing + - name: Fall back to main Dockerfile if missing run: | if [[ ! -f Dockerfile ]]; then - echo "::notice::Dockerfile not present at ${GITHUB_REF}; falling back to docker-custom-image" - git fetch --depth=1 origin docker-custom-image + echo "::notice::Dockerfile not present at ${GITHUB_REF}; falling back to main" + git fetch --depth=1 origin main git checkout FETCH_HEAD -- Dockerfile entrypoint.sh docker/ fi From 88eb49b7d277c80bdd8a2f74074492dda8acf51f Mon Sep 17 00:00:00 2001 From: Nando Vieira Date: Thu, 7 May 2026 17:30:32 -0700 Subject: [PATCH 6/7] Make the Docker image self-contained. --- .github/workflows/docker.yml | 121 +++++++++++++++++++---------------- Dockerfile | 21 +++++- 2 files changed, 85 insertions(+), 57 deletions(-) diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 32ccbb4986..00cde0cef0 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -24,6 +24,7 @@ defaults: jobs: build: strategy: + fail-fast: false matrix: include: - runs-on: ubuntu-latest @@ -34,73 +35,80 @@ jobs: permissions: contents: read steps: + # The workflow's own ref provides Dockerfile + entrypoint.sh + docker/. + # The binary's source is cloned by the Dockerfile itself based on CLI_REF. - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - with: - ref: ${{ github.event_name == 'workflow_dispatch' && inputs.ref || github.ref }} - - name: Resolve rust image + - name: Fall back to main packaging files if missing + run: | + if [[ ! -f Dockerfile ]]; then + echo "::notice::Dockerfile not present at ${GITHUB_REF}; falling back to main" + git fetch --depth=1 origin main + git checkout FETCH_HEAD -- Dockerfile .dockerignore entrypoint.sh docker/ + fi + + - name: Resolve build args run: | rust_image="${{ github.event_name == 'workflow_dispatch' && inputs.rust_image || 'latest' }}" + cli_ref="${{ github.event_name == 'workflow_dispatch' && inputs.ref || github.ref_name }}" echo "RUST_IMAGE=${rust_image}" >> "$GITHUB_ENV" + echo "CLI_REF=${cli_ref}" >> "$GITHUB_ENV" + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4 + + - name: Log in to Docker Hub + uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4 + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} - # Build inside the rust_image container so the binary's glibc and linked - # libraries match the runtime base image. Each matrix runner is native - # arch, so docker pulls the matching variant from the multi-arch manifest - # — no QEMU emulation. - - name: Build binary + - id: build + name: Build and push by digest + uses: docker/build-push-action@bcafcacb16a39f128d818304e6c9c0c18556b85f # v7 + with: + context: . + platforms: linux/${{ matrix.arch }} + outputs: type=image,name=stellar/stellar-cli,push-by-digest=true,name-canonical=true,push=true + build-args: | + RUST_IMAGE=${{ env.RUST_IMAGE }} + CLI_REF=${{ env.CLI_REF }} + + - name: Export digest run: | - # Compute the revision on the host so we don't need git inside the - # container. soroban-cli's source reads GIT_REVISION via env!() at - # compile time; setting it here lets cargo pass it through to rustc. - git_revision="$(git rev-parse HEAD)" - docker run --rm \ - -v "$(pwd):/src" \ - -w /src \ - -e "GIT_REVISION=${git_revision}" \ - "rust:${RUST_IMAGE}" \ - sh -c " - apt-get update && \ - apt-get install -y --no-install-recommends \ - build-essential cmake pkg-config \ - libssl-dev libudev-dev libdbus-1-dev && \ - cargo build --package stellar-cli --release - " - - - name: Upload binary + mkdir -p "${RUNNER_TEMP}/digests" + digest="${{ steps.build.outputs.digest }}" + touch "${RUNNER_TEMP}/digests/${digest#sha256:}" + + - name: Upload digest uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: - name: stellar-${{ matrix.arch }} - path: target/release/stellar + name: digest-${{ matrix.arch }} + path: ${{ runner.temp }}/digests/* + if-no-files-found: error retention-days: 1 - docker: + manifest: needs: build runs-on: ubuntu-latest permissions: contents: read steps: - # Check out the workflow's own ref so we use the Dockerfile + entrypoint - # from the branch/PR running this workflow, not from the (potentially - # older) ref being built. If that ref doesn't carry a Dockerfile (e.g. - # an older release tag), fall back to a known-good branch. - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - - name: Fall back to main Dockerfile if missing + - name: Ensure docker/README.md is available run: | - if [[ ! -f Dockerfile ]]; then - echo "::notice::Dockerfile not present at ${GITHUB_REF}; falling back to main" + if [[ ! -f docker/README.md ]]; then git fetch --depth=1 origin main - git checkout FETCH_HEAD -- Dockerfile entrypoint.sh docker/ + git checkout FETCH_HEAD -- docker/ fi - - name: Download binaries + - name: Download digests uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 with: - pattern: stellar-* - merge-multiple: false - - - name: Set up QEMU - uses: docker/setup-qemu-action@ce360397dd3f832beb865e1373c09c0e9f86d70a # v4 + path: ${{ runner.temp }}/digests + pattern: digest-* + merge-multiple: true - name: Set up Docker Buildx uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4 @@ -116,9 +124,6 @@ jobs: rust_image="${{ github.event_name == 'workflow_dispatch' && inputs.rust_image || 'latest' }}" echo "RUST_IMAGE=${rust_image}" >> "$GITHUB_ENV" - # The docker job's checkout points at the workflow ref (for the Dockerfile), - # not at the binary's ref, so HEAD here doesn't represent what was built. - # Resolve the SHA of the binary ref via ls-remote for the SHA-tag path. - name: Resolve build commit SHA run: | ref="${{ github.event_name == 'workflow_dispatch' && inputs.ref || github.ref_name }}" @@ -161,15 +166,21 @@ jobs: echo "DOCKER_TAGS=stellar/stellar-cli:${BUILD_SHA}${suffix}" >> $GITHUB_ENV fi - - name: Build and push - uses: docker/build-push-action@bcafcacb16a39f128d818304e6c9c0c18556b85f # v7 - with: - context: . - platforms: linux/amd64,linux/arm64 - push: true - tags: ${{ env.DOCKER_TAGS }} - build-args: | - RUST_IMAGE=${{ env.RUST_IMAGE }} + - name: Create and push manifest list + working-directory: ${{ runner.temp }}/digests + run: | + IFS=',' read -ra TAGS <<< "${DOCKER_TAGS}" + tag_args=() + for tag in "${TAGS[@]}"; do + tag_args+=(-t "$tag") + done + + digest_refs=() + for f in *; do + digest_refs+=("stellar/stellar-cli@sha256:${f}") + done + + docker buildx imagetools create "${tag_args[@]}" "${digest_refs[@]}" - name: Update Docker Hub description run: | diff --git a/Dockerfile b/Dockerfile index f2a557c4af..24a3d70b5f 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,22 @@ ARG RUST_IMAGE=latest + +FROM rust:${RUST_IMAGE} AS builder + +RUN apt-get update && \ + apt-get install -y --no-install-recommends \ + build-essential cmake pkg-config git \ + libssl-dev libudev-dev libdbus-1-dev && \ + rm -rf /var/lib/apt/lists/* + +ARG CLI_REPO=https://github.com/stellar/stellar-cli +ARG CLI_REF=main + +WORKDIR /src +RUN git clone "${CLI_REPO}" . && \ + git checkout "${CLI_REF}" + +RUN cargo build --package stellar-cli --release + FROM rust:${RUST_IMAGE} RUN rustup target add wasm32v1-none @@ -7,8 +25,7 @@ RUN apt-get update && \ apt-get install -y --no-install-recommends libudev1 libssl3 libdbus-1-3 && \ rm -rf /var/lib/apt/lists/* -ARG TARGETARCH -COPY stellar-${TARGETARCH}/stellar /usr/local/bin/stellar +COPY --from=builder /src/target/release/stellar /usr/local/bin/stellar RUN chmod +x /usr/local/bin/stellar ENV STELLAR_CONFIG_HOME=/config From b2a3a117d03d2d9d52bf0bdbe11d5431b403e6af Mon Sep 17 00:00:00 2001 From: Nando Vieira Date: Thu, 7 May 2026 17:52:18 -0700 Subject: [PATCH 7/7] Use docker-custom-image as fallback for testing. --- .github/workflows/docker.yml | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 00cde0cef0..932d74782b 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -39,11 +39,12 @@ jobs: # The binary's source is cloned by the Dockerfile itself based on CLI_REF. - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - - name: Fall back to main packaging files if missing + # NOTE: switch fallback ref to `main` once this branch lands. + - name: Fall back to docker-custom-image packaging files if missing run: | if [[ ! -f Dockerfile ]]; then - echo "::notice::Dockerfile not present at ${GITHUB_REF}; falling back to main" - git fetch --depth=1 origin main + echo "::notice::Dockerfile not present at ${GITHUB_REF}; falling back to docker-custom-image" + git fetch --depth=1 origin docker-custom-image git checkout FETCH_HEAD -- Dockerfile .dockerignore entrypoint.sh docker/ fi @@ -96,10 +97,11 @@ jobs: steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + # NOTE: switch fallback ref to `main` once this branch lands. - name: Ensure docker/README.md is available run: | if [[ ! -f docker/README.md ]]; then - git fetch --depth=1 origin main + git fetch --depth=1 origin docker-custom-image git checkout FETCH_HEAD -- docker/ fi