Skip to main content

🏗️ Docker Architecture

Last lesson you learned what a container is. Now meet the machinery that actually builds and runs one. When you type docker run, that command is not the whole story — it's a message handed off to a background service that does the real work. Understanding this handoff is what turns Docker from a set of memorized incantations into a system you can reason about and debug.

Week 11 · Day 1 (Monday: Docker Fundamentals) · Lecture 2

🎯 Learning Objectives

By the end of this lesson, you will be able to:

  • Describe Docker's client–server architecture and name its major parts
  • Explain the roles of the Docker client, the daemon (dockerd), and the registry
  • Trace step by step what happens when you run docker run nginx
  • Distinguish the four core Docker objects: images, containers, networks, and volumes
  • Explain how the daemon delegates real execution to containerd and runc
  • Use inspection commands to observe the architecture on your own machine

Estimated Time: 60 minutes

Practice: Run docker version and docker info and identify each architectural component in the output.

In This Lesson

The Big Picture

Understanding Docker's architecture is like knowing how a car works under the hood. You can drive without it — but the moment something goes wrong, or you want to tune performance, that knowledge is the difference between guessing and diagnosing.

At the highest level, Docker has three players: a client you type commands into, a daemon that does the actual work, and a registry that stores images. Everything else in this lesson is detail hanging off this triangle.

graph LR A["Docker Client
the docker command"] -->|REST API| B["Docker Daemon
dockerd"] B -->|manages| C["Images & Containers"] B -->|pulls / pushes| D["Registry
Docker Hub"]

Keep that picture in mind. Next we zoom into each corner.

Client–Server Architecture

Docker uses a client–server model. The part you interact with (the client) is deliberately separate from the part that does the heavy lifting (the server, i.e. the daemon). They talk over a well-defined REST API — usually over a local socket, but it can be over the network too.

📖 The restaurant analogy

You (the user) tell the waiter (Docker client) what you want. The waiter writes it on a standard order slip (the REST API) and passes it to the kitchen (the daemon), which actually cooks the meal (builds images, runs containers). The waiter then brings the result back to you. You never step into the kitchen yourself — and you don't need to, as long as the order slip is understood by both sides.

This separation is powerful: because the client and daemon communicate over an API, the client on your laptop can control a daemon running on a remote server just as easily as a local one. It's also why third-party tools (Docker Desktop's GUI, Portainer, VS Code's Docker extension) can all drive the same daemon — they speak the same API.

The Docker Client

The Docker client is the docker command-line tool — the primary way you interact with Docker. Its job is narrow and clear:

  • Accept commands you type (docker build, docker run, docker pull, …)
  • Translate them into REST API calls to the daemon
  • Display the daemon's results and output back to you

Notice what the client does not do: it never runs a container itself. It's a messenger. When you run this:

# You type this in your terminal (the client):
docker run -d -p 8080:80 nginx

…the client packages "run a detached nginx container publishing port 8080 to 80" into an API request and sends it to the daemon. The daemon does everything else.

Because the client only talks to the daemon over an API, you can point it at a different daemon entirely:

# Point the client at a remote daemon over TCP
export DOCKER_HOST=tcp://remote-docker-host:2375
docker info    # now reports the REMOTE daemon's info

⚠️ A remote daemon is a security surface

Exposing the daemon over plain TCP (port 2375) gives anyone who can reach it full control of the host — access to the Docker socket is effectively root access. In real setups you'd protect it with TLS or, better, use SSH (DOCKER_HOST=ssh://user@host). Recognize the pattern, but don't leave an unprotected TCP daemon exposed.

Think of the client like a TV remote: the same remote can control different TVs (daemons) depending on which one it's pointed at.

The Docker Daemon (dockerd)

The Docker daemon, the program named dockerd, is the persistent background service that does all the real work. It listens for API requests and manages every Docker object on the host.

graph TD A["Docker Daemon (dockerd)"] --> B["Builds & stores images"] A --> C["Creates & runs containers"] A --> D["Sets up networks"] A --> E["Manages volumes"] A --> F["Listens for API requests"]

If the client is the waiter, the daemon is the entire kitchen: it's where fuel becomes motion. You rarely interact with it directly, but nothing happens without it. (If you ever get "Cannot connect to the Docker daemon," it means the kitchen is closed — the daemon isn't running.)

