Overview
The Docker "exec format error" typically means the kernel can’t execute a file inside the container. Two common causes:
- Architecture mismatch (e.g., running an amd64 binary on an arm64 host without emulation)
- Script issues: Windows CRLF line endings, missing/incorrect shebang, or non-executable permissions
This guide shows how to diagnose and fix both quickly.
Symptoms
- Error messages you may see:
- exec format error
- bad interpreter: /bin/sh^M: No such file or directory
- no such file or directory (when the file exists but the interpreter doesn’t)
| Symptom | Likely cause |
|---|---|
| Immediate failure when container starts (even /bin/sh) | Image arch ≠ host arch (no emulation) |
| "^M" in error message | CRLF line endings |
| "bad interpreter" or "no such file or directory" | Wrong shebang or missing shell/interpreter |
Quickstart: Fix in minutes
- Check host and image architecture:
- Host: uname -m
- Image: docker buildx imagetools inspect IMAGE or docker image inspect --format '{{.Architecture}}/{{.Os}}' IMAGE
- If mismatched, rebuild or pull the correct platform image:
- docker build --platform linux/amd64 -t myapp .
- Or multi-arch: docker buildx build --platform linux/amd64,linux/arm64 -t myuser/myapp:latest --push .
- Normalize line endings on scripts before COPY:
- dos2unix script.sh or git config/text attributes (see below)
- Ensure a valid shebang and executable bit:
- First line: #!/bin/sh (or #!/usr/bin/env bash if bash is installed)
- chmod +x script.sh
- Use JSON-form ENTRYPOINT/CMD in Dockerfile to avoid shell surprises:
- ENTRYPOINT ["/app/entrypoint.sh"]
- If you must run cross-arch locally, enable emulation and expect slower performance:
- docker run --privileged --rm tonistiigi/binfmt --install all
- Verify inside the container:
- file /app/entrypoint.sh
- ls -l /app/entrypoint.sh
- Rebuild cleanly:
- docker build --no-cache -t myapp . && docker run --rm myapp
Minimal working example
This example shows a safe script entrypoint and a Dockerfile that avoids line-ending and shebang issues.
entrypoint.sh:
#!/bin/sh
set -eu
printf "Hello from %s on %s\n" "entrypoint" "$(uname -m)"
exec "$@"
Dockerfile:
FROM alpine:3.20
# Avoid CRLF issues by ensuring scripts are LF and executable
WORKDIR /app
COPY entrypoint.sh ./
RUN sed -i 's/\r$//' /app/entrypoint.sh \
&& chmod +x /app/entrypoint.sh
# Demonstrate JSON-form entrypoint
ENTRYPOINT ["/app/entrypoint.sh"]
CMD ["sh", "-c", "echo default cmd"]
Build and run:
# Build for your host arch (auto-detected) or specify explicitly
# Example forcing amd64:
docker build --platform linux/amd64 -t myapp .
docker run --rm myapp
If you accidentally copy a CRLF script without conversion, you might see:
- bad interpreter: /bin/sh^M: No such file or directory The sed line in the Dockerfile strips CRLF as a safeguard, but prefer fixing line endings in your repo (see below).
Diagnose: Architecture mismatch
- Identify host and image arch:
uname -m # host arch (e.g., x86_64, aarch64)
docker buildx imagetools inspect IMAGE # shows available platforms
- Inspect a local image:
docker image inspect --format '{{.Os}}/{{.Architecture}}' myapp
- Run-time checks:
docker run --rm --entrypoint uname IMAGE -m # prints container arch
- Fixes:
- Build for the correct platform:
docker build --platform linux/arm64 -t myapp:arm64 .
- Produce multi-arch images with buildx:
docker buildx create --use
docker buildx build \
--platform linux/amd64,linux/arm64 \
-t myuser/myapp:latest \
--push .
- Pull the right variant if using official images with manifests:
docker run --platform linux/amd64 ...
- If cross-running locally, install binfmt emulators (expect overhead):
docker run --privileged --rm tonistiigi/binfmt --install all
Diagnose: Line endings, shebang, permissions
- Detect CRLF on host:
file entrypoint.sh # should say "ASCII text" (no CRLF)
grep -n $'\r' entrypoint.sh || echo "No CRLF"
- Convert to LF on host:
dos2unix entrypoint.sh # or: sed -i 's/\r$//' entrypoint.sh
- Enforce LF in Git:
- One-time setting:
git config core.autocrlf false
- .gitattributes (commit to repo):
*.sh text eol=lf
Dockerfile text eol=lf
- Shebang rules:
- Use an interpreter that exists in the image:
- Alpine: /bin/sh (BusyBox ash)
- Debian/Ubuntu: /bin/sh is dash; /bin/bash requires bash package
- Portable option:
#!/usr/bin/env bash
Ensure bash is installed if you use it:
RUN apk add --no-cache bash # Alpine
# or
RUN apt-get update && apt-get install -y --no-install-recommends bash && rm -rf /var/lib/apt/lists/*
- Executable bit and JSON entrypoint:
chmod +x entrypoint.sh
ENTRYPOINT ["/app/entrypoint.sh"]
CMD ["your", "args"]
Common pitfalls
- Building on Windows and copying scripts with CRLF into the image
- Using /bin/bash in Alpine without installing bash
- Missing shebang on a script used as ENTRYPOINT or CMD
- Relying on shell-form ENTRYPOINT which invokes /bin/sh implicitly
- Hidden BOM at start of file; remove with sed -i '1s/^\xEF\xBB\xBF//' file
- Running amd64 images on arm64 hosts without binfmt emulation
Performance notes
- Prefer native-arch images in production. Emulation (qemu-binfmt) can be 2–5× slower.
- Build multi-arch images once, then pull the matching variant by default.
- Use buildx with cache to avoid repeated cross-compilation costs:
docker buildx build --platform linux/amd64,linux/arm64 \
--cache-to type=inline --cache-from type=registry,ref=myuser/myapp:cache \
-t myuser/myapp:latest --push .
- Avoid converting line endings inside Dockerfiles on every build; fix them in the repo to keep layers cacheable.
FAQ
Q: Why do I get "no such file or directory" when the script exists? A: The interpreter in the shebang is missing or has CRLF (e.g., /bin/sh^M). Ensure LF endings and a valid interpreter path.
Q: Can I run amd64 images on an arm64 host? A: Yes, with binfmt emulation or Docker Desktop’s built-in qemu. Expect slower performance; prefer native builds for production.
Q: How do I force LF endings in Git on Windows? A: Add .gitattributes with "*.sh text eol=lf" and set git config core.autocrlf false. Re-clone or re-normalize files.
Q: JSON vs shell-form ENTRYPOINT? A: JSON-form avoids invoking /bin/sh and reduces surprises with shebangs and quoting, making startup more predictable.