Building Multi-Arch Container Images for arm64 and amd64 Clusters
Mixed arm64 and amd64 nodes break single-arch images. Learn to build multi-arch manifests with buildx, test them, and avoid exec format errors in Kubernetes.
- #kubernetes
- #containers
- #docker
- #arm64
- #ci-cd
The error that taught me about multi-arch images was four words long: exec format error. We had added a batch of Graviton (arm64) nodes to a cluster to cut costs, the scheduler placed a pod there, and the amd64 image we built on our CI runners refused to run. The container was fine. The CPU architecture was not. That is the moment most teams discover that a container image is tied to a specific architecture unless you deliberately build it to support several.
Modern clusters are increasingly mixed: arm64 for cost, amd64 for compatibility, sometimes both in the same node pool. If your image only ships one architecture, the scheduler will eventually land it on the wrong kind of node. The fix is a multi-arch image — really a manifest list that points at one image per architecture, so the node pulls the variant it can actually run. I let an AI assistant scaffold the buildx commands and CI YAML, then verify the result myself.
What “multi-arch” really means
A multi-arch image is not one binary that runs everywhere. It is a manifest list: a small index that maps each platform to a concrete image digest. When a node pulls myorg/app:1.4.0, the container runtime reads the list, finds the entry matching its own architecture, and pulls that. The node never knows or cares that other architectures exist.
You can see the structure with docker buildx imagetools:
docker buildx imagetools inspect myorg/app:1.4.0
Manifest list:
linux/amd64 sha256:1a2b...
linux/arm64 sha256:9f8e...
If you only see one platform there, your “multi-arch” image is not multi-arch, and a node of the other architecture will fail to start the pod.
Building with buildx
Docker’s buildx driver builds for multiple platforms in one command. You need a builder backed by QEMU emulation (or native runners per architecture):
docker buildx create --name multi --use
docker buildx build \
--platform linux/amd64,linux/arm64 \
--tag myorg/app:1.4.0 \
--push \
.
Two things matter here. First, --push is effectively required — a multi-platform build cannot load into your local Docker image store, because the store holds one architecture. It goes straight to the registry as a manifest list. Second, emulated cross-builds are slow. Compiling arm64 under QEMU on an amd64 runner can be several times slower than native. For anything compute-heavy, build each architecture on a native runner and merge.
Pro Tip: If your build mysteriously segfaults only on the emulated architecture, suspect QEMU, not your code. Switch that leg to a native arm64 runner before you spend an afternoon debugging a “bug” that only exists under emulation.
Writing a Dockerfile that cooperates
Single-stage Dockerfiles usually just work, but the moment you cross-compile, the build platform and target platform differ. BuildKit exposes that distinction through automatic build args:
FROM --platform=$BUILDPLATFORM golang:1.22 AS build
ARG TARGETOS
ARG TARGETARCH
WORKDIR /src
COPY . .
RUN CGO_ENABLED=0 GOOS=$TARGETOS GOARCH=$TARGETARCH \
go build -o /out/app .
FROM gcr.io/distroless/static
COPY --from=build /out/app /app
ENTRYPOINT ["/app"]
The build stage runs on $BUILDPLATFORM (fast, native to the runner) but compiles for $TARGETARCH. That is native-speed cross-compilation for Go and Rust, no QEMU needed for the compile step. CGO and dynamically linked dependencies are where this gets harder.
Pinning architecture in Kubernetes
Sometimes you want a workload to land only on one architecture — maybe a sidecar that genuinely is single-arch. Use a nodeSelector on the well-known label:
spec:
nodeSelector:
kubernetes.io/arch: arm64
That label is set automatically on every node. It is the cleanest way to confine a stubborn single-arch image to nodes that can run it, while your real multi-arch app schedules freely. If you find yourself doing this a lot, it is a sign the image should just be made multi-arch.
Verifying in CI before you ship
The check that would have saved me the exec format error is cheap. After the build, inspect the manifest and fail the pipeline if an expected platform is missing:
docker buildx imagetools inspect myorg/app:1.4.0 \
| grep -q linux/arm64 || { echo "arm64 missing"; exit 1; }
Better still, run the image under emulation in CI to confirm it actually executes:
docker run --rm --platform linux/arm64 myorg/app:1.4.0 --version
If that exits cleanly, a real arm64 node will run it too.
Where AI helps
Buildx flags, the $TARGETARCH Dockerfile pattern, and the CI verification steps are exactly the kind of well-trodden boilerplate an assistant nails. I describe the languages and target platforms and let a tool like Claude, Cursor, or Warp’s terminal generate the commands, then I read them. The AI is a fast junior engineer — great at recalling the flag you forgot, oblivious to whether your CGO dependency cross-compiles. It does not touch the cluster. I run the builds and pushes with my own registry credentials; the model never gets them, and it never gets a kubeconfig. If you want the generated Dockerfile and pipeline reviewed before merge, the code review dashboard flags missing --platform args and single-arch slips. There are container build prompts in the prompt library too.
Conclusion
A container image carries an architecture, and a mixed cluster will eventually test that. Build manifest lists with buildx, cross-compile natively where you can, verify the platforms exist in CI, and pin stubborn single-arch images with kubernetes.io/arch. Let AI write the buildx and Dockerfile boilerplate, keep the registry and cluster credentials with a human, and you will never see exec format error in production again. More container and cluster guides live under kubernetes-helm.
Download the Free 500-Prompt DevOps AI Toolkit
500 battle-tested, copy-paste AI prompts engineered by a senior systems engineer — every one with fill-in placeholders and safety/back-out notes. Drop your email and it's yours.
- 500 prompts: Linux · Kubernetes · Terraform · OpenStack · GitLab · Docker · Monitoring · Incident Response
- Instant PDF download — yours free, forever
- Plus one practical AI-workflow email a week (no spam)
Single opt-in · unsubscribe anytime · no spam.