Skip to main content
DevOps Insightsguides & answers

Docker · troubleshooting

exec format error

The kernel refused to run the container's entrypoint because the file is not something it can execute here. Usually the image was built for another CPU architecture, amd64 on an arm64 node or the reverse. A script with no #! line, or a file that is not a program at all, fails the same way. Windows line endings and glibc binaries on Alpine do not cause this error; they give no such file or directory.

First published

New to this? Read what is actually happening first, then come back to the steps.

On this page · 9 sections

Before you start

  • The image name, and Docker or crane to inspect it.
  • The architecture of the machine or node that runs it: uname -m on a host, or kubectl get nodes -L kubernetes.io/arch.
  • The Dockerfile or build pipeline, because the fix is usually in how the image is built.

Run this first

step 1 of 3
kubectl logs <pod> -n <namespace> --previous

Look for exec <path>: exec format error, and which path it names

The message is written to the container's own output, so it is in the logs of the run that failed.

Work out which cause you have

A few questions to narrow the list. Every answer ends in a command that confirms or rules the cause out — this cannot see your cluster, so nothing here is a certainty until you have checked.

Narrow it down

0 answered · nothing is sent anywhere

What does file say about the path in the error, taken from the image?

docker create, docker cp, then file

Cause space

4 of 4 still possible

  • The image was built for a different CPU architectureCommon
  • The entrypoint is a script without a #! lineOccasional
  • A foreign platform was requested on a host with no emulationOccasional
  • The file at the entrypoint path is not a program at allRare

Nothing ruled out yet. Answer the question above and the branches your answer eliminates will strike through here.

Or diagnose it manually

In this order. The first command usually contains the whole answer.

  1. Step 1

    kubectl logs <pod> -n <namespace> --previous

    The message is written to the container's own output, so it is in the logs of the run that failed.

    Look for exec <path>: exec format error, and which path it names

  2. Step 2

    kubectl get nodes -L kubernetes.io/arch
    docker buildx imagetools inspect <image>

    Compares the nodes' architectures with the platforms the image was built for.

    Look for A node architecture missing from the image's Manifests list

  3. Step 3

    docker create --name inspect <image>
    docker cp inspect:<path> ./bin && docker rm inspect
    file ./bin

    Checks the file itself rather than the image's label, which says nothing about binaries copied in during the build.

    Look for x86-64 or ARM aarch64, ASCII text with no script, or something that is not a program

Every cause, and how to fix it

Ordered by how often each one turns out to be the answer.

The image was built for a different CPU architecture

Common

An image built on an amd64 laptop or CI runner is run on an arm64 node, such as Graviton or Apple Silicon, or the reverse, and the image has no build for the node's architecture. The kernel's ELF loader rejects the binary as the wrong architecture.

Confirm

docker image inspect --format '{{.Os}}/{{.Architecture}}' <image>
kubectl get nodes -L kubernetes.io/arch

Different architectures. The image's field is metadata only, so for a binary copied into the image, check the binary itself with file

Fix

  • Build a multi-platform image so each node pulls the matching build: docker buildx build --platform linux/amd64,linux/arm64 and push it.
  • For compiled languages, cross-compile in the Dockerfile instead of emulating, for example Go with GOOS and GOARCH set from the TARGETOS and TARGETARCH build arguments.
  • As a stopgap in Kubernetes, pin the pod to matching nodes with nodeSelector: kubernetes.io/arch: amd64. It hides the problem rather than fixing it.
docker buildx imagetools inspect <image>
docker buildx build --platform linux/amd64,linux/arm64 -t <image> --push .

The first lists the platforms an image in the registry was built for. The second builds both and pushes them. Docker Desktop and Engine 29 use the containerd image store, which can also hold a multi-platform image locally; the docker-container driver cannot, so push from there.

The entrypoint is a script without a #! line

Occasional

The kernel runs a script directly only if its first two bytes are #!. Without that line, or with a byte-order mark saved before it by a Windows editor, it is not a recognized format. An interactive shell would run it anyway, which is why it works when you test it by hand.

Confirm

head -c 3 entrypoint.sh | od -c

# and ! as the first two characters. Anything else means no shebang; 357 273 277 is a UTF-8 byte-order mark in front of it

Fix

  • Add a first line such as #!/bin/sh, saved without a byte-order mark.
  • Or make the shell explicit in the image: ENTRYPOINT ["/bin/sh", "/entrypoint.sh"].

A foreign platform was requested on a host with no emulation

Occasional

docker run --platform or a multi-platform build asked for an architecture the host cannot run natively, and no QEMU handler is registered. --platform only chooses which build to pull; something still has to execute it. In builds, a RUN step for the other platform fails this way.

Confirm

docker run --rm --platform linux/arm64 alpine uname -m

aarch64 means emulation works on this host. exec format error means it is not installed

Fix

  • Install emulation: docker run --privileged --rm tonistiigi/binfmt --install arm64, or in GitHub Actions use docker/setup-qemu-action before docker/setup-buildx-action. Docker Desktop includes it.
  • Better for compiled code: cross-compile on the build platform with FROM --platform=$BUILDPLATFORM, which avoids emulation, and is much faster.

The file at the entrypoint path is not a program at all

Rare

An empty file, an HTML error page saved by a failed download, or a Git LFS pointer committed instead of the real binary has neither ELF headers nor a #! line, so the kernel cannot run it.

Confirm

docker create --name inspect <image>
docker cp inspect:/app ./app && docker rm inspect
file ./app

empty, ASCII text, or HTML document instead of an ELF executable

Fix

  • Fetch the real file: run git lfs pull before the build, or fix the download URL.
  • Make the download fail the build when it fails, with curl -fsSL, and verify a checksum.

Understanding it properly

Skip this if you are mid-incident — the working part of the page is above. Worth reading afterwards, because understanding the mechanism is what stops the next one.

In plain terms

Programs are built for one kind of processor. An image built on an Intel or AMD machine will not run on an ARM machine such as an Apple Silicon Mac or an AWS Graviton server, and the other way round. When a container tries, Linux answers exec format error. The same answer comes back for a script that does not say on its first line which program should run it.

Reading what a file was built for

Run with file 5.41 on macOS, not in a container, against four sample files: minimal ELF headers for x86-64 and for arm64, a script with no #! line, and a script saved with Windows line endings. It shows how file labels each kind; compare the result with the architecture of the machine that has to run it.

Command

file app-amd64 app-arm64 entrypoint-noshebang.sh entrypoint-crlf.sh

Output

app-amd64:               ELF 64-bit LSB executable, x86-64, version 1 (SYSV)
app-arm64:               ELF 64-bit LSB executable, ARM aarch64, version 1 (SYSV)
entrypoint-noshebang.sh: ASCII text
entrypoint-crlf.sh:      POSIX shell script text executable, ASCII text, with CRLF line terminators

How to read it

  • x86-64 is what Docker and Kubernetes call amd64, and ARM aarch64 is arm64. If this does not match the node's kubernetes.io/arch label, that is the cause.
  • Real binaries add more after these fields, such as dynamically or statically linked; the architecture field is the one that matters here.
  • ASCII text with no mention of a script means the file has no #! line, so the kernel cannot run it directly: exec format error.
  • with CRLF line terminators points at a different error. The kernel looks for an interpreter literally named /bin/sh followed by a carriage return, and the message is no such file or directory.

Words used on this page

CPU architecture
The instruction set a program is compiled for. Docker, OCI and Kubernetes call the common ones amd64 and arm64; uname -m calls the same ones x86_64 and aarch64.
ENOEXEC
The Linux error behind the message. execve(2) describes it as a file not in a recognized format, for the wrong architecture, or with some other format error.
Shebang
The #! first line of a script, naming the program that runs it, such as #!/bin/sh. Without it the kernel cannot execute the script directly.
Multi-platform image
One image name pointing at builds for several architectures. Each machine pulls the one that matches it.
binfmt_misc and QEMU
A kernel feature and an emulator that together let a machine run programs built for another architecture, slowly.
What is actually happening

The container runtime starts the entrypoint with the execve system call. When the kernel has no way to execute the file, execve fails with ENOEXEC, and runc reports it in the container's own output as exec <path>: exec format error. Before runc 1.1.0, released in January 2022, the same failure read standard_init_linux.go:<line>: exec user process caused: exec format error; the line number is just runc's own source line.

Because the failure happens after the container has started, it appears in the container's logs, not as a start error. In Kubernetes the container exits with Reason: Error and goes into CrashLoopBackOff. runc's source exits with 255 from version 1.2 and with 1 in 1.1, so the exit code depends on the runtime version; it is not Docker's 126.

With an architecture mismatch, the message often names a shell script, such as exec /usr/local/bin/docker-entrypoint.sh: exec format error. The script is fine. The kernel then had to run /bin/sh for it, and /bin/sh is the binary built for the other architecture.

When Docker runs an image whose architecture differs from the host's and you did not pass --platform, it prints a warning first: The requested image's platform … does not match the detected host platform … and no specific platform was requested. It is only a warning; the container still starts and then fails, unless emulation is installed.

Three look-alikes have different errors. A #! line ending in a Windows carriage return, and a glibc-linked binary in an Alpine image, both give no such file or directory, because the interpreter or loader path does not exist. A file without the execute permission gives permission denied. None of them is fixed by rebuilding for another architecture.

How to tell this is your problem
WhereWhat you see
docker run or kubectl logs --previousexec /app: exec format error
Older runtimes, before runc 1.1.0standard_init_linux.go:228: exec user process caused: exec format error
docker run, before the errorWARNING: The requested image's platform (<image platform>) does not match the detected host platform (<host platform>) and no specific platform was requested
kubectl describe podWaiting: CrashLoopBackOff; Last State: Terminated, Reason: Error, a non-zero exit code

How to know it is actually fixed

  • The container starts and stays running, and kubectl logs shows the application's own output.
  • docker buildx imagetools inspect lists every architecture your nodes use.
  • In a mixed cluster, the pod runs on a node of each architecture: schedule one copy on each to prove it.

Stopping it happening again

  • Build and push multi-platform images in CI by default, so a new node type cannot break a deployment.
  • Add a CI check that runs the image on each target platform, for example with docker run --platform and a smoke command.
  • Commit scripts with LF line endings and a #! line, for example with a .gitattributes rule of *.sh text eol=lf.

Security considerations

  • Registering QEMU with docker run --privileged tonistiigi/binfmt changes kernel settings for the whole host. On shared CI runners use the official setup action or image, pinned, rather than an arbitrary image that asks for --privileged.
  • If the Dockerfile downloads a binary, verify its checksum in the same step. A checksum catches both a wrong-architecture download and a tampered or truncated one.
  • Do not swap in an unofficial image just because it was built for your architecture. Use the publisher's multi-platform image, or build it yourself from source.

Sources

Behaviour described here is drawn from official documentation. Where a figure could not be confirmed on an official page it is attributed in the text rather than stated as canonical.

Did this get you to an answer?

No text box on purpose — please do not paste production logs anywhere

Something wrong or out of date? Report it