
Table of Contents
By Khimananda Oli | Last reviewed: September 2026
Shipping software that runs natively on both Intel servers and ARM-based cloud instances requires more than just tagging two separate images. Multi-Arch Docker Images with Buildx: One Build for amd64 and arm64 is now the standard approach for teams deploying to heterogeneous infrastructure like AWS Graviton, Azure Cobalt, or Apple Silicon development environments. Instead of maintaining parallel pipelines or suffering through slow QEMU emulation for every build, modern Buildx workflows leverage native builders and intelligent caching to produce manifest lists efficiently. This guide covers the exact configuration I use in production to keep build times low and compatibility high.
docker buildx create --use, then run docker buildx build --platform linux/amd64,linux/arm64 -t <image> --push .. For production CI, always pair this with native remote builders or GitHub Actions runners to avoid severe QEMU performance penalties during cross-compilation.How do you configure Multi-Arch Docker Images with Buildx for amd64 and arm64?
The foundation of any cross-platform container strategy is the Buildx builder instance. The default Docker builder cannot produce multi-platform manifests; it only builds for your host's native architecture. You must explicitly create and bootstrap a new builder context that supports multiple platforms. Before diving into complex CI configurations, verify your local environment supports virtualization, as this is required for the fallback emulation layer when native hardware isn't present. If you are setting up a fresh Ubuntu workstation for this work, follow the steps in install Docker on Ubuntu to ensure the container runtime and kernel modules are correctly configured first.
Creating and inspecting the builder
Run the following command to create a persistent builder named multiarch. The --driver docker-container flag is critical because the default docker driver does not support multi-platform builds or advanced cache exports.
'# Create and activate the multi-arch builder
docker buildx create --name multiarch --driver docker-container --use
'# Bootstrap the builder (pulls the buildkit image)
docker buildx inspect --bootstrap
'# Verify supported platforms
docker buildx ls When you inspect the builder, look for linux/amd64 and linux/arm64 in the Platforms column. If linux/arm64 is missing on an x86 host, Docker will automatically use QEMU user-mode emulation. While functional, emulation is painfully slow—often 5x to 10x slower than native execution. For local development, this is acceptable for testing; for CI, it is a bottleneck you must eliminate.
The universal build command
Once the builder is active, the actual build command is straightforward. The key flags are --platform to specify targets and --push to export directly to the registry. Note that you cannot load multi-platform images into the local Docker daemon with --load; the local daemon only supports single-architecture images. You must push to a registry or export to OCI layout.
docker buildx build \
--platform linux/amd64,linux/arm64 \
--tag myregistry.io/app:v1.2.0 \
--push \
. Why are my cross-platform Docker builds so slow?
If your arm64 builds on an x86 machine take twenty minutes instead of two, you are hitting the QEMU wall. QEMU translates ARM instructions to x86 at runtime, which introduces massive overhead. In my experience helping teams optimize their build pipeline automation best practices, eliminating QEMU from the critical path is usually the single biggest time saver.
Native builders vs. emulation
The solution is to run builds on native hardware whenever possible. There are three primary strategies:
- Cloud-native runners: Use AWS CodeBuild with Graviton, GitHub Actions with
ubuntu-24.04-armrunners, or GitLab CI with ARM tags. Each runner builds its native platform, and a final step merges the manifests. - Remote Buildx nodes: Append a remote ARM builder to your local Buildx instance using SSH. This lets you trigger a unified build command while the heavy lifting happens on native silicon elsewhere.
- Hybrid matrix builds: Split the build into separate jobs per architecture, push intermediate artifacts, and use
docker buildx imagetools createto assemble the manifest list without rebuilding.
For most teams in 2026, GitHub Actions' native ARM runners offer the best balance of cost and simplicity. They eliminate emulation entirely without requiring you to manage separate infrastructure.
Optimizing Dockerfiles for cross-compilation
Even with native hardware, poorly structured Dockerfiles waste time. Multi-stage builds are non-negotiable. More importantly, leverage BuildKit's cache mounts to persist package manager caches and build artifacts across runs. This prevents re-downloading dependencies for each platform.
FROM golang:1.23-alpine AS builder
ARG TARGETARCH
WORKDIR /app
'# Cache Go modules across builds
COPY go.mod go.sum ./
RUN --mount=type=cache,target=/go/pkg/mod \
go mod download
'# Build for the target architecture natively
COPY . .
RUN --mount=type=cache,target=/root/.cache/go-build \
CGO_ENABLED=0 GOARCH=$TARGETARCH go build -o /server .
FROM alpine:3.20
COPY --from=builder /server /usr/local/bin/server
CMD ["/server"] The TARGETARCH argument is automatically injected by Buildx. Using cache mounts here means your second build (for the other platform) benefits from shared module downloads, even if the compilation itself is platform-specific.
How do you integrate Buildx into CI/CD pipelines?
Local builds are fine for verification, but production images should always originate from CI. The integration pattern depends on your platform, but the principles remain consistent: authenticate securely, maximize cache reuse, and validate before promotion. When designing these pipelines, align them with your broader CI/CD best practices for small teams and solo developers to avoid over-engineering early on.
GitHub Actions with native ARM
GitHub now offers native ARM64 runners. Use a matrix strategy to build each platform in parallel, then merge. This avoids QEMU entirely and leverages GitHub's built-in cache API.
jobs:
build:
runs-on: ${{ matrix.runner }}
strategy:
matrix:
include:
- runner: ubuntu-24.04
platform: linux/amd64
- runner: ubuntu-24.04-arm
platform: linux/arm64
steps:
- uses: actions/checkout@v4
- uses: docker/setup-buildx-action@v3
- uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- uses: docker/build-push-action@v6
with:
platforms: ${{ matrix.platform }}
push: true
tags: ghcr.io/${{ github.repository }}:${{ github.sha }}-${{ matrix.platform }}
cache-from: type=gha
cache-to: type=gha,mode=max
merge:
needs: build
runs-on: ubuntu-24.04
steps:
- uses: docker/setup-buildx-action@v3
- uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- run: |
docker buildx imagetools create \
-t ghcr.io/${{ github.repository }}:${{ github.sha }} \
ghcr.io/${{ github.repository }}:${{ github.sha }}-linux/amd64 \
ghcr.io/${{ github.repository }}:${{ github.sha }}-linux/arm64 Caching strategies that actually work
Multi-arch builds double your cache surface area. Misconfigured caching can actually slow things down due to upload/download overhead. Use inline metadata for small projects and GHA/S3 backends for larger ones. Always set mode=max to cache all layers, not just the final one. For monorepos, scope cache keys by service path to prevent cache thrashing between unrelated components.
What are common pitfalls when building multi-platform containers?
Even with correct tooling, subtle issues creep in. These are the failures I see most often in audit trails and incident postmortems.
| Pitfall | Symptom | Fix |
|---|---|---|
| Hardcoded architecture in Dockerfile | exec format error on ARM despite successful build | Use TARGETARCH arg; never hardcode amd64 in RUN commands |
| Missing base image variant | Build fails for arm64 with "manifest unknown" | Verify base image supports all target platforms via docker manifest inspect |
| Incompatible native binaries | Precompiled tools fail at runtime on secondary arch | Download binaries conditionally using TARGETARCH or compile from source |
| Cache poisoning | Wrong architecture layers served from cache | Include platform in cache key; use scoped GHA cache scopes |
Local --load with multi-platform | Error: "cannot load multi-platform image" | Use --push or --output type=oci; local daemon is single-arch only |
Debugging platform-specific failures
When a build passes on amd64 but fails on arm64, don't guess. Run an interactive debug session targeting the failing platform. Buildx supports --progress=plain for full output visibility, and you can attach a shell to the build container for inspection.
'# Debug arm64 build interactively
docker buildx build --platform linux/arm64 --progress=plain --target builder -t debug-arm .
'# Inspect a specific platform's manifest
docker manifest inspect myregistry.io/app:v1.2.0 | jq '.manifests[] | select(.platform.architecture=="arm64")' Always validate the final manifest list before promoting to production. A missing platform in the manifest means silent failures for users on that architecture. Automated validation gates in your pipeline catch this before customers do.
Production Checklist for Multi-Arch Docker Images with Buildx
Getting Multi-Arch Docker Images with Buildx: One Build for amd64 and arm64 working locally is a milestone, but production readiness demands rigor. Before merging your next multi-arch PR, verify these items:
- Manifest completeness: Run
docker manifest inspectagainst your tagged image. Confirm bothamd64andarm64entries exist with correct digests. - Runtime validation: Pull and run the image on actual ARM hardware (or a cloud ARM VM). Emulation tests pass syntax checks but miss runtime segfaults.
- Base image parity: Ensure your base image tag resolves to the same version across architectures. Pin digests, not floating tags, for reproducible builds.
- Cache hygiene: Audit your cache configuration. Stale caches cause intermittent failures that vanish on retry. Rotate cache keys monthly or on base image updates.
- SBOM generation: Generate Software Bill of Materials per platform. Supply chain security requires knowing exactly what shipped in each architecture variant.
Multi-architecture support is no longer optional for teams serving diverse cloud environments. The tooling has matured significantly, but success still depends on understanding the underlying mechanics rather than copying snippets blindly. Start with native runners where budget allows, fall back to optimized QEMU with aggressive caching where it doesn't, and always validate the final artifact on real hardware. If your team needs help architecting a compliant, efficient container pipeline, reach out to discuss your infrastructure.