Running Containers
By the end of this lesson
Start, stop, inspect and remove containers.
docker run does two separate things: it creates a container from an image, then it starts it. The first half is the one people forget, and it explains a surprising amount of everyday confusion.
Every docker run creates a new container. Run the same command five times and you have five containers, each with its own identifier, its own name and its own writable layer. Stopping one does not remove it — it sits there, taking up space, invisible to docker ps. That is why a machine used for development quietly accumulates dozens of stopped containers.
# Start the API in the background with a name you chose
docker run -d --name employees-api -p 8080:8080 anvi/employees-api:1.4.0
# What is running
docker ps
# Everything, stopped containers included
docker ps -a
# What the application has written
docker logs employees-api
docker logs -f --tail 50 employees-api
# A shell inside the running container
docker exec -it employees-api sh
# Ask it to stop, then delete it
docker stop employees-api
docker rm employees-api
# A one-off command in a container that deletes itself afterwards
docker run --rm mcr.microsoft.com/dotnet/sdk:9.0 dotnet --version- -d runs detached: the container starts and your prompt comes back. Without it your terminal stays attached to the process, and pressing Ctrl+C stops the container.
- --name gives you a handle for every later command. Skip it and Docker invents a name, which works but means copying identifiers around.
- docker ps lists running containers only. docker ps -a includes stopped ones, and the gap between those two outputs answers most "where did my container go" questions.
- docker logs shows whatever the process wrote to standard output and standard error. -f follows new output as it arrives; --tail limits how much history you get first.
- docker exec starts an extra process inside a container that is already running. It cannot wake a stopped one.
- docker stop asks the process to shut down and waits — by default ten seconds — before forcing it. docker rm then deletes the stopped container and its writable layer.
- --rm removes the container as soon as its process ends. For a one-off command like checking a version, it keeps the machine tidy.
The flags worth memorising, because you will type them constantly:
- -d
- Detached. Start in the background and return the prompt.
- --name
- A name of your choosing. Must be unique among containers that still exist, stopped ones included.
- -p host:container
- Publish a container port on a host port. Covered in detail in the next lesson.
- -e name=value
- Set an environment variable for this container. The Environment Variables lesson goes further.
- -it
- Keep input open and attach a terminal. Needed for an interactive shell, pointless for a background service.
- --rm
- Remove the container when it exits. Use it for anything throwaway.
- --restart
- What should happen if the container exits — for example unless-stopped, which brings it back after a crash or a host reboot.
A container passes through a small set of states, and each command moves it between them:
Created
docker create makes a container without starting it. You rarely do this directly, because docker run does it for you.
Running
The main process is alive. This is the only state docker ps shows, and the only state docker exec can work with.
Exited
The main process ended, either because you stopped it or because it finished or crashed. The writable layer and the logs are still there, which is what makes a crashed container worth reading before removing.
Running again
docker start restarts the same container. Same name, same writable layer, same identifier. This is not what docker run does.
Removed
docker rm deletes the container, its writable layer and its logs. This is the point at which anything you have not stored elsewhere is gone.
# How much has accumulated
docker ps -a --filter status=exited
# Remove named ones you are finished with
docker rm employees-api-old employees-api-older
# Remove every stopped container on this machine
docker container prune
# Disk taken by images, containers, volumes and build cache
docker system df- The filter is the honest way to see the scale of it. On a machine used for a few weeks of development, the list is usually longer than expected.
- docker container prune is not scoped to your project. It removes every stopped container on the machine, including ones another tool or another person's work created. It asks for confirmation — read the prompt rather than accepting reflexively.
- Anything held only in a removed container's writable layer goes with it, and there is no undo. Check docker ps -a first if you are not sure what is in the list.
- docker system df shows where the disk has gone. It is usually images and build cache rather than containers, which is worth knowing before you start deleting containers to free space.
Summary
- docker run creates and starts a new container every time it is invoked
- docker start resumes an existing container with its writable layer intact
- docker ps shows running containers; docker ps -a includes stopped ones
- docker stop requests a shutdown and waits before forcing it; docker rm discards the container, its layer and its logs
- --name for anything you will return to, --rm for anything throwaway
Practice
Attempt each one before opening the solution. Getting it wrong first is how the idea sticks.
Try it yourself
Walk the lifecycle
Start a container with a name, check docker ps, then stop it. Run docker ps and docker ps -a and compare the two outputs.
Start it again with docker start, confirm it is back, then remove it. Finally, run the original docker run command twice in a row without a name and see how many containers you end up with.
Show solution
After stopping, docker ps shows nothing and docker ps -a shows the container with an Exited status. Nothing has been deleted — the container is just not running.
docker start brings the same container back, with the same name and identifier. Two unnamed docker run commands give you two separate containers, which is the mechanism behind every machine cluttered with stopped ones.
The habit worth forming from this: use --name for anything you will come back to, and --rm for anything you will not.
docker run -d --name lifecycle-demo alpine:3.20 sleep 600
docker ps
docker stop lifecycle-demo
docker ps
docker ps -a
docker start lifecycle-demo
docker rm -f lifecycle-demoThink about it
Think about it
A colleague's laptop has 47 stopped containers on it. Nothing is broken, but they are asking whether it matters.
What is actually being consumed, and what would you change about how they work rather than just clearing the list?
Show solution
Each stopped container holds its writable layer and its logs, so the cost is disk space and a cluttered docker ps -a that makes real containers harder to find. Nothing is running, so there is no CPU or memory cost.
Clearing the list solves today. The habit change is --rm on anything throwaway and --name on anything worth returning to, which means the list stays short enough to be useful.
There is one trade-off to name: --rm also discards the logs when the container exits. For something you are debugging, keeping the stopped container is the point.
Saved in this browser only.