Fix Docker "exec format error" (Architecture Mismatch)
The container starts, then dies with "exec format error." Here's why the CPU architecture is wrong, how to confirm it, and how to build images that just run.
Key takeaways
- Docker exec format error means the kernel cannot run the container's entrypoint, almost always because the image was built for another CPU, such as an arm64 image from an Apple Silicon Mac on an amd64 server.
- Compare docker image inspect against uname -m, then rebuild with docker buildx build --platform linux/amd64,linux/arm64.
On this page
Docker's "exec format error" means the kernel refused to run the container's entrypoint, and the cause is almost always an architecture mismatch: an arm64 image on an amd64 host, or the reverse. Confirm with docker image inspect --format '{{.Architecture}}' against uname -m, then rebuild multi-arch with docker buildx build --platform linux/amd64,linux/arm64.
exec /usr/local/bin/app: exec format error
The four causes, with the one-line check for each:
- Architecture mismatch (by far the most common): image arch differs from
uname -m. - Missing shebang on a script entrypoint:
head -1 entrypoint.shshows no#!. - UTF-8 byte order mark before the
#!:file entrypoint.shreports "with BOM". - Wrong-format binary copied in from the build host:
file /usr/local/bin/appshows the wrong machine.
This got a lot more common once laptops moved to Apple Silicon. Your laptop is arm64, your production servers are probably amd64 (x86_64). You build locally, it runs on your machine, and the deploy box refuses to execute it.
What "exec format error" actually means
When the kernel runs a binary, it reads the file header to learn the instruction set. If the header says ARM and the CPU speaks x86, execve(2) fails with ENOEXEC, which the man page describes as an executable "not in a recognized format, is for the wrong architecture". Docker passes that message straight through. It's a kernel error; Docker is the messenger.
Diagnosis: confirm it's the architecture
Before you change anything, prove the mismatch. Three commands settle it.
Check what the image was built for:
docker image inspect myapp:latest --format '{{.Architecture}} / {{.Os}}'
# amd64 / linux or arm64 / linux
Check what your host actually is:
uname -m
# x86_64 -> you need amd64 images
# aarch64 -> you need arm64 images
And the platform Docker itself reports:
docker version --format '{{.Server.Arch}}'
If the image says arm64 and uname -m says x86_64, that's your whole problem right there. No further debugging needed.
For a multi-arch image already in a registry, you can see every variant it ships without pulling it:
docker buildx imagetools inspect myorg/myapp:latest
If your platform is missing from that list, the image was never built for you.
Fix 1: pull and run the right platform
If a correct build exists in the registry, ask for it explicitly:
docker pull --platform linux/amd64 myorg/myapp:latest
docker run --platform linux/amd64 myorg/myapp:latest
This is the quick unblock, but treat it as a band-aid. If you have to hand --platform to every command, the real fix is building the image properly.
Fix 2: build multi-arch with buildx (the real fix)
The right answer is to publish one image tag that carries both architectures, so nobody ever has to think about it again. Docker Buildx does this in a single command.
Set up a builder once:
docker buildx create --name multi --use --bootstrap
Then build for both and push in one shot:
docker buildx build \
--platform linux/amd64,linux/arm64 \
-t myorg/myapp:latest \
--push .
That publishes a manifest list. When someone on an arm64 box pulls myapp:latest, they transparently get the arm64 layers; the amd64 server gets amd64. Same tag, correct binary, zero flags at runtime.
One catch: where the result can live depends on your image store. With a docker-container builder like the one created above, --load only handles a single-platform result, so push multi-arch builds straight to the registry. Docker Desktop and Docker Engine 29.0+ default to the containerd image store, which can hold multi-platform images locally (as of October 2026). Older engines upgraded in place may still use the classic store.
Fix 3: QEMU emulation when you must build the other arch locally
If your builder is amd64 but you need to produce arm64 layers (or vice versa), install the binfmt handlers so QEMU can emulate the foreign architecture during the build:
docker run --privileged --rm tonistiigi/binfmt --install all
After that, buildx can cross-build both platforms from one machine. It's slower because the foreign side runs under emulation, but it works. This is what most single-runner CI setups quietly rely on.
Fix 4: pin your base image to the right arch
Sometimes the mismatch sneaks in through the base image. A FROM line without a digest can resolve to whatever the local daemon cached, which may not match what CI produces. If you're pinning, pin to a manifest that actually covers your targets, and let buildx pick the platform rather than hardcoding an arch-specific tag like arm64v8/node. Arch-specific base tags are the classic way to accidentally lock an image to one platform forever.
The variant that isn't about CPU: scripts
If your entrypoint is a shell script, the kernel reads the first two bytes looking for #!. When they aren't there, the script has no interpreter and execve returns the same ENOEXEC:
- No shebang at all. The file starts straight into commands. A shell would guess
sh; Docker's runtime callsexecvedirectly and does not. - A UTF-8 BOM saved by a Windows editor puts three invisible bytes before
#!, so the kernel doesn't recognize it.
Two look-alikes produce a different error, exec /entrypoint.sh: no such file or directory, because the interpreter named in the shebang doesn't exist (ENOENT):
- Windows line endings.
#!/bin/sh\rasks for an interpreter literally namedsh\r. Rundos2unixor set*.sh text eol=lfin.gitattributes. #!/bin/bashon Alpine, which ships only/bin/shunless you install bash.
head -1 /entrypoint.sh # is the shebang there and sane?
file /entrypoint.sh # look for "with BOM" or "CRLF line terminators"
If the image architecture matches your host, look at the script.
Questions people ask
How do I fix exec format error on an Apple Silicon Mac?
Your Mac builds linux/arm64 images by default, and an amd64 server cannot execute them. Build for the server with docker buildx build --platform linux/amd64 -t myorg/myapp --push ., or for both with --platform linux/amd64,linux/arm64. Docker Desktop ships QEMU emulation, so the cross-build works on the Mac, just slower than a native build.
Can I run an amd64 image on an arm64 host anyway?
Yes, through emulation. Register QEMU handlers with docker run --privileged --rm tonistiigi/binfmt --install all, then start the container with docker run --platform linux/amd64 IMAGE. It works for testing and CI, but expect emulated workloads to run noticeably slower than native ones, so publish a native arm64 variant for anything long-running.
Why does my entrypoint script give exec format error?
Because the kernel could not find #! in the first two bytes of the file. Either the script has no shebang, or an editor saved it with a UTF-8 byte order mark in front of it. Add #!/bin/sh as line one, re-save without a BOM, and rebuild. If the error says "no such file or directory" instead, check for CRLF line endings.
The call we'd make
Stop building single-arch images by hand. The moment your team has even one Apple Silicon laptop and one x86 server, single-arch is a trap that will bite you on a Friday deploy. Move the buildx --platform linux/amd64,linux/arm64 --push build into CI so every published tag is multi-arch by default, and drop --platform from your run commands entirely. Runtime flags papering over a build problem are debt, and this is one of the cheapest debts to pay off. If you hit other variants of the same class of failure, our Docker troubleshooting guide maps the rest.
Sources
Kubernetes FailedScheduling — Taints, Affinity, and Resources
A pod sits Pending and the scheduler quietly logs why it rejected every node. Learn to read that one line and fix the real cause instead of guessing.
GitLab CI/CD Best Practices in 2026: Pipelines You Can Trust
A production-focused GitLab CI/CD guide: include/extends templates, rules and workflow, needs DAGs, cache vs artifacts, protected environments, masked/protected variables and Vault, built-in security scanning, and review apps — with copy-paste examples.
More from DevOps
Explore more articles in this category
GitHub Copilot CLI Sandboxing Is GA: What Actually Changed
GitHub made local sandboxing generally available for Copilot CLI on October 7, so the default now restricts the agent instead of trusting it.
The Tensorlake npm Compromise Targeted Your AI Tool Configs
A compromised npm release went after cloud credentials as expected, and local AI coding-assistant config files, which most teams never treated as secrets.
GitLab's New Rate Limits: What to Fix Before Oct 19
GitLab is capping unauthenticated API calls at 60 an hour starting October 19, and the preview windows land before most teams will have noticed.
You might have missed
Evergreen posts worth revisiting.