Docker Multi-Architecture Images: A Complete Guide to ARM64 and AMD64 Compatibility
Introduction
Have you ever built a Docker image on your MacBook and deployed it to production, only to encounter a cryptic error like no matching manifest for linux/amd64? You're not alone. With Apple's transition to ARM-based M1 and M2 chips, the gap between development and production environments has widened, creating new challenges for containerization.
In this comprehensive guide, we'll explore why Docker images aren't always portable across different CPU architectures, how Docker Desktop handles this complexity, and most importantly, how to build universal multi-architecture images that work seamlessly across all platforms.
Understanding CPU Architectures: ARM64 vs AMD64
Modern computers use different instruction set architectures (ISAs) that define how the CPU processes instructions. The two dominant architectures in today's computing landscape are:
AMD64 (x86_64)
- Used by: Intel and AMD processors
- Common in: Traditional laptops, desktops, and most cloud servers (AWS EC2, Google Cloud, Azure)
- Characteristics: CISC (Complex Instruction Set Computing) architecture
- Also known as: x86_64, x64
ARM64 (aarch64)
- Used by: Apple Silicon (M1/M2/M3), AWS Graviton, Raspberry Pi 4+, many mobile devices
- Common in: Modern MacBooks, tablets, smartphones, edge computing
- Characteristics: RISC (Reduced Instruction Set Computing) architecture
- Also known as: aarch64, ARMv8
Why does this matter? Code compiled for one architecture cannot run natively on another. A Docker image built for ARM64 won't work on an AMD64 processor without emulation, and vice versa.
How Docker Works on macOS and Windows
A common misconception is that Docker runs natively on macOS and Windows. In reality, Docker is fundamentally a Linux technology that requires a Linux kernel to function.
The Virtual Machine Layer
Docker Desktop creates a lightweight Linux virtual machine in the background:
| Host Operating System | Virtualization Technology | Linux VM Architecture |
|---|---|---|
| macOS (Intel) | HyperKit / Virtualization.framework | AMD64 (x86_64) |
| macOS (Apple Silicon) | Virtualization.framework | ARM64 (aarch64) |
| Windows (Intel/AMD) | WSL 2 (Windows Subsystem for Linux) | AMD64 (x86_64) |
| Windows (ARM) | WSL 2 | ARM64 (aarch64) |
Critical insight: The Linux VM architecture matches your host CPU architecture. This means: - Mac M1/M2 → Linux ARM64 VM → Docker images are built for ARM64 - Mac Intel → Linux AMD64 VM → Docker images are built for AMD64
Even though it's "just Linux" underneath, the architecture difference matters.
The Production Deployment Problem
Consider this common scenario:
- Development: You build and test your application on a MacBook M1 (ARM64)
- Container build:
docker build -t myapp:latest .creates an ARM64 image - Registry push:
docker push myapp:latestuploads the ARM64 image to Docker Hub - Production deployment: Your cloud server (AWS EC2 t3.medium running on Intel Xeon) tries to pull the image
Result:
Error response from daemon: no matching manifest for linux/amd64 in the manifest list entries
Your production server cannot run an ARM64 image because it uses an AMD64 processor.
Real-World Impact: When Architectures Collide
Case Study: The Silent Deployment Failure
A development team experienced intermittent deployment failures in their CI/CD pipeline. Some developers could push images that deployed successfully, while others' images consistently failed in production.
Root cause: Half the team used Intel MacBooks (AMD64), while the other half had upgraded to M1 MacBooks (ARM64). The images built on M1 machines weren't compatible with their AMD64 production infrastructure.
Cost: 12 hours of downtime, frustrated developers, and delayed feature releases.
The Solution: Multi-Architecture Images
Multi-architecture (multi-arch) images solve this problem by packaging multiple image variants under a single tag. When you docker pull a multi-arch image, Docker automatically selects the variant matching your platform.
How Multi-Arch Images Work
Multi-arch images use a manifest list (also called a "fat manifest") that references multiple platform-specific images:
myapp:latest (manifest list)
├── linux/amd64 → sha256:abc123...
└── linux/arm64 → sha256:def456...
When a user runs docker pull myapp:latest:
1. Docker checks the manifest list
2. Identifies the host architecture (amd64 or arm64)
3. Pulls only the matching image variant
Result: One image tag works everywhere.
Building Multi-Architecture Images with Docker Buildx
Docker Buildx is Docker's extended build system that supports multi-platform builds. It's included with Docker Desktop by default.
Step 1: Create a Builder Instance
Buildx uses builder instances to perform cross-platform builds:
# Create a new builder instance
docker buildx create --name multiplatform --driver docker-container --use
# Verify the builder
docker buildx inspect --bootstrap
What this does: Creates a containerized build environment capable of building for multiple architectures simultaneously.
Step 2: Build for Multiple Platforms
The magic happens with the --platform flag:
docker buildx build \
--platform linux/amd64,linux/arm64 \
--tag username/myapp:latest \
--push \
.
Flag breakdown:
- --platform linux/amd64,linux/arm64: Build for both architectures
- --tag username/myapp:latest: Image name and tag
- --push: Push to registry (required for multi-arch images)
- .: Build context (current directory)
Why is --push required?
Multi-architecture images create a manifest list that references multiple image layers across different architectures. This manifest cannot be stored in your local Docker daemon—it must be published to a container registry.
If you need to test locally without pushing, you can build for your current platform only:
docker buildx build --platform linux/arm64 --load -t myapp:latest .
The --load flag loads the image into your local Docker daemon, but only works for single-platform builds.
Step 3: Verify the Multi-Arch Image
Inspect the manifest to confirm both architectures are present:
docker manifest inspect username/myapp:latest
Expected output (abbreviated):
{
"schemaVersion": 2,
"mediaType": "application/vnd.docker.distribution.manifest.list.v2+json",
"manifests": [
{
"mediaType": "application/vnd.docker.distribution.manifest.v2+json",
"digest": "sha256:1234567890abcdef...",
"platform": {
"architecture": "amd64",
"os": "linux"
}
},
{
"mediaType": "application/vnd.docker.distribution.manifest.v2+json",
"digest": "sha256:fedcba0987654321...",
"platform": {
"architecture": "arm64",
"os": "linux"
}
}
]
}
✅ Both amd64 and arm64 manifests present.
Advanced: Cross-Platform Build Optimization
Building for multiple architectures can be time-consuming. Here are optimization strategies:
1. Use Build Caching
Buildx supports distributed caching with GitHub Actions cache or registry cache:
docker buildx build \
--platform linux/amd64,linux/arm64 \
--cache-from type=registry,ref=username/myapp:buildcache \
--cache-to type=registry,ref=username/myapp:buildcache,mode=max \
--tag username/myapp:latest \
--push \
.
2. Multi-Stage Builds
Optimize Dockerfile with multi-stage builds to reduce image size:
# Build stage
FROM --platform=$BUILDPLATFORM golang:1.21 AS builder
ARG TARGETARCH
WORKDIR /build
COPY . .
RUN GOARCH=$TARGETARCH go build -o app
# Runtime stage
FROM alpine:latest
COPY --from=builder /build/app /usr/local/bin/app
CMD ["app"]
Platform variables:
- $BUILDPLATFORM: Architecture of the build host
- $TARGETPLATFORM: Architecture being built for
- $TARGETARCH: Target architecture (amd64, arm64, etc.)
3. Conditional Dependencies
Some dependencies are architecture-specific:
FROM node:18
# Install architecture-specific tools
RUN if [ "$(uname -m)" = "x86_64" ]; then \
apt-get install -y specific-amd64-package; \
else \
apt-get install -y specific-arm64-package; \
fi
Troubleshooting Common Issues
Issue 1: Authentication Failed
ERROR: failed to push: requested access to the resource is denied
Solution: Login to your container registry:
docker login
# Enter username and password when prompted
Issue 2: Platform Emulation is Slow
Building for a non-native architecture uses QEMU emulation, which can be significantly slower.
Solution: Use native builders or cloud build services (GitHub Actions with multiple runners, Docker Hub Automated Builds).
Issue 3: Cannot Load Multi-Arch Image Locally
ERROR: multi-platform images cannot be loaded directly
Solution: Either:
- Push to registry and pull back: docker push then docker pull
- Build for local platform only: docker buildx build --platform linux/arm64 --load -t myapp .
Issue 4: Image Size Doubled
Q: "Does multi-arch double my image size?"
A: - In registry: Yes, both image variants are stored - When pulled: No, only the matching architecture is downloaded - Cost impact: Minimal for most use cases, but consider private registry storage limits
Best Practices for Production
1. Always Build Multi-Arch in CI/CD
Ensure your CI/CD pipeline builds for all target platforms:
# GitHub Actions example
- name: Build and push multi-arch image
uses: docker/build-push-action@v5
with:
platforms: linux/amd64,linux/arm64
push: true
tags: username/myapp:latest
2. Use Specific Base Images
Some base images don't support all architectures. Verify support:
docker manifest inspect alpine:latest | jq '.manifests[].platform'
3. Tag with Architecture Suffix for Testing
During development, tag architecture-specific builds:
docker buildx build --platform linux/amd64 -t myapp:latest-amd64 --push .
docker buildx build --platform linux/arm64 -t myapp:latest-arm64 --push .
4. Document Architecture Requirements
In your README:
## Supported Architectures
This image supports the following architectures:
- `linux/amd64` - Intel/AMD 64-bit
- `linux/arm64` - ARM 64-bit (Apple Silicon, AWS Graviton)
Performance Considerations
Native vs Emulated Performance
| Scenario | Build Time | Runtime Performance |
|---|---|---|
| Native build (ARM64 on M1) | Baseline | 100% |
| Native build (AMD64 on Intel) | Baseline | 100% |
| Emulated build (AMD64 on M1) | 3-10x slower | N/A (runs in production) |
| Emulated runtime (ARM64 on AMD64 server) | N/A | 5-20x slower |
Takeaway: Always deploy images matching your production architecture. Emulation is acceptable for local development, unacceptable for production.
The Future: ARM in the Cloud
ARM adoption is accelerating in cloud infrastructure:
- AWS Graviton3: 25% better price-performance than x86
- Google Tau T2A: ARM-based VMs in Google Cloud
- Azure Cobalt: Microsoft's ARM-based cloud instances
- Hetzner: ARM-based dedicated servers
Multi-architecture support isn't just a compatibility fix—it's future-proofing your infrastructure for the ARM-dominated future.
Conclusion
Docker's promise of "build once, run anywhere" requires conscious effort in a multi-architecture world. By adopting Docker Buildx and multi-architecture images, you ensure your applications work seamlessly across development laptops, CI/CD runners, and production servers—regardless of underlying CPU architecture.
Key takeaways: 1. Docker Desktop's architecture matches your CPU, not just "Linux" 2. Multi-arch images use manifest lists to support multiple platforms 3. Docker Buildx makes cross-platform builds straightforward 4. Always build for your production architecture, not just your development machine 5. ARM adoption is growing—multi-arch support future-proofs your containers
Additional Resources
- Docker Buildx Documentation
- Multi-platform image specification
- Docker manifest command reference
- QEMU user emulation
Have questions about Docker multi-architecture images? Found this guide helpful? Share your experience in the comments below.