Building multi-arch images with buildx and QEMU

When your team mixes Apple Silicon and x86 hardware, you need one published image tag that resolves to the right architecture on each host. docker buildx with QEMU emulation builds both architectures and pushes them as a single manifest list. This page is the concrete build-and-publish procedure behind the Multi-Architecture Builds for ARM & x86 cluster.

Prerequisites

  • Docker Engine 24+ with buildx
  • Push access to a registry that supports manifest lists (GHCR, ECR, Docker Hub)
  • A Dockerfile that references TARGETARCH/TARGETOS for any binary download

How-To Steps

  1. Register QEMU binfmt handlers so the daemon can emulate the foreign architecture:

    docker run --privileged --rm tonistiigi/binfmt --install all
    docker buildx ls   # confirm linux/amd64 and linux/arm64 are listed
    
  2. Create and select a buildx builder backed by the container driver:

    docker buildx create --name multiarch --driver docker-container --use
    docker buildx inspect --bootstrap
    
  3. Build both architectures and push the manifest in one command:

    docker buildx build \
      --platform linux/amd64,linux/arm64 \
      --tag ghcr.io/org/dev-image:1.4.0 \
      --cache-to type=registry,ref=ghcr.io/org/dev-image:buildcache,mode=max \
      --cache-from type=registry,ref=ghcr.io/org/dev-image:buildcache \
      --push .
    
  4. Resolve the manifest digest and pin it in devcontainer.json:

    docker buildx imagetools inspect ghcr.io/org/dev-image:1.4.0 --format '{{.Manifest.Digest}}'
    
    { "image": "ghcr.io/org/dev-image:1.4.0@sha256:9f2b...", "remoteUser": "vscode" }
    
  5. Verify both architectures are present:

    docker buildx imagetools inspect ghcr.io/org/dev-image:1.4.0   # lists amd64 and arm64
    

Common Pitfalls

SymptomRoot CauseRemediation
buildx can’t target arm64QEMU binfmt not installedRun tonistiigi/binfmt --install all
--push required errorDefault builder can’t export manifest listsUse a docker-container driver builder
Build extremely slowEmulating the non-native archBuild each arch on a native runner, merge with imagetools create
Tag becomes single-archA later docker push overwrote the manifestAlways publish via buildx --platform

Conclusion

The invariant: one buildx build --platform invocation produces the manifest list, and you pin its digest. Reserve QEMU for convenience builds; for speed, build natively per architecture in CI and merge. Either way, the published tag stays multi-arch and reproducible.

FAQ

Do I need a separate builder, or will the default do? The default docker driver can’t export manifest lists. Create a docker-container driver builder with buildx create to push multi-arch images.

How do I avoid emulation slowness in CI? Run an amd64 job and an arm64 job on native runners, build single-arch images, then combine them with docker buildx imagetools create -t tag amd64-ref arm64-ref.