Docker ‘exec format error’ means the kernel cannot execute the binary or script you specified. This happens when the architecture doesn’t match, the file is missing execute permissions, or the script has the wrong line endings. This guide shows you how to diagnose and fix it.

Symptom

Running a container fails with:

docker run myapp:latest
standard_init_linux.go:228: exec user process caused "exec format error"

Or when explicitly running a command:

docker run myapp:latest ./start.sh
exec format error

The error appears immediately after docker run without any output from the container.

Why it happens

Ranked by likelihood:

  1. Architecture mismatch (amd64 vs arm64) — The image was built for Intel/AMD (amd64) but you are running it on Apple Silicon, ARM server, or inside a container on a different architecture.
  2. Missing shebang in a shell script — The script lacks the first line #!/bin/sh or #!/bin/bash, so the kernel doesn’t know which interpreter to use.
  3. CRLF line endings instead of LF — The script or binary was created on Windows with CRLF line endings. Linux expects LF, so the shebang is corrupted.
  4. File is not executable — The script or binary is missing the execute bit (chmod +x). The kernel refuses to run non-executable files.
  5. Binary is built for a different OS — The binary is for Windows, macOS, or a different Linux distro and cannot run in your container.
  6. Base image is incompatible with the binary — Alpine Linux vs Debian/Ubuntu have different libc versions; a binary built for one may not run in the other.

Fix

1. Architecture mismatch (amd64 vs arm64)

Check what architecture your image is built for:

docker inspect myapp:latest | grep -i architecture

Output example:

"Architecture": "amd64"

Check what architecture your system is:

docker run --rm myapp:latest uname -m
# Output: aarch64 (ARM64) or x86_64 (amd64)

# Or on your host
uname -m

If they don’t match, rebuild the image for the correct architecture using Docker BuildX:

# Enable BuildX (usually already enabled in Docker Desktop)
docker buildx create --use

# Build for your target architecture
docker buildx build --platform linux/arm64 \
  -t myregistry/myapp:arm64 \
  --push .

To build for multiple architectures at once:

docker buildx build --platform linux/amd64,linux/arm64,linux/arm/v7 \
  -t myregistry/myapp:latest \
  --push .

Then run with the correct tag:

# On Apple Silicon (arm64)
docker run --rm myregistry/myapp:latest

# Or explicitly specify platform if multi-arch image exists
docker run --rm --platform linux/arm64 myregistry/myapp:latest

For testing without rebuilding, you can emulate the architecture:

# Run amd64 image on arm64 machine (slow, emulated)
docker run --rm --platform linux/amd64 myapp:latest

In your Dockerfile, use a multi-architecture base image:

FROM node:20-alpine    # This image supports amd64, arm64, and others
COPY . /app
WORKDIR /app
CMD ["node", "server.js"]

2. Missing shebang in a shell script

The script must start with a shebang line so the kernel knows which interpreter to use.

Check the script:

docker run --rm <image> head -1 /app/entrypoint.sh

If the first line is empty or doesn’t start with #!, add a shebang:

#!/bin/sh
echo "Starting app"
exec node /app/server.js

The most common shebangs:

#!/bin/sh          # POSIX shell (smallest, most compatible)
#!/bin/bash        # Bash (more features, Alpine doesn't have it by default)
#!/usr/bin/env sh  # Portable, looks for sh in PATH
#!/usr/bin/python3 # Python
#!/usr/bin/node    # Node.js

Update your Dockerfile:

FROM node:20-alpine
COPY entrypoint.sh /app/entrypoint.sh
RUN chmod +x /app/entrypoint.sh
COPY . /app
WORKDIR /app
ENTRYPOINT ["/app/entrypoint.sh"]

And ensure the script has the shebang:

#!/bin/sh
set -e
exec node /app/server.js

Rebuild and test:

docker build -t myapp:latest .
docker run --rm myapp:latest

3. CRLF line endings instead of LF

If the script was created on Windows, it might have CRLF line endings. Check:

docker run --rm <image> file /app/entrypoint.sh
# Output: ASCII text, with CRLF line terminators

Or:

docker run --rm <image> od -c /app/entrypoint.sh | head
# Look for \r\n instead of just \n

Fix it in the Dockerfile using dos2unix or sed:

FROM node:20-alpine
RUN apk add --no-cache dos2unix
COPY entrypoint.sh /app/entrypoint.sh
RUN dos2unix /app/entrypoint.sh && chmod +x /app/entrypoint.sh

Or use sed (no extra dependency):

FROM node:20-alpine
COPY entrypoint.sh /app/entrypoint.sh
RUN sed -i 's/\r$//' /app/entrypoint.sh && chmod +x /app/entrypoint.sh

The sed command removes carriage returns (\r).

Alternatively, configure Git to use LF on Windows:

git config core.autocrlf false

Then re-add and commit the file:

git rm --cached entrypoint.sh
git add entrypoint.sh
git commit -m "Fix line endings"

Rebuild the image:

docker build -t myapp:latest .
docker run --rm myapp:latest

4. File is not executable

The script must have the execute bit set. Check in the Dockerfile:

FROM node:20-alpine
COPY entrypoint.sh /app/entrypoint.sh
RUN chmod +x /app/entrypoint.sh    # Make executable
ENTRYPOINT ["/app/entrypoint.sh"]

