Skip to main content
Fix Docker "exec format error" (Architecture Mismatch)

Fix Docker "exec format error" (Architecture Mismatch)

KU
• 7 min read•Updated •50 views

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.

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.

code
exec /usr/local/bin/app: exec format error

The four causes, with the one-line check for each:

  1. Architecture mismatch (by far the most common): image arch differs from uname -m.
  2. Missing shebang on a script entrypoint: head -1 entrypoint.sh shows no #!.
  3. UTF-8 byte order mark before the #!: file entrypoint.sh reports "with BOM".
  4. Wrong-format binary copied in from the build host: file /usr/local/bin/app shows 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:

bash.bash
docker image inspect myapp:latest --format '{{.Architecture}} / {{.Os}}'
# amd64 / linux    or    arm64 / linux

Check what your host actually is:

bash.bash
uname -m
# x86_64   -> you need amd64 images
# aarch64  -> you need arm64 images

And the platform Docker itself reports:

bash.bash
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:

bash.bash
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:

bash.bash
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:

bash.bash
docker buildx create --name multi --use --bootstrap

Then build for both and push in one shot:

bash.bash
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:

bash.bash
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 calls execve directly 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\r asks for an interpreter literally named sh\r. Run dos2unix or set *.sh text eol=lf in .gitattributes.
  • #!/bin/bash on Alpine, which ships only /bin/sh unless you install bash.
bash.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

Explore topics:DockerDevOps
React
Share this post
KU

Kiril Urbonas

AI Engineer

553 articles
View all articles by Kiril Urbonas

You might have missed

Evergreen posts worth revisiting.