01
Distinguish a missing file from a missing interpreter
An executable script begins with a shebang that names the interpreter. A CRLF line ending can leave a carriage-return byte at the end of that path. The script can exist at exactly the configured path while the resulting interpreter path does not.
Do not assume every startup failure is CRLF. A script asking for /bin/bash will still fail in an image without Bash even with LF endings. An architecture mismatch or a dynamically linked binary without its loader needs another investigation.
02
Inspect the image without invoking its failing entrypoint
Override the entrypoint with an interpreter known to exist in the image. The commands below use a Debian-based test image; a distroless image requires a different inspection method. Check the file mode, first-line bytes, and the actual interpreter path.
The 0d 0a sequence at the end of a shebang is CRLF. The human-readable first line can conceal that extra byte, so a normal cat output is not conclusive. Also inspect runtime mounts if the image file looks correct but the Compose service still fails.
docker image inspect --format '{{json .Config.Entrypoint}}' your-image:dev
docker run --rm --entrypoint sh your-image:dev -c 'ls -l /usr/local/bin/start-demo; head -n 1 /usr/local/bin/start-demo | od -An -tx1; ls -l /bin/sh'03
Create a reproducible failing script
Use a new directory for this experiment. The Python command deliberately writes CRLF bytes, so the case does not depend on your editor or operating system defaults. It is not an instruction to normalize arbitrary binary files.
Save the Dockerfile from the next section beside entrypoint.sh. In validation, the executable CRLF script built successfully but failed when the container used it as its default entrypoint.
python3 - <<'PYFILE'
from pathlib import Path
Path('entrypoint.sh').write_bytes(b'#!/bin/sh\r\nprintf "started\\n"\r\n')
PYFILE04
Keep the executable and interpreter explicit
This test image has /bin/sh available and copies the script with a known mode. That removes a missing executable bit as the explanation for the CRLF reproduction. Build with docker build -t entrypoint-proof . and run it without overriding its entrypoint.
Using sh entrypoint.sh as a workaround changes the execution path and can mask the original problem. Correct the file that the image intends to execute, then test that exact default startup path.
FROM python:3.12-slim
COPY --chmod=755 entrypoint.sh /usr/local/bin/start-demo
ENTRYPOINT ["/usr/local/bin/start-demo"]05
Normalize the source and verify the rebuilt image
For the disposable file, replace CRLF with LF and rebuild. The corrected default entrypoint should print started and exit zero. Our lab observed that result without changing the image interpreter or entrypoint path.
For the repository, a .gitattributes entry of *.sh text eol=lf makes the intended checkout line ending explicit. Review a scoped renormalization diff if existing tracked scripts need conversion. Keep executable mode handling separate from line-ending handling.
python3 - <<'PYFILE'
from pathlib import Path
path=Path('entrypoint.sh')
path.write_bytes(path.read_bytes().replace(b'\r\n',b'\n'))
PYFILE
docker build --tag entrypoint-proof .
docker run --rm entrypoint-proof
# Suggested .gitattributes entry:
# *.sh text eol=lf06
Return to the other branches if it still fails
Confirm that the rebuilt image, rather than an old tag or a host-mounted copy, is what the service runs. If the shebang is clean, verify the interpreter exists and the script is executable under the configured user.
For a binary rather than a script, inspect its architecture and required loader. A similar error string does not justify applying a text conversion to an executable binary.
Know the limits
The reproduced case is a Linux /bin/sh script with a CRLF shebang. Similar startup messages can have other causes, including a missing interpreter, missing dynamic loader, wrong architecture, a mount hiding the file, or incorrect permissions. Shell error wording varies. Reproduction checked on 26 October 2026 using Linux containers on Docker Engine 29.6.2 and Compose 5.3.1; this is the validation environment, not a minimum-version claim.
Sources & further reading
A technical publication by CoreTrailKit.
Browse all articles