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 errorOr, when the failing file is named:
exec /usr/local/bin/docker-entrypoint.sh: exec format errorThe 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 -mprintsx86_64(amd64) oraarch64/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, setDOCKER_DEFAULT_PLATFORM=linux/amd64so every build matches. - If both are the same architecture, inspect the entrypoint instead: a
docker-entrypoint.shsaved with Windows CRLF endings or a missing#!shebang fails the same way. - On an Apple Silicon Mac, check
docker context ls; a remoteamd64VM 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:
- A single-platform image on the other architecture. A CI runner on
linux/amd64pushedmyapp:1.4.2; a developer on an arm64 Mac (or an arm64 node) pulls and runs it. - Native dependencies built on the wrong host. A base image can be multi-arch while the code inside is not. An arm64
node_modulesfolder (or a Go/Rust binary) bind-mounted orCOPYed into an amd64 image fails the same way, because the compiled.nodefile or binary is the wrong arch. - 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\nline endings, makes the kernel read#!/bin/sh\ras 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}}'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/amd64If 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 .--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 .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 .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'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, addplatform: linux/amd64under the service while you migrate. - In CI, build on the same architecture you deploy to and pass
--platformexplicitly. - 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 entersCrashLoopBackOffwith 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 oklinux/amd64
okIn 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=20Interview 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
- Docker interview questions and Docker MCQs
- Docker images versus running containers
- Docker ENTRYPOINT and CMD
- Docker multi-stage builds
- Docker image tags and digests
- Kubernetes ImagePullBackOff, where a
no match for platformevent names the same architecture problem - Official: docker buildx build and Multi-platform images