Container lifecycle, logs and debugging
States and restart policies, where stdout goes, exec versus attach, inspect and exit codes, and the reflex that finds the fault quickly.
States and restart policies
| State | Means | How you get out |
|---|---|---|
created | Exists but never started | docker start |
running | PID 1 is alive | docker stop |
paused | Frozen by the freezer cgroup | docker unpause |
exited (N) | PID 1 finished with status N | docker start or docker rm |
restarting | Restart policy is looping on a crash | Read the logs, fix the cause |
dead | The runtime cannot clean it up | docker rm -f |
docker ps -a --format 'table {{.Names}}\t{{.Status}}\t{{.Image}}'
docker inspect -f '{{.State.Status}} {{.State.ExitCode}} {{.State.OOMKilled}}' app
docker inspect app --format '{{json .HostConfig.RestartPolicy}}'
docker stop app # SIGTERM, then SIGKILL after the grace period
docker stop -t 30 app # give it 30 seconds to flush
docker start app # same configuration as the original run| Restart policy | Behaviour |
|---|---|
no | Never restart - the default |
on-failure[:max] | Restart only on a non-zero exit |
always | Restart even after a manual stop, and after a daemon restart |
unless-stopped | Restart always, except when you stopped it yourself |
--restart unless-stoppedis the usual production choice;alwayswill fight you when you intentionally stop a container for maintenance.- A crash loop hides the real error: read the first ten log lines of the first failed attempt, not the noise from the hundredth restart.
docker update --restart unless-stopped appchanges the policy without recreating the container.
Logs: where they come from
docker logs app # everything PID 1 ever wrote
docker logs -f --tail 100 app # follow the last 100 lines
docker logs -t --since 30m app # timestamps, last 30 minutes
docker logs --until 2026-09-18T09:00:00 app
docker inspect -f '{{.HostConfig.LogConfig.Type}}' app
ls -lh $(docker inspect -f '{{.LogPath}}' app) # the file on the host
# rotate it - json-file grows without limit by default
docker run -d --log-opt max-size=10m --log-opt max-file=3 --name web nginx
# or globally, in /etc/docker/daemon.json
{ "log-driver": "json-file", "log-opts": { "max-size": "10m", "max-file": "3" } }docker logsshows stdout and stderr only. A process that writes to a file inside the container is invisible here.- Applications should log to stdout and let the platform collect it — that is what makes containers portable between engines and orchestrators.
- The default
json-filedriver never rotates on its own and is the usual cause of a full/var/lib/docker. - Other drivers (
journald,syslog,fluentd,gelf) send logs elsewhere, and thendocker logsreturns nothing — check the driver before you conclude the app is silent.
Getting inside a running container
| Tool | Opens | Use it when |
|---|---|---|
docker exec -it app sh | A new process in the same namespaces | Almost always - it leaves PID 1 running |
docker attach app | Your terminal onto PID 1's streams | You need to interact with the main process itself |
docker top app | The process list | You want to see what PID 1 spawned |
docker stats app | Live CPU, memory, network and I/O | Something is slow or getting killed |
docker events --filter container=app | The daemon's event stream | You need to know when it died and who killed it |
docker diff app | Files changed in the writable layer | You suspect the app edits itself |
docker exec -it app sh
docker exec app env | sort
docker exec app cat /etc/hosts
docker exec app ls -l /app
# use the image's own tools when a container has no shell at all
docker run --rm -it --pid container:app --network container:app nicolaka/netshoot
docker cp app:/var/log/app/error.log ./error.log
docker cp ./config.yml app:/app/config.yml
docker commit app debug-snapshot:1 # last resort: turn a broken state into an image
docker inspect -f '{{range .Mounts}}{{.Source}} -> {{.Destination}}{{"\n"}}{{end}}' app⚠️
Everything you change with
docker exec or docker cp lives in the writable layer and vanishes the moment the container is replaced. Use it to diagnose, never to deploy: a fix belongs in the image or in a mounted config file, or it will disappear on the next restart.FAQ
Exit code 0 but the container stopped?
PID 1 completed successfully — that is a normal exit for a one-shot task. If it should have stayed up, the command is not a long-running foreground process. Exit 137 means SIGKILL or an out-of-memory kill, and 143 means it received SIGTERM.
How do I read the logs of a container that keeps restarting?
Read the tail of the current attempt with
docker logs --tail 50 app, then the beginning of the failure with docker logs --since around the first crash. Consider pausing the loop with --restart no while you investigate.Related
Installing Docker and the core CLI workflow Production concerns: resources, health and logging
Last refreshed 2026-09-18.