Debugging Containers
Find out why a container exits, crashes or misbehaves: exit codes, logs, inspect, exec, restart policies, health checks, stats and docker cp.
On this page 12 sections
Introduction
Containers fail like any software: a typo in a command, a missing package, a wrong port, an app that runs out of memory. The good news is that Docker gives you a small, reliable set of tools to find out what happened.
- A step-by-step routine for diagnosing a broken container.
- How to read exit codes, logs and docker inspect.
- How restart policies and health checks keep services running.
- How to look inside running containers with exec, top, stats, diff and cp.
A Debugging Checklist
Put this debugging routine in a sensible order.
- docker inspect NAME — check the exit code, command, ports and environment
- Fix the cause, rebuild if needed, and start a new container
- docker ps -a — is the container running, and what is its status?
- docker logs NAME — what did the app print before it failed?
- docker exec -it NAME sh — look around inside, if it is running
The Container Will Not Start
If the command itself is wrong, Docker cannot start the container at all. A typo:
docker run --name oops quotes-api pyhton app.pyDocker reports that the executable “pyhton” was not found in $PATH, and docker run exits with code 127. The container exists but never ran, so docker ps -a shows it with status Created. Read the error, fix the command, then remove the failed container with docker rm oops.
The Container Crashes
More often the app starts and then fails. Here the app tries to import a package that is not installed in the image:
docker run -d --name crash quotes-api python -c "import yaml"docker ps -a --filter name=crash --format "{{.Names}}: {{.Status}}"The status is Exited (1). The logs show exactly why — a Python traceback ending in ModuleNotFoundError: No module named 'yaml':
docker logs crashdocker inspect crash --format "Status: {{.State.Status}}, exit code: {{.State.ExitCode}}"The fix is in the image: add the package to requirements.txt and rebuild. Logs are kept after a container stops, so you can always read why it died — until you remove it.
Restart Policies
A restart policy tells Docker what to do when a container’s main process exits:
| Policy | Behaviour |
|---|---|
| no (default) | Never restart |
| on-failure[:N] | Restart only after a non-zero exit code, optionally at most N times |
| unless-stopped | Always restart (also after a reboot), unless you stopped it yourself |
| always | Always restart, even if you stopped it, once the Docker daemon restarts |
docker run -d --name flaky --restart on-failure:3 alpine sh -c "echo starting...; sleep 1; exit 1"docker logs flakydocker inspect flaky --format "Restarts: {{.RestartCount}}, status: {{.State.Status}}"After a few seconds the log shows “starting...” four times — the first run plus three restarts — and then Docker gives up, leaving the container exited. A container stuck in a restart loop shows the status Restarting in docker ps; its logs tell you why it keeps failing.
Health Checks
A running process is not necessarily a working app — it could be stuck. A health check is a command Docker runs regularly inside the container; if it fails several times in a row, the container is marked unhealthy. The slim Python image has no curl, so the check uses Python itself:
docker run -d --name api -p 5000:5000 \ --health-cmd "python -c \"import urllib.request; urllib.request.urlopen('http://localhost:5000/')\"" \ --health-interval 5s \ quotes-apidocker ps --filter name=api --format "{{.Names}}: {{.Status}}"The status goes from (health: starting) to (healthy). Health checks are usually written into the Dockerfile with HEALTHCHECK (lesson 14), and Compose uses them for depends_on conditions (lesson 10).
Looking Inside a Running Container
| Command | What it shows |
|---|---|
| docker exec -it api sh | A shell inside the container (bash if the image has it) |
| docker exec api ls -la /app | Run one command, e.g. check the files are where you expect |
| docker top api | The processes running inside the container |
| docker stats --no-stream api | CPU, memory, network and disk use (without --no-stream it updates live) |
| docker diff api | Files added (A), changed (C) or deleted (D) since the container started |
| docker cp api:/app/app.py . | Copy a file out of a container (or in, with the arguments swapped) |
| docker logs --since 10m -f api | Follow logs from the last ten minutes |
| docker inspect api | Everything Docker knows: config, mounts, networks, state |
| docker events | A live stream of what Docker is doing (start, die, health_status…) |
docker exec api python -c "import flask; print(flask.__version__)"docker exec api sh -c "echo debug > /tmp/notes.txt"docker diff apiThe first command confirms which Flask version is really inside the image; docker diff then lists /tmp/notes.txt as added. Remember that changes made by hand inside a container are lost when it is recreated — fix the Dockerfile, not the container.
docker ps -a shows a container with status Exited (137). What most likely happened?
Exit Codes Reference
| Exit code | Usual meaning |
|---|---|
| 0 | Finished successfully |
| 1 | General application error — read the logs |
| 125 | docker run itself failed (bad option, port in use…) |
| 126 | The command exists but could not be executed (permissions) |
| 127 | Command not found — typo or missing program in the image |
| 137 | Killed with SIGKILL — docker kill or out of memory |
| 143 | Stopped with SIGTERM — a normal docker stop |
Common Mistakes
- Removing a crashed container before reading its logs.
- Using --rm while debugging, so the container and its logs disappear immediately.
- Fixing things by hand inside a container instead of in the Dockerfile.
- Expecting tools like curl, ps or bash in slim and minimal images — use the language itself or a debugging container.
Frequently Asked Questions
Read docker logs first. To look around, start the image with a shell instead of its normal command: docker run -it --rm --entrypoint sh quotes-api.
Docker Desktop and recent Docker versions offer docker debug, and in general you can run a tools container that shares the target’s network or process namespace, e.g. docker run -it --rm --network container:api nicolaka/netshoot.
By default Docker keeps them as JSON files managed by the daemon. Lesson 14 shows how to limit their size so they cannot fill the disk.
Key Takeaways
- Start with docker ps -a and docker logs — they explain most problems.
- Exit codes point to the cause: 127 command not found, 137 killed, 1 app error.
- Restart policies keep services running; health checks detect stuck apps.
- exec, top, stats, diff and cp let you look inside running containers.
- Fix problems in the Dockerfile and recreate the container.
Summary
You now have a dependable routine for fixing broken containers. Next you will learn to share images through registries such as Docker Hub.