Building Multi-Architecture Infrastructure: ARM64, x86 and Beyond
The x86 monoculture in server infrastructure is ending. ARM64 processors from AWS (Graviton), Oracle (Ampere), Apple (M-series), and the Raspberry Pi foundation deliver competitive or superior performance per watt at lower cost. Running Docker workloads across mixed architectures is now a practical reality, but it introduces new challenges in image building, deployment, and cluster management.
This guide covers the practical aspects of building and deploying Docker infrastructure that spans ARM64 and x86, from building multi-arch images to managing mixed-architecture clusters.
The ARM64 Server Landscape
| Platform | Architecture | Use Case | Cost Advantage |
|---|---|---|---|
| AWS Graviton3/4 | ARM64 (Neoverse V1/V2) | Production cloud workloads | ~20-40% cheaper than x86 equivalents |
| Oracle Ampere A1 | ARM64 (Altra) | Cloud, free tier (4 cores, 24GB) | Free tier is unbeatable |
| Hetzner CAX | ARM64 (Ampere Altra) | European hosting | ~30% cheaper than x86 |
| Raspberry Pi 5 | ARM64 (Cortex-A76) | Homelab, edge computing | $60-80 per node |
| Apple M-series | ARM64 (custom) | Development machines | Best dev experience on ARM |
| NVIDIA Jetson | ARM64 + GPU | Edge AI, ML inference | Specialized workloads |
x86 vs ARM64 Performance
The performance comparison depends heavily on workload type:
- Web servers and API services: ARM64 is generally equivalent or better, especially for Go, Rust, and Node.js workloads. The higher core count at the same price point favors concurrent workloads.
- Databases: PostgreSQL and MySQL run well on ARM64. Benchmarks show Graviton3 matching or exceeding comparable x86 instances for database workloads.
- Compiled languages (Go, Rust, C): Excellent ARM64 support. Go has had first-class ARM64 support since Go 1.16.
- JVM workloads: Modern JVMs (Java 17+) have mature ARM64 support. Performance is competitive with x86.
- Legacy x86 software: No native option. QEMU emulation works but adds 2-10x overhead.
Key insight: For most containerized workloads (web services, APIs, databases), ARM64 delivers equivalent performance at 20-40% lower cost. The exception is workloads that depend on x86-specific libraries or AVX/SSE instructions.
Docker Multi-Architecture Images
A multi-arch Docker image is actually a manifest list that points to architecture-specific images. When you docker pull nginx, Docker automatically selects the image matching your CPU architecture.
# Inspect a multi-arch image
docker manifest inspect nginx:latest
# Shows something like:
# {
# "manifests": [
# {"platform": {"architecture": "amd64", "os": "linux"}, ...},
# {"platform": {"architecture": "arm64", "os": "linux"}, ...},
# {"platform": {"architecture": "arm", "os": "linux", "variant": "v7"}, ...}
# ]
# }
# Check what platform your current image targets
docker image inspect myapp:latest | jq '.[0].Architecture'
Building Multi-Arch Images with buildx
Docker buildx is the tool for creating multi-architecture images. It can build for multiple platforms in a single command, using either QEMU emulation or native build nodes.
Method 1: QEMU Emulation (Simple, Slower)
# Set up QEMU for cross-platform building
docker run --rm --privileged multiarch/qemu-user-static --reset -p yes
# Create a new builder instance
docker buildx create --name multiarch --driver docker-container --use
docker buildx inspect --bootstrap
# Build and push for multiple architectures
docker buildx build \
--platform linux/amd64,linux/arm64 \
--tag myregistry/myapp:latest \
--push \
.
# Build for a specific platform locally
docker buildx build \
--platform linux/arm64 \
--tag myapp:latest-arm64 \
--load \
.
Method 2: Native Build Nodes (Fast, More Setup)
# Create a builder that uses native build nodes
docker buildx create --name multiarch-native --driver docker-container
# Add an ARM64 node (SSH to a remote ARM64 machine)
docker buildx create --name multiarch-native \
--append \
--node arm64-builder \
--platform linux/arm64 \
ssh://user@arm64-server
# Use the multi-node builder
docker buildx use multiarch-native
# Build (each platform compiles natively - much faster)
docker buildx build \
--platform linux/amd64,linux/arm64 \
--tag myregistry/myapp:latest \
--push \
.
Writing Architecture-Aware Dockerfiles
# Multi-stage Dockerfile that works on both architectures
FROM --platform=$BUILDPLATFORM golang:1.22-alpine AS builder
# These are set automatically by buildx
ARG TARGETOS
ARG TARGETARCH
WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 GOOS=$TARGETOS GOARCH=$TARGETARCH \
go build -ldflags="-s -w" -o /app/server ./cmd/server
FROM alpine:3.19
RUN apk --no-cache add ca-certificates tzdata
COPY --from=builder /app/server /usr/local/bin/server
EXPOSE 8080
CMD ["server"]
For compiled languages like Go and Rust, cross-compilation is faster than QEMU emulation. The $BUILDPLATFORM runs the build tools natively, while $TARGETOS and $TARGETARCH ensure the output binary targets the correct platform.
--platform=$BUILDPLATFORM argument ensures build tools run natively.
CI/CD for Multi-Arch Images
# GitHub Actions workflow for multi-arch builds
name: Build Multi-Arch Image
on:
push:
tags: ['v*']
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Login to Registry
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build and Push
uses: docker/build-push-action@v5
with:
context: .
platforms: linux/amd64,linux/arm64
push: true
tags: |
ghcr.io/${{ github.repository }}:latest
ghcr.io/${{ github.repository }}:${{ github.ref_name }}
cache-from: type=gha
cache-to: type=gha,mode=max
Mixed-Architecture Docker Swarm
Docker Swarm natively supports mixed-architecture clusters. When you deploy a multi-arch image, each node pulls the image matching its architecture:
# Initialize swarm on an x86 manager
docker swarm init
# Join ARM64 nodes (Raspberry Pi, Graviton, etc.)
docker swarm join --token SWMTKN-xxx manager-ip:2377
# Deploy a service - Swarm handles architecture automatically
docker service create \
--name web \
--replicas 6 \
--publish 80:8080 \
myregistry/myapp:latest
# x86 nodes pull the amd64 image
# ARM64 nodes pull the arm64 image
# Constrain to specific architectures if needed
docker service create \
--name gpu-service \
--constraint 'node.platform.arch == x86_64' \
myregistry/gpu-app:latest
# Label nodes by architecture for placement control
docker node update --label-add arch=arm64 pi-node-1
docker service create \
--constraint 'node.labels.arch == arm64' \
--name edge-service \
myregistry/edge-app:latest
Raspberry Pi Cluster
Raspberry Pi clusters are an excellent and affordable way to learn distributed systems and run lightweight production workloads:
# Prepare a Raspberry Pi for Docker
# 1. Flash Raspberry Pi OS Lite (64-bit)
# 2. Enable cgroups (required for Docker)
# Add to /boot/firmware/cmdline.txt:
cgroup_enable=cpuset cgroup_enable=memory cgroup_memory=1
# 3. Install Docker
curl -fsSL https://get.docker.com | sh
sudo usermod -aG docker $USER
# 4. Set up swap (optional but recommended)
sudo dphys-swapfile swapoff
sudo sed -i 's/CONF_SWAPSIZE=.*/CONF_SWAPSIZE=2048/' /etc/dphys-swapfile
sudo dphys-swapfile setup
sudo dphys-swapfile swapon
# 5. Optimize Docker for Pi
# /etc/docker/daemon.json
{
"storage-driver": "overlay2",
"log-driver": "json-file",
"log-opts": {
"max-size": "10m",
"max-file": "3"
}
}
Cost Analysis
The financial case for ARM64 is compelling, especially in the cloud:
| Provider | x86 Instance | ARM64 Instance | Savings |
|---|---|---|---|
| AWS (4 vCPU, 16GB) | t3.xlarge: $0.1664/hr | t4g.xlarge: $0.1344/hr | ~19% |
| AWS (8 vCPU, 32GB) | m6i.2xlarge: $0.384/hr | m7g.2xlarge: $0.3264/hr | ~15% |
| Oracle Cloud | VM.Standard.E4.Flex | VM.Standard.A1.Flex: Free (4 cores) | 100% |
| Hetzner | CPX31: ~$15/mo | CAX31: ~$11/mo | ~27% |
| Homelab (Raspberry Pi 5) | Mini PC: ~$200-400 | Pi 5 8GB: ~$80 | ~60-80% |
linux/arm64 platform support. Most popular open-source projects now provide multi-arch images, but niche or proprietary software may be x86-only.
Deployment Considerations
- Test on target architecture: Cross-compiled binaries should be tested on actual ARM64 hardware, not just in QEMU. Performance characteristics differ.
- Watch for architecture-specific dependencies: Native extensions in Python, Node.js, and Ruby may need architecture-specific builds. Alpine-based images sometimes have fewer pre-built ARM64 packages.
- Use manifest lists, not separate tags: Push
myapp:latestas a multi-arch manifest, notmyapp:latest-arm64. This lets Docker handle architecture selection transparently. - Monitor performance per architecture: Tools like usulnet that support multi-node monitoring help you compare container performance across ARM64 and x86 nodes, identifying any architecture-specific bottlenecks.
- Plan your build pipeline: Multi-arch builds with QEMU can be 5-10x slower than native builds. Use native build nodes or cross-compilation to keep CI/CD times reasonable.
Multi-architecture infrastructure is no longer experimental. The combination of competitive ARM64 cloud instances, mature Docker tooling, and broad software support makes it a practical choice for reducing costs and improving performance. Start by building multi-arch images for your key services, deploy to one ARM64 node alongside your x86 fleet, and expand as you validate compatibility and performance.