Skip to content

Fix: Docker exec format error

FixDevs · (Updated: )

Part of:  Docker, DevOps & Infrastructure

Quick Answer

How to fix Docker exec format error caused by architecture mismatch (ARM vs x86), missing shebang line, wrong entrypoint, and multi-platform build issues.

The Error

You run a Docker container and get:

exec /usr/local/bin/docker-entrypoint.sh: exec format error

Or variations:

standard_init_linux.go:228: exec user process caused: exec format error
exec /app/start.sh: exec format error
exec /usr/bin/python3: exec format error

The container starts but immediately exits with this error. Docker cannot execute the entrypoint or command because the binary format is incompatible with the container’s architecture or the script is malformed.

Why This Happens

The kernel raises ENOEXEC (translated by Docker as “exec format error”) whenever it is asked to run a file whose first bytes it cannot interpret. For ELF binaries this means the architecture tag in the ELF header does not match the host CPU. For shell scripts it means the shebang line is missing, points to a non-existent interpreter, or has been mangled by line-ending conversion.

Containers do not change CPU architecture. A container image built on Apple Silicon defaults to linux/arm64, and that image cannot run on a linux/amd64 host unless QEMU or Rosetta is configured to emulate it. When you pull an image from a registry, Docker normally picks the variant that matches the host, but you can override this with --platform, which is the most common way to end up running an incompatible binary.

