# Fix Docker "exec format error" (Architecture Mismatch)

URL: https://www.devopsness.com/blog/fix-docker-exec-format-error
Author: Kiril Urbonas
Published: 2026-07-11
Updated: 2026-10-03
Categories: DevOps
Tags: Docker, Containers, DevOps, Debugging, Build

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

---
# Fix Docker "exec format error" (Architecture Mismatch)

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:

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
docker image inspect myapp:latest --format '{{.Architecture}} / {{.Os}}'
# amd64 / linux    or    arm64 / linux
```

Check what your host actually is:

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

And the platform Docker itself reports:

```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
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
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
docker buildx create --name multi --use --bootstrap
```

Then build for both and push in one shot:

```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
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
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](/blog/docker-error-troubleshooting-guide) guide maps the rest.

## Sources

- [Docker Docs: Multi-platform builds](https://docs.docker.com/build/building/multi-platform/)
- [Docker Docs: docker buildx build reference (--platform, --load, --push)](https://docs.docker.com/reference/cli/docker/buildx/build/)
- [execve(2) Linux manual page: ENOEXEC and ENOENT](https://man7.org/linux/man-pages/man2/execve.2.html)
- [tonistiigi/binfmt: QEMU emulator installer](https://github.com/tonistiigi/binfmt)

