If you build images on an Apple Silicon Mac and push them to an x86_64 Linux server, the obvious approach breaks: a default docker build produces an arm64 image, and the server can't run it. The fix is a multi-architecture image — a single tag in the registry that points at multiple per-architecture builds, with the registry handing out the right one to each puller. docker buildx is the tool that makes them.
The one-line version
docker buildx create --use --name multiarch
docker buildx build --platform linux/amd64,linux/arm64 -t USERNAME/IMAGE:TAG --push .That builds your Dockerfile for both architectures, packages them into a manifest list, and pushes to the registry. Anyone pulling the tag gets the build for their CPU automatically.
What buildx actually does
docker buildx is a Docker CLI plugin that exposes BuildKit's more advanced features, including building for multiple platforms at once. It uses a separate "builder instance" (not the default Docker builder) and can run builds either:
- Natively for the host's CPU (fast).
- Under QEMU emulation for foreign architectures (slow, but works).
- On a remote build node with the matching CPU (fast, requires setup).
For most "build on Mac, deploy to Linux x86" cases, the workflow is: use buildx + QEMU emulation, accept the slower amd64 build, push the multi-arch result to a registry.
Setup: create a buildx builder
By default, Docker uses a builder named default that doesn't support multi-arch. You need a new builder:
docker buildx create --use --name multiarchThat creates a builder called multiarch and switches to it. Check:
docker buildx ls
# NAME/NODE DRIVER/ENDPOINT STATUS PLATFORMS
# multiarch * docker-container
# multiarch0 desktop-linux running linux/amd64, linux/arm64, ...
# default docker
# default default running linux/arm64multiarch is now your active builder. Switching back to default:
docker buildx use defaultRegister QEMU emulation (for cross-arch builds)
If you're on Apple Silicon building for amd64 (or vice versa), QEMU needs to be set up to emulate the foreign architecture. Docker Desktop usually does this automatically; if not:
docker run --privileged --rm tonistiigi/binfmt --install allThat registers binfmt_misc handlers for every architecture binfmt supports. Check what's registered:
docker run --privileged --rm tonistiigi/binfmtApple Silicon Macs with Docker Desktop usually have this set up out of the box.
Build for two platforms
docker buildx build --platform linux/amd64,linux/arm64 \
-t USERNAME/IMAGE:TAG \
--push .Two key flags:
--platform— comma-separated list of platforms. Each isos/arch[/variant].linux/amd64andlinux/arm64are the two you'll use 95% of the time.--push— push the result to the registry. For multi-arch builds this is essentially required.
--push vs --load — the gotcha
# Single-arch build to your local Docker image store
docker buildx build --platform linux/amd64 -t my-image --load .
# Multi-arch build pushed to a registry
docker buildx build --platform linux/amd64,linux/arm64 -t my-image --push .
# Multi-arch build to your LOCAL image store — DOES NOT WORK
docker buildx build --platform linux/amd64,linux/arm64 -t my-image --load .
# Error: docker exporter does not currently support exporting manifest listsThe local Docker image store represents one image per tag. A multi-arch image is multiple images under one tag (a "manifest list"). The local store can't represent that, so multi-arch builds must export somewhere that can — either a registry (--push) or an OCI tarball (--output type=oci,dest=...).
The pragmatic workflow: push to a registry (Docker Hub, GHCR, your private registry), or run buildx for just one platform with --load when you want to test locally.
Building for a registry that requires authentication
# Log in first
docker login # Docker Hub
docker login ghcr.io # GitHub Container Registry
docker login registry.example.com # private registry
# Then build and push
docker buildx build --platform linux/amd64,linux/arm64 \
-t ghcr.io/your-org/my-image:v1 \
--push .docker login stores credentials buildx then uses for the push.
Building in CI
GitHub Actions has a published action for this:
# .github/workflows/build.yml
name: Build multi-arch image
on:
push:
branches: [main]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: docker/setup-qemu-action@v3
- uses: docker/setup-buildx-action@v3
- uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- uses: docker/build-push-action@v6
with:
context: .
platforms: linux/amd64,linux/arm64
push: true
tags: ghcr.io/${{ github.repository }}:latestGitLab, CircleCI, and others have similar patterns — the core is setting up QEMU, configuring buildx, and using the right registry credentials.
Verify the manifest
After pushing, confirm both architectures are in the registry:
docker manifest inspect USERNAME/IMAGE:TAGYou should see entries for linux/amd64 and linux/arm64 (and any others you built). The platform field on each blob is what Docker matches against when someone pulls.
Caching across architecture builds
Multi-arch builds are slow because emulated builds run 5-10x slower than native. Build cache helps a lot:
docker buildx build --platform linux/amd64,linux/arm64 \
--cache-from type=registry,ref=USERNAME/IMAGE:cache \
--cache-to type=registry,ref=USERNAME/IMAGE:cache,mode=max \
-t USERNAME/IMAGE:latest \
--push .BuildKit pushes intermediate cache layers to the registry tagged as :cache, and pulls them on subsequent builds. The second build of an unchanged Dockerfile takes seconds instead of minutes.
Common pitfalls
docker buildinstead ofdocker buildx build. The default builder doesn't support multi-platform. Always usebuildxfor cross-arch.- Trying
--loadon a multi-arch build. Doesn't work. Either build single-arch and load, or build multi-arch and push. - No QEMU registered. Cross-arch builds fail with "exec format error" on the build commands themselves. Run
docker run --privileged --rm tonistiigi/binfmt --install allonce. - Slow builds. Emulation is slow. For frequent multi-arch builds, consider:
- Native build nodes — buildx can use a remote arm64 server alongside your amd64 host so each arch builds natively.
- CI matrix builds — let your CI builders provide native arm64 (GitHub Actions has arm64 runners; AWS CodeBuild has arm64 capacity).
- Pushing to a registry that doesn't support OCI manifest lists. Modern Docker Hub, GHCR, GCR, ECR all do. Some older self-hosted registries (Harbor < 2.x, Nexus < specific versions) don't. Upgrade the registry.
What to do next
- exec format error — Apple Silicon and Multi-Arch Images — the symptom that motivates this article.
- How to Install Docker — Docker Desktop already includes buildx; if you're on bare Docker Engine, install separately.
- Docker Image Size Optimization — applies to multi-arch builds too.
FAQ
Sources
Authoritative references this article was fact-checked against.
- Multi-platform images — Docker docsdocs.docker.com
- docker/buildx — GitHubgithub.com

