KhueApps
Home/DevOps/Fix /bin/sh^M: bad interpreter in Docker containers

Fix /bin/sh^M: bad interpreter in Docker containers

Last updated: October 06, 2025

Overview

The error /bin/sh^M: bad interpreter in a Linux container means the script file has Windows (CRLF) line endings. Linux expects LF. The extra carriage return (^M) in the shebang (#!/bin/sh) prevents the kernel from finding the interpreter.

This guide shows fast ways to convert line endings, how to detect the problem, and how to prevent it in Docker-based workflows.

Quickstart

  • One-time fix (local): convert files to LF using dos2unix or sed.
  • In Dockerfile: strip CRLF after COPY using sed, or ensure the build context already has LF via .gitattributes.
  • Prevent regressions: add .gitattributes rules and configure Git to avoid CRLF in scripts.

Minimal Working Example (reproduce and fix)

Reproduce the error

Create a Dockerfile that writes a CRLF-terminated script and tries to run it:

# Dockerfile
FROM alpine:3.20
RUN printf '#!/bin/sh\r\necho "Hello from CRLF"\r\n' \
    > /usr/local/bin/run.sh \
 && chmod +x /usr/local/bin/run.sh
CMD ["/usr/local/bin/run.sh"]

Build and run:

docker build -t crlf-demo .
docker run --rm crlf-demo
# -> /usr/local/bin/run.sh: /bin/sh^M: bad interpreter: No such file or directory

Fix inside the Dockerfile

Add a conversion step that removes CR characters (\r):

# Dockerfile (fixed)
FROM alpine:3.20
RUN printf '#!/bin/sh\r\necho "Hello from LF"\r\n' \
    > /usr/local/bin/run.sh \
 && sed -i 's/\r$//' /usr/local/bin/run.sh \
 && chmod +x /usr/local/bin/run.sh
CMD ["/usr/local/bin/run.sh"]

Rebuild and run:

docker build -t crlf-fixed .
docker run --rm crlf-fixed
# -> Hello from LF

Step-by-step fixes

  1. Convert line endings locally
  • Using dos2unix:
    • macOS/Linux: dos2unix script.sh
    • Windows (Git Bash/MSYS2/WSL): dos2unix script.sh
  • Using sed (portable in containers):
    • sed -i 's/\r$//' script.sh
  • Re-run: ./script.sh or use in your image.
  1. Convert in Docker build
  • After copying from host:
FROM alpine:3.20
WORKDIR /app
COPY . .
# Convert all shell scripts and entrypoints to LF
RUN find . -type f \( -name "*.sh" -o -name "entrypoint" -o -name "*.env" \) \
    -exec sed -i 's/\r$//' {} + \
 && chmod +x /app/entrypoint.sh
CMD ["/app/entrypoint.sh"]
  • Alternative with dos2unix (adds a package):
RUN apk add --no-cache dos2unix \
 && find . -type f -name "*.sh" -exec dos2unix {} +
  1. Fix via Git settings (prevention)
  • Add a .gitattributes file to normalize endings:
# .gitattributes
*.sh text eol=lf
*.bash text eol=lf
Dockerfile text eol=lf
*.env text eol=lf
  • On Windows, prefer repository-driven EOLs and disable global CRLF conversion:
    • git config --global core.autocrlf false
  • Re-checkout files to apply attributes:
    • git rm --cached -r . && git reset --hard
  1. Bypass shebang temporarily
  • Running through the interpreter ignores the shebang line ending:
    • sh script.sh or bash script.sh
  • Useful for debugging, but not a fix for ENTRYPOINT/CMD.

Detecting CRLF quickly

  • file:
    • file -b script.sh → “… with CRLF line terminators”
  • grep for CR (carriage return):
    • grep -Irl $'\r' . (lists files containing \r)
  • View bytes:
    • od -An -t x1 -c script.sh | head (look for 0d aka \r)

Where the problem appears in Docker

  • COPYing from a Windows host into a Linux image preserves CRLF if present.
  • Bind-mounted volumes from Windows into Linux containers often carry CRLF.
  • The first line (shebang) is critical; a CR after /bin/sh breaks direct execution.

Hardening your Docker workflow

  • Normalize at source with .gitattributes. Example policy:
    • * text=auto (default text normalization)
    • *.sh text eol=lf and Dockerfile text eol=lf for critical files
  • Validate in CI:
    • ! grep -Irl $'\r' -- . or a dedicated lint step to fail the build if CRLF is present.
  • Normalize during build as a safety net using sed or dos2unix.
  • Ensure executable bits are set after copying: chmod +x entrypoint.sh.

Pitfalls

  • Converting binaries: Do not run sed/dos2unix on non-text files. Restrict patterns (e.g., *.sh, Dockerfile).
  • Editor settings: IDEs on Windows may reintroduce CRLF. Set per-project EOL to LF for scripts.
  • core.autocrlf=true on Windows: This often creates CRLF on checkout. Prefer core.autocrlf=false with .gitattributes enforcing LF for scripts.
  • Mixed endings: A file with LF body but CRLF shebang still fails when executed directly.
  • Alpine busybox sh vs bash: The error is from the kernel before the shell runs; changing shells won’t help until line endings are fixed.

Performance notes

  • Avoid installing dos2unix at runtime; do conversions in the build to keep images small.
  • sed-based conversion is fast and requires no extra packages.
  • Converting once at build time is cheaper than per-container startup hooks.
  • Minimizing extra RUN layers: group find/sed in a single RUN to reduce image layers.

Tiny FAQ

  • Why does ^M appear?

    • ^M represents a carriage return (CR, \r) from Windows CRLF endings. Linux expects only LF (\n).
  • Why does sh script.sh work but ./script.sh fails?

    • Direct execution uses the shebang; CR there breaks interpreter resolution. Invoking sh bypasses the shebang.
  • Does the base image matter?

    • Any Linux base will fail on CRLF shebangs. The fix is to use LF line endings.
  • Can I fix this without changing files?

    • As a workaround, execute via sh script.sh. For containers (ENTRYPOINT/CMD), you must convert to LF.
  • Best long-term fix?

    • Enforce LF via .gitattributes and verify in CI; optionally strip CRLF during Docker builds as a safeguard.

Series: Docker

DevOps