Skip to content

Fix: Docker exec /entrypoint.sh: no such file or directory

FixDevs · (Updated: )

Part of:  Docker, DevOps & Infrastructure

Quick Answer

How to fix Docker entrypoint not found error caused by wrong file path, Windows line endings, missing shebang, wrong base image, and multi-stage build issues.

The Error

You run a Docker container and it immediately exits with:

exec /entrypoint.sh: no such file or directory

Or variations:

exec /app/start.sh: no such file or directory
standard_init_linux.go:228: exec user process caused: no such file or directory
exec /usr/local/bin/docker-entrypoint.sh: no such file or directory
OCI runtime create failed: container_linux.go:380: starting container process caused: exec: "/entrypoint.sh": stat /entrypoint.sh: no such file or directory

Docker cannot find or execute the entrypoint script. The file either does not exist in the container, has wrong line endings, is not executable, or references a missing interpreter.

Why This Happens

When Docker starts a container, it runs the ENTRYPOINT or CMD command. If the specified file cannot be found or executed, the container fails immediately.

The confusing part: this error can appear even when the file exists in the container. The “no such file or directory” message is the literal text of ENOENT, the Linux error returned by the execve(2) syscall when any of the path components it needs to resolve are missing. The kernel does not distinguish between “the executable does not exist” and “the interpreter referenced in the executable’s shebang line does not exist” — both surface as the same error. That single ambiguity is the reason this error is so often misdiagnosed.

So no such file or directory can refer to:

  1. The script itself — the file path is wrong or the file was not copied.
  2. The interpreter — the shebang line (#!/bin/bash) references a binary that does not exist in the image.
  3. A linked library — the binary exists but depends on a shared library that is missing.
  4. Windows line endings\r\n line endings corrupt the shebang line, making the interpreter path invalid.

Common causes:

  • Windows line endings (CRLF). The most common cause. The file was edited on Windows and has \r\n line endings.
  • File not copied into the image. The COPY or ADD instruction has the wrong path.
  • Wrong base image. The script uses #!/bin/bash but the image only has #!/bin/sh (Alpine Linux).
  • File not executable. The script does not have execute permissions.
  • Multi-stage build. The file was in a build stage but not copied to the final stage.
  • Binary compiled for wrong architecture. An amd64 binary running on an arm64 image.

The fastest way to bisect causes 1–4 is to override the entrypoint and shell in: docker run --rm -it --entrypoint /bin/sh myimage then ls -la /entrypoint.sh && head -1 /entrypoint.sh. If the file is missing, it is cause 1. If the file exists but head -1 shows #!/bin/bash\r or a path that does not exist in the image, it is cause 4 or 3 respectively. If the shebang is fine but the file is not executable, the permissions need a chmod +x.

In Production: Incident Lens

In production this is a pod crash loop on startup, and it is one of the highest-blast-radius deploy failures because it happens before any health check runs. The typical pattern: a new image was built in CI, tagged :latest or pushed under a new SHA, the orchestrator pulls it, the container exits immediately with no such file or directory, the orchestrator restarts it, and after N restarts the pod enters crash-loop backoff. Every pod of this service is in the same state because they all run the same broken image. The blast radius is “no pods of this service can start at all” — the existing pods continue to serve traffic until they get evicted, then the service is down.

The monitoring signal is liveness probe failure across the replica set within seconds of a new rollout, combined with a non-zero container restart count growing at the orchestrator’s backoff rate (1s, 10s, 20s, 40s, … 5m). On Kubernetes, the pod status is CrashLoopBackOff with the container’s last termination reason Error and exit code 127 (command not found) or 126 (permission denied). On ECS, the task definition cycles through stop and start with the same exit code. On a plain Docker host with restart: unless-stopped, you see the container appearing and disappearing in docker ps -a.

Recovery is rollback to the previous image SHA — not to :latest, which still points at the broken image, but to the specific SHA digest that was working before. Postmortem preventives are an image smoke test in CI that builds the image, runs it for 10 seconds with a no-op CMD, and verifies the container does not exit with a non-zero code before tagging the image as deployable. Add RUN /entrypoint.sh --version or equivalent to the Dockerfile so the build itself fails if the script cannot execute — that turns a runtime crash into a build-time error. A .gitattributes rule pinning shell scripts to LF and a pre-commit hook running dos2unix --info on staged .sh files prevent the CRLF variant entirely.

Fix 1: Fix Windows Line Endings (CRLF → LF)

The most common cause. Windows text editors add \r (carriage return) characters that break the shebang line:

#!/bin/bash\r    ← The \r is invisible but the kernel looks for "/bin/bash\r" which doesn't exist

Fix in the Dockerfile:

COPY entrypoint.sh /entrypoint.sh
RUN sed -i 's/\r$//' /entrypoint.sh && chmod +x /entrypoint.sh

Fix before building — convert on your machine:

# Using dos2unix
dos2unix entrypoint.sh

# Using sed
sed -i 's/\r$//' entrypoint.sh

# Using tr
tr -d '\r' < entrypoint.sh > entrypoint_fixed.sh && mv entrypoint_fixed.sh entrypoint.sh

Fix with .gitattributes (permanent):

# Force LF line endings for all shell scripts
*.sh text eol=lf
entrypoint.sh text eol=lf
Dockerfile text eol=lf

Fix in VS Code: Click on CRLF in the bottom status bar and switch to LF.

Pro Tip: Always add a .gitattributes file to your project with *.sh text eol=lf. This prevents Git from converting line endings to CRLF on Windows checkouts, which is the root cause of most Docker entrypoint issues on Windows.

Fix 2: Verify the File Exists in the Image

The file might not be copied correctly:

Debug — check what is in the image:

# Run a shell in the image to inspect
docker run --rm -it --entrypoint /bin/sh myimage

# List the file
ls -la /entrypoint.sh

# Or use docker inspect
docker run --rm --entrypoint ls myimage -la /entrypoint.sh

Check your COPY instruction:

# Wrong — source path is wrong
COPY ./scripts/entrypoint.sh /entrypoint.sh
# The file might be at ./entrypoint.sh, not ./scripts/

# Fixed — verify the path relative to the build context
COPY entrypoint.sh /entrypoint.sh

Check .dockerignore:

# If .dockerignore contains this, the file is excluded from the build context!
*.sh
entrypoint.sh

Remove the entry from .dockerignore or add an exception:

*.sh
!entrypoint.sh

Fix 3: Fix the Shebang Line

The script’s shebang must reference an interpreter that exists in the image:

Alpine Linux does not have bash by default:

#!/bin/bash    ← Does NOT exist in Alpine
#!/bin/sh      ← Works in Alpine (uses ash/busybox)

Fix: Use sh instead of bash:

#!/bin/sh
set -e

echo "Starting application..."
exec "$@"

Fix: Install bash in Alpine:

FROM alpine:3.19
RUN apk add --no-cache bash
COPY entrypoint.sh /entrypoint.sh
RUN chmod +x /entrypoint.sh
ENTRYPOINT ["/entrypoint.sh"]

Fix: Use a Debian-based image that includes bash:

FROM debian:bookworm-slim
# bash is available by default

Common Mistake: Developing scripts on a machine with bash and deploying to Alpine containers that only have sh. If your script uses bash-specific features (arrays, [[ ]], ${var//pattern/replacement}), either install bash or rewrite using POSIX sh syntax.

Fix 4: Fix File Permissions

The entrypoint script must be executable:

COPY entrypoint.sh /entrypoint.sh
RUN chmod +x /entrypoint.sh
ENTRYPOINT ["/entrypoint.sh"]

Or set permissions before copying:

# On your machine
chmod +x entrypoint.sh
git add entrypoint.sh
git commit -m "Make entrypoint executable"

Using COPY with —chmod (Docker BuildKit, Docker 20.10+):

# syntax=docker/dockerfile:1
COPY --chmod=755 entrypoint.sh /entrypoint.sh

Fix 5: Fix Multi-Stage Build Issues

In multi-stage builds, files from earlier stages are not automatically available:

Broken:

FROM node:22 AS build
COPY entrypoint.sh /entrypoint.sh
RUN npm run build

FROM node:22-alpine
# entrypoint.sh is NOT here — it was in the build stage
ENTRYPOINT ["/entrypoint.sh"]

Fixed — copy from the build stage:

FROM node:22 AS build
COPY entrypoint.sh /entrypoint.sh
RUN npm run build

FROM node:22-alpine
COPY --from=build /entrypoint.sh /entrypoint.sh
COPY --from=build /app/dist /app/dist
RUN chmod +x /entrypoint.sh
ENTRYPOINT ["/entrypoint.sh"]

Or copy directly in the final stage:

FROM node:22 AS build
RUN npm run build

FROM node:22-alpine
COPY entrypoint.sh /entrypoint.sh
COPY --from=build /app/dist /app/dist
RUN chmod +x /entrypoint.sh
ENTRYPOINT ["/entrypoint.sh"]

Fix 6: Fix Binary Architecture Mismatch

A binary compiled for the wrong CPU architecture triggers the same error:

# amd64 binary on arm64 platform (Apple Silicon Mac)
exec /app/myserver: no such file or directory

Check the binary architecture:

file myserver
# myserver: ELF 64-bit LSB executable, x86-64  ← amd64

Fix: Build for the correct platform:

# Build for amd64
docker build --platform linux/amd64 -t myimage .

# Build for arm64
docker build --platform linux/arm64 -t myimage .

# Build multi-platform
docker buildx build --platform linux/amd64,linux/arm64 -t myimage .

For Go binaries:

GOOS=linux GOARCH=amd64 go build -o myserver

For statically linked binaries in scratch/distroless:

# Ensure static linking
CGO_ENABLED=0 GOOS=linux go build -a -o myserver

Fix 7: Fix ENTRYPOINT Syntax

Docker has two ENTRYPOINT forms. The wrong form can cause issues:

Exec form (recommended):

ENTRYPOINT ["/entrypoint.sh"]

Shell form (runs through /bin/sh -c):

ENTRYPOINT /entrypoint.sh

The exec form does NOT use a shell. If your script needs shell features, either:

  1. Use the shell form.
  2. Or explicitly call the shell:
ENTRYPOINT ["/bin/sh", "/entrypoint.sh"]

CMD with ENTRYPOINT:

ENTRYPOINT ["/entrypoint.sh"]
CMD ["--default-flag"]
# Runs: /entrypoint.sh --default-flag

Make sure the entrypoint script handles $@ to pass CMD arguments:

#!/bin/sh
set -e
echo "Initializing..."
exec "$@"   # Execute the CMD arguments

Fix 8: Fix Missing Shared Libraries

For compiled binaries, missing shared libraries cause “no such file or directory”:

# Check what libraries the binary needs
docker run --rm --entrypoint ldd myimage /app/myserver
# libpthread.so.0 => not found
# libc.musl-x86_64.so.1 => not found

Fix: Use a compatible base image:

# If compiled on Ubuntu, use Ubuntu (not Alpine)
FROM ubuntu:24.04
COPY myserver /app/myserver

# Or use Alpine if compiled with musl
FROM alpine:3.19
RUN apk add --no-cache libc6-compat  # Adds glibc compatibility
COPY myserver /app/myserver

Fix: Compile statically:

# For Go
FROM golang:1.23 AS build
ENV CGO_ENABLED=0
RUN go build -o /myserver

FROM scratch
COPY --from=build /myserver /myserver
ENTRYPOINT ["/myserver"]

Still Not Working?

Inspect the image layer by layer:

docker history myimage
docker inspect myimage

Use docker run with a different entrypoint to debug:

docker run --rm -it --entrypoint /bin/sh myimage
# Then manually try running the entrypoint
/entrypoint.sh

Check for invisible characters (BOM, zero-width spaces):

hexdump -C entrypoint.sh | head -5
# Should start with 23 21 2f (#!/), not EF BB BF (UTF-8 BOM)

Check for a path component owned by a missing user or group. In rootless Docker or in Podman, the container runs as a remapped UID. If your entrypoint lives at /home/appuser/entrypoint.sh and the UID that owns /home/appuser does not exist in the container’s /etc/passwd, execve may return ENOENT instead of EACCES on certain kernel versions. Either move the entrypoint to a path owned by root (/usr/local/bin/) or add the user explicitly:

RUN useradd -ms /bin/sh appuser
USER appuser

Check for a volume mount that shadows the entrypoint at runtime. If your docker run command includes -v /host/path:/ or -v ./:/app and the entrypoint sits at a path under that mount target, the bind mount replaces the in-image content at startup. The image has the file, but the running container sees an empty directory at that path. Run with the mount removed to confirm:

# Without the mount — works
docker run --rm myimage

# With the mount — fails
docker run --rm -v $(pwd):/app myimage

If this is the issue, mount your code somewhere other than the entrypoint’s parent directory.

Check for .dockerignore patterns that match by negation order. A .dockerignore line like **/* followed later by !entrypoint.sh does not always re-include the file if an intermediate pattern excludes its parent directory. Re-include the directory explicitly:

**/*
!entrypoint.sh
!scripts/
!scripts/entrypoint.sh

Check for Buildx cache corruption. A stale BuildKit cache layer can preserve a previous state where the file was missing. Force a clean rebuild:

docker buildx build --no-cache --pull -t myimage .

For Docker exec format errors when the binary’s architecture is wrong rather than the path, see Fix: Docker exec format error. For Docker container name conflicts after a crashed entrypoint leaves a dead container behind, see Fix: Docker container name already in use. For permission errors that surface as the script being unable to read its own dependencies, see Fix: Docker volume permission denied. For images that exit immediately and end up in a restart loop instead of a clean error, see Fix: Docker container keeps restarting.

F

FixDevs

Solo developer based in Japan. Every solution is cross-referenced with official documentation and tested before publishing.

Was this article helpful?

Related Articles