Verify:

docker run --rm <image> ls -la /app/entrypoint.sh
# Should show: -rwxr-xr-x (with x in permissions)

Also ensure the file was committed to Git with execute permissions:

# On your host (Linux/macOS)
chmod +x entrypoint.sh
git add entrypoint.sh
git commit -m "Add execute permission"

Windows users: If you are on Windows and committing to Git, configure Git to preserve permissions:

git config core.fileMode true

Then:

chmod +x entrypoint.sh    # This works in Git Bash or WSL2
git add entrypoint.sh
git commit -m "Add execute permission"

Rebuild:

docker build -t myapp:latest .
docker run --rm myapp:latest

5. Binary is built for a different OS

If you have a compiled binary (e.g., Go, Rust, C++), it must be compiled for Linux. Check:

# On your host
file /usr/local/bin/myapp
# Should show: ELF 64-bit LSB executable, x86-64, ...

# Don't build on macOS and try to run in a Linux container
# Always build in a container or cross-compile correctly

In your Dockerfile, build the binary inside the container:

# Multi-stage build
FROM golang:1.22-alpine AS builder
WORKDIR /app
COPY . .
RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o myapp .

FROM alpine:3.20
COPY --from=builder /app/myapp /usr/local/bin/myapp
RUN chmod +x /usr/local/bin/myapp
ENTRYPOINT ["myapp"]

The key is GOOS=linux GOARCH=amd64 to cross-compile for Linux. For other languages:

Rust:

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

FROM alpine:3.20
COPY --from=builder /app/target/x86_64-unknown-linux-gnu/release/myapp /usr/local/bin/myapp

Node.js (compiled binaries):
Usually not needed for Node.js, but if you are using native modules, rebuild them in the container:

FROM node:20-alpine
WORKDIR /app
COPY . .
RUN npm install

6. Base image incompatible with the binary

If a binary was built on Ubuntu but you are using Alpine Linux (different libc), it might not run. Check:

docker run --rm ubuntu:24.04 ldd /usr/bin/node | head
# Shows glibc and other libraries

docker run --rm alpine:3.20 ldd /usr/bin/node | head
# Shows musl libc (different from glibc)

A binary built with glibc won’t run with musl libc. Use a base image that matches the binary:

# If built for glibc (Ubuntu, Debian):
FROM debian:bookworm
COPY myapp /usr/local/bin/myapp
RUN chmod +x /usr/local/bin/myapp

# If built for musl (Alpine):
FROM alpine:3.20
COPY myapp /usr/local/bin/myapp
RUN chmod +x /usr/local/bin/myapp

Or rebuild the binary in the correct base image:

FROM debian:bookworm AS builder
RUN apt-get update && apt-get install -y build-essential
WORKDIR /app
COPY . .
RUN make build

FROM debian:bookworm
COPY --from=builder /app/myapp /usr/local/bin/myapp

Prevent it

  1. Always test the image before pushing:

    docker build -t myapp:latest .
    docker run --rm myapp:latest
    

  2. Use multi-architecture builds from the start:

    docker buildx build --platform linux/amd64,linux/arm64 \
      -t myregistry/myapp:latest --push .
    

  3. Add shebangs to all shell scripts:

    #!/bin/sh
    set -e
    exec mycommand
    

  4. Check line endings when copying files from Windows:

    RUN sed -i 's/\r$//' /app/entrypoint.sh
    

  5. Make scripts executable in the Dockerfile:

    RUN chmod +x /app/entrypoint.sh
    

  6. Use multi-stage builds to compile binaries inside the container:

    FROM golang:1.22 AS builder
    RUN go build -o myapp .
    
    FROM alpine:3.20
    COPY --from=builder /app/myapp /usr/local/bin/myapp
    

  7. Match base image to the binary’s libc:
    – glibc (Ubuntu, Debian, CentOS) → use those base images
    – musl (Alpine) → use Alpine

FAQ

Why does my Docker container fail with ‘exec format error’ on Apple Silicon?

Your image is built for amd64 (Intel). Rebuild with docker buildx --platform linux/arm64,linux/amd64 or run it emulated with docker run --platform linux/amd64.

What does ‘exec format error’ actually mean?

The kernel cannot execute the binary. Either it’s the wrong architecture, the file doesn’t have execute permissions, it’s missing a shebang (for scripts), or it has CRLF line endings.

How do I check what architecture my Docker image is built for?

Run docker inspect <image> | grep Architecture or docker run <image> uname -m. Also check docker buildx ls to see available platforms.

Why does my shell script fail in Docker with ‘exec format error’?

The script is missing a shebang (first line must be #!/bin/sh or #!/bin/bash), has CRLF line endings (dos2unix can fix), or is not executable (chmod +x in Dockerfile).

How this guide was verified

Written by hand from the official documentation, every command re-checked, and reviewed a second time before publishing. Last reviewed on .

Official sources read for this article

Share.

Haythem Rehouma builds and teaches DevOps and AI tooling: Kubernetes, Docker, Terraform, Claude Code, ChatGPT and n8n. Every KubPoint guide is written from the official docs and re-tested before it goes live.

Comments are closed.