Introduction

Docker images have a hidden characteristic that most developers don’t think about until it bites them: they’re built for specific processor architectures. When you run docker build, you’re creating an image that works on your current architecture - but what happens when that image needs to run somewhere else?

ARM-based servers like Apple M1 and AWS Graviton instances are becoming mainstream due to their superior power efficiency and cost-effectiveness - AWS says that Graviton3 delivers up to 25% better price-performance than comparable x86 instances. This architectural shift makes multi-arch compatibility essential.

Let’s explore this through practical demonstration and understand how multi-architecture images solve a fundamental compatibility problem in container deployments.

Demonstrating the Single-Architecture Problem

To understand multi-arch images, let’s see the problem they solve through practical demonstration:

1. Create a Test Application

Go application that shows architecture:

// main.go
package main

import (
    "fmt"
    "runtime"
)

func main() {
    fmt.Printf("Hello from %s/%s!\n", runtime.GOOS, runtime.GOARCH)
    fmt.Printf("This process is running on: %s\n", runtime.GOARCH)
}

Dockerfile for the application:

FROM golang:1.19-alpine AS builder
WORKDIR /app
COPY main.go .
RUN go build -o app main.go

FROM alpine:latest
RUN apk --no-cache add ca-certificates
WORKDIR /root/
COPY --from=builder /app/app .
CMD ["./app"]

2. Build Single-Architecture Image

  1. Build command: docker build -t myapp:single-arch .
  2. Inspect architecture: docker image inspect myapp:single-arch | grep -i arch
  3. Result: Image built specifically for amd64 (x86_64) architecture

3. Demonstrate the Mismatch Problem

  1. Run on ARM system: docker run myapp:single-arch
  2. Output shows warning:
    WARNING: The requested image's platform (linux/amd64) does not match 
    the detected host platform (linux/arm64/v8) and no specific platform 
    was requested
    Hello from linux/amd64!
    This process is running on: amd64
    

The application runs, but through emulation. This comes with significant performance penalties - CPU-intensive workloads can see performance degradation due to instruction translation overhead. When Docker runs a mismatched architecture image, it either uses emulation (with performance penalties) or fails, forcing a choice between compatibility and performance.

Building Multi-Architecture Images

Multi-architecture images solve this by packaging multiple architecture variants under a single image tag.

Step 1: Set up Docker Buildx

  1. Check existing builders: docker buildx ls
  2. Create multi-platform builder: docker buildx create --name multiarch-builder --driver docker-container --use
  3. Bootstrap the builder: docker buildx inspect --bootstrap

Why docker-container driver? The docker-container driver is needed because it provides emulation capabilities for cross-platform builds, allowing you to build ARM64 images on AMD64 hosts and vice versa.

Step 2: Build for Multiple Architectures

docker buildx build \
  --platform linux/amd64,linux/arm64 \
  -t myapp:multiarch \
  --push .

What this command does:

  • ✅ Builds same Dockerfile for both AMD64 and ARM64
  • ✅ Creates separate complete images for each architecture
  • ✅ Packages them under one tag with manifest list
  • ✅ Pushes everything to registry

Multi-Arch Build Process

Understanding the Registry Structure

How Multi-Arch Images Are Stored

Inspect the structure:

docker buildx imagetools inspect myapp:multiarch

Manifest Inspection Output

Registry Components

  1. Manifest List (OCI Image Index)

    • Single entry point for the multi-arch image
    • Contains references to individual architecture manifests
  2. Individual Manifests

    • One manifest for each architecture (AMD64, ARM64, etc.)
    • Each contains layer information for that specific architecture
  3. Different SHA256 Digests

    • Unique identifier for each platform-specific image
    • Ensures integrity and allows for efficient caching

Registry Structure Visualization

Running Multi-Architecture Images

The Same Command, Different Results

Run the same command on different architectures:

docker run myapp:multiarch

Results by Architecture

On ARM64 system:

Hello from linux/arm64!
This process is running on: arm64

ARM Execution Output

On AMD64 system:

Hello from linux/amd64!
This process is running on: amd64

x86 Execution Output

How Automatic Selection Works

  1. Docker fetches the manifest list from the registry
  2. Selects the matching architecture based on the host platform
  3. Downloads only the relevant layers for that architecture
  4. Result: Native performance without emulation overhead

Limitations

When Multi-Arch Images Don’t Work

  • Applications with proprietary x86-only dependencies
  • Legacy applications with hardcoded architecture assumptions

Resource Costs to Consider

  • ⏱️ Build time increases 2-4x (building for multiple architectures)
  • 💾 Registry storage increases proportionally (separate images per architecture)
  • 🔍 Each architecture variant requires separate vulnerability scanning

GitHub Actions CI/CD Pipeline Integration

Example workflow for automated multi-arch builds:

- name: Build multi-arch
  uses: docker/build-push-action@v3
  with:
    platforms: linux/amd64,linux/arm64
    push: true
    tags: myapp:latest

Key benefits:

  • ✅ Automated builds on every push/PR
  • ✅ Consistent multi-arch image generation
  • ✅ No manual intervention required

Conclusion

Multi-architecture Docker images solve the architecture compatibility problem by packaging multiple variants under a single tag with automatic selection. The trade-off between build complexity and runtime performance becomes worthwhile as ARM-based infrastructure becomes mainstream.

The fundamental shift: from “build once, run anywhere with emulation” to “build for everywhere, run natively anywhere.”