The daemon delegates the real execution

Early Docker was monolithic — the daemon did everything itself. Modern Docker is layered and standards-based. The daemon doesn't personally spawn container processes; it hands that job down a chain of specialized, OCI-compliant components:

graph LR A["dockerd
high-level daemon"] --> B["containerd
manages lifecycle"] B --> C["runc
spawns the process"] C --> D["Running Container"]
  • containerd — a core runtime that manages the full container lifecycle (pulling images, starting/stopping) and can be reused by other tools, including Kubernetes.
  • runc — a small, portable tool that implements the OCI runtime spec and actually creates the container process by talking to the kernel's namespaces and cgroups.

Kitchen analogy again: the head chef (dockerd) coordinates, the sous-chef (containerd) manages the flow of dishes, and the line cook (runc) does the hands-on cooking of each individual dish. The split means each piece can be improved independently — and lower layers like containerd can power other systems entirely.

Registries

A registry is a store for Docker images — the place the daemon pulls images from and pushes images to. It's the "GitHub for images."

graph LR A["Developer"] -->|docker push| B["Registry
Docker Hub"] B -->|docker pull| C["Server / Teammate"]
  • Docker Hub — Docker's public registry and the default. When you docker pull nginx, it comes from here.
  • Private registries — self-hosted or cloud (Amazon ECR, Google Artifact Registry, Azure Container Registry, GitHub Container Registry) for your organization's own images.
# Pull a public image from Docker Hub (the default registry)
docker pull nginx:latest

# Tag your local image for your namespace, then publish it
docker tag myapp:1.0 yourusername/myapp:1.0
docker push yourusername/myapp:1.0

# Pull from a private registry by fully-qualified name
docker pull registry.example.com/myapp:1.0

A registry is like a library of blueprints: anyone with access can check out a copy to build their own structure, and the standard format means the same blueprint works everywhere.

💡 Registries add security features

Serious registries offer authentication (who can push/pull), image signing (verify an image wasn't tampered with), and vulnerability scanning (flag known CVEs in your image layers). You'll lean on these when you deploy for real.

What Happens on docker run

Let's tie the pieces together by tracing one command end to end. You type docker run nginx on a fresh machine that has never seen the nginx image. Here's the conversation between the parts:

sequenceDiagram participant U as You participant C as Docker Client participant D as Docker Daemon participant R as Registry U->>C: Type docker run nginx C->>D: Send run request over the REST API D->>D: Look for the nginx image locally D->>R: Image not found so pull nginx R-->>D: Send the image layers D->>D: Create a container and start it via containerd and runc D-->>C: Return the container status C-->>U: Print the result to your terminal
  1. The client turns your command into a REST API request and sends it to the daemon.
  2. The daemon checks whether the nginx image is already in its local store.
  3. It isn't, so the daemon pulls it from the registry (Docker Hub by default), downloading the image layers.
  4. The daemon creates a container from the image and starts it, delegating the actual process creation to containerd and runc.
  5. The daemon reports status back to the client, which prints it for you.

Run the same command a second time and step 3 disappears — the image is already local, so the daemon goes straight to creating the container. That's the layer cache from the previous lesson working at the whole-image level.

Docker Objects

The daemon manages four core kinds of object. You'll spend the rest of the week working with all of them.

Images

A read-only template built from a Dockerfile, composed of layers, identified by name and tag (e.g. node:20-alpine). Images are the blueprint — you don't live in a blueprint, you live in the house built from it.

Containers

A runnable instance of an image, with its own filesystem, network stack, and process space. Containers can be started, stopped, moved, and deleted, and connected to networks and volumes. If the image is the blueprint, the container is the actual house with people living in it.

Networks

How containers talk to each other and the outside world. The default bridge driver puts containers on a private virtual network; host removes isolation and uses the host's network directly; none disables networking; overlay spans multiple hosts (used with orchestration).

graph LR A["Container A"] --- B["Bridge Network"] C["Container B"] --- B B --- D["Host Interface"] D --- E["Outside World"]

Volumes

Docker-managed persistent storage that outlives any single container. Because containers are ephemeral, anything that must survive a restart or redeploy — a database's data, user uploads — belongs in a volume.

graph TD A["Container"] -->|mounts| B["Volume"] C["Host Filesystem"] -->|stores| B D["A New Container"] -.->|can re-mount| B

A volume is like a storage unit you can attach to any house: move out of one container and into another, and your stored belongings come with you, intact.

ObjectWhat it isLives as long as…
ImageRead-only blueprint of layersYou keep it (until docker rmi)
ContainerRunning instance of an imageIts lifecycle (until removed)
NetworkVirtual network connecting containersYou keep it (until docker network rm)
VolumePersistent, container-independent storageYou keep it (survives container removal)

Practice & Quiz

🏋️ Exercise 1: Read the architecture in docker version

Goal: See the client–server split with your own eyes. Run the command below and identify which block describes the client and which describes the server (the daemon).

docker version
💡 Hint

The output is divided into two labelled sections. One is literally headed "Client" and the other "Server." The Server section is your daemon; look for a line mentioning containerd and runc underneath it.

✅ Solution

The Client block shows your docker CLI version and API version. The Server block shows the daemon (Engine), plus the versions of containerd and runc it delegates to. Seeing both in one command is the client–server architecture: the client asked the server for its version and printed both. If the Server block is missing, the daemon isn't running.

🏋️ Exercise 2: Trace the pull

Goal: Predict, then observe. On a machine that has never pulled hello-world, run it and watch the output. Which architectural components are involved, and in what order?

docker run hello-world
✅ Solution

The client sends the run request to the daemon; the daemon finds no local hello-world image, so it prints "Unable to find image… locally" and pulls it from Docker Hub (the registry); then it creates and starts a container via containerd/runc; the container prints its message and exits; the client relays that output to your terminal. Run it a second time and the pull step is skipped — the image is now local.

🎯 Quick Quiz

Question 1: When you type docker run, which component actually creates and runs the container?

Question 2: How does the Docker client communicate with the daemon?

Question 3: Which Docker object should hold a database's data so it survives the container being removed?

Best Practices & Pitfalls

✅ Do

  • Reach for docker info and docker version first when troubleshooting — they reveal daemon state
  • Protect a remote daemon with TLS or SSH; treat socket access as root access
  • Use named volumes for anything that must persist
  • Let the daemon and its runtimes stay current — containerd/runc updates carry security fixes

❌ Don't

  • Expose the daemon over plain, unauthenticated TCP
  • Assume the client "runs" containers — it only sends requests
  • Store important data in a container's writable layer
  • Ignore "Cannot connect to the Docker daemon" — it means the daemon isn't running, not that your command is wrong

⚠️ The Docker socket is root

Anyone who can reach /var/run/docker.sock can start a container that mounts the host filesystem and effectively becomes root on the host. Be extremely careful about who — and which containers — you grant socket access to.

Summary

🎉 Key Takeaways

  • Docker is client–server: the client sends commands, the daemon does the work, and they talk over a REST API
  • The daemon (dockerd) manages every Docker object and delegates real execution down to containerd then runc
  • A registry (Docker Hub by default) is where images are pulled from and pushed to
  • On docker run: client → daemon → (pull if needed) → create & start container → results back to you
  • The four core objects are images, containers, networks, and volumes — volumes are what make data persist

📚 Additional Resources

🚀 What's Next?

Now that you know how the parts fit together, it's time to drive. Next up: Basic Docker Commands — the day-to-day vocabulary (run, ps, images, exec, logs, stop, rm) that lets you create, inspect, and clean up containers with confidence.

🎉 You've seen under the hood!

Every command in the next lesson is just a message to the daemon — you now know exactly where it goes and what it triggers.