How to Build and Push a Multi-Arch Docker Image in CI
QEMU plus Buildx lets one CI step emit a manifest list that serves the right image to each architecture.
Set up QEMU for emulation and Buildx as the builder, then pass platforms: linux/amd64,linux/arm64 to docker/build-push-action. The registry receives a manifest list, and clients pull the matching arch automatically.
Steps
- Set up QEMU with
docker/setup-qemu-actionso the runner can emulate other architectures. - Set up Buildx with
docker/setup-buildx-action. - Log in to the registry, then run
build-push-actionwith a comma-separatedplatforms:list.
Workflow
.github/workflows/docker.yml
jobs:
image:
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
steps:
- uses: actions/checkout@v4
- uses: docker/setup-qemu-action@v3
- 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:
push: true
platforms: linux/amd64,linux/arm64
tags: ghcr.io/${{ github.repository }}:latestGotchas
- Emulated arm64 builds run several times slower than native; cache layers to offset it.
- A multi-platform build cannot use
load: true(the daemon holds one arch only); push to a registry instead. - Latchkey offers native arm64 runners, so multi-arch builds avoid QEMU emulation overhead.
Related guides
How to Push by Digest and Create a Manifest List in CIBuild each architecture in a separate CI job, push it by digest, then merge the digests into one multi-arch m…
How to Cache Docker Layers With a Registry Cache in CIPersist Docker build layers across CI runs with Buildx registry cache (type=registry), pushing and pulling ca…