The core idea
Images contain compiled code for one CPU architecture. Apple Silicon Macs and many cloud servers (AWS Graviton, for
example) are linux/arm64; most other servers are linux/amd64. docker buildx can build for a platform other than
your own, or for several at once, publishing them under one tag as an index so each machine pulls the right one.
Build for another platform
On an arm64 Mac:
$ docker buildx build --platform linux/amd64 -t notes-api:amd64 --load .$ docker image inspect -f '{{.Os}}/{{.Architecture}}' notes-api:amd64 notes-apilinux/amd64linux/arm64$ docker run --rm --platform linux/amd64 --entrypoint node notes-api:amd64 -p 'process.arch'x64
The amd64 build took 22 seconds here instead of a few, and the container still ran. Both are thanks to QEMU
emulation: Docker Desktop translates amd64 instructions to arm64 on the fly. It works, but it's slow, so heavy builds
(compiling native modules, big npm cis) under emulation can take many times longer. On Linux, install emulation with
the tonistiigi/binfmt image or docker/setup-qemu-action in CI.
Build for both at once
$ docker buildx build --platform linux/amd64,linux/arm64 -t localhost:5050/notes-api:multi --push .#26 pushing manifest for localhost:5050/notes-api:multi@sha256:7804d1e8406c...$ docker buildx imagetools inspect localhost:5050/notes-api:multiName: localhost:5050/notes-api:multiMediaType: application/vnd.oci.image.index.v1+jsonName: localhost:5050/notes-api:multi@sha256:917b5acd462e...Platform: linux/amd64Name: localhost:5050/notes-api:multi@sha256:a0e6a0ef445c...Platform: linux/arm64Name: localhost:5050/notes-api:multi@sha256:d06733d4f30e......
Output trimmed (the local registry is from 02.06 Tags, digests and registries). One tag, an index, two images. The third entry is an
attestation: provenance metadata BuildKit attaches describing how the image was built. Its platform shows as
unknown/unknown, and Docker ignores it when choosing what to run.
Multi-platform images usually go straight to a registry with --push. Loading them into your local image store with
--load needs the containerd image store, which recent Docker Desktop versions use by default.
Faster than emulation
- Build natively per platform in CI (amd64 and arm64 runners) and merge the results; fastest for heavy builds.
- Cross-compile in the build stage for languages that support it (Go, Rust): run the build stage on the native
platform with
FROM --platform=$BUILDPLATFORMand compile for$TARGETPLATFORM. No emulation at all. - For the notes API, emulation is fine: the only slow part is
npm ci, and the cache (07.05 Build cache in CI) skips it most of the time.
You push an image built on an M-series Mac with plain docker build to a registry. An amd64 server pulls it and the container exits immediately with 'exec format error'. What's the fix?
Build the notes API for linux/amd64 and check the architecture with docker image inspect. Then build for both platforms, push to the local registry from 02.06 Tags, digests and registries, and inspect the index with docker buildx imagetools inspect.
STUCK? ▸
"multiple platforms feature is currently not supported for docker driver": switch to the containerd image store in Docker Desktop's settings, or create a builder with docker buildx create --use --driver docker-container.
Recap
Interview questions
How do multi-architecture Docker images work?