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 Container That Will Not Start

I have shipped containers with this exact error more times than I want to admit. The file is right there in my source tree, the COPY line in the Dockerfile looks fine, the local build succeeds — and the container exits in 30ms with a message that says the file does not exist. The cause is almost never what the message says. You run a Docker container and it immediately dies 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.

Quick Reference Before You Dive In

If you arrived here from Google with a crash-looping container, the five facts that resolve roughly 90 percent of the cases I have triaged:

  1. The most common cause is Windows line endings (CRLF) in the entrypoint script, even if you do not develop on Windows. A teammate who does is enough. Run sed -i 's/\r$//' entrypoint.sh first.
  2. The error is from the execve(2) syscall. “no such file or directory” can mean the script itself, the interpreter referenced in the shebang line, a shared library the binary needs, or no match for the requested CPU architecture. The kernel uses the same ENOENT message for all four.
  3. Alpine images use /bin/sh (BusyBox ash), not bash. A #!/bin/bash shebang fails on any Alpine-based image unless you apk add --no-cache bash first.
  4. Apple Silicon and other arm64 hosts build x86_64 images by default if BuildKit picks the wrong platform. Use docker build --platform linux/amd64 explicitly when the image must run on amd64 nodes.
  5. The fastest diagnostic is docker run --rm -it --entrypoint /bin/sh myimage, then ls -la /entrypoint.sh and head -1 /entrypoint.sh. Three lines of output usually tell you which of the four ENOENT meanings applies.

The rest of this article covers each of those in detail, plus the failure modes most other guides skip.

What Docker Is Really Saying

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. The Docker runtime relays the kernel error verbatim because it is bound by the OCI runtime specification to propagate the underlying exec failure without reinterpretation.

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.

Docker Feature Version History

Several Docker features used in the fixes below shipped at specific points in the Engine and Buildx release timeline. Knowing which version you are on determines which workaround is available without an upgrade.

FeatureDocker versionNotes
Multi-stage builds (FROM ... AS name, COPY --from=name)17.05 (May 2017)Lets the final image stay small while the build stage carries compilers and dev dependencies.
BuildKit (experimental)18.09 (Nov 2018)Opt-in with DOCKER_BUILDKIT=1. Required for --chmod, --mount=type=cache, and most modern Dockerfile syntax.
docker buildx and multi-platform builds19.03 (Jul 2019)The --platform linux/amd64,linux/arm64 syntax for one-command multi-arch builds.
COPY --chmod=NNNBuildKit with # syntax=docker/dockerfile:1.3Skips the separate RUN chmod +x step. Requires the BuildKit syntax directive at the top of the Dockerfile.
--platform flag stable on docker build20.10 (Dec 2020)Stops the silent host-platform inheritance on Apple Silicon and other arm64 hosts.
BuildKit becomes default builder23.0 (Feb 2023)Legacy builder is still selectable via DOCKER_BUILDKIT=0, but BuildKit-only syntax now works without extra setup.
docker debug (Docker Desktop only)Docker Desktop 4.27 (Feb 2024)A transient debug shell over a running or stopped container, including distroless / scratch images that have no /bin/sh of their own. See the docker debug reference.

Cross-check dates against the Docker Engine release notes before depending on them for a specific patch version; Docker patches frequently and a few of the listed features back-ported into earlier minor versions during the release cycle.

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.

If your team includes anyone on Windows, I treat a .gitattributes with *.sh text eol=lf as table stakes. I have personally chased this CRLF bug for a full afternoon before I learned to standardize line endings at the repository level, and I have never seen it return on a project that committed .gitattributes from day one. Add it before anything else.

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

A specific footgun I have hit on every project that uses Alpine images: I write the entrypoint on macOS or Ubuntu where /bin/sh is bash, then ship it to Alpine where /bin/sh is BusyBox ash. Bash-specific syntax (arrays, [[ ]], ${var//pattern/replacement}) silently breaks. Either apk add --no-cache bash in the image, or rewrite for POSIX sh and run shellcheck -s sh in CI to catch the slips early.

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

The --chmod flag is a BuildKit feature and only takes effect when BuildKit is the active builder (the default since Docker 23.0, opt-in via DOCKER_BUILDKIT=1 on earlier versions). See the official COPY reference for the full list of flags supported per BuildKit version.

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 multi-architecture builds the canonical reference is the Docker multi-platform documentation. The full list of platform identifiers BuildKit accepts (linux/amd64, linux/arm64, linux/arm/v7, linux/386, etc.) lives in the buildx reference.

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"]

No-Shell Images and the PID 1 Problem

Two modern Dockerfile patterns intersect with this error in ways tutorials skip.

Distroless and scratch base images. These minimal images contain only your application binary and its dependencies. There is no shell, no ls, no cat, and no /bin/sh. The standard diagnostic command docker run --rm -it --entrypoint /bin/sh myimage fails immediately on these images because the shell does not exist to fall back to. Three workarounds I use:

  • Use the :debug variant of distroless images (for example gcr.io/distroless/static:debug) which include BusyBox tools. This is the Google-supported escape hatch and the cleanest path when the project already uses distroless.
  • Add a debug stage to a multi-stage Dockerfile. Copy the binary into an Alpine or Debian image first, debug there, then move to distroless once the binary runs. This keeps the production image small without sacrificing debuggability during development.
  • Use docker debug if you have Docker Desktop 4.27 or later. It provides a transient shell environment around the container without modifying the image, including for shell-less images.

For statically linked binaries running on scratch, the additional consideration is that ldd is not available inside the container. If your binary still fails with no such file or directory even though it exists, the cause is almost always a missing shared library (Fix 8) that you cannot diagnose from inside the image because the diagnostic tool is absent. The workaround is to run ldd on the host, against the binary before it goes into the image, then add a non-distroless debug build target so you can inspect the runtime dependencies.

PID 1 and signal handling. When your container starts, the entrypoint process becomes process ID 1 inside the container’s PID namespace. PID 1 has two special responsibilities that ordinary processes do not: it receives the signals sent to the container (SIGTERM on docker stop, SIGKILL after the grace period), and it is supposed to reap zombie child processes left behind when subprocesses exit. A shell script as PID 1 does neither correctly. The visible symptom is docker stop mycontainer taking ten seconds to actually terminate the container, because the SIGTERM never propagates to the application and Docker has to escalate to SIGKILL after the default grace period.

This problem does not cause no such file or directory, but it surfaces next: once your entrypoint runs cleanly, the next bug is usually a slow shutdown. The fixes:

  • Run the container with docker run --init, which inserts the tini init system as PID 1 automatically. This is the simplest path and works for almost every workload.
  • Or include tini or dumb-init in the image as the actual ENTRYPOINT, and have it exec your script.
  • Or write the entrypoint to handle SIGTERM explicitly using a trap:
#!/bin/sh
trap 'kill -TERM $PID' TERM INT
"$@" &
PID=$!
wait $PID

The two concerns interlock: getting the entrypoint to run correctly is step one, making it a well-behaved PID 1 is step two. I treat the PID 1 question as part of the “entrypoint is correct” checklist on any new image.

Diagnostic Flowchart for ENOENT

The same no such file or directory message has four distinct meanings. This walk through bisects them in roughly the order I would on a live incident.

docker run myimage  ->  exec /entrypoint.sh: no such file or directory
            |
            v
1. Drop into a shell in the image:
     docker run --rm -it --entrypoint /bin/sh myimage
            |
            v
2. Inside, run:  ls -la /entrypoint.sh
            |
   +--------+---------+
   |                  |
   v                  v
NOT FOUND       FILE EXISTS
   |                  |
   v                  v
Cause A:        3. Inside, run:  head -1 /entrypoint.sh
file not copied        |
(Fix 2: COPY,  +-------+----------+--------+
.dockerignore, |       |          |        |
multi-stage)   v       v          v        v
            Shows    Shebang     Shebang   Continue
            "\r" at  points at   looks
            end of   missing     fine
            shebang  interpreter
              |        |          |
              v        v          v
            Cause B  Cause C    4. Inside: ls -l /entrypoint.sh
            (CRLF,   (wrong          - missing x in perms
             Fix 1)  base image,           -> Cause D (Fix 4)
                     Fix 3)            otherwise:
                                  5. Inside: file /entrypoint.sh
                                       - ELF for wrong arch
                                              -> Cause E (Fix 6)
                                       - or run ldd /path/to/binary
                                              not-found shared libs
                                              -> Cause F (Fix 8)

The single most diagnostic command is head -1. It reveals CRLF (which is invisible to cat), missing interpreter paths, and BOM bytes all at once. I run it before guessing at any of the fixes.

What Other Tutorials Get Wrong About This Error

Most articles I have read about exec /entrypoint.sh: no such file or directory give a list of fixes without explaining why the same error message points at four different causes. The gaps I see most often:

They only mention CRLF. CRLF is the most common single cause, but it is one of four. An article that only covers it sends every reader with an arm64 architecture mismatch down the wrong path. The kernel-level ENOENT ambiguity is the only honest framing.

They confuse this with exec format error. The two errors are siblings but not synonyms. no such file or directory is ENOENT; exec format error is ENOEXEC, raised when the file exists but is not a valid executable for the running architecture (e.g., a 16-bit MS-DOS binary, or a script with no shebang). The fix paths for the two errors only partially overlap. See Docker exec format error for the ENOEXEC sibling.

They recommend the shell form of ENTRYPOINT as a fix. Switching from ENTRYPOINT ["/entrypoint.sh"] to ENTRYPOINT /entrypoint.sh makes Docker wrap the command in /bin/sh -c, which can mask the underlying cause. The script may now succeed because the shell handles a missing shebang or a non-executable file gracefully, but the real bug (the CRLF, the missing chmod, the wrong base image) is still there and will resurface the moment you switch back to exec form or move to a distroless image with no shell.

They skip the silent multi-platform case entirely. On Apple Silicon and other arm64 hosts, BuildKit picks the build platform from the host by default. If your CI runs on amd64 and your laptop runs on arm64, the image that worked for you may fail in CI with this exact error and no other diagnostic signal. The --platform flag is not optional once a single arm64 machine is in the pipeline.

They miss the volume mount shadowing pattern. A docker run -v ./:/app myimage for “live reload during dev” replaces the in-image content at the mount target, including the entrypoint if it lives under that path. The image build is clean, the inspection shows the file, and the runtime still fails. The fix is to mount somewhere else, not to debug the build.

They omit the diagnostic command. Most articles open with the fixes. The single most useful action is the inspection: docker run --rm -it --entrypoint /bin/sh myimage followed by ls -la and head -1. Three commands separate the four causes in roughly thirty seconds.

Failures I Have Hit That Are Not in the Docs

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 .

Frequently Asked Questions

What is the difference between ENTRYPOINT and CMD?

ENTRYPOINT is the command that always runs when the container starts. CMD provides default arguments to that command. Used together, ENTRYPOINT ["/entrypoint.sh"] with CMD ["--default-flag"] produces /entrypoint.sh --default-flag at startup. The CMD portion can be overridden at docker run time by passing arguments after the image name. The ENTRYPOINT can only be overridden with the --entrypoint flag. The convention I follow: ENTRYPOINT runs environment setup and ends with exec "$@", and CMD is the actual long-running process the container is built to host.

Why does my container exit immediately?

A Docker container exits as soon as its main process exits. If your entrypoint runs a setup script and ends without an exec call to a long-running process, the container terminates the moment the script returns. The fix is usually exec "$@" at the end of the entrypoint, which replaces the shell with the CMD command rather than letting the shell exit. If the entrypoint dies with no such file or directory specifically, work through Fix 1 through Fix 8 above; for other immediate-exit causes (missing exec, malformed CMD, errors inside the script), see Docker container keeps restarting.

What is the difference between no such file or directory and exec format error?

Both come from the kernel but mean different things. no such file or directory is ENOENT from execve(2): the kernel could not resolve a path it needed, which can be the script itself, the interpreter named in the shebang, a required shared library, or a binary built for the wrong architecture. exec format error is ENOEXEC: the file exists and the architecture matches, but the kernel does not recognize the file as a valid executable format. This usually means the script has no shebang line at all, or the file is text masquerading as a binary. The fix paths only partially overlap. See Docker exec format error for the ENOEXEC sibling error.

How do I keep a container running after the entrypoint finishes?

The container stays alive as long as PID 1 stays alive. If you want a container that runs indefinitely for debugging or experimentation, append a long-running command at the end of the entrypoint such as tail -f /dev/null, or use that as the entrypoint itself. For production services, PID 1 should be the actual application (a web server, a worker process, etc.) and the container survives as long as it does. Be aware of the PID 1 / signal handling concerns covered in the section above.

What is PID 1 in Docker?

When a container starts, the first process spawned inside the container’s PID namespace gets process ID 1. PID 1 inherits two special responsibilities from the kernel: it receives the signals sent to the container by Docker (SIGTERM during docker stop, SIGKILL after the grace period), and it is supposed to reap zombie children left behind when subprocesses exit. Shell scripts running as PID 1 fail at both jobs unless explicitly programmed to handle them. The practical fix is to run a real init system like tini either via docker run --init or by including it in the image as the entrypoint.

How do I debug a container that will not start?

The universal pattern across every fix in this article is to override the entrypoint and drop into a shell:

docker run --rm -it --entrypoint /bin/sh myimage

From the shell, run ls -la /entrypoint.sh, head -1 /entrypoint.sh, file /entrypoint.sh, and (for binaries) ldd /path/to/binary. Those four commands separate the four meanings of ENOENT. For distroless and scratch images that have no shell, see the “No-Shell Images” section above; Docker Desktop 4.27+ provides docker debug as a modern alternative that works even on shell-less images.

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