04 - Container Lifecycle¶
What this session is¶
About 30 minutes. The container's life: create, start, pause, stop, restart, exec into, view logs, remove. Plus how to debug a container that won't start.
Container states¶
A container is in one of these states at any given time:
- created - Docker has set it up but not started it.
- running - main process is alive.
- paused - all processes frozen (uncommon).
- restarting - Docker is restarting it.
- exited - main process finished (with some exit code).
- dead - broken, can't recover. Remove and recreate.
docker ps shows running. docker ps -a shows all states.
List¶
docker ps # only running
docker ps -a # all states
docker ps -q # only IDs (useful in scripts)
docker ps --filter "status=exited" # filter
docker ps --format "table {{.ID}}\t{{.Image}}\t{{.Status}}" # custom columns
The default columns: ID, image, command, created, status, ports, name.
Start, stop, restart¶
When you docker run, it creates + starts. You can also do them separately:
docker create --name foo nginx # created but not running
docker start foo # starts (returns immediately)
docker start -a foo # starts AND attaches stdout/stderr
For an already-running container:
docker stop foo # SIGTERM, then SIGKILL after 10s
docker stop -t 30 foo # custom grace period (30s)
docker kill foo # immediate SIGKILL
docker restart foo # stop + start
docker stop is the polite option: it sends SIGTERM, gives the process up to 10 seconds to clean up, then SIGKILLs. docker kill is immediate. Use stop unless the process is wedged.
Logs¶
docker logs foo # all stdout/stderr from the main process
docker logs -f foo # follow (like tail -f)
docker logs --tail 100 foo # last 100 lines
docker logs --since 10m foo # last 10 minutes
docker logs -f --tail 20 foo # follow, starting from last 20
-f is the most-used. Open a second terminal, run docker logs -f myservice, watch as you exercise the service.
Exec: get a shell inside a running container¶
docker exec -it foo sh # shell into the container
docker exec foo cat /etc/hostname # one-off command
docker exec -e KEY=val foo env # one-off with extra env
exec is one of the most useful debugging tools. Container's web server returning 500s? docker exec -it web bash and look around. Database container won't accept connections? docker exec -it db psql and check from inside.
Many minimal images don't have bash. Use sh instead.
Stats¶
docker stats # live CPU/memory/network for all running
docker stats --no-stream # one-shot snapshot
docker stats foo # one container
Useful for quick "is this container hot?" checks.
Inspect a container¶
Long JSON: configuration, mounts, network settings, environment, restart policy. Useful when "why is this container behaving like X?"
Specific fields with --format:
docker inspect --format='{{.State.Status}}' foo
docker inspect --format='{{.NetworkSettings.IPAddress}}' foo
docker inspect --format='{{range .Mounts}}{{.Source}} -> {{.Destination}}{{"\n"}}{{end}}' foo
Cleanup¶
docker rm foo # remove a stopped container
docker rm -f foo # force (stops + removes a running one)
docker container prune # remove all stopped containers
Containers using --rm clean themselves up. Without --rm, they accumulate. Periodic docker container prune is fine.
Debugging "container won't start"¶
A container that exits immediately is the most common debug case. Workflow:
-
Check logs:
The last few lines of stdout/stderr almost always tell you why. Missing env var? Bad config file? Permission denied? -
Check exit code:
Exited (0)- finished normally.Exited (1)- error.Exited (139)- segfault.Exited (137)- killed (often OOM). -
Try running interactively - bypass the image's default command:
Now you have a shell in a fresh container of the same image. Inspect: are the files there? Is the script executable? Does the binary even run? -
Recreate the exact env, then exec into it:
-
Read the Dockerfile (page 05) - find the original repo on Docker Hub, look at the
ENTRYPOINT/CMD. Sometimes the image expects environment variables you didn't set.
A real session¶
Imagine the issue: "my Postgres container exits immediately."
docker run -d --name pg postgres:16
docker ps # not there!
docker ps -a # shows Exited (1)
docker logs pg
Output ends with:
Fix:
docker rm pg
docker run -d --name pg -e POSTGRES_PASSWORD=secret postgres:16
docker ps # running now
docker logs pg # confirm clean startup
This loop (run → check logs → fix → run again) is most of container debugging.
Restart policies¶
For containers you want to survive crashes or host reboots:
Policies:
- no (default) - never restart.
- on-failure - restart if exit code != 0.
- on-failure:3 - at most 3 times.
- always - always restart (even if you docker stop it).
- unless-stopped - always restart, EXCEPT if you manually stopped it.
unless-stopped is the right policy for most services.
Exercise¶
-
Run, inspect, exec, log:
-
Watch stats while running a CPU-bound container:
-
Debug a "container won't start":
-
Auto-restart:
What you might wonder¶
"My container exits with code 137 - what is that?"
SIGKILL (9) + 128 = 137. Either out of memory (Docker OOM-killed it) or someone docker killed it. Check docker inspect --format='{{.State.OOMKilled}}' foo.
"My container exits with 139?" SIGSEGV (11) + 128 = 139. Segfault - the program crashed. Look at logs; check if you're missing a library or running the wrong architecture image.
"Can I attach my terminal to a running container instead of exec?"
docker attach foo connects your stdin/stdout to the main process. Different from exec (which starts a new process inside). Less useful in practice; people use exec more.
Done¶
- Understand container states.
- List, start, stop, restart, kill containers.
- Read logs (and follow with
-f). - Exec into running containers.
- Inspect for configuration details.
- Debug containers that won't start.
- Set restart policies.
Next: Building images with Dockerfile →