Part 15 — Docker in depth
Part 14 showed you what a container actually is from the kernel's point of view — namespaces, cgroups, a filesystem. This part is the practical layer built on top of that mechanism: Docker specifically, running and understanding real containers rather than the primitives underneath them.
Chapter 47 — Running your first containers, and understanding what happened
In one sentence
docker run does several distinct things in sequence — pull an image if
needed, create a container from it, and start the process inside — and
naming each step precisely turns "Docker magic" into a small, predictable
sequence you fully understand.
Why this matters
Every Docker command you'll ever run is a variation on this same handful of verbs — knowing them precisely, not just by muscle memory, is what lets you debug when something doesn't behave as expected.
How it actually works
docker pull ubuntu:22.04 # download an IMAGE (Chapter 46) from
# a registry (Chapter 52), if not
# already present locally
docker run ubuntu:22.04 echo "hi" # CREATE a container from that
# image, and START the process inside
# it (this implicitly pulls first, if
# needed)
docker ps # list RUNNING containers
docker ps -a # list ALL containers, including
# stopped ones
docker stop <container> # send SIGTERM (Chapter 20!),
# wait, then SIGKILL if it hasn't
# exited -- EXACTLY Kubernetes' pod
# termination sequence from Chapter
# 20, one layer down
docker rm <container> # remove a STOPPED container
# (and its writable layer, Chapter 46
# -- any unsaved changes are gone)
docker exec -it <container> bash # run an ADDITIONAL process
# inside an ALREADY-RUNNING container
# -- useful for poking around, NOT
# how the container's main process
# started
docker logs <container> # see the STDOUT/STDERR
# (Chapter 28's file descriptors 1/2)
# of the container's main process
FLAGS WORTH KNOWING IMMEDIATELY:
-d detached -- run in the background, return your
terminal immediately (Chapter 32's background jobs,
one layer up)
-it interactive + a pseudo-TTY -- lets you actually type
into the container's process (needed for `docker exec
... bash` to feel like a real shell)
--rm automatically remove the container once it exits --
good hygiene for throwaway/test runs
--name X give it a memorable name instead of a random one
-p HOST:CONTAINER publish a port (Chapter 50 covers this properly)
Real-world example
`docker run hello-world` is, quite literally, Docker's OWN official
"does this actually work" onboarding test -- documented in Docker's
getting-started material since the project's early public releases,
and still the first command most engineers ever run with Docker
installed. It's worth understanding EXACTLY what it does, because
it's a genuinely complete, minimal demonstration of this chapter's
whole sequence: Docker checks for the `hello-world` image locally
(usually not found, the first time), PULLS it from Docker Hub
(Chapter 52's registry), CREATES a new container from it, STARTS the
tiny program inside (which just prints an explanatory message and
exits immediately), and the container then sits, STOPPED (Chapter 3's
exit -- the process finished, exit status 0), visible in `docker ps
-a` but not `docker ps`.
Running it once, and then deliberately walking through `docker ps
-a`, `docker logs`, and `docker rm` on the resulting stopped
container -- rather than just trusting the friendly printed message
-- is a genuinely good way to make this chapter's whole vocabulary
concrete on your very first real Docker command.
Try it yourself
# The official one, examined properly
docker run hello-world
docker ps -a # see it, STOPPED, exit code 0
docker logs $(docker ps -a -q --filter ancestor=hello-world | head -1)
# Run something longer-lived, and practice the full lifecycle
docker run -d --name webtest nginx
docker ps # RUNNING
curl -s localhost:80 > /dev/null 2>&1 || echo "(not published yet --
Chapter 50 covers -p properly; this container's port 80 isn't
reachable from the host yet)"
docker exec webtest ls /usr/share/nginx/html # poke around inside
docker logs webtest
docker stop webtest # SIGTERM, then SIGKILL if needed
docker ps -a # STOPPED, not gone
docker rm webtest # actually removed now
docker ps -a | grep webtest || echo "confirmed: gone"
# Clean up hello-world containers too
docker rm $(docker ps -a -q --filter ancestor=hello-world) 2>/dev/null
Common mistakes
- Confusing
docker stop(graceful, Chapter 20's SIGTERM) withdocker kill(immediate SIGKILL). Usestopby default; reach forkillonly when a container genuinely won't respond to the polite request. - Forgetting
docker rmis needed afterdocker stop. A stopped container still exists (and its writable layer still takes disk space) until explicitly removed. - Using
docker execand assuming it's "how the container started."execruns an ADDITIONAL process alongside the main one — killing yourexec'd shell does NOT stop the container itself.
Check yourself
- List, in order, what
docker runactually does the first time you run a given image. - What's the difference between
docker stopanddocker rm? - Why does killing a
docker exec'd shell not stop the container?
(Answers: Appendix E.)
Further reading
- Docs: Docker's own "Getting Started" guide — still one of the clearest, most concrete introductions available, directly from the source.
- Reference:
docker --help,docker run --help(genuinely worth reading in full once). - Practice: run
docker eventsin one terminal while you run commands in another — watch the exact sequence of internal events fire in real time.