Ch. 13 · Docker

Docker 'exec format error': When the Image CPU Does Not Match

Fix 'exec format error' in Docker: confirm the CPU architecture mismatch with uname and docker inspect, then rebuild with the right --platform or buildx target.

~6 min readintermediateupdated Oct 4, 2026

A container that worked on a colleague’s laptop starts on yours and dies at once:

standard_init_linux.go:228: exec user process caused: exec format error
Text

Or, when the failing file is named:

exec /usr/local/bin/docker-entrypoint.sh: exec format error
Text

The kernel did read the file, but the ELF header says it was compiled for a different instruction set than the CPU it is running on now. This is an execve failure (ENOEXEC) from the kernel, not an application or shell error, and it is one of the reasons an image that pulls successfully can still crash-loop in Kubernetes.

Quick fix checklist

  • Check the host: uname -m prints x86_64 (amd64) or aarch64/arm64.
  • Check the image: docker image inspect <image> --format '{{.Architecture}}/{{.Os}}'.
  • If they differ, rebuild for the host or the deployment target: docker buildx build --platform linux/amd64 -t <image> .
  • For Compose or plain docker build, set DOCKER_DEFAULT_PLATFORM=linux/amd64 so every build matches.
  • If both are the same architecture, inspect the entrypoint instead: a docker-entrypoint.sh saved with Windows CRLF endings or a missing #! shebang fails the same way.
  • On an Apple Silicon Mac, check docker context ls; a remote amd64 VM context runs amd64 images natively and hides the problem locally.

Before you start

You need the full error line (it names the binary), the host architecture (uname -m) and the image architecture (docker image inspect). The examples were checked against Docker Engine 27/28 and Docker Desktop; docker buildx is bundled with the CLI. If you are on an arm64 machine pulling a public amd64-only image, the fix is usually to rebuild rather than to enable emulation, because emulation is slow and hides the portability bug.

Why it happens

When you run a container, the kernel executes a file with execve(). The first bytes of an ELF executable encode the target machine (EM_X86_64, EM_AARCH64, and so on). If that value does not match the CPU, the kernel returns ENOEXEC and the runtime prints exec format error. Nothing is “installed wrong”: the binary is simply compiled for a CPU the machine does not have.

Three situations produce it:

  1. A single-platform image on the other architecture. A CI runner on linux/amd64 pushed myapp:1.4.2; a developer on an arm64 Mac (or an arm64 node) pulls and runs it.
  2. Native dependencies built on the wrong host. A base image can be multi-arch while the code inside is not. An arm64 node_modules folder (or a Go/Rust binary) bind-mounted or COPYed into an amd64 image fails the same way, because the compiled .node file or binary is the wrong arch.
  3. A script, not the image. If the error names a shell script, the script itself is being executed as a program. A shebang missing its first two bytes, or a script saved with \r\n line endings, makes the kernel read #!/bin/sh\r as an unknown interpreter.

The distinction matters: cases 1 and 2 mean the image is wrong for the host, while case 3 means the image is right but one file inside it is malformed.

Step-by-step walkthrough

Step 1: Confirm the host architecture

uname -m                 # Linux/macOS host
docker info --format '{{.OSType}}/{{.Architecture}}'
Terminal

On a Mac the first line is your macOS architecture; the docker info line is the architecture of the Docker VM or remote daemon. They can differ, so use the docker info value when you build or run.

Step 2: Confirm the image architecture

docker image inspect myapp:1.4.2 --format '{{.Os}}/{{.Architecture}}'
# linux/amd64
Terminal

If that does not match docker info, you have found the cause.

Step 3: Rebuild for the right target

docker buildx build --platform linux/amd64 -t myapp:1.4.2 .
Terminal

--platform sets the target architecture for the whole build, including every FROM pull. To make it the default for a session or CI job:

export DOCKER_DEFAULT_PLATFORM=linux/amd64
docker build -t myapp:1.4.2 .
Terminal

Build a multi-arch image once and let each host pull its own variant:

docker buildx build --platform linux/amd64,linux/arm64 -t registry.example.com/myapp:1.4.2 --push .
Terminal

Step 4: Rule out the entrypoint file

If uname -m and the image architecture already match, open the file the error names:

docker run --rm --entrypoint sh myapp:1.4.2 -c 'head -n 1 /usr/local/bin/docker-entrypoint.sh | cat -A'
Terminal

A trailing ^M$ on the #! line is a CRLF ending; a missing #! at the start is a script with no interpreter. Fix the editor (add a .gitattributes with *.sh text eol=lf) and rebuild so the shebang is #!/bin/sh.

Step 5: Make the platform explicit everywhere

  • In docker-compose.yml, add platform: linux/amd64 under the service while you migrate.
  • In CI, build on the same architecture you deploy to and pass --platform explicitly.
  • In Kubernetes, if you must run a foreign-arch image, the node needs binfmt_misc/QEMU registered (Docker Desktop does this automatically); otherwise the Pod enters CrashLoopBackOff with this error in its logs.

Worked scenario

A team switches CI to arm64 runners to save money. The image now builds linux/arm64, but production nodes are amd64. Pods pull fine, then restart with exec format error in the logs. docker image inspect on the built tag shows linux/arm64; kubectl get nodes -o wide shows amd64 on every node. The fix is to build --platform linux/amd64 (or push a multi-arch manifest with buildx --platform linux/amd64,linux/arm64 --push). Until that ships, running the new image locally with docker run --platform linux/amd64 reproduces the same failure, confirming it is the build target and not the node.

Common mistake

The reflex fix is to enable QEMU emulation on the host and move on. That works, but every instruction is translated: builds and test runs become several times slower, and the underlying portability bug (an arm64-only base or a baked-in native module) stays hidden until a real arm64 node hits it. Emulation is a bridge for testing, not a substitute for building for the target architecture.

Verify the behavior

After rebuilding, the same tag should report the target architecture and start:

docker image inspect myapp:1.4.2 --format '{{.Os}}/{{.Architecture}}'
docker run --rm myapp:1.4.2 echo ok
Terminal
linux/amd64
ok
Text

In Kubernetes, confirm the Pod stays Running and its logs show real application output instead of the exec line:

kubectl get pods -n shop -l app=myapp
kubectl logs -n shop deploy/myapp --tail=20
Terminal

Interview exercise

“An image builds and pushes without errors, runs on every developer’s machine, but the Kubernetes Pods it deploys to keep crashing with exec format error. Where do you look first, and how do you stop it recurring?”

Answer and reasoning

Two machines can agree on everything except CPU architecture, so I would compare the image’s manifest with the node’s architecture before reading application code. docker image inspect <tag> --format '{{.Architecture}}' (or docker manifest inspect) gives the image side; kubectl get nodes -o wide (and uname -m on a node) gives the node side. A mismatch explains a faultless build that fails only at run time. The recurring fix is to build for the deploy target: pass --platform in CI, or publish a multi-arch image so each node pulls the right variant, and assert the platform in the pipeline. Emulation on the nodes would only mask it and slow everything down.

Continue learning

More in Docker

esc