build(docker): publish a multi-arch (amd64 + arm64) image - #425
Conversation
`ghcr.io/solana-foundation/pay:latest` is a single amd64 manifest, not a manifest list, so it cannot run on arm64 hosts (Graviton, Fargate arm64, Apple Silicon without emulation) even though the release workflow already ships `pay-aarch64-unknown-linux-gnu`. Build each architecture on a runner of that architecture and merge the two digests into one manifest list. QEMU is not an option here: `rust/Dockerfile` compiles the workspace from source, and emulating that build would take hours. `ubuntu-24.04-arm` is free for public repositories, so the extra runner costs nothing. The `docker/metadata-action` tag configuration moves to the merge job unchanged — tags belong to the manifest list, not to either single-arch image. The `workflow_dispatch` dry run still builds both architectures and pushes nothing.
|
@jackrieck is attempting to deploy a commit to the Solana Foundation Team on Vercel. A member of the Team first needs to authorize it. |
Greptile SummaryThis change builds amd64 and arm64 container images on native runners, publishes their digest references, and merges them into one multi-architecture GHCR manifest. The manual Confidence Score: 3/5Do not merge until the commit is recreated or amended with a verified signature. Git verification was executed against the exact commit and confirmed that it has no signature. The reported workflow inspection failure was directly disproved by exercising the relevant manual-dispatch metadata path. Files Needing Attention: The commit associated with this pull request needs a verified signature; no source-file change is needed for the disproved Docker inspection concern.
|
|
|
||
| env: | ||
| IMAGE: ghcr.io/solana-foundation/pay | ||
| # A dry run (workflow_dispatch with push=false) still builds every |
There was a problem hiding this comment.
Commit signature verification is missing
Commit 62d427072c002b70a9a9731cf9b1366755082adb is unsigned, so this pull request does not meet the repository requirement for signed and verified commits. Recreate or amend the commit with an approved signing key before merging.
Context Used: Request changes if the commits are not signed (ver... (source)
Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!
Artifacts
Executed script that verifies the requested commit signature
- The exact Bash source executed against commit 62d4270, showing the reproducible Git verification commands.
Observed unsigned commit verification output
- Captured output from the executed verification script: Git verification exits 1 and reports signature status N, confirming the commit is unsigned.
|
Ran the dispatch offered above on a fork, since Run: https://github.com/jackrieck/pay/actions/runs/31407993608 The published result is an OCI image index rather than a single manifest: For contrast, the same command against the current published image returns And the arm64 image resolved from that index runs: No QEMU anywhere in the run — each arch compiled on a runner of its own architecture, in parallel. One incidental finding while packaging this image downstream, in case it's useful: the two Linux release binaries have different glibc floors. |
|
@jackrieck thanks for your contribution! could you please address greptiles feedback and make sure we're reaching a 5/5 confidence score? thanks! |
Problem
ghcr.io/solana-foundation/pay:latestis a single-architecture amd64 image, nota manifest list, so it cannot run on arm64 hosts — Graviton, Fargate arm64, or
Apple Silicon without emulation.
A multi-arch image would return
application/vnd.oci.image.index.v1+jsonwith amanifestsarray. This returns one manifest, pinned to amd64.The cause is that
.github/workflows/docker.ymlpasses noplatforms:todocker/build-push-action, so the build inherits theubuntu-latestrunner'sarchitecture.
arm64 is clearly a supported target —
release-cli.ymlalready shipspay-aarch64-unknown-linux-gnu.tar.gzon every release. Only the containermisses it.
Approach
Each architecture builds on a runner of that architecture, and a
mergejobcombines the two digests into one manifest list. This is Docker's documented
multi-runner distributed build
pattern.
The obvious one-line fix — adding
platforms: linux/amd64,linux/arm64on asingle amd64 runner — is not viable here.
rust/Dockerfilecompiles theworkspace from source (
cargo build --release --features gcp_kms,redis-session-store,with cmake and libclang), and the arm64 half of that build would run under QEMU
emulation. The current native build takes ~10 minutes; the emulated one would
be measured in hours and could plausibly hit the 6-hour job ceiling.
Native runners avoid that entirely, and cost nothing here:
ubuntu-24.04-armisa standard GitHub-hosted runner, and
"use of the standard GitHub-hosted runners is free and unlimited on public
repositories".
The two builds run in parallel, so wall-clock time is roughly unchanged.
Changes
.github/workflows/docker.ymlonly.buildbecomes a two-entry matrix:linux/amd64onubuntu-latest,linux/arm64onubuntu-24.04-arm. No QEMU, nosetup-qemu-action.push-by-digest=true,name-canonical=true) anduploads the digest as an artifact.
mergejob runsdocker buildx imagetools createto assemble thedigests into one manifest list, then inspects the result.
docker/metadata-actiontag configuration moves to themergejobunchanged — including
type=match,pattern=^pay-v(.*)$,group=1andlatest. Tags belong to the manifest list, not to either single-arch image.The per-arch job keeps a
metadata-actionstep for labels only.workflow_dispatchpushinput behaves as before: a dry run still buildsboth architectures (that being the point of it) and writes nothing to GHCR.
Its
outputsresolves totype=cacheonlyand themergejob is skipped.Verification
actionlint1.7.7 (which runs shellcheck overrun:blocks) reports nofindings for the modified workflow.
rust/Dockerfilebuilds natively forlinux/arm64unmodified — verifiedlocally on Apple Silicon with
docker build --platform linux/arm64 ./rust,which is the same unemulated path
ubuntu-24.04-armtakes.cargo build --release --features gcp_kms,redis-session-storefinished in 4m32s, and theresulting image runs:
The
mergejob'simagetools createinvocation was exercised against arecorded
DOCKER_METADATA_OUTPUT_JSONfor apay-v0.27.0tag push, andassembles the expected command:
I could not do a full end-to-end dispatch on my fork: GitHub disables Actions on
forks until a maintainer enables them in the UI, and I have no way to click that
from here. Happy to run one if that would help — or a
workflow_dispatchwithpush: falseon this branch would exercise both builds without touching theregistry.
🤖 Generated with Claude Code