Skip to main content
DevOps Insightsguides & answers

Docker · troubleshooting

exec /entrypoint.sh: no such file or directory

The entrypoint exists, but the program that runs it does not: a Windows line ending in its first line, bash missing from the image, or a glibc binary on Alpine. Fix the line endings, the first line or the base image.

Reproduced on macOS 27.0.1, . Where the text says “reproduced”, the behaviour was run on that version, not taken from documentation.

First published

New to this? Read what is actually happening first, then come back to the steps.

On this page · 10 sections

Before you start

  • The image, to look inside it: docker run --rm --entrypoint sh IMAGE (or another shell the image has).
  • The entrypoint script from the repository, and file(1) to check its line endings.

Run this first

step 1 of 2
docker inspect --format '{{json .Config.Entrypoint}} {{json .Config.Cmd}}' IMAGE

Look for The path named in the error

Shows exactly what the container runs at start.

Work out which cause you have

A few questions to narrow the list. Every answer ends in a command that confirms or rules the cause out — this cannot see your cluster, so nothing here is a certainty until you have checked.

Narrow it down

0 answered · nothing is sent anywhere

What does file say about the entrypoint?

Cause space

3 of 3 still possible

  • The script has Windows line endingsCommon
  • The #! line names an interpreter the image does not haveCommon
  • A glibc binary in an Alpine imageOccasional

Nothing ruled out yet. Answer the question above and the branches your answer eliminates will strike through here.

Check it with a tool

Or diagnose it manually

In this order. The first command usually contains the whole answer.

  1. Step 1

    docker inspect --format '{{json .Config.Entrypoint}} {{json .Config.Cmd}}' IMAGE

    Shows exactly what the container runs at start.

    Look for The path named in the error

  2. Step 2

    docker run --rm --entrypoint sh IMAGE -c 'head -1 ENTRYPOINT_PATH | od -c | head -2'

    Reads the #! line inside the image, where the build may have changed it.

    Look for \r at the end, or an interpreter path to check exists

Every cause, and how to fix it

Ordered by how often each one turns out to be the answer.

The script has Windows line endings

Common

The entrypoint was edited or checked out on Windows (or with core.autocrlf), so its first line is #!/bin/sh followed by a carriage return. The kernel looks for an interpreter literally named /bin/sh\r, which does not exist.

Confirm

file entrypoint.sh
head -1 entrypoint.sh | od -c | head -2

with CRLF line terminators, and \r before \n on the first line

Fix

  • Convert the file to LF: sed -i 's/\r$//' entrypoint.sh (GNU sed; on macOS sed -i '' 's/\r$//' entrypoint.sh), or dos2unix entrypoint.sh, and commit it.
  • Stop it coming back: add a .gitattributes line such as *.sh text eol=lf, so Git checks scripts out with LF on every machine.
  • Or normalise during the build: RUN sed -i 's/\r$//' /usr/local/bin/entrypoint.sh in the Dockerfile.
printf '*.sh text eol=lf\n' >> .gitattributes && git add --renormalize . && git commit -m 'Scripts use LF'

Then rebuild the image. --renormalize rewrites files already committed to match the new rule; checked on Git 2.50.1 with a CRLF script.

The #! line names an interpreter the image does not have

Common

#!/bin/bash in an Alpine, distroless or scratch image, which have no bash (distroless and scratch have no shell at all), or #!/usr/bin/env python where python is not installed.

Confirm

head -1 entrypoint.sh
docker run --rm --entrypoint sh IMAGE -c 'ls -l /bin/bash /bin/sh 2>&1'

The interpreter in the #! line missing from the image

Fix

  • Use an interpreter the image has: #!/bin/sh with POSIX syntax on Alpine, or install bash (apk add --no-cache bash) if the script needs it.
  • On distroless or scratch, do not use a script entrypoint; run the binary directly in ENTRYPOINT ["/app"].

A glibc binary in an Alpine image

Occasional

A program built on a glibc distribution (Debian, Ubuntu) is copied into an Alpine image. The file exists, but the loader it names, /lib64/ld-linux-x86-64.so.2, does not.

Confirm

docker run --rm --entrypoint sh IMAGE -c 'apk add --no-cache file >/dev/null; file /app/BINARY'

dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, on an Alpine image

Fix

  • Build the binary in an Alpine stage (against musl), or use a glibc base image such as a Debian slim one for the final stage.
  • For Go, build a static binary: CGO_ENABLED=0 go build, which needs no loader at all.

Understanding it properly

Skip this if you are mid-incident — the working part of the page is above. Worth reading afterwards, because understanding the mechanism is what stops the next one.

In plain terms

When a container starts, the kernel runs its entrypoint. If that file is a script, the first line names the program that runs it, such as /bin/sh. If that program is not in the image, or the line secretly ends with an invisible Windows carriage return so the name is wrong, the kernel reports no such file or directory, even though your script is right there. The message is about the interpreter, not your file.

Words used on this page

Shebang (#!)
The first line of a script, naming the interpreter the kernel should run it with, for example #!/bin/sh.
CRLF
Windows line endings: carriage return plus line feed. In a #! line the carriage return becomes part of the interpreter's name.
ELF interpreter
For a dynamically linked binary, the loader named inside it (for glibc builds, /lib64/ld-linux-x86-64.so.2). Alpine, which uses musl, does not have it.
ENOENT
The error code behind no such file or directory. execve returns it when the file, its script interpreter or its ELF interpreter is missing.
What is actually happening

Linux's execve(2) returns ENOENT not only when the file is missing, but when "a script or ELF interpreter does not exist". The container runtime passes that on, so the message names your entrypoint while the missing thing is the interpreter.

Reproduced on macOS 27.0.1, whose kernel behaves the same way: a script starting #!/bin/sh with a Windows line ending, and a script whose #! names an interpreter that is not installed, both exist and both fail with No such file or directory. Removing the carriage return makes the first one run.

Current runc reports exec /path/to/entrypoint: no such file or directory. Older runc wrapped the same failure as standard_init_linux.go:NNN: exec user process caused: no such file or directory. A command that is not on PATH at all gives a different message: executable file not found in $PATH.

A binary compiled against glibc and copied into an Alpine (musl) image fails the same way: the file is there, but its ELF interpreter is not. This is the reverse of exec format error, which means the file exists but is for another CPU architecture.

How to tell this is your problem
WhereWhat you see
docker run or container logsexec /usr/local/bin/docker-entrypoint.sh: no such file or directory
Older Docker or runcstandard_init_linux.go:228: exec user process caused: no such file or directory
KubernetesThe container exits at once and the pod goes to CrashLoopBackOff; kubectl logs --previous shows the line above
file entrypoint.shPOSIX shell script text executable, ASCII text, with CRLF line terminators

How to know it is actually fixed

  • The container starts, and docker logs shows the entrypoint's own output.
  • file on the script reports no CRLF line terminators.

Stopping it happening again

  • Add *.sh text eol=lf (and any other scripts) to .gitattributes.
  • Build binaries in the same base image family they will run in, or build them statically.
  • Use the shebang the image supports: #!/bin/sh unless you know bash is there.

Security considerations

  • Do not fix a missing interpreter by installing a full shell and toolchain into a minimal production image without thinking: it widens what an attacker can use. Prefer making the entrypoint match what the image already has.

Sources

Behaviour described here is drawn from official documentation. Where a figure could not be confirmed on an official page it is attributed in the text rather than stated as canonical.

Did this get you to an answer?

No text box on purpose — please do not paste production logs anywhere

Something wrong or out of date? Report it