Build Docker Images for ARM and x86
25 Sep 2026One docker buildx build command can produce an image that runs natively on both ARM (Apple Silicon dev machines, AWS Graviton, Raspberry Pi) and x86. Without it, an image built on your M-series laptop silently fails or runs under slow emulation on an x86 production box, and vice versa.
Why this is a problem now
Docker images are architecture-specific binaries wrapped in layers. An image built on ARM contains ARM machine code. Run it on an x86 host and Docker either refuses (exec format error) or, if you have QEMU emulation installed, runs it dramatically slower than a native build.
This didn’t used to matter when everyone built on x86 and deployed to x86. It matters now because a lot of developer laptops are ARM (Apple Silicon) while a lot of cloud instances are still x86 — or increasingly, cheaper ARM instances like Graviton, which flips the problem the other direction.
The fix: buildx and manifest lists
docker buildx builds for multiple architectures and pushes a single manifest list — one tag that points to different images depending on the pulling machine’s architecture. docker pull myapp:latest on ARM gets the ARM image; the same pull on x86 gets the x86 image. Same tag, correct binary, no manual selection.
Setting it up
Create a builder that supports multiple platforms:
docker buildx create --name multiarch --use
docker buildx inspect --bootstrap
Build and push both architectures in one command:
docker buildx build \
--platform linux/amd64,linux/arm64 \
-t yourname/myapp:latest \
--push .
--push is required here — buildx can’t load a multi-platform result into your local Docker daemon the way a normal build does, because your daemon only runs one architecture. You either push to a registry, or build one platform at a time with --load for local testing.
Verifying the result
docker buildx imagetools inspect yourname/myapp:latest
This lists every architecture the manifest covers. You should see both linux/amd64 and linux/arm64 entries with distinct digests.
What actually breaks
Base images without multi-arch support. Most official images (node, python, postgres, nginx) publish multi-arch manifests already. A niche or unmaintained base image might only ship linux/amd64, in which case your build fails on the ARM leg with a pull error for that platform — check the image’s tags page before you build.
Native dependencies compiled at build time. A Dockerfile that compiles a C extension, or a go build without GOARCH awareness, needs to actually cross-compile or run natively on each target platform during the build. Buildx handles this via QEMU emulation by default when building for a foreign architecture, which works but is noticeably slower than building each platform on its native hardware.
CI runners. If your CI only has x86 runners, building the ARM leg goes through QEMU emulation — correct, but a build that takes two minutes natively can take ten or more emulated. If build time matters, use a CI provider with native ARM runners for that leg instead of emulating it.
Comparison
| Approach | Speed | Setup |
|---|---|---|
| Single-arch, matches deploy target | Fastest | None extra |
| buildx with QEMU emulation | Slow for the emulated leg | One buildx create |
| buildx with native runners per arch | Fast on both | Native ARM + x86 CI runners |
The gotcha
If your production host is ARM and you build only on your x86 CI runner without --platform linux/arm64 in the build command, the push succeeds, the pull on the ARM host fails with a platform mismatch, and it looks like a registry problem. Check --platform first before you go digging anywhere else.