

Sarthak Varshney is a Docker Captain, 5x C# Corner MVP, and 2x Alibaba Cloud MVP, with over six years of hands-on experience in the IT industry, specializing in cloud computing, DevOps, and modern application infrastructure. He is an Author and Associate Consultant, known for working extensively with cloud platforms and container-based technologies in real-world environments.
So you've started learning Docker. You've probably already run docker run hello-world and felt that little thrill when it worked. But here's the thing — most tutorials show you how to start a container and then kind of just... leave you there. What actually happens inside a container's life? Where does it come from? What does it do all day? And when it's done, how do you properly clean it up?
Think of a container like a temporary worker you hire for a specific job. You bring them in, they do their work, and eventually their contract ends. That whole arc — from hiring to farewell — that's the container lifecycle. Let's walk through it together, step by step.
Before diving into states, let's quickly clear up one thing that trips everyone up at the start.
An image is like a cookie cutter. It defines the shape — the OS, the dependencies, the app code. A container is the actual cookie. You can make a hundred cookies from the same cutter, and each one is its own thing.
docker images # shows your cookie cutters (images)
docker ps -a # shows your cookies (containers)
Keep this in mind throughout the article. When we talk about lifecycle, we're always talking about the container, not the image.
When you use docker create, Docker sets up everything the container needs — the filesystem, the network configuration, the environment variables — but doesn't actually start running anything yet.
docker create --name my-worker nginx
Run this and then check:
docker ps -a
You'll see something like:
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
a1b2c3d4e5f6 nginx "/docker-entrypoint.…" 5 seconds ago Created my-worker
See that STATUS column? It says Created. The container exists, it has an ID, it has a name — but it's just sitting there, not doing anything. Like an employee who's signed the offer letter but hasn't shown up to the office yet.
When would you actually use docker create instead of docker run?
Honestly, not that often in day-to-day work. But it's useful when you want to prepare a container ahead of time and start it later — maybe in a script where you set things up in a specific order before flipping the switch.
This is the state most people are familiar with. A running container has an active process inside it. It's consuming CPU, maybe memory, doing its job.
docker start my-worker
Or the more common approach — combine create and start into one shot:
docker run --name my-app -d nginx
The -d flag means "detached" — run it in the background so your terminal isn't taken over. Think of it like starting a background service rather than running a foreground script.
Check the running containers:
docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
b2c3d4e5f6a7 nginx "/docker-entrypoint.…" 8 seconds ago Up 7 seconds 80/tcp my-app
Now STATUS shows Up 7 seconds. It's alive and working.
Getting inside a running container:
docker exec -it my-app bash
This is like walking into the office and sitting at your worker's desk. You're now inside the container's filesystem, running commands inside its environment. Type exit when you're done, and the container keeps running — you just walked out of the office, the worker is still there.
Watching what the container is doing:
docker logs my-app
docker logs -f my-app # -f means "follow" — real-time log streaming
This is your window into what the container's main process is outputting. If something breaks, this is usually the first place you look.
Pausing a container is one of those features that feels niche until the moment you actually need it. When you pause a container, the processes inside it are frozen in place — not killed, not stopped, just suspended.
docker pause my-app
docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
b2c3d4e5f6a7 nginx "/docker-entrypoint.…" 2 minutes ago Up 2 minutes (Paused) 80/tcp my-app
Notice the status says Up 2 minutes (Paused). The container technically still "exists" in the running sense — it's in memory — but none of its internal processes are doing anything. It's frozen.
What's the analogy here? Imagine you're watching a movie and you hit pause. The movie is still loaded in memory, the timestamp is saved, everything is exactly where it was. You didn't close the app. You just... froze it.
When is this actually useful?
To un-pause:
docker unpause my-app
And it picks up exactly where it left off.
One important note: Pausing doesn't mean the container is saved to disk. If your host machine restarts while a container is paused, it's gone. Pause is a runtime feature, not a persistence mechanism.
At some point, the container needs to stop. Maybe the job is done, maybe you want to restart it, maybe something went wrong. Stopping a container means the main process inside it ends.
docker stop my-app
Docker sends a SIGTERM signal to the main process, giving it a chance to wrap up gracefully — close connections, write final logs, etc. If the process doesn't respond within 10 seconds, Docker then sends SIGKILL, which is the hard "turn it off" signal.
docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
b2c3d4e5f6a7 nginx "/docker-entrypoint.…" 5 minutes ago Exited (0) 12 seconds ago my-app
Exited (0) means it stopped cleanly. The 0 is the exit code — zero means success. If you see something like Exited (1) or Exited (137), that's a clue that something went wrong.
The difference between docker stop and docker kill:
docker kill my-app # immediately sends SIGKILL — no grace period
Use docker stop for normal shutdowns. Use docker kill only when a container is truly unresponsive. It's the difference between politely asking someone to leave and physically removing them. The first is almost always better.
Restarting a stopped container:
docker start my-app
The container comes back, using the same configuration. Your data inside the container (if any) is still there, assuming you haven't deleted the container. This is why stopped containers still show up in docker ps -a — they're not gone, just inactive.
Here's how all the states connect:
Image --> [docker create] --> Created
|
[docker start]
|
Running <----> [docker pause / unpause] Paused
|
[docker stop]
|
Stopped (Exited)
|
[docker rm]
|
Gone (deleted)
And of course, docker run skips straight from Image to Running in a single command.
This is where a lot of beginners make a mess without realizing it. Every time you run a container, it creates a stopped container when it exits — unless you specifically told it not to. These pile up over time.
Check the graveyard:
docker ps -a
If you've been experimenting for a while, you might see ten, twenty, thirty stopped containers just sitting there. They're not using CPU or memory, but they do take up disk space (for their filesystem layers).
Removing a single container:
docker rm my-app
This only works on stopped containers. If it's still running, you'll get an error — Docker won't let you delete something that's still running unless you force it:
docker rm -f my-app # -f forces removal even if running
Removing all stopped containers at once:
docker container prune
Docker will ask for confirmation, then wipe out everything that's exited. Super useful for cleaning house.
Let's put all the cleanup commands together in one place, because this is where Docker beginners often feel overwhelmed.
Remove a specific container:
docker rm <container-name-or-id>
Remove all stopped containers:
docker container prune
Remove a specific image:
docker rmi <image-name-or-id>
Remove all unused images:
docker image prune
Remove everything unused (containers, images, networks, build cache):
docker system prune
Add -a to the above if you want it to also remove images that aren't actively being used by a running container:
docker system prune -a
Check what's using disk space:
docker system df
This gives you a breakdown of how much space containers, images, volumes, and build cache are consuming. Really useful before running a big prune.
--rm Flag — Auto-Cleanup on ExitHere's a trick that will save you a lot of pruning headaches: the --rm flag.
docker run --rm --name temp-task alpine echo "Hello from a throwaway container"
When this container finishes, it automatically deletes itself. No more docker ps -a graveyard filling up with one-off containers.
This is especially useful for:
If you're running something long-lived (like a web server), you probably don't want --rm. But for anything that runs and finishes, it's a great habit.
Mistake #1: Not using --rm for short-lived containers
Every time you run docker run alpine echo hello without --rm, you leave a dead container behind. Run this ten times and you've got ten ghost containers. Use --rm for anything that isn't meant to be persistent.
Mistake #2: Confusing docker stop with docker rm
docker stop just pauses the lifecycle at the Stopped state. The container still exists. docker rm is what actually deletes it. A lot of beginners think stopping a container cleans it up — it doesn't.
Mistake #3: Running docker system prune -a without checking first
This command is powerful and irreversible. Always run docker system df first to understand what you're about to delete. Also be careful on shared machines — you might remove images that your colleagues are relying on.
Mistake #4: Expecting data to persist after docker rm
By default, data written inside a container's filesystem is gone when the container is removed. If you need data to survive a container's death, you need volumes:
docker run -v my-data:/app/data myapp
Without volumes, treat the container's filesystem as temporary scratch space.
Mistake #5: Forgetting that docker ps only shows running containers
This one trips people up constantly. You run a container, it exits, and then you type docker ps and see nothing — and think it disappeared. It didn't. It's stopped. Use docker ps -a to see everything.
Sometimes you need to check a container's state in a script. You can do this with:
docker inspect my-app --format '{{.State.Status}}'
This will output something like running, paused, exited, or created. Really useful for health checks or automation scripts where you need to branch logic based on container state.
You can also get the full state object:
docker inspect my-app --format '{{json .State}}' | python3 -m json.tool
This shows the exit code, the start time, the stop time, whether it's paused, running, dead — the whole picture.
Alright, your turn. Here's a hands-on exercise that takes you through the entire lifecycle in one go. Do this in your terminal:
Step 1: Create a container without starting it
docker create --name lifecycle-demo nginx
docker ps -a # confirm it shows as "Created"
Step 2: Start it and verify it's running
docker start lifecycle-demo
docker ps # should show as "Up"
Step 3: Pause it and observe
docker pause lifecycle-demo
docker ps # should show "Up X seconds (Paused)"
# try curl localhost:80 if nginx is port-mapped — what happens?
docker unpause lifecycle-demo
Step 4: Exec inside and poke around
docker exec -it lifecycle-demo bash
ls /etc/nginx # look around nginx's config
exit
Step 5: View its logs
docker logs lifecycle-demo
Step 6: Stop it gracefully
docker stop lifecycle-demo
docker ps -a # confirm it shows as "Exited (0)"
Step 7: Clean it up
docker rm lifecycle-demo
docker ps -a # it should be gone now
Bonus challenge: Repeat the whole thing, but use a single docker run --rm -d --name lifecycle-demo2 nginx command to start it, then stop it and see what happens automatically.
The container lifecycle isn't complicated once you see it for what it is — a simple story with a beginning, a middle, and an end. Containers are born from images, they do their work in the running state, sometimes pause for a bit, then stop when their job is done. The cleanup commands are your way of tidying up after them.
The biggest shift in thinking here is treating containers as disposable and ephemeral rather than precious and permanent. Unlike virtual machines that you might keep around for years and carefully maintain, containers are meant to come and go. That's not a weakness — it's the whole point. Spin one up, do the work, tear it down. The image always exists to create another one.
Once this mental model clicks, Docker starts feeling much less mysterious. You're not just running commands blindly anymore — you understand the story behind every container ID you see in docker ps -a.
And now you do.
Happy containerizing! 🐳