feat: publish multi-arch client images #54

Merged
claudiomaradonna merged 3 commits from feat/multiarch-images into master 2026-10-26 18:34:35 +01:00
Collaborator

Why

The first publish-images run after #53 failed at the Dockerfile's first
RUN with exec /bin/sh: exec format error: the runner is arm64, not
amd64 as #53 assumed, and could not run the amd64 base image. The runner
host now has qemu-user-binfmt, and inside its dind service
docker run --platform linux/amd64 ubuntu:26.04 uname -m prints
x86_64. Since the runner builds arm64 natively, the images now cover
both platforms, so Linux arm64 hosts stop building locally too. Building
amd64 only was set aside because arm64 hosts would keep building, and
arm64 only because amd64 hosts would go back to building.

What changed

The workflow builds every client for linux/amd64 (emulated) and
linux/arm64 as <image>:latest-<arch>. Only after all six builds does
it push them, and only after every push does it join each pair into one
manifest tagged latest and the client version. A failed build
publishes nothing, and a failed push leaves the public latest and
version tags as they were. The job checks that buildx is available
before it builds anything.

install.sh now pulls on Linux amd64 and arm64 hosts. macOS and other
architectures still build locally. The README names the architectures
that pull. The development guide describes the per-platform builds, the
manifests, and the QEMU emulator the runner's machine needs, with a
check of it.

How to verify

  1. On this branch, run publish-images from the Actions tab. It should
    end green. Each of codex-cli, claude-code and opencode-cli
    should show latest, a version tag, latest-amd64 and
    latest-arm64, and docker buildx imagetools inspect git.unitoo.it/claudiomaradonna/claude-code:latest should list
    linux/amd64 and linux/arm64.
  2. On a Linux arm64 host with rootless Podman, run ./install.sh claude.
    It should print Pulling claude image ... and build nothing. On
    master the same command builds the image.

make lint && .agents/hooks/shellcheck.sh repo && make test passes on
the tip. The rewritten tests/unit/test_publish.sh tests and the arm64
pull test fail on master. Not tried: the workflow on the real runner,
and make test-e2e, which needs real rootless Podman.

Considerations

Three of the six builds run under QEMU and are much slower. Whether
timeout-minutes: 120 is enough will only show on the first run.
latest-<arch> tags stay visible in the registry. In exchange, the
script works with any image store on the dind daemon. Hosts still trust
whatever was last pushed, now on arm64 as well. The build-to-push window
is wider, so this still assumes the runner's dind daemon is not shared
with untrusted jobs.

## Why The first `publish-images` run after #53 failed at the Dockerfile's first `RUN` with `exec /bin/sh: exec format error`: the runner is arm64, not amd64 as #53 assumed, and could not run the amd64 base image. The runner host now has `qemu-user-binfmt`, and inside its dind service `docker run --platform linux/amd64 ubuntu:26.04 uname -m` prints `x86_64`. Since the runner builds arm64 natively, the images now cover both platforms, so Linux arm64 hosts stop building locally too. Building amd64 only was set aside because arm64 hosts would keep building, and arm64 only because amd64 hosts would go back to building. ## What changed The workflow builds every client for linux/amd64 (emulated) and linux/arm64 as `<image>:latest-<arch>`. Only after all six builds does it push them, and only after every push does it join each pair into one manifest tagged `latest` and the client version. A failed build publishes nothing, and a failed push leaves the public `latest` and version tags as they were. The job checks that buildx is available before it builds anything. `install.sh` now pulls on Linux amd64 and arm64 hosts. macOS and other architectures still build locally. The README names the architectures that pull. The development guide describes the per-platform builds, the manifests, and the QEMU emulator the runner's machine needs, with a check of it. ## How to verify 1. On this branch, run `publish-images` from the Actions tab. It should end green. Each of `codex-cli`, `claude-code` and `opencode-cli` should show `latest`, a version tag, `latest-amd64` and `latest-arm64`, and `docker buildx imagetools inspect git.unitoo.it/claudiomaradonna/claude-code:latest` should list `linux/amd64` and `linux/arm64`. 2. On a Linux arm64 host with rootless Podman, run `./install.sh claude`. It should print `Pulling claude image ...` and build nothing. On `master` the same command builds the image. `make lint && .agents/hooks/shellcheck.sh repo && make test` passes on the tip. The rewritten `tests/unit/test_publish.sh` tests and the arm64 pull test fail on `master`. Not tried: the workflow on the real runner, and `make test-e2e`, which needs real rootless Podman. ## Considerations Three of the six builds run under QEMU and are much slower. Whether `timeout-minutes: 120` is enough will only show on the first run. `latest-<arch>` tags stay visible in the registry. In exchange, the script works with any image store on the dind daemon. Hosts still trust whatever was last pushed, now on arm64 as well. The build-to-push window is wider, so this still assumes the runner's dind daemon is not shared with untrusted jobs.
The runner is arm64, so the amd64-only build failed with an exec format
error. Each client is now built for linux/amd64, emulated through the
runner host's QEMU binfmt handlers, and for linux/arm64, pushed per
platform, and joined into one manifest for latest and its version.
The published images now carry linux/arm64 as well, so Linux arm64
hosts pull them like amd64 hosts; macOS and other architectures still
build locally.
docs: describe multi-arch published images
Some checks failed
publish-images / publish (push) Has been cancelled
45463c22a7
The README names the Linux architectures that pull, and the development
guide covers the per-platform builds, the manifest, and the QEMU
emulator an arm64 runner host needs for amd64.
claudiomaradonna deleted branch feat/multiarch-images 2026-10-26 18:34:41 +01:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
claudiomaradonna/azkaban!54
No description provided.