The script case is more subtle. The kernel looks at the first two bytes (#!) and reads the rest of the line as the path to the interpreter. If \r is present (Windows line endings), the kernel will look for /bin/bash\r rather than /bin/bash, and that path does not exist. The error message points at the script itself rather than at the interpreter, which is what makes this so confusing.

Two main causes:

  1. Architecture mismatch. The image was built for a different CPU architecture. An image built for linux/amd64 (Intel/AMD) cannot run on linux/arm64 (Apple Silicon, AWS Graviton) and vice versa.

  2. Missing or wrong shebang line. A shell script used as the entrypoint does not have a proper #!/bin/bash or #!/bin/sh line at the top, so the kernel does not know how to execute it.

Less common causes:

  • Windows line endings in scripts. \r\n line endings in shell scripts cause the shebang interpreter to not be found.
  • Binary compiled for wrong OS. A macOS or Windows binary inside a Linux container.
  • Corrupted binary. The executable file is damaged.

Platform and Environment Differences

The same image fails on one host and runs on another depending on how the local Docker engine handles cross-architecture execution. Knowing what your environment does by default is the fastest way to diagnose the error.

Apple Silicon Macs (M1/M2/M3/M4) run linux/arm64 natively. To run linux/amd64 images, Docker Desktop ships two emulation paths: QEMU (the default) and Rosetta. Rosetta is faster and more compatible with glibc-based binaries, and you enable it under Settings then Features in development then “Use Rosetta for x86_64/amd64 emulation on Apple Silicon.” If Rosetta is off, certain JIT-heavy workloads (Node native modules, Java, ARM-unfriendly C extensions) fail with exec format errors or segfaults rather than running slowly.

Linux hosts rely on binfmt_misc to route foreign-architecture binaries to QEMU. The kernel only does this when the appropriate handler is registered, usually by running docker run --privileged --rm tonistiigi/binfmt --install all. Without that, an arm64 image on an amd64 host fails immediately. CI images on Ubuntu runners often have the handlers preinstalled, but custom self-hosted runners typically do not.

Windows hosts with Docker Desktop and WSL2 run a Linux VM in the background. The architecture of that VM is the same as the host CPU, so amd64 images run natively. Path translation between the Windows host and the WSL2 VM does not affect exec directly, but mounted scripts cross filesystem boundaries and pick up \r\n line endings if Git’s core.autocrlf is left at the default true. Always verify scripts inside the container with file rather than trusting their appearance on the host.

docker buildx and multi-platform builds create a manifest list that points at multiple architecture-specific images. When you pull python:3.12-slim on Apple Silicon, the manifest tells Docker to fetch the arm64 variant. When you build with --platform linux/amd64,linux/arm64 --push, you get both. Building only one platform and deploying to the other is the single largest source of this error in production.

Alpine Linux is the other recurring trap. Alpine ships only BusyBox ash and does not include bash unless you apk add --no-cache bash. A script with #!/bin/bash copied into an Alpine image gives the same exec format error even though both the architecture and the script body are correct, because the interpreter does not exist on disk.

Fix 1: Fix Architecture Mismatch

Check the image’s architecture:

docker inspect --format='{{.Architecture}}' my-image

Check your host’s architecture:

uname -m
# x86_64 = amd64
# aarch64 = arm64

Common mismatch: Building on an Apple Silicon Mac (arm64) and deploying to an x86_64 server, or vice versa.

Fix: Build for the target architecture:

# Build for amd64 (Intel/AMD servers)
docker build --platform linux/amd64 -t my-app .

# Build for arm64 (Apple Silicon, AWS Graviton)
docker build --platform linux/arm64 -t my-app .

Fix: Build multi-platform images:

# Create a builder that supports multiple platforms
docker buildx create --use

# Build for both architectures
docker buildx build --platform linux/amd64,linux/arm64 -t my-app --push .

This creates a manifest that includes images for both architectures. Docker automatically pulls the correct one.

Pro Tip: Always use --platform linux/amd64 in CI/CD pipelines that deploy to x86_64 servers, even if the CI runner is ARM-based. This prevents architecture mismatch surprises in production.

Fix 2: Add the Shebang Line

Shell scripts must start with a shebang line that tells the kernel which interpreter to use:

Broken — missing shebang:

# start.sh
echo "Starting app..."
exec python3 app.py

Fixed:

#!/bin/bash
echo "Starting app..."
exec python3 app.py

Or for maximum portability:

#!/bin/sh
echo "Starting app..."
exec python3 app.py

In Alpine-based images, use /bin/sh:

Alpine uses ash (BusyBox), not bash. If your script starts with #!/bin/bash but the image does not have bash:

exec /app/start.sh: exec format error

Fix: Use #!/bin/sh or install bash:

# Option 1: Use sh
# In start.sh: #!/bin/sh

# Option 2: Install bash
RUN apk add --no-cache bash

For a related problem where the entrypoint file is missing entirely, see Fix: Docker entrypoint not found.

Fix 3: Fix Windows Line Endings (CRLF)

If you develop on Windows, scripts might have \r\n (CRLF) line endings. The kernel reads the shebang as #!/bin/bash\r and tries to find an interpreter called bash\r — which does not exist.

Fix: Convert line endings in the Dockerfile:

COPY start.sh /app/start.sh
RUN sed -i 's/\r$//' /app/start.sh && chmod +x /app/start.sh

Fix: Convert before building:

# Linux/macOS
sed -i 's/\r$//' start.sh

# Or using dos2unix
dos2unix start.sh

Fix: Use .gitattributes to prevent CRLF in the repo:

*.sh text eol=lf
Dockerfile text eol=lf

Common Mistake: Building Docker images on Windows without configuring line endings. Git on Windows converts \n to \r\n by default (core.autocrlf=true). This silently corrupts shell scripts. Always set eol=lf in .gitattributes for scripts and Dockerfiles.

Fix 4: Fix the Entrypoint in Dockerfile

A wrong entrypoint format can cause exec format error:

Broken — shell form without shebang:

COPY app.py /app/
ENTRYPOINT python3 /app/app.py

If Docker uses exec form internally and python3 is not found in the expected location, you get the error.

Fixed — exec form (preferred):

ENTRYPOINT ["python3", "/app/app.py"]

Fixed — shell form with explicit shell:

ENTRYPOINT ["/bin/sh", "-c", "python3 /app/app.py"]

For custom scripts:

COPY start.sh /app/start.sh
RUN chmod +x /app/start.sh
ENTRYPOINT ["/app/start.sh"]

Make sure start.sh has the shebang line and correct line endings.

Fix 5: Fix Docker Compose Platform

Specify the platform in docker-compose.yml:

services:
  app:
    image: my-app:latest
    platform: linux/amd64

Or build with the platform:

services:
  app:
    build:
      context: .
      dockerfile: Dockerfile
      platforms:
        - linux/amd64

This is especially important on Apple Silicon Macs where Docker defaults to linux/arm64, but many images only support linux/amd64.

Fix 6: Fix Python/Node Binary Issues

If the exec format error points to a language runtime binary:

exec /usr/bin/python3: exec format error

The base image was built for a different architecture.

Fix: Use the correct base image:

# This automatically picks the right architecture:
FROM python:3.12-slim

# Or explicitly specify:
FROM --platform=linux/amd64 python:3.12-slim

Fix: Check available architectures for the image:

docker manifest inspect python:3.12-slim | grep architecture

For Docker daemon issues that prevent containers from starting at all, see Fix: Docker daemon is not running.

Fix 7: Fix Compiled Binaries

If you copy a pre-compiled binary into the container, it must match the container’s architecture:

Broken:

# Binary compiled on macOS ARM
COPY ./my-app /app/my-app
ENTRYPOINT ["/app/my-app"]

Fixed — compile inside the container:

FROM golang:1.22 AS builder
WORKDIR /app
COPY . .
RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o my-app

FROM alpine:3.19
COPY --from=builder /app/my-app /app/my-app
ENTRYPOINT ["/app/my-app"]

For Rust:

FROM rust:1.77 AS builder
WORKDIR /app
COPY . .
RUN cargo build --release --target x86_64-unknown-linux-musl

FROM alpine:3.19
COPY --from=builder /app/target/x86_64-unknown-linux-musl/release/my-app /app/my-app
ENTRYPOINT ["/app/my-app"]

Always compile inside the Docker build stage to ensure architecture compatibility.

Fix 8: Debug the Error

Check the file type:

docker run --entrypoint file my-image /app/start.sh
# Should show: Bourne-Again shell script, ASCII text executable
# If it shows: data — the file is corrupted or has wrong format

Check the binary architecture:

docker run --entrypoint file my-image /usr/bin/python3
# Should show: ELF 64-bit LSB executable, x86-64
# If it shows: ELF 64-bit LSB executable, ARM aarch64 — architecture mismatch

Check for missing dependencies:

docker run --entrypoint ldd my-image /app/my-app
# Shows dynamic library dependencies
# "not found" entries indicate missing libraries

If the error is about image pulling rather than execution, see Fix: Docker image not found.

Still Not Working?

Check for QEMU emulation. Docker Desktop on Mac and Windows uses QEMU to run images for different architectures. QEMU is slower and can fail with complex binaries. If possible, use native architecture images.

Check for static vs dynamic linking. Dynamically linked binaries need the correct shared libraries. Alpine uses musl instead of glibc. A binary linked against glibc fails on Alpine:

# Use a glibc-based image instead of Alpine:
FROM debian:12-slim

Or compile with static linking.

Check for BOM (Byte Order Mark). Some editors add a BOM at the beginning of files. This invisible character breaks the shebang line. Check with:

xxd start.sh | head -1
# Should start with 2321 (#!/) not efbb bf23 (BOM + #!/)

Remove the BOM with:

sed -i '1s/^\xEF\xBB\xBF//' start.sh

Check Docker Desktop settings. On macOS, Docker Desktop has a “Use Rosetta for x86_64/amd64 emulation on Apple Silicon” option. Enabling this can improve compatibility with amd64 images.

Check binfmt registration on bare-metal Linux. Self-hosted CI runners and bare-metal servers do not register QEMU handlers automatically. Without them, foreign-architecture binaries fail instantly with no useful log. Install the handlers with docker run --privileged --rm tonistiigi/binfmt --install all and verify with cat /proc/sys/fs/binfmt_misc/qemu-aarch64 (or the equivalent for your target architecture).

Check for a manifest list mismatch in CI. When a CI job pulls an image that was pushed without a manifest list (single-arch only), it will fail on any host that does not match that one architecture. Use docker manifest inspect <image> to see exactly which architectures the tag covers, and if only one is present, rebuild with docker buildx build --platform linux/amd64,linux/arm64 --push.

Check for --entrypoint overrides hiding the real problem. A previous step or compose override may set entrypoint: to a path that does not exist, producing exec format error as a side effect. Run the container with --entrypoint sh -it to drop into a shell and verify the file with file and head -1 directly. For disk space issues preventing image pulls, see Fix: Docker no space left on device.

F

FixDevs

Solo developer based in Japan. Every solution is cross-referenced with official documentation and tested before publishing.

Was this article helpful?

Related Articles