Apple M1 Chip
ARM architecture
exec format error
containerization
Docker troubleshooting

exec format error when running containers build with Apple M1 Chip ARM based systems

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Introduction

The "exec format error" occurs when you try to run a Docker container built for one CPU architecture (ARM/aarch64) on a host with a different architecture (x86_64/amd64), or vice versa. Apple M1/M2/M3 chips use ARM architecture, so images built on these machines default to linux/arm64. When deployed to x86_64 servers (AWS EC2, most cloud VMs), the binary format is incompatible and the kernel refuses to execute it. The fix is to build multi-platform images using docker buildx with the --platform flag.

The Error

bash
1# Built on M1 Mac (ARM), deployed to x86_64 server
2docker run my-app:latest
3# exec /usr/local/bin/node: exec format error
4
5# Or in Kubernetes
6# CrashLoopBackOff with "exec format error" in logs

The kernel cannot execute an ARM binary on an x86 processor (or vice versa), so the container immediately crashes.

Fix 1: Build for the Target Platform

bash
1# On M1 Mac, build specifically for amd64 (x86_64)
2docker build --platform linux/amd64 -t my-app:latest .
3
4# Run locally (uses QEMU emulation on M1)
5docker run --platform linux/amd64 my-app:latest
6
7# Push to registry — the image works on x86_64 servers
8docker push my-app:latest

The --platform linux/amd64 flag tells Docker to build for x86_64 regardless of the host architecture. On M1 Macs, Docker Desktop uses QEMU to emulate x86_64 during the build.

Fix 2: Build Multi-Platform Images with buildx

Build a single image that supports both ARM and x86:

bash
1# Create a buildx builder (one-time setup)
2docker buildx create --name multiplatform --use
3docker buildx inspect --bootstrap
4
5# Build and push for both platforms
6docker buildx build \
7  --platform linux/amd64,linux/arm64 \
8  -t myregistry/my-app:latest \
9  --push .

Docker creates a manifest list that includes both architectures. When a host pulls the image, it automatically selects the matching architecture.

bash
# Inspect the manifest to see supported platforms
docker manifest inspect myregistry/my-app:latest
# Shows linux/amd64 and linux/arm64 entries

Fix 3: Specify Platform in docker-compose.yml

yaml
1# docker-compose.yml
2services:
3  app:
4    build:
5      context: .
6      dockerfile: Dockerfile
7      platforms:
8        - linux/amd64
9    image: my-app:latest
10    platform: linux/amd64

Or for multi-platform builds:

yaml
1services:
2  app:
3    build:
4      context: .
5      platforms:
6        - linux/amd64
7        - linux/arm64

Fix 4: CI/CD Multi-Platform Builds

GitHub Actions

yaml
1# .github/workflows/build.yml
2jobs:
3  build:
4    runs-on: ubuntu-latest
5    steps:
6      - uses: actions/checkout@v4
7
8      - name: Set up QEMU
9        uses: docker/setup-qemu-action@v3
10
11      - name: Set up Docker Buildx
12        uses: docker/setup-buildx-action@v3
13
14      - name: Build and push
15        uses: docker/build-push-action@v5
16        with:
17          context: .
18          platforms: linux/amd64,linux/arm64
19          push: true
20          tags: ghcr.io/myorg/my-app:latest

QEMU is required on x86 CI runners to build ARM images (and vice versa).

Fix 5: Check and Fix Existing Images

bash
1# Check an image's architecture
2docker inspect my-app:latest | grep Architecture
3# "Architecture": "arm64"  <-- This won't run on amd64
4
5# Or using manifest
6docker manifest inspect --verbose my-app:latest | grep architecture
7
8# Pull a specific platform from a multi-arch image
9docker pull --platform linux/amd64 nginx:latest

Dockerfile Best Practices for Multi-Platform

dockerfile
1# Use official multi-platform base images
2FROM --platform=$TARGETPLATFORM node:20-alpine
3
4# TARGETPLATFORM and BUILDPLATFORM are automatic buildx variables
5# TARGETPLATFORM = the platform you're building FOR
6# BUILDPLATFORM = the platform you're building ON
7
8WORKDIR /app
9COPY package*.json ./
10RUN npm ci --production
11COPY . .
12EXPOSE 3000
13CMD ["node", "server.js"]

For compiled languages, handle architecture-specific builds:

dockerfile
1FROM --platform=$BUILDPLATFORM golang:1.22 AS builder
2
3# Build arguments set automatically by buildx
4ARG TARGETOS TARGETARCH
5
6WORKDIR /app
7COPY . .
8
9# Cross-compile for the target platform
10RUN CGO_ENABLED=0 GOOS=$TARGETOS GOARCH=$TARGETARCH \
11    go build -o /app/server ./cmd/server
12
13FROM --platform=$TARGETPLATFORM alpine:3.19
14COPY --from=builder /app/server /usr/local/bin/server
15CMD ["server"]

Performance Considerations

bash
1# Native builds are fastest
2# M1 Mac building for arm64 — native speed
3docker build --platform linux/arm64 -t my-app .
4
5# Cross-architecture builds use QEMU emulation — 2-10x slower
6# M1 Mac building for amd64 — emulated
7docker build --platform linux/amd64 -t my-app .

For faster CI builds, use architecture-matched runners:

  • Use runs-on: ubuntu-latest (x86) for amd64 builds
  • Use ARM-based runners (e.g., GitHub ARM runners, AWS Graviton) for arm64 builds

Common Pitfalls

  • Building on M1 without --platform: Default builds on M1 produce linux/arm64 images. Most cloud servers are linux/amd64. Always specify --platform linux/amd64 when building images intended for x86 servers.
  • Base image not available for target platform: Not all Docker images support multiple architectures. Check the image's supported platforms on Docker Hub before using FROM in a multi-platform build.
  • QEMU emulation failures: Some binaries behave differently under QEMU emulation, causing build failures that do not occur natively. If a build fails only under emulation, consider cross-compilation instead of emulation.
  • Forgetting --push with buildx multi-platform builds: docker buildx build --platform linux/amd64,linux/arm64 does not store the image locally by default. Use --push to push to a registry, or --load (single platform only) to load locally.
  • Kubernetes pulling wrong architecture: If your cluster has mixed ARM and x86 nodes, use node selectors or nodeAffinity to ensure pods run on nodes matching the image architecture, or use multi-platform images.

Summary

  • "exec format error" means the container image architecture does not match the host
  • Use --platform linux/amd64 when building on M1/ARM for deployment to x86 servers
  • Use docker buildx build --platform linux/amd64,linux/arm64 to create multi-platform images
  • Multi-platform images use a manifest list that automatically selects the correct architecture
  • Always build for the target deployment architecture in CI/CD pipelines

Course illustration
Course illustration

All Rights Reserved.