🐳 Lesson 2: Docker Architecture
Open the hood. See how the docker command you typed in Lesson 1 actually became a running container — the client/daemon split, the core objects, image layers, and the lifecycle that ties it all together.
🎯 Learning Objectives
By the end of this lesson, you will be able to:
- Diagram Docker's client/server model and explain how the
dockerCLI talks to the daemon over a REST API - Identify Docker's core objects — images, containers, registries, volumes, and networks — and how they relate
- Explain image layers and the union filesystem, including the container's thin writable layer
- Trace a container through its lifecycle (pull → create → start → stop → remove) and inspect it with real commands
Estimated Time: 40–50 minutes
Prerequisite: Lesson 1: Introduction to Docker
In This Lesson
The Client/Server Model
When you ran docker run hello-world in Lesson 1, it felt like one program did all the work. It didn't. Docker is split into two pieces that talk to each other:
- The Docker client (
docker) — the command-line tool you type into. It's a thin messenger: it turns your command into an API request and shows you the response. - The Docker daemon (
dockerd) — the long-running background service that does the real work: building images, pulling from registries, and creating and running containers.
The client talks to the daemon over a REST API. On Linux and macOS that conversation travels over a Unix socket at /var/run/docker.sock; it can also run over a network socket. Because it's just an API, the client and daemon don't have to be on the same machine — your local docker can drive a daemon on a remote host.
Here is what actually happened inside that one docker run hello-world command from Lesson 1:
💡 Why the split matters: because the client is just an API caller, the same daemon can be driven by the CLI, by Docker Desktop's GUI, bydocker compose, or by an automation script. They all speak the same REST API todockerd.
Core Docker Objects
Everything the daemon manages falls into a handful of object types. Learn these five nouns and the rest of Docker becomes vocabulary you already know.
| Object | What it is | Kitchen analogy |
|---|---|---|
| Image | A read-only, layered template that packages an app and its dependencies | The recipe (immutable) |
| Container | A running (or stopped) instance of an image, with a thin writable layer on top | The dish you cooked from the recipe |
| Registry | A store for images you can pull from or push to (Docker Hub is the default) | The cookbook library |
| Volume | Docker-managed storage that persists data independently of any container | The pantry that outlives one meal |
| Network | A virtual network that lets containers find and talk to each other | The intercom between stations |
📖 Definition
Registry vs repository vs tag: a registry (like Docker Hub) hosts many repositories (like library/nginx), and each repository holds multiple tags (like 1.27 or latest). The full name docker.io/library/nginx:1.27 spells out all three.
You'll meet volumes and networks in depth in later modules. For now, remember that they exist outside any single container's lifespan: delete a container and its writable layer vanishes, but a named volume and its data survive.
Under the Daemon: containerd & runc
The daemon doesn't spawn Linux processes all by itself. Modern Docker delegates the low-level work to a layered runtime stack, each piece with one job:
dockerd— the high-level daemon. Handles the API, image builds, networking, and volumes.containerd— a container runtime that manages the full container lifecycle: pulling images, managing storage, and supervising running containers. (It's a graduated CNCF project used well beyond Docker.)runc— the low-level tool that actually creates the container by asking the Linux kernel for namespaces and cgroups. It implements the OCI (Open Container Initiative) runtime spec.
⚠️ You rarely touch these directly. The takeaway is the separation of concerns: becauseruncandcontainerdfollow open standards (OCI), a container built with Docker also runs on other OCI-compliant runtimes like those in Kubernetes. Docker isn't a walled garden.
Image Layers & the Union Filesystem
A Docker image isn't one monolithic blob — it's a stack of read-only layers. Each instruction in a Dockerfile (which you'll write in Module 2) adds a layer capturing just the filesystem changes it made. Docker stacks these layers into a single view using a union filesystem (typically OverlayFS).
Two properties fall out of this design, and they explain a lot of Docker's speed:
- Layers are cached and shared. If ten images all start
FROM debian:12, that base layer is stored once. Pulling a new image only downloads layers you don't already have. - Containers are cheap. Starting a container doesn't copy the image. Docker just adds a small writable layer on top of the shared read-only layers. Ten containers from one image share all the read-only layers and differ only in their own thin writable layer.
📖 Copy-on-write
When a running container modifies a file that lives in a read-only layer, the union filesystem copies that file up into the writable layer first, then edits the copy. The original layer is never touched — which is exactly why the same image can back many containers safely. This is called copy-on-write.
⚠️ The writable layer is disposable. Anything written inside a container (not into a volume) lives only in that writable layer and is gone the moment you docker rm the container. That's why persistent data belongs in a volume, not the container's own filesystem.
The Container Lifecycle
A container moves through a predictable set of states. The convenience command docker run quietly stitches the first few together — understanding the individual steps demystifies what it does.
docker run bundles pull + create + start; a stopped container can be restarted or removed.Mapped to commands:
| Step | Command | What happens |
|---|---|---|
| Pull | docker pull nginx | Downloads the image's layers to local disk |
| Create | docker create nginx | Makes a container (writable layer + config) but does not start it |
| Start | docker start <id> | Runs the container's process |
| Stop | docker stop <id> | Sends SIGTERM, then SIGKILL — process ends, writable layer preserved |
| Remove | docker rm <id> | Deletes the container and its writable layer for good |
So the single command from Lesson 1, docker run hello-world, is really pull (because the image was missing) → create → start, all in one line. Now you can see why the second run was instant: the pull step was skipped because the image was already cached.
Hands-On: Inspect the Engine
Let's confirm all of this on your own machine. These commands are read-only — they only report state, so they're safe to run anytime.
1. See the client/daemon split
docker version prints two blocks — one for the Client and one for the Server (the daemon). Seeing them side by side is the client/server model made concrete:
docker version
Output:
Client:
Version: 27.3.1
API version: 1.47
Go version: go1.22.7
Context: default
Server: Docker Engine - Community
Engine:
Version: 27.3.1
API version: 1.47 (minimum version 1.24)
containerd:
Version: 1.7.22
runc:
Version: 1.1.14
Notice the server block lists containerd and runc — the exact runtime stack you saw in the previous section.
2. Survey the whole host
docker info summarizes the daemon's world: how many images and containers exist, which storage driver backs the union filesystem, and system resources.
docker info
Output (trimmed):
Client:
Version: 27.3.1
Plugins:
buildx: Docker Buildx (Docker Inc.)
compose: Docker Compose (Docker Inc.)
Server:
Containers: 3
Running: 1
Paused: 0
Stopped: 2
Images: 12
Server Version: 27.3.1
Storage Driver: overlayfs
Default Runtime: runc
Kernel Version: 6.8.0-45-generic
Total Memory: 15.4GiB
The Storage Driver: overlayfs line is the union filesystem from Section 4, in the flesh. The compose and buildx plugins are why modern Docker uses docker compose (a plugin, one word with a space) rather than the old standalone docker-compose binary.
3. Peel apart an image's layers
docker image history lists the layers of an image, newest on top. Each row is one Dockerfile instruction that produced a layer:
docker pull nginx
docker image history nginx
Output (trimmed):
IMAGE CREATED CREATED BY SIZE
a9d06877f4a1 2 weeks ago CMD ["nginx" "-g" "daemon off;"] 0B
<missing> 2 weeks ago EXPOSE map[80/tcp:{}] 0B
<missing> 2 weeks ago COPY docker-entrypoint.sh / # buildkit 7.6kB
<missing> 2 weeks ago RUN /bin/sh -c set -x && groupadd ... 109MB
<missing> 3 weeks ago /bin/sh -c #(nop) ENV NGINX_VERSION=1.27.2 0B
<missing> 3 weeks ago /bin/sh -c #(nop) ADD file:... in / 77.9MB
Read it bottom-up and you can see the image being built: a base filesystem is added, packages are installed, config is copied, a port is declared, and finally a start command is set. The 0B rows are metadata-only layers (like CMD and EXPOSE) — they change configuration without adding files.
🏋️ Exercise: Watch the lifecycle by hand
Objective: Drive a container through each lifecycle state yourself, instead of letting docker run hide the steps.
Instructions:
- Create without starting:
docker create --name web nginx - Confirm it exists but isn't running:
docker ps -a(look for status Created). - Start it:
docker start web, thendocker psto see it running. - Stop it:
docker stop web. - Remove it:
docker rm web, thendocker ps -ato confirm it's gone.
💡 Hint
docker ps shows only running containers; add -a to include stopped and created ones. If docker stop feels slow, that's the 10-second grace period between SIGTERM and SIGKILL.
✅ What you should observe
After create, the container shows status Created and never ran. start flips it to Up; stop to Exited; and after rm it disappears from docker ps -a entirely — its writable layer deleted. You just performed by hand exactly what docker run does in one shot.
Mental Model & Best Practices
You now have the whole map. A few habits will keep it useful as the course gets more hands-on:
- Think client → daemon, always. When a command behaves oddly, ask which side is at fault. "Cannot connect to the Docker daemon" almost always means
dockerdisn't running — not that your CLI is broken. - Treat images as immutable, containers as disposable. Don't hand-edit files inside a running container to "fix" it — those changes die with the container. Change the image (via a Dockerfile) and re-run.
- Order Dockerfile steps from least to most frequently changed. Because layers are cached, putting stable steps (installing dependencies) before volatile ones (copying your code) means rebuilds reuse the cache and finish faster. You'll practice this in Module 2.
- Store real data in volumes, never the writable layer. The writable layer is scratch space; volumes are the durable pantry.
- Scan images with
docker scout. The olddocker scancommand has been removed;docker scout cves nginxis the current way to check an image for known vulnerabilities. - Trust the standards. Because Docker builds on OCI,
containerd, andrunc, the images you build here run on Kubernetes and other platforms unchanged. You're learning portable skills, not a single vendor's quirks.
✅ The one-sentence summary
You type a command into the client, which sends it over an API to the daemon, which uses containerd and runc to turn layered read-only images into containers (each with a thin writable layer), pulling from and pushing to registries along the way.
Quick Quiz
Question 1: When you type docker run, which component actually creates and runs the container?
Question 2: What does starting a container add on top of the image's read-only layers?
Question 3: Where should persistent data that must survive a container's removal be stored?
Summary
🎉 Key Takeaways
- Docker is client/server. The
dockerCLI sends API requests over a socket to thedockerddaemon, which does the real work. - Five core objects — images, containers, registries, volumes, and networks — cover everything the daemon manages.
- Under the daemon,
containerdandrunc(following OCI standards) turn images into real Linux processes using namespaces and cgroups. - Images are stacked read-only layers that are cached and shared; a running container adds only a thin writable layer via copy-on-write.
- The lifecycle is pull → create → start → stop → remove, and
docker runbundles the first three into one command.
📚 Additional Resources
- Docker architecture — the official client/daemon overview
- Storage drivers & the union filesystem — how layers and copy-on-write work
- docker image history — the command reference for inspecting layers
- Docker Scout — the modern image-scanning tool that replaced
docker scan
🚀 What's Next?
You know how the engine is built. Time to drive it. In the next lesson you'll run, name, and manage your first real containers — putting the lifecycle you just learned into your fingers, one command at a time.
🎉 Lesson 2 complete!
The architecture is no longer a black box. Everything else in this course is now just details filling in a map you already have.