Multi-Architecture Docker Images
Learn to create multi-architecture Docker images with practical hands-on steps. Master cross-platform builds, manifest tooling, and troubleshooting for ARM64 and AMD64 targets in this Docker tutorial.
Focus: create multi-architecture docker images
When your CI pipeline builds an image but it fails to run on an ARM-based server like AWS Graviton or a Raspberry Pi, you're facing the classic single-architecture trap. Modern cloud and edge environments are a mix of x86_64 (AMD64) and ARM64 hardware; shipping only one architecture means some users can't pull your image. Multi-architecture Docker images solve this by bundling variants for different CPU architectures under a single tag, so Docker automatically selects the right build when pulling.
The problem this lesson solves
Imagine you develop on a Mac with an Apple Silicon (ARM64) chip and push an image. A teammate on a Linux server (AMD64) pulls it — and gets a cryptic error:
WARNING: The requested image's platform (linux/arm64) does not match the detected host platform (linux/amd64) and no specific platform was requested
Or worse, the container crashes immediately. Without multi-architecture support, you must manually tag and push separate images (e.g., myapp:arm64, myapp:amd64), then expect users to pick the right one. That's error-prone and scales poorly.
Key pain points:
- Mixed hardware environments – CI runners on AMD64 but production on ARM64, or vice versa.
- Edge/IoT devices – Raspberry Pis, Jetson Nanos, and other ARM boards need ARM64 images.
- Developer friction – Users expect
docker run myappto just work, regardless of their CPU. - Manual overhead – Maintaining separate tags for each architecture is a maintenance nightmare.
This lesson teaches you to create a single multi-architecture Docker image that works across platforms, using Docker's built-in buildx plugin and manifests.
Core concept / mental model
A multi-architecture Docker image is not a single layer blob — it's a manifest list (also called a fat manifest) that references separate image manifests for each platform.
Think of it like a DVD box set:
- The cover slip lists all languages supported. That's the manifest list.
- Inside, each disc contains a different language (e.g., English, Spanish, French). Each disc is a platform-specific image (AMD64, ARM64, ARM v7).
- When you put the DVD in a player, the player reads the cover slip and automatically loads the right disc. Docker does the same: it inspects the host's architecture and pulls the correct variant.
Key definitions:
| Term | Meaning |
|---|---|
| Manifest list | A JSON object pointing to one or more platform-specific manifests, typically stored in the registry under the same tag. |
| Platform-specific manifest | Describes layers and config for a single architecture (e.g., linux/amd64, linux/arm64). |
| buildx | Docker CLI plugin that enables multi-architecture builds using QEMU emulation or remote builders. |
| QEMU | A user-space emulator that allows an AMD64 host to build ARM64 binaries (or vice versa). |
Buildx handles the complexity. You define a single docker buildx build command with --platform flags, and it produces all the manifests and pushes them to the registry as one unit.
How it works step by step
-
Enable BuildKit – Docker Desktop ships with BuildKit enabled; on Linux, ensure
DOCKER_BUILDKIT=1or usedocker buildx. -
Create a builder instance – Buildx manages its own builder that can use multiple nodes (local or remote). Run
docker buildx create --name mybuilder --useto create and set it as the active builder. -
Set up emulation (if needed) – If your host can't natively build for a target platform (e.g., building ARM64 on AMD64), install QEMU binfmt handlers:
bash docker run --privileged --rm tonistiigi/binfmt --install all -
Build with
--platform– The magic line:bash docker buildx build --platform linux/amd64,linux/arm64,linux/arm/v7 \ -t myregistry.io/myapp:latest --push .This command: - Builds the image for all three platforms in parallel. - Creates a manifest list undermyregistry.io/myapp:latest. - Pushes everything directly to the registry (local images are not stored unless you also use--load). -
Verify – Use
docker buildx imagetoolsto inspect the manifest list:bash docker buildx imagetools inspect myregistry.io/myapp:latest
Node diagram (mental — words only):
[Build host (AMD64)]
│
├── buildx → QEMU → linux/arm64 build (emulated)
├── buildx → QEMU → linux/arm/v7 build (emulated)
└── buildx → native → linux/amd64 build
│
└── Registry: manifest list (myapp:latest)
├── linux/amd64 manifest
├── linux/arm64 manifest
└── linux/arm/v7 manifest
Hands-on walkthrough
Prerequisites
- Docker 20.10+ with buildx plugin (built into Docker Desktop)
- A registry where you can push images (Docker Hub, GitHub Container Registry, or a local registry)
- Sample project: a simple Node.js or Go app
Step 1: Create a simple app
Create a Dockerfile that is architecture-agnostic:
# Dockerfile
FROM alpine:latest
RUN apk add --no-cache curl
COPY hello.sh /hello.sh
RUN chmod +x /hello.sh
CMD ["/hello.sh"]
hello.sh:
#!/bin/sh
echo "Hello from $(uname -m)!"
Step 2: Set up buildx
docker buildx create --name multibuilder --use
docker buildx inspect --bootstrap
Expected output snippet:
Name: multibuilder
Driver: docker-container
Nodes:
Name: multibuilder0
Endpoint: unix:///var/run/docker.sock
Status: running
Platforms: linux/amd64, linux/amd64/v2, linux/arm64, linux/arm/v7, linux/arm/v6
Pro Tip: Run
docker run --privileged --rm tonistiigi/binfmt --install allif you don't see ARM platforms listed.
Step 3: Build and push multi-architecture image
docker buildx build --platform linux/amd64,linux/arm64,linux/arm/v7 \
-t <your-registry>/hello-multi:latest --push .
Expected behavior:
- The command runs multiple builds in parallel.
- On first build, it may be slower because QEMU emulation is used for non-native platforms.
- No local image is created unless you add
--load(but--loadonly works for one platform at a time).
Step 4: Verify the manifest list
docker buildx imagetools inspect <your-registry>/hello-multi:latest
Expected output (truncated):
Name: docker.io/<your-registry>/hello-multi:latest
MediaType: application/vnd.docker.distribution.manifest.list.v2+json
Manifests:
linux/amd64
linux/arm64
linux/arm/v7
Step 5: Test on different architectures
Pull and run on an ARM64 machine (e.g., Raspberry Pi):
docker run --rm <your-registry>/hello-multi:latest
# Output: Hello from aarch64!
On amd64:
docker run --rm <your-registry>/hello-multi:latest
# Output: Hello from x86_64!
Pro Tip: To test locally without leaving your machine, you can use
--platformflag withdocker runand QEMU emulation, but that requiresbinfmtsetup and runs your container under emulation (slower).
Compare options / when to choose what
| Approach | Pros | Cons | Best for |
|---|---|---|---|
Docker buildx with --platform |
One command, automatic manifest list, parallel builds | Requires buildx + QEMU emulation (slower); no local image for each platform without --load |
Most users; CI/CD pipelines |
| Manual manifest create + amend | Full control, can reuse already-pushed platform images | More steps, error-prone, easy to mis-match digests | Advanced users, or when you have existing platform-specific images |
| CI matrix builds (e.g., GitHub Actions) | Separate jobs per platform, no emulation bottleneck | More complex config; manual merging of manifests | Large teams; when native builders are available (e.g., macOS ARM runner + Linux AMD64 runner) |
When to avoid multi-architecture:
- You control all deployments and know they're all same architecture.
- You're distributing to only one hardware type (e.g., all x86_64 servers).
- The Dockerfile contains platform-specific instructions (e.g.,
FROM arm64v8/ubuntu) that cannot be trivially made portable.
When it's essential:
- Publishing public images (Docker Hub, GitHub Container Registry).
- Supporting both Apple Silicon (M1/M2/M3) developers and Linux servers.
- IoT/edge computing where devices are ARM-based.
Troubleshooting & edge cases
Build fails or is very slow on ARM builds
- Problem: QEMU emulation is slow — expect 3–10x slower than native.
- Fix: Use a remote builder (e.g., an ARM EC2 instance) as a build node in buildx. Alternatively, use CI jobs with native ARM runners.
exec user process caused: exec format error
- Problem: The container has binaries for the wrong architecture.
- Fix: Verify you pushed a multi-arch image. Run
docker buildx imagetools inspect. Ensure you're not accidentally pulling a single-arch image.
no matching manifest for linux/arm64 in the manifest list entries
- Problem: The manifest list doesn't include an entry for your host's architecture.
- Fix: Re-build with all required platforms. Check that
--platformlist covers your target.
Local --load only works for one platform
- Problem: You want to test the multi-arch image locally but
--loadonly stores one platform. - Workaround: Use
docker buildx build --load --platform linux/amd64for local testing, then rebuild with--pushwithout--loadfor the full multi-arch push.
Using FROM with a non-multi-arch base image
- Problem: Base image (e.g.,
ubuntu:18.04) may not support ARM64. - Fix: Use official multi-arch base images like
alpine,ubuntu:22.04, ornode:20-alpine. Check the registry manifest.
What you learned & what's next
You now understand how to create multi-architecture Docker images using docker buildx. You can:
- Explain the concept of a manifest list vs platform-specific manifest.
- Set up a buildx builder with QEMU emulation.
- Write a
docker buildx buildcommand that builds for multiple platforms. - Verify the manifest list with
docker buildx imagetools inspect. - Troubleshoot common issues like missing platforms or exec format errors.
This skill enables you to ship Docker images that work across developer laptops, CI runners, and production servers — regardless of CPU architecture.
What's next? Move on to optimizing image sizes with multi-stage builds, or explore running containers with resource constraints using --memory and --cpus. You're now equipped to build portable, platform-agnostic Docker images.
Practice recap
Mini exercise: Create a simple Go HTTP server Dockerfile that prints its listening architecture. Use buildx to build and push multi-architecture images for linux/amd64 and linux/arm64. Then pull and run both locally using docker run --platform to confirm the output differs. This cements the multi-arch workflow in a practical scenario.
Common mistakes
- Forgetting to install QEMU binfmt handlers — without them, builds for non-native architectures fail silently or hang. Always run
docker run --privileged --rm tonistiigi/binfmt --install allfirst. - Using
--loadand expecting a multi-arch image locally —--loadonly works for one platform; you must use--pushto the registry for multi-architecture to work. - Assuming all base images are multi-architecture — e.g.,
ubuntu:18.04may lack ARM64 support. Always check the registry withdocker buildx imagetools inspecton the base image. - Overlooking
--platformin the DockerfileRUNcommands — if you useRUN apt-get installand the package is architecture-specific, the build may succeed for one arch and fail for another.
Variations
- Using
docker manifest createanddocker manifest annotatemanually instead of buildx — gives full control but requires separatedocker pushfor each platform image first. - Leveraging a CI matrix (e.g., GitHub Actions) with separate jobs per architecture, then merging manifests in a final step — avoids QEMU emulation slowdown.
- Using BuildKit directly via
buildctlfor advanced caching and remote builders — more flexible but less integrated than buildx.
Real-world use cases
- Shipping a public Docker image that runs on both AMD64 servers and ARM64 AWS Graviton instances without separate tags.
- Building an IoT application handler that must deploy to Raspberry Pi (ARMv7) and an x86_64 edge gateway from the same CI pipeline.
- Developing a Microservices platform where each developer runs Docker Desktop on Apple Silicon while production runs on Linux AMD64 VMs.
Key takeaways
- Multi-architecture Docker images use a manifest list to bundle platform-specific images under one tag.
- Docker buildx with
--platformis the recommended way to build cross-platform images in one command. - QEMU emulation enables building for non-native architectures on your host, but is slower than native builders.
- Use
docker buildx imagetools inspectto verify the manifest list after push. - Push to a registry;
--loadstores only one platform locally — multi-architecture only works when pushed. - Always choose multi-architecture base images like Alpine or official Ubuntu 22.04+ to avoid architecture mismatch.
Keep learning
Related tutorials, quizzes, and articles for this topic.
Discussion
Questions, corrections, and tips help everyone reading this page.
0 comments
Add a comment
No comments yet — start the thread.