Fix: Docker Multi-Platform Build Not Working — buildx Fails, Wrong Architecture, or QEMU Error
Quick Answer
How to fix Docker multi-platform build issues — buildx setup, QEMU registration, --platform flag usage, architecture-specific dependencies, and pushing multi-arch manifests to a registry.
The Problem
docker buildx build --platform linux/arm64 fails immediately:
ERROR: failed to solve: failed to read dockerfile: no such file or directoryOr the build runs but the image is the wrong architecture:
docker build --platform linux/arm64 -t myimage .
docker run --rm myimage uname -m
# x86_64 — expected aarch64Or QEMU-based cross-compilation fails mid-build:
#8 [linux/arm64 4/7] RUN npm install
exec /bin/sh: exec format errorOr pushing a multi-platform manifest to a registry fails:
ERROR: failed to push: unexpected status: 400 Bad RequestWhy This Happens
Docker’s multi-platform builds have several prerequisites that aren’t set up by default:
docker build(classic builder) ignores--platformfor cross-arch builds — onlydocker buildxwith a builder that supports multiple platforms can actually build for a different architecture. The defaultdocker buildcommand may silently build for the host architecture.- QEMU must be registered as a binfmt handler — to run non-native binaries (e.g., ARM64 binaries on an x86_64 host), the kernel’s binfmt_misc must map ARM64 executables to QEMU. Without this, you get
exec format error. - The builder instance must be multi-platform-capable — the default buildx builder (
docker-containerdriver) may only support a single platform. You need a builder created with thedocker-containerdriver and QEMU available. --loadand--pushare mutually exclusive with multi-platform —--load(load image into local Docker) only works for single-platform builds. Multi-platform builds must be pushed directly to a registry with--push, or exported with--output.
Fix 1: Set Up buildx and QEMU Correctly
Install and configure the prerequisites for multi-platform builds:
# Step 1: Verify buildx is available
docker buildx version
# buildx v0.12.0 docker-desktop ← OK
# If not found: install Docker Desktop or update Docker Engine
# Step 2: Register QEMU binfmt handlers (Linux hosts only)
# Docker Desktop on Mac/Windows does this automatically
docker run --privileged --rm tonistiigi/binfmt --install all
# Installs QEMU for arm, arm64, riscv64, ppc64le, s390x, mips, mips64
# Verify QEMU registration
ls /proc/sys/fs/binfmt_misc/
# Should show: qemu-aarch64, qemu-arm, etc.
# Step 3: Create a new builder with multi-platform support
docker buildx create --name multiarch-builder --driver docker-container --use
docker buildx inspect --bootstrap
# Should show: Platforms: linux/amd64, linux/arm64, linux/arm/v7, ...Verify the builder can handle your target platform:
docker buildx ls
# NAME/NODE DRIVER/ENDPOINT STATUS PLATFORMS
# multiarch-builder * docker-container running linux/amd64, linux/arm64, linux/arm/v7
# If your platform is missing, rebuild with QEMU registered first
docker buildx rm multiarch-builder
docker run --privileged --rm tonistiigi/binfmt --install all
docker buildx create --name multiarch-builder --driver docker-container --use
docker buildx inspect --bootstrapFix 2: Build and Push Multi-Platform Images
The correct workflow for multi-platform images:
# Build for multiple platforms and push to registry in one step
docker buildx build \
--platform linux/amd64,linux/arm64 \
--tag myregistry/myimage:latest \
--push \
.
# Build for a single non-native platform (for testing)
docker buildx build \
--platform linux/arm64 \
--tag myimage:arm64-test \
--load \ # Load into local Docker daemon (single platform only)
.
# Build and export to local tar file
docker buildx build \
--platform linux/arm64 \
--output type=oci,dest=myimage-arm64.tar \
.
# Verify the manifest has multiple architectures
docker buildx imagetools inspect myregistry/myimage:latest
# Outputs manifest list with amd64 and arm64 entriesMulti-platform build in GitHub Actions:
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
# Installs QEMU binfmt handlers automatically
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
# Creates a multi-platform capable builder
- name: Log in 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@v6
with:
context: .
platforms: linux/amd64,linux/arm64
push: true
tags: ghcr.io/${{ github.repository }}:latest
cache-from: type=gha
cache-to: type=gha,mode=maxFix 3: Handle Architecture-Specific Dependencies
Some dependencies install different binaries per architecture. Handle this in the Dockerfile:
# Use ARG TARGETARCH — automatically set by buildx to the target platform
FROM node:20-alpine
ARG TARGETARCH
ARG TARGETOS
# Install architecture-specific binary
RUN case "${TARGETARCH}" in \
"amd64") ARCH="x64" ;; \
"arm64") ARCH="arm64" ;; \
"arm") ARCH="armv7" ;; \
*) echo "Unsupported architecture: ${TARGETARCH}" && exit 1 ;; \
esac && \
wget -O /usr/local/bin/mybin "https://releases.example.com/mybin-linux-${ARCH}" && \
chmod +x /usr/local/bin/mybin
COPY . .
RUN npm ci
CMD ["node", "server.js"]Use --platform=$BUILDPLATFORM for build-time tools:
# syntax=docker/dockerfile:1
# Build stage runs on the host platform (fast, no emulation)
FROM --platform=$BUILDPLATFORM node:20 AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci # Runs natively on host — much faster than under QEMU
COPY . .
RUN npm run build
# Runtime stage runs on the target platform
FROM node:20-alpine
WORKDIR /app
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
CMD ["node", "dist/server.js"]Note:
--platform=$BUILDPLATFORMtells Docker to run that stage on the host platform (e.g., amd64) even when building for arm64. This avoids QEMU emulation for slow build tools like compilers and package managers.
Cross-compile CGO Go binaries:
# syntax=docker/dockerfile:1
FROM --platform=$BUILDPLATFORM golang:1.22 AS builder
ARG TARGETOS TARGETARCH
WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download
COPY . .
# CGO_ENABLED=0 for static binary; set GOOS/GOARCH for cross-compilation
RUN CGO_ENABLED=0 GOOS=${TARGETOS} GOARCH=${TARGETARCH} \
go build -ldflags="-s -w" -o /app/server .
FROM --platform=$TARGETPLATFORM scratch
COPY --from=builder /app/server /server
ENTRYPOINT ["/server"]Fix 4: Fix QEMU exec format error
exec format error during a build step means QEMU isn’t handling the binary format:
# Check QEMU is installed and binfmt is registered
cat /proc/sys/fs/binfmt_misc/qemu-aarch64
# enabled
# interpreter /usr/bin/qemu-aarch64-static
# flags: OCF
# ...
# If qemu-aarch64 doesn't exist, re-register:
docker run --privileged --rm tonistiigi/binfmt --install arm64
# Verify QEMU is working by running an ARM64 container
docker run --rm --platform linux/arm64 alpine uname -m
# aarch64 ← correctPersistent QEMU registration across reboots (Linux):
# Install qemu-user-static package (registers permanently)
sudo apt-get install -y qemu-user-static
sudo systemctl restart systemd-binfmt
# Or use the tonistiigi/binfmt image with --persistent flag
docker run --privileged --rm tonistiigi/binfmt --install all --persistentFix 5: Cache Multi-Platform Builds
Multi-platform builds are slow without caching. Use registry-based caching:
# Push cache to registry (works across machines and CI runs)
docker buildx build \
--platform linux/amd64,linux/arm64 \
--cache-from type=registry,ref=myregistry/myimage:buildcache \
--cache-to type=registry,ref=myregistry/myimage:buildcache,mode=max \
--tag myregistry/myimage:latest \
--push \
.
# GitHub Actions cache (for private repos or avoiding registry costs)
docker buildx build \
--cache-from type=gha \
--cache-to type=gha,mode=max \
...Build matrix strategy — native builds for each arch (faster than QEMU):
# GitHub Actions: build natively on arm64 and amd64 runners, then merge
jobs:
build:
strategy:
matrix:
include:
- platform: linux/amd64
runner: ubuntu-latest
- platform: linux/arm64
runner: ubuntu-24.04-arm # GitHub's ARM64 runner
runs-on: ${{ matrix.runner }}
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 }}
- name: Build and push by digest
id: build
uses: docker/build-push-action@v6
with:
platforms: ${{ matrix.platform }}
outputs: type=image,name=ghcr.io/${{ github.repository }},push-by-digest=true,name-canonical=true,push=true
- name: Export digest
run: |
mkdir -p /tmp/digests
digest="${{ steps.build.outputs.digest }}"
touch "/tmp/digests/${digest#sha256:}"
- uses: actions/upload-artifact@v4
with:
name: digests-${{ matrix.platform == 'linux/amd64' && 'amd64' || 'arm64' }}
path: /tmp/digests/*
merge:
needs: build
runs-on: ubuntu-latest
steps:
- uses: actions/download-artifact@v4
with:
pattern: digests-*
merge-multiple: true
path: /tmp/digests
- uses: docker/setup-buildx-action@v3
- uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Create manifest list and push
run: |
docker buildx imagetools create \
--tag ghcr.io/${{ github.repository }}:latest \
$(printf 'ghcr.io/${{ github.repository }}@sha256:%s ' *)
working-directory: /tmp/digestsStill Not Working?
--load fails with multi-platform — docker buildx build --load only supports a single platform. When building for linux/amd64,linux/arm64, you must use --push to a registry or --output type=oci,dest=archive.tar. To test locally, build for a single platform first: --platform linux/arm64 --load.
Python packages fail to compile for ARM64 under QEMU — packages that compile C extensions (like numpy, Pillow, cryptography) can take 10-100x longer under QEMU emulation compared to native. Use pre-built wheels when available (pip install --only-binary=:all: package) or use the --platform=$BUILDPLATFORM technique with cross-compilation.
Registry doesn’t support manifest lists — older registries (including some self-hosted Harbor instances) may not support OCI manifest lists. Update your registry to a version that supports the OCI Image Index specification, or push each architecture separately with different tags.
Alpine-based images failing for ARM — some Alpine packages have ARM-specific issues. If you see Bus error or Illegal instruction during package installation, try using a Debian-based image (-slim variants) instead. ARM support in Alpine improved significantly after Alpine 3.17.
For related Docker issues, see Fix: Docker Build Arg Not Available and Fix: Docker Multi-Stage Build Failed.
Solo developer based in Japan. Every solution is cross-referenced with official documentation and tested before publishing.
Was this article helpful?
Related Articles
Fix: GitHub Actions Docker Build and Push Failing
How to fix GitHub Actions Docker build and push errors — registry authentication, image tagging, layer caching, multi-platform builds, and GHCR vs Docker Hub setup.
Fix: AWS ECR Authentication Failed (docker login and push Errors)
How to fix AWS ECR authentication errors — no basic auth credentials, token expired, permission denied on push, and how to authenticate correctly from CI/CD pipelines and local development.
Fix: Coolify Not Working — Deployment Failing, SSL Not Working, or Containers Not Starting
How to fix Coolify self-hosted PaaS issues — server setup, application deployment, Docker and Nixpacks builds, environment variables, SSL certificates, database provisioning, and GitHub integration.
Fix: Docker Secrets Not Working — BuildKit --secret Not Mounting, Compose Secrets Undefined, or Secret Leaking into Image
How to fix Docker secrets — BuildKit secret mounts in Dockerfile, docker-compose secrets config, runtime vs build-time secrets, environment variable alternatives, and verifying secrets don't leak into image layers.