How to Fix Docker 'exec format error': ARM vs x86 Architecture Mismatch
Threat/Impact Level: HIGH | Exploitability/Downtime Risk: HIGH | Time to Fix: 10 mins
TL;DR
- What broke: Your Docker image was compiled for one CPU architecture (e.g., ARM64 on an Apple M1 MacBook) and is being executed on a host with a different architecture (e.g., x86_64/amd64 on AWS EC2 or ECS). The kernel refuses to exec the binary.
- How to fix it: Rebuild using
docker buildxwith an explicit--platform linux/amd64flag, or publish a multi-arch manifest so the correct image is pulled automatically per host. - Fast path: Drop your Dockerfile or failing
docker runcommand into the Client-Side Sandbox above to get auto-refactored build commands without sending your config to a third-party server.
The Incident (What Does the Error Mean?)
Raw error — typically surfaced in container logs, ECS task stopped reason, or kubectl describe pod:
standard_init_linux.go:228: exec user process caused: exec format error
or on newer runtimes:
exec /usr/local/bin/entrypoint.sh: exec format error
Immediate consequence: The container starts, the kernel attempts to hand off execution to the binary inside the image, detects the ELF header declares an incompatible machine type, and kills the process with ENOEXEC. The container exits with code 1 immediately. No traffic is served. If this is a Kubernetes Deployment, every replica crashes in a CrashLoopBackOff. If this is ECS, the task enters STOPPED and the service fails to reach desired count.
The Attack Vector / Blast Radius
This is not a security vulnerability — it is a total availability failure. The blast radius depends on your deployment target:
- Local dev → CI/CD pipeline break: An engineer on Apple Silicon (
arm64) builds and pushes an image without specifying platform. The image tag in ECR/Docker Hub is silentlyarm64. The next CI deploy to anx86_64ECS cluster or GKE node pool pulls that tag and crashes every task/pod. - Cascading rollout failure: If
imagePullPolicy: Alwaysis not set, older running pods may stay up while new pods crash, creating a split-brain partial outage that is hard to diagnose. - Silent tag overwrite: On multi-developer teams, whoever pushes last wins. An M2 MacBook user doing
docker push myapp:latestsilently overwrites a previously workingamd64image with anarm64one. No warning. Production breaks on next deploy or pod reschedule. - Serverless / Lambda container images: AWS Lambda only runs
x86_64orarm64— whichever you configured at function creation. A mismatched image causes every invocation to fail with the same error.
How to Fix It (The Solution)
Basic Fix — Force Platform at Build Time
If you only need to target one architecture (most common case: deploying to amd64 cloud infrastructure from an Apple Silicon laptop):
- docker build -t myapp:latest .
+ docker build --platform linux/amd64 -t myapp:latest .
For docker-compose local builds targeting a remote amd64 host:
services:
app:
build:
+ platform: linux/amd64
context: .
Enterprise Best Practice — Multi-Arch Manifest with docker buildx
The correct production pattern is to publish a multi-arch image manifest so the correct variant is pulled automatically on any host architecture. This eliminates the problem permanently.
Step 1 — Create and use a buildx builder with multi-platform support:
- docker build -t registry.example.com/myapp:1.4.2 .
+ docker buildx create --name multiarch-builder --use
+ docker buildx build \
+ --platform linux/amd64,linux/arm64 \
+ --tag registry.example.com/myapp:1.4.2 \
+ --push \
+ .
Step 2 — Pin the base image platform in your Dockerfile to avoid pulling the wrong base:
- FROM node:20-alpine
+ FROM --platform=$BUILDPLATFORM node:20-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci --omit=dev
COPY . .
+ ARG TARGETPLATFORM
CMD ["node", "server.js"]
$BUILDPLATFORM is the machine running the build. $TARGETPLATFORM is the platform being compiled for. Using both correctly prevents cross-compilation issues in multi-stage builds.
Step 3 — Verify the manifest after push:
docker buildx imagetools inspect registry.example.com/myapp:1.4.2
# Should show both:
# linux/amd64
# linux/arm64
💡 Tired of pasting proprietary configs into ChatGPT? Generic AI tools log your company's ARNs, DB strings, and private keys. StackEngine is a zero-backend, pure Client-Side WASM utility. Drop your failing config into the sandbox above. We redact your secrets locally in the browser and auto-generate the refactored code using your own API key.
Prevention in CI/CD
1. Enforce platform flag in your CI build step (GitHub Actions example):
- name: Build and push multi-arch image
uses: docker/build-push-action@v5
with:
platforms: linux/amd64,linux/arm64
push: true
tags: registry.example.com/myapp:${{ github.sha }}
The docker/build-push-action handles buildx setup automatically. Never use a bare docker build in CI targeting cloud deployments.
2. Add a Hadolint rule or OPA policy to reject unplatformed base images in PRs:
# .hadolint.yaml
rules:
- id: DL3029
severity: error
# Flags: FROM without --platform in a multi-stage or cross-compile context
3. Kubernetes: Use a ValidatingAdmissionWebhook or OPA Gatekeeper constraint to reject pods whose image manifests do not include the node's architecture. Alternatively, use node affinity with kubernetes.io/arch: amd64 to hard-pin workloads to matching nodes until your images are fully multi-arch.
4. Tag discipline — never use :latest in production. Use immutable SHA-pinned tags (myapp@sha256:...) or semantic version tags built by CI. This prevents silent overwrites by a developer's local docker push.
5. Checkov policy for Dockerfiles (IaC scanning):
checkov -d . --check CKV_DOCKER_2
# Also add a custom check validating BUILDPLATFORM ARG is declared
Running Checkov in your PR pipeline catches misconfigurations before they reach a registry.