Docker Multi-Architecture Images: Building for ARM64 and AMD64

Learn why your Docker images fail across different CPU architectures and how to build universal multi-platform images that work on Apple Silicon M1/M2, Intel, and AMD processors.

Menu

Theme

Toggle between light and dark mode

Language

Choose your preferred language

Font Size

Adjust text size for better readability

Content Width

Choose optimal width for your screen

Table of Contents

Show or hide the table of contents

Actions

More options coming soon...

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:

  1. Development: You build and test your application on a MacBook M1 (ARM64)
  2. Container build: docker build -t myapp:latest . creates an ARM64 image
  3. Registry push: docker push myapp:latest uploads the ARM64 image to Docker Hub
  4. 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


Have questions about Docker multi-architecture images? Found this guide helpful? Share your experience in the comments below.

Other pages