Zum Inhalt springen
EdgeServers
Blog

docker

Multi-arch Docker builds in 2026 — shipping ARM and x86 from the same pipeline

Graviton, Ampere, and Apple Silicon make ARM real in production. Here's how we build multi-arch images that work everywhere, without 3x the build time.

26. Mai 2026 · 8 min · von Sudhanshu K.

Multi-arch Docker builds in 2026 — shipping ARM and x86 from the same pipeline

A few years ago, "ARM in production" was a hobby project. In 2026, AWS Graviton instances are routinely 20-40% cheaper for the same workload, GCP's Tau T2A and Axion are mainstream, Azure Ampere is GA, and half the development team is on Apple Silicon. ARM isn't a side concern anymore; if your container images don't run natively on ARM, you're leaving meaningful money and developer experience on the table.

The good news: multi-arch Docker builds in 2026 are easier than single-arch builds were in 2019. The trick is knowing where the time and cost actually go. Here's the pipeline we use across managed Docker customers.

What "multi-arch" actually means

A multi-arch image is not one image. It's a manifest list (OCI image index) that points to multiple image manifests, one per architecture:

registry.example.com/my-app:v1.2.3  (manifest list)
├── linux/amd64 → sha256:abc...
├── linux/arm64 → sha256:def...
└── linux/arm/v7 → sha256:ghi...

When Docker (or containerd) pulls my-app:v1.2.3 on an arm64 host, it transparently picks the arm64 manifest. On x86, it picks amd64. The user-facing image reference is identical; the pulled bytes differ. This is what makes the deploy pipeline simple — you push one tag, every host pulls the right variant automatically.

The naive approach: don't do this

The path of least resistance is QEMU emulation in CI:

- uses: docker/setup-qemu-action@v3
- uses: docker/setup-buildx-action@v3
- run: docker buildx build --platform linux/amd64,linux/arm64 -t my-app:v1 --push .

This works. It also takes 4-8x longer than a native build for the non-native arch, because every instruction is being translated through QEMU. For a Rust or Go build, this is genuinely painful — your cargo build --release step might go from 3 minutes to 25 minutes.

The cost shows up two places:

  • CI minutes — emulated builds are slow, and minutes are billed
  • Developer feedback time — a 25-minute build is too long to iterate on

For small images (a static binary, a Node app, a Python app without native extensions), QEMU is fine. For anything compute-heavy in the build, it's not.

The native multi-arch approach

The pattern that scales: use native build hosts per architecture and combine them with docker manifest create.

jobs:
  build-amd64:
    runs-on: ubuntu-latest    # x86_64
    steps:
    - uses: actions/checkout@v4
    - uses: docker/setup-buildx-action@v3
    - run: |
        docker buildx build --platform linux/amd64 \
          -t ghcr.io/example/my-app:${{ github.sha }}-amd64 \
          --push .
 
  build-arm64:
    runs-on: ubuntu-24.04-arm    # GitHub-hosted ARM runner
    steps:
    - uses: actions/checkout@v4
    - uses: docker/setup-buildx-action@v3
    - run: |
        docker buildx build --platform linux/arm64 \
          -t ghcr.io/example/my-app:${{ github.sha }}-arm64 \
          --push .
 
  manifest:
    needs: [build-amd64, build-arm64]
    runs-on: ubuntu-latest
    steps:
    - run: |
        docker buildx imagetools create \
          -t ghcr.io/example/my-app:${{ github.sha }} \
          -t ghcr.io/example/my-app:latest \
          ghcr.io/example/my-app:${{ github.sha }}-amd64 \
          ghcr.io/example/my-app:${{ github.sha }}-arm64

Three jobs, two of which run in parallel on their native architecture. The third stitches them together. The end result is a multi-arch tag, but each architecture was built at native speed.

This pattern requires that your CI has both x86 and ARM runners. GitHub Actions added free ubuntu-24.04-arm runners in 2024; GitLab has had saas-linux-medium-arm64 for longer; CircleCI has ARM resource classes. If you're self-hosting CI, you'll need an ARM runner — a single c7g.large EC2 instance is plenty for most workloads.

Dockerfile changes for multi-arch

Most Dockerfiles work unchanged. The exceptions:

Pin downloads by architecture

If your Dockerfile downloads pre-built binaries (e.g., wget https://...kubectl), you'll need to pick the right one for the target architecture. BuildKit exposes TARGETARCH:

ARG TARGETARCH
RUN wget -qO /usr/local/bin/kubectl \
  https://dl.k8s.io/release/v1.30.0/bin/linux/${TARGETARCH}/kubectl && \
  chmod +x /usr/local/bin/kubectl

For more complex downloads (e.g., distributions that use x86_64 vs aarch64 naming):

ARG TARGETPLATFORM
RUN case "$TARGETPLATFORM" in \
    "linux/amd64") ARCH=x86_64 ;; \
    "linux/arm64") ARCH=aarch64 ;; \
    esac && \
  wget -qO- "https://example.com/release/${ARCH}.tar.gz" | tar -xz -C /opt

Don't hardcode native packages

Some Python wheels and npm modules ship pre-built native binaries for x86 only. If you pip install them on ARM, pip falls back to building from source, which often fails or takes forever. Check requirements.txt against PyPI for ARM wheel availability before assuming a pure-Python build will work cross-arch.

Same for node-gyp-based npm packages — historically a source of pain. Most popular packages have caught up by 2026, but verify.

Test on both architectures, every build

Multi-arch sounds easy until your ARM image segfaults at runtime because a transitive dependency had a different bug on ARM. Real story from a customer migration last year.

The fix is to actually run the ARM image as part of CI, not just build it:

  smoke-test-arm64:
    needs: [build-arm64]
    runs-on: ubuntu-24.04-arm
    steps:
    - run: |
        docker run --rm ghcr.io/example/my-app:${{ github.sha }}-arm64 \
          /app --version
        # ... and any other smoke tests

This doesn't cover everything, but it catches "the binary doesn't start" and "core syscall doesn't exist," which are 70% of cross-arch failures.

Cost story

For a typical web application image, here's how the numbers shake out across the three patterns:

ApproachBuild time (CI mins)Cost on GitHub Actions (per build)
amd64-only4 min$0.032
amd64 + arm64 via QEMU emulation22 min$0.176
amd64 + arm64 native parallel5 min (parallel)$0.080

Native parallel costs 2.5x amd64-only, but the alternative (running half your fleet emulated, paying x86 prices for what could be Graviton) costs far more in cloud spend. For customers on AWS, the typical savings from moving compute-heavy services to Graviton is 25-40% on the EC2/ECS line, which dwarfs the CI cost increase.

For DigitalOcean customers, the new Premium AMD vs Premium Intel pricing differences are smaller, but DOKS clusters with mixed node types still benefit from being able to schedule onto the cheapest pool.

Caching across architectures

You want each architecture's build cache to be persistent, but separate. Buildx supports this with registry-based caching:

docker buildx build --platform linux/amd64 \
  --cache-from type=registry,ref=ghcr.io/example/my-app:buildcache-amd64 \
  --cache-to type=registry,ref=ghcr.io/example/my-app:buildcache-amd64,mode=max \
  -t ghcr.io/example/my-app:${{ github.sha }}-amd64 \
  --push .

The cache lives alongside the images in your registry. The first build of a new dependency is slow; every subsequent build hits cache and finishes in seconds.

What we ship by default

For managed Docker customers, our default pipeline:

  1. Build amd64 and arm64 natively in parallel
  2. Combine into a multi-arch manifest
  3. Sign the manifest (one signature covers the whole multi-arch image)
  4. Generate per-architecture SBOMs
  5. Scan per-architecture
  6. Smoke-test both variants
  7. Push, attest, deploy

End-to-end: 6-8 minutes for most images. That's the same as a single-arch pipeline three years ago, but now you get every host architecture for free. The economics of ARM compute have shifted enough that not doing this in 2026 is a real cost line on your cloud bill.

Sudhanshu K. is a Staff DevOps engineer at EdgeServers (RemotIQ Pty Ltd, ABN 91 682 628 128). He runs the same image on his M3 MacBook, his Graviton EC2 fleet, and his Tau T2A test cluster — all from one git push.