Docker

exec format error

Your container image was built for a different CPU architecture, usually arm64 on Apple Silicon running on amd64 servers. How to confirm it and build multi-arch.

medium fix7 min read

docker. The error
standard_init_linux.go:228: exec user process caused: exec format error

exec /app/server: exec format error

no matching manifest for linux/amd64 in the manifest list entries

Do this first3 steps

Run these in order. Each one tells you what its output means before you change anything.

  1. 1

    Compare the image architecture against the machine

    docker image inspect myapp:1.0 --format '{{.Architecture}}/{{.Os}}' && uname -m

    arm64 against x86_64, or the reverse, is the whole bug. An image built on an Apple Silicon Mac and run on an ordinary cloud node is by far the most common path to this.

  2. 2

    On Kubernetes, check what architectures the nodes are

    kubectl get nodes -o custom-columns=NAME:.metadata.name,ARCH:.status.nodeInfo.architecture

    A mixed-architecture cluster schedules the Pod anywhere, so the same image works on some nodes and fails on others, which reads as a flaky deploy rather than a build problem.

  3. 3

    Build for every architecture you actually run on

    docker buildx build --platform linux/amd64,linux/arm64 -t ghcr.io/acme/payments-api:1.4.2 --push .

    A multi-arch manifest fixes it for every consumer at once. If you only ever deploy to one target, docker build --platform linux/amd64 is enough.

All 10 sections

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

MessageWhere it surfaces
exec user process caused: exec format errorContainer starts then immediately dies
exec /app/server: exec format errorSame, newer runtime wording
no matching manifest for linux/amd64Pull 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

  1. docker image inspect <image> --format '{{.Architecture}}'. What is it?
  2. uname -m or kubectl get nodes -o custom-columns=...ARCH. What do you need?
  3. Mismatch → build with --platform linux/amd64, or multi-arch with buildx.
  4. Multi-arch builds need --push; --load cannot hold multiple platforms.
  5. docker buildx imagetools inspect to confirm both platforms are published.
  6. Mixed-architecture cluster → publish multi-arch, or pin with a kubernetes.io/arch node selector.
  7. 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.

Learn the underlying concept

Other Docker errors