The Container Lifecycle: Commands, Exit Codes and docker inspect
The commands you'll use daily, what an exit code is telling you, and why docker inspect is the habit that makes generated commands safe to run.
From Model to Muscle Memory
The first two guides built the model: a container is a process with a restricted view, created by a chain of software you can now name. This one turns that into daily practice — the states a container moves through, the handful of commands worth knowing properly, and the single habit that will save you the most time.
That habit, stated up front: when a container misbehaves, stop reading the command that created it and start reading what the container actually got.
The Lifecycle
The state that causes the most confusion is Exited. An exited container has not gone anywhere. It still exists, still holds the writable layer from guide one, and still occupies disk. It just has no running process.
This explains two things beginners find odd:
docker psshows nothing after your container "disappeared" — becausedocker pslists only running containers.docker ps -alists everything.- A container exits the instant its main process exits. There's no background service keeping it up. Run a container whose command is
echo helloand it is Exited before you finish reading the output — correctly so.
This is the most common "why did my container stop immediately?" answer. A container needs a process that keeps running — a server that listens, a worker that loops. Handing it a command that completes in 3ms produces a container that lives for 3ms. Nothing is broken.
The Commands That Carry the Weight
You can go a long way with seven:
| Command | What it's for |
|---|---|
docker run | Create and start a container from an image |
docker ps -a | List containers — including exited ones |
docker logs | Read what the container wrote to stdout/stderr |
docker exec | Run an additional process inside a running container |
docker inspect | Dump the container's full configuration and state as JSON |
docker stop | Ask politely (SIGTERM), then force (SIGKILL) after a grace period |
docker rm | Delete the container and its writable layer |
Two distinctions are worth being precise about.
run vs start. docker run makes a new container from an image every time. docker start resumes an existing one. Running docker run five times leaves you with five containers, not one restarted five times — a very common source of accumulated clutter.
exec vs attach. docker exec -it <container> sh starts a new process (a shell) inside a running container, which is what you want for poking around. docker attach connects to the existing main process — and typing Ctrl+C there sends an interrupt to the application itself, which usually stops the container. Reach for exec.
docker logs shows only what the process wrote to stdout and stderr. If your application writes to a log file inside the container, docker logs will be empty and everything looks broken. Logging to stdout is the container convention for exactly this reason.
Exit Codes Are Diagnostic Information
docker ps -a shows an exit code for every stopped container, and it's often the fastest available clue. The convention that makes it readable: a process killed by signal N reports 128 + N.
| Exit code | Meaning | What it usually indicates |
|---|---|---|
0 | Clean exit | The process finished its work — often entirely normal |
1 | General application error | Read the logs; this came from your code |
127 | "command not found" from the shell | Typo in CMD/ENTRYPOINT, or a binary missing from a slim base image |
137 | 128 + 9 → SIGKILL | Hit a memory limit and was OOM-killed, or didn't shut down in time and was force-killed |
143 | 128 + 15 → SIGTERM | Terminated normally by docker stop |
137 deserves the most attention, because it has two quite different causes that you separate by timing. If the container died under load, suspect the memory limit — that's cgroups from guide one doing its job. If it died roughly ten seconds after a docker stop, it ignored the polite request and was force-killed, which is a signal-handling problem that the next phase of this roadmap takes apart.
Check yourself
A container is recreated after an image update and exits immediately with code 127. Nothing appears in the logs. Which explanation fits best?
Restart Policies
By default a stopped container stays stopped. --restart changes that, and the four options differ in ways that matter:
no— the default. No automatic restarts.on-failure[:max]— restart only on a non-zero exit code, optionally capped at a number of attempts. Good for batch work that should retry but not loop forever.always— restart whenever it stops, and start it again when the daemon restarts. A container you stopped by hand will come back when the daemon does.unless-stopped— likealways, except a container you stopped manually stays stopped across daemon restarts. Usually the friendlier choice for long-running services.
A restart policy is not a fix for a broken container — it's a way to survive transient failures. Pairing always with an application that crashes on startup produces an endless restart loop that hides the real error. If you see a container with a high restart count, read the logs before tuning the policy.
docker inspect: the Source of Truth
Everything above helps you see that something is wrong. docker inspect tells you what the container actually received — the merged, resolved result of your image defaults, your flags, and any Compose file involved.
Raw output is a large JSON document, so filter it:
# What is actually mounted, and where?
docker inspect --format '{{json .Mounts}}' myapp | jq
# What environment variables did it really get?
docker inspect --format '{{json .Config.Env}}' myapp | jq
# Which networks is it on, and what is its IP?
docker inspect --format '{{json .NetworkSettings.Networks}}' myapp | jq
# Why did it stop? State, exit code, OOM flag, timestamps.
docker inspect --format '{{json .State}}' myapp | jqThat last one is the fastest OOM diagnosis available: .State.OOMKilled is a boolean, sitting right next to ExitCode and the start and finish timestamps.
Why this is the habit that matters
Consider the failure from guide one — a database container that lost its data. The Compose file declared a volume. The command looked right. But inspect showed the volume mounted at /data, while the database was writing to /var/lib/postgresql/data. Both facts are invisible in the config file and obvious in the inspect output.
This generalises, and it's the reason to build the habit now rather than later. You will increasingly get docker run lines and Dockerfiles from an AI assistant, a Stack Overflow answer, or a colleague's snippet. These are often excellent starting points and genuinely worth using. But "it started without an error" is a much weaker signal than it feels like — a container can start perfectly while mounting a volume nowhere useful, joining the wrong network, or carrying an environment variable that silently shadows the one you set.
So make the loop: run it, then inspect it, and confirm the specific things you were relying on. You're checking observed behaviour rather than re-reading the instructions and hoping you'd have noticed.
Check yourself
You're handed a long docker run command for a Postgres container with several -v and -e flags. It starts cleanly and the logs look healthy. What's the most useful next step before trusting it with data?
Cleaning Up
Exited containers, unused images and orphaned volumes accumulate steadily. Two commands to know, and one to respect:
docker ps -a # see what's actually accumulated
docker system df # disk used by images, containers, volumes, cache
docker container prune # remove all stopped containers
docker system prune # remove stopped containers, unused networks, dangling imagesdocker system prune --volumes also deletes unused volumes — and "unused" means "not currently attached to a container", which includes the volume holding the database of a container you removed an hour ago. Check docker volume ls before adding that flag.
Phase 1 in Four Sentences
- A container is a process with a restricted view, built from namespaces, cgroups and a layered filesystem — and its writable layer dies with it.
- The
dockerCLI is a client;dockerd, containerd, a shim andruncdo the real work, which is why the Docker socket is a host-level trust decision. - Containers exit when their main process exits, and the exit code usually tells you why.
docker inspectshows what a container actually received — trust it over the command that created it.
What's Next
Phase 2 moves from running containers to building them: how layers and the build cache really work, what BuildKit adds, and how multi-stage builds turn a 2GB image into a 145MB one. It's also where the most common credential leak in container work happens — and how to avoid it.