exec format error means the kernel was handed a binary it cannot execute. In containers this is almost always a CPU architecture mismatch. An arm64 image running on amd64 hardware, or the reverse.
It became common the moment Apple Silicon Macs did. Build an image on an M-series Mac, push it, deploy to an amd64 server, and this is what you get.
Confirm the architecture
Check what the image actually is:
docker image inspect myapp:1.0 --format '{{.Architecture}}/{{.Os}}'
arm64/linux
Then check what the target expects:
# Your machine
uname -m # arm64 or x86_64
# A Kubernetes node
kubectl get nodes -o custom-columns=\
NAME:.metadata.name,ARCH:.status.nodeInfo.architecture
NAME ARCH
ip-10-0-1-42 amd64
arm64 image, amd64 node. That is the whole problem.
For an image in a registry, inspect the manifest without pulling it:
docker manifest inspect ghcr.io/acme/payments-api:1.4.2 \
| grep -A2 platform
A multi-architecture image lists several entries. A single-architecture one lists exactly one, and if that one is not your target, you have found it.
The three variants of this error
| Message | Where it surfaces |
|---|---|
exec user process caused: exec format error | Container starts then immediately dies |
exec /app/server: exec format error | Same, newer runtime wording |
no matching manifest for linux/amd64 | Pull fails: the registry has no build for your platform |
The third is the friendlier one, because the registry noticed before anything ran. The first two mean the image was pulled successfully and only failed when the kernel tried to execute the binary.
Fix 1: Build for the target platform
If you only need one architecture, name it explicitly:
docker build --platform linux/amd64 -t myapp:1.0 .
docker push myapp:1.0
On Apple Silicon this uses emulation, so the build is noticeably slower, often several times slower for a compile-heavy image. It works, and for occasional builds it is the simplest answer.
Note that --platform on docker build requires BuildKit, which is the default in current Docker versions.
Fix 2: Build multi-architecture with buildx
The better answer for anything shared. One image reference serves both architectures, and each host pulls the variant it needs.
# One-time setup
docker buildx create --name multiarch --use
docker buildx inspect --bootstrap
# Build and push both at once
docker buildx build \
--platform linux/amd64,linux/arm64 \
-t ghcr.io/acme/payments-api:1.4.2 \
--push .
--push is required for multi-platform builds. The local Docker image store cannot hold a multi-arch manifest, so --load only works for a single platform. Trying to build multi-arch without pushing fails with a message about the docker exporter not supporting multiple platforms, which is confusing until you know why.
Verify the result:
docker buildx imagetools inspect ghcr.io/acme/payments-api:1.4.2
Manifests:
Platform: linux/amd64
Platform: linux/arm64
Fix 3: Pin the base image platform in the Dockerfile
For a Dockerfile that must always produce one architecture regardless of where it is built:
FROM --platform=linux/amd64 node:22-alpine
Use this sparingly. It defeats multi-arch builds entirely, since every stage is forced to one platform, which is occasionally what you want and usually not.
A more useful pattern uses BuildKit's automatic platform arguments, so a cross-compiling build knows both what it is running on and what it is building for:
FROM --platform=$BUILDPLATFORM golang:1.23 AS build
ARG TARGETOS TARGETARCH
WORKDIR /src
COPY . .
RUN CGO_ENABLED=0 GOOS=$TARGETOS GOARCH=$TARGETARCH go build -o /out/server .
FROM alpine:3.20
COPY --from=build /out/server /server
CMD ["/server"]
The build stage runs natively on the builder, fast, and cross-compiles to the target. For Go and Rust this avoids emulation entirely and is dramatically quicker than the --platform approach.
In CI
GitHub Actions and GitLab runners are amd64 by default, so an image built there runs on amd64 nodes and fails on Graviton or Apple Silicon.
# GitHub Actions
- uses: docker/setup-qemu-action@v3
- uses: docker/setup-buildx-action@v3
- uses: docker/build-push-action@v6
with:
platforms: linux/amd64,linux/arm64
push: true
tags: ghcr.io/acme/payments-api:${{ github.sha }}
setup-qemu-action provides the emulation needed to build the non-native architecture. Expect the build to take meaningfully longer, which is a real argument for cross-compilation where your language supports it.
On Kubernetes with mixed nodes
A cluster with both amd64 and Graviton nodes will schedule Pods onto either, so a single-architecture image fails intermittently. Working on some nodes and crash-looping on others, which is a genuinely confusing symptom.
Either publish multi-arch images, or constrain scheduling while you sort it out:
spec:
nodeSelector:
kubernetes.io/arch: amd64
The node selector is a workaround. Multi-arch images are the fix, and they are what let you adopt Graviton's roughly 20% cost saving without maintaining two pipelines.
Not an architecture problem?
Occasionally exec format error means something else:
A script with no shebang. A file executed directly needs #!/bin/sh on the first line, or the kernel has no idea how to run it.
Windows line endings. A script saved with CRLF has \r at the end of the shebang, so the kernel looks for an interpreter literally named /bin/sh\r. The resulting error sometimes appears as no such file or directory naming a path that clearly exists, which is maddening.
file entrypoint.sh
# entrypoint.sh: POSIX shell script, ASCII text, with CRLF line terminators
sed -i 's/\r$//' entrypoint.sh
Add a .gitattributes with *.sh text eol=lf to stop it recurring on mixed-OS teams.
A checklist
docker image inspect <image> --format '{{.Architecture}}'. What is it?uname -morkubectl get nodes -o custom-columns=...ARCH. What do you need?- Mismatch → build with
--platform linux/amd64, or multi-arch with buildx. - Multi-arch builds need
--push;--loadcannot hold multiple platforms. docker buildx imagetools inspectto confirm both platforms are published.- Mixed-architecture cluster → publish multi-arch, or pin with a
kubernetes.io/archnode selector. - Architecture matches → check the script has a shebang and LF line endings.
Frequently Asked Questions
What causes "exec format error" in Docker?
The kernel was given a binary compiled for a different CPU architecture. Most often an arm64 image built on an Apple Silicon Mac being run on amd64 servers. Confirm with docker image inspect <image> --format '{{.Architecture}}' and compare against your target, which is uname -m locally or the architecture field on your Kubernetes nodes. The fix is building for the right platform, or publishing a multi-architecture image that serves both.
How do I build a Docker image for amd64 on an Apple Silicon Mac?
docker build --platform linux/amd64 -t myapp:1.0 . builds for the target using emulation, which works but is noticeably slower, sometimes several times slower for compile-heavy images. For anything shared, use docker buildx build --platform linux/amd64,linux/arm64 --push to publish a multi-architecture image instead, so each host pulls the variant it needs and you are not choosing one architecture at the expense of the other.
Why does my multi-platform buildx build fail without --push?
Because the local Docker image store cannot hold a multi-architecture manifest. It stores one image per tag. --load therefore only works when building a single platform. A multi-platform build must go straight to a registry with --push, which is where a manifest list can exist. If you need the image locally for testing, build just your own platform with --load, and build both with --push when publishing.
What does "no matching manifest for linux/amd64" mean?
The registry holds the image but has no build for your platform. It is the same architecture mismatch caught earlier, at pull time rather than at execution, which is why the message is more helpful than a bare exec format error. Check what the image actually publishes with docker manifest inspect <image>, and either build the missing architecture or pick a base image that publishes it. Most official images are multi-arch; smaller third-party ones frequently are not.
How do I handle mixed amd64 and Graviton nodes in Kubernetes?
Publish multi-architecture images, which lets any node pull the variant it needs and is what makes adopting Graviton's roughly 20% saving practical. Until then, a single-architecture image on a mixed cluster fails intermittently. Running on some nodes and crash-looping on others as the scheduler places Pods, which is a confusing symptom. As a stopgap, constrain scheduling with nodeSelector: kubernetes.io/arch: amd64, but treat that as temporary rather than the solution.
I get exec format error but the architecture matches. What else causes it?
Two things. A script executed directly with no shebang line, so the kernel does not know which interpreter to use. Add #!/bin/sh as the first line. Or Windows CRLF line endings, where the trailing \r makes the kernel look for an interpreter literally named /bin/sh\r; this sometimes surfaces as no such file or directory naming a path that obviously exists. Check with file script.sh, fix with sed -i 's/\r$//', and add *.sh text eol=lf to .gitattributes.