Fix: Docker exec /entrypoint.sh: no such file or directory
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 directoryOr variations:
exec /app/start.sh: no such file or directorystandard_init_linux.go:228: exec user process caused: no such file or directoryexec /usr/local/bin/docker-entrypoint.sh: no such file or directoryOCI runtime create failed: container_linux.go:380: starting container process caused: exec: "/entrypoint.sh": stat /entrypoint.sh: no such file or directoryDocker 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:
- 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.shfirst. - 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 sameENOENTmessage for all four. - Alpine images use
/bin/sh(BusyBox ash), not bash. A#!/bin/bashshebang fails on any Alpine-based image unless youapk add --no-cache bashfirst. - Apple Silicon and other arm64 hosts build x86_64 images by default if BuildKit picks the wrong platform. Use
docker build --platform linux/amd64explicitly when the image must run on amd64 nodes. - The fastest diagnostic is
docker run --rm -it --entrypoint /bin/sh myimage, thenls -la /entrypoint.shandhead -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:
- The script itself — the file path is wrong or the file was not copied.
- The interpreter — the shebang line (
#!/bin/bash) references a binary that does not exist in the image. - A linked library — the binary exists but depends on a shared library that is missing.
- Windows line endings —
\r\nline 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\nline endings. - File not copied into the image. The
COPYorADDinstruction has the wrong path. - Wrong base image. The script uses
#!/bin/bashbut 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.
| Feature | Docker version | Notes |
|---|---|---|
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 builds | 19.03 (Jul 2019) | The --platform linux/amd64,linux/arm64 syntax for one-command multi-arch builds. |
COPY --chmod=NNN | BuildKit with # syntax=docker/dockerfile:1.3 | Skips the separate RUN chmod +x step. Requires the BuildKit syntax directive at the top of the Dockerfile. |
--platform flag stable on docker build | 20.10 (Dec 2020) | Stops the silent host-platform inheritance on Apple Silicon and other arm64 hosts. |
| BuildKit becomes default builder | 23.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 existFix in the Dockerfile:
COPY entrypoint.sh /entrypoint.sh
RUN sed -i 's/\r$//' /entrypoint.sh && chmod +x /entrypoint.shFix 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.shFix with .gitattributes (permanent):
# Force LF line endings for all shell scripts
*.sh text eol=lf
entrypoint.sh text eol=lf
Dockerfile text eol=lfFix 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.shCheck 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.shCheck .dockerignore:
# If .dockerignore contains this, the file is excluded from the build context!
*.sh
entrypoint.shRemove the entry from .dockerignore or add an exception:
*.sh
!entrypoint.shFix 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 defaultA 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.shThe --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 directoryCheck the binary architecture:
file myserver
# myserver: ELF 64-bit LSB executable, x86-64 ← amd64Fix: 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 myserverFor statically linked binaries in scratch/distroless:
# Ensure static linking
CGO_ENABLED=0 GOOS=linux go build -a -o myserverFix 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.shThe exec form does NOT use a shell. If your script needs shell features, either:
- Use the shell form.
- Or explicitly call the shell:
ENTRYPOINT ["/bin/sh", "/entrypoint.sh"]CMD with ENTRYPOINT:
ENTRYPOINT ["/entrypoint.sh"]
CMD ["--default-flag"]
# Runs: /entrypoint.sh --default-flagMake sure the entrypoint script handles $@ to pass CMD arguments:
#!/bin/sh
set -e
echo "Initializing..."
exec "$@" # Execute the CMD argumentsFix 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 foundFix: 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/myserverFix: 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
:debugvariant of distroless images (for examplegcr.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 debugif 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 itexecyour script. - Or write the entrypoint to handle SIGTERM explicitly using a
trap:
#!/bin/sh
trap 'kill -TERM $PID' TERM INT
"$@" &
PID=$!
wait $PIDThe 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 myimageUse docker run with a different entrypoint to debug:
docker run --rm -it --entrypoint /bin/sh myimage
# Then manually try running the entrypoint
/entrypoint.shCheck 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 appuserCheck 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 myimageIf 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.shCheck 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 myimageFrom 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.
Solo developer based in Japan. Every solution is cross-referenced with official documentation and tested before publishing.
Was this article helpful?
Related Articles
Fix: Coolify Not Working — Deployment Failing, SSL Not Working, or Containers Not Starting
How to fix Coolify self-hosted PaaS issues — server setup, application deployment, Docker and Nixpacks builds, environment variables, SSL certificates, database provisioning, and GitHub integration.
Fix: Docker Secrets Not Working — BuildKit --secret Not Mounting, Compose Secrets Undefined, or Secret Leaking into Image
How to fix Docker secrets — BuildKit secret mounts in Dockerfile, docker-compose secrets config, runtime vs build-time secrets, environment variable alternatives, and verifying secrets don't leak into image layers.
Fix: Docker Compose Healthcheck Not Working — depends_on Not Waiting or Always Unhealthy
How to fix Docker Compose healthcheck issues — depends_on condition service_healthy, healthcheck command syntax, start_period, custom health scripts, and debugging unhealthy containers.
Fix: Docker Multi-Platform Build Not Working — buildx Fails, Wrong Architecture, or QEMU Error
How to fix Docker multi-platform build issues — buildx setup, QEMU registration, --platform flag usage, architecture-specific dependencies, and pushing multi-arch manifests to a registry.