Container Startup Failures — One Guide to the Whole Class
Exec format errors, permission denied, missing commands, bad entrypoints, immediate exits, and CrashLoopBackOff — diagnosed as one family rather than a page per error string.
There are perhaps a dozen distinct reasons a container fails to start, and roughly a hundred error strings they can produce. Writing a page per error string would be easy and would leave you worse off, because the string you got is often not the one that names your actual problem.
This covers the whole class, in the order the checks should be run.
Start here, always
# Docker: why did it exit, and what did it say?
docker ps -a --filter "name=<name>" --format '{{.Status}}'
docker logs --tail 50 <container>
docker inspect --format '{{.State.ExitCode}} {{.State.Error}}' <container>
# Kubernetes: the previous container's logs survive the restart
kubectl describe pod <pod> | sed -n '/Events:/,$p'
kubectl logs <pod> --previous --tail=50
kubectl get pod <pod> -o jsonpath='{.status.containerStatuses[*].lastState.terminated.reason}'
The exit code narrows it fast
| Code | Meaning | Where to look |
|---|---|---|
| 0 | Exited normally | The command completed. Usually a foreground/background problem |
| 1 | Application error | Your logs. This is an application failure, not a container one |
| 2 | Shell misuse | Malformed command, usually in shell-form CMD |
| 126 | Found but not executable | Permissions or missing execute bit |
| 127 | Command not found | Wrong path, or missing from the image |
| 137 | SIGKILL | OOM kill, or a failed graceful shutdown after SIGTERM |
| 139 | Segmentation fault | Often architecture mismatch or a broken binary |
| 143 | SIGTERM | Terminated cleanly by the orchestrator |
Codes 137 and 143 are the pair most often confused. 143 means the process shut down when asked. 137 means it did not, and was killed — either by the OOM killer or after ignoring SIGTERM through the grace period.
The seven causes
1. The process is not in the foreground
The container’s lifetime is the lifetime of PID 1. If your command starts a service and returns, the container exits with code 0 and everything looks fine except that nothing is running.
CMD ["nginx"] # daemonises, container exits
CMD ["nginx", "-g", "daemon off;"] # stays in the foreground
Anything with a -d, --daemon, --background, or & is suspect. Exit code 0 with no error is
the signature.
2. Exec format error
exec /app/server: exec format error
Almost always an architecture mismatch: an image built for amd64 running on arm64, or the
reverse. Increasingly common as ARM hosts become normal.
docker inspect --format '{{.Architecture}}/{{.Os}}' myimage:tag
uname -m
The other cause is a script with no shebang line, or with a shebang carrying Windows line endings
— #!/bin/sh\r is not a valid interpreter path, and the error message will not mention the \r.
file ./entrypoint.sh # look for "CRLF line terminators"
Fix the build to target the right platform with --platform, or normalise line endings and add
the shebang.
3. Permission denied
exec /app/server: permission denied
Either the file is not executable, or the user cannot reach it.
COPY --chmod=0755 entrypoint.sh /app/entrypoint.sh
If the executable bit is set, the problem is the user. A USER 10001 that cannot traverse a
directory owned by root with restrictive permissions produces exactly this. Check the full path,
not just the file.
4. Command not found
exec: "/app/server": stat /app/server: no such file or directory
The file is not where the image thinks it is. Look, rather than reason about it:
Common causes: a multi-stage COPY --from pointing at the wrong path, a build that silently
produced nothing, or a binary that exists but was dynamically linked against libraries the final
image does not have. That last one produces “not found” for a file you can see, which is
confusing until you know it — the missing thing is the loader, not the binary.
docker run --rm --entrypoint ldd myimage:tag /app/server
5. OOM kill (137)
The container exceeded its memory limit and was killed.
kubectl get pod <pod> -o jsonpath='{.status.containerStatuses[*].lastState.terminated.reason}'
# OOMKilled
If it dies during startup, the limit is below what the application needs to initialise. If it dies later under load, the limit is below its working set.
Runtimes that are not container-aware are a specific trap: a JVM or similar that reads host memory rather than the cgroup limit will size its heap for the machine and get killed by a limit it does not know about.
6. Configuration or dependency failure (exit 1)
The container started, the application ran, and it exited. This is an application failure and the logs will say so — a missing environment variable, an unreachable database, an unparseable config file.
In Kubernetes this presents as CrashLoopBackOff with increasing backoff. The loop is a symptom;
the cause is in --previous logs.
7. Killed by a probe before it was ready
The application is fine and needs longer to start than the probe allows. The liveness probe fails, the container is killed, and it restarts — forever.
startupProbe:
httpGet: { path: /healthz, port: 8080 }
failureThreshold: 30
periodSeconds: 10 # allows up to 5 minutes to start
livenessProbe:
httpGet: { path: /healthz, port: 8080 }
periodSeconds: 10 # only begins after startupProbe succeeds
A startupProbe is the correct answer. Loosening the liveness probe to accommodate startup
leaves you unable to detect a genuinely hung process later.
The decision tree
Container will not stay running.
Exit code?
├─ 0 → process is not in the foreground, or the command completed
├─ 1 → application error. Read --previous logs; this is not a container problem
├─ 126 → found but not executable → chmod, or the user cannot traverse the path
├─ 127 → not found → wrong path, missing binary, or missing shared library
├─ 137 → killed
│ ├─ terminated.reason == OOMKilled → memory limit
│ └─ otherwise → ignored SIGTERM through the grace period
├─ 139 → segfault → architecture mismatch or broken binary
├─ 143 → clean SIGTERM. Something asked it to stop; find out what
└─ exec format error → architecture mismatch, or shebang / CRLF
No exit code, restarts forever, logs stop mid-startup?
└─ probe killing it before ready → add a startupProbe
Validating a fix
# It stays up
docker run -d --name check myimage:tag && sleep 5 && docker ps --filter name=check
# It stops promptly (proves SIGTERM is reaching the process)
time docker stop check
# In Kubernetes: no restarts after the change
kubectl get pod <pod> -w
kubectl get pod <pod> -o jsonpath='{.status.containerStatuses[*].restartCount}'
A restart count that stays at its current value for several minutes under normal load is the
confirmation. A pod that is Running but whose restart count is still climbing has not been
fixed.
Verification status
This resource has not been executed end to end in a lab environment. Commands and configuration are reviewed by an engineer, but treat them as reference rather than as a tested procedure.
Author
James Joyner
Builds and operates the infrastructure layers underneath production AI systems.
James founded Inside The AI Stack to publish the kind of infrastructure and operations material he wanted while running production systems: specific, tested where it claims to be tested, and written by someone who has had to fix the thing at 3am. He works across AI infrastructure, private cloud, and platform engineering, and reviews every technical resource published here before it is marked as verified.
- AI infrastructure
- OpenStack operations
- Kubernetes
- Terraform
- Linux systems engineering
- Observability
Primary sources
- Dockerfile reference — Docker
Continue from here
Related resources chosen because they are the next thing you would actually need — not because they share a keyword.
Docker Production Engineering
Building container images for production: reproducible builds, correct signal handling, non-root runtime, layer strategy, and keeping secrets out of image history.
Kubernetes Workload Troubleshooting
A single diagnostic path for workloads that will not schedule, will not stay up, or keep getting evicted — driven by events and previous logs rather than guesswork.
Lab: CrashLoopBackOff
A service crash-looping after a routine config change, with logs that show a normal startup and no error anywhere. Work out what is killing it.
Tool
Docker Production Auditor
Check a Dockerfile against production-readiness rules.
Available now
Newsletter
Inside The AI Stack Brief
A practical weekly briefing on AI engineering, infrastructure, production operations, and the technologies powering the AI stack.
One email a week. No sponsorship placements inside the technical sections. Unsubscribe in one click.