Chapter 1: Foundations
Topic 05

The Docker Architecture

ArchitectureDaemon

"Docker" is not one program. The docker you type is a thin CLI that sends HTTP requests to a long-running daemon called dockerd; the daemon hands the real container work to containerd, which in turn spawns runc to set up the namespaces and cgroups from topic 03 and start the process. Four components in a chain, each with one job.

Understanding the client → daemon → containerd → runc path explains three things at once: why Docker needs a running background service, why it usually needs root, and what is left if you remove Docker itself. None of those make sense if you think of docker as the thing that runs containers — because it does not.

One command, four components in a chain
docker CLI
dockerd
containerd
runc
running container
Who calls whom, and who actually touches the kernel
docker        # the CLI you type — stateless, just sends HTTP
  └─▶ dockerd       # the daemon — owns images, networks, volumes
        └─▶ containerd    # manages image + container lifecycle
              └─▶ runc         # sets up namespaces/cgroups, execs the process, exits
                    └─▶ containerd-shim   # the lingering parent of your container

Read that chain top to bottom: each layer delegates downward and does less low-level work than the one below it. The CLI knows nothing about kernels; runc knows nothing about HTTP. The error messages you will hit map directly onto this chain, which is the practical payoff of learning it.

Client and Daemon

The docker CLI is stateless. It holds no images, runs no containers, and remembers nothing between invocations — it serializes your command into an HTTP request and sends it to the daemon over a socket, by default the Unix socket at /var/run/docker.sock. The daemon, dockerd, owns everything: the image store, the running containers, the networks, and the volumes. Stop dockerd and every container stops with it, because the daemon is the process actually supervising them.

This client-server split is also a security fact. Anyone who can reach the daemon socket can tell the daemon to do anything — including run a container that mounts the host's root filesystem — so access to /var/run/docker.sock is effectively root on the host. That single sentence explains most of the security advice in this chapter and Chapter 10.

The Daemon Delegates

dockerd handles the high-level API — building images, creating networks, managing volumes, exposing the REST endpoints the CLI calls — but it does not manage the container lifecycle itself. It delegates that to containerd, a separate daemon that pulls and stores images and supervises running containers at a lower level. The division is deliberate: dockerd is the Docker-specific front end, and containerd is a general-purpose container manager that has a life of its own beyond Docker.

runc and the OCI Runtime

When a container actually needs to start, containerd calls runc, the reference implementation of the OCI runtime specification. runc does the kernel work topic 03 described: it creates the namespaces, configures the cgroups, sets up the union mount, and exec's the container's process. Then it exits. It is a short-lived tool that builds the fence and walks away, not a supervisor that stays running.

What remains as the container's parent is a lightweight containerd-shim process. The shim keeps the container's stdio open and reports the exit code back to containerd, which is why your containers survive a restart of dockerd itself — the shims, not the daemon, are holding them. One shim per container is the price of decoupling the daemon's lifecycle from the containers it started.

Why It's Layered This Way

The split lets each component evolve and be reused independently, and the clearest payoff is Kubernetes. Kubernetes talks to containerd directly through the Container Runtime Interface and skips dockerd entirely — it never needed the Docker-specific front end. That is exactly what the "Kubernetes deprecated Docker" headlines of 2020 meant: Kubernetes dropped the dockerd shim layer it used to talk through, not Docker images.

The images Docker builds are OCI images, and OCI images run unchanged on containerd, on Podman, and under Kubernetes. Nothing about your docker build output stopped working when Kubernetes dropped the daemon. Conflating "Kubernetes dropped dockerd" with "Docker images stopped working" caused a wave of needless panic — the artifact and the daemon are independent.

Rootful by Default, and the Socket

By default dockerd runs as root, so every docker command you issue executes with root authority on the host. That is convenient and dangerous in equal measure: a container can be told to bind-mount / from the host and edit any file on the machine. Chapter 10 covers rootless mode, which runs the daemon as an unprivileged user, but the default install is rootful.

Two consequences follow directly. Mounting /var/run/docker.sock into a container hands that container root-equivalent control of the host's Docker — a routine and dangerous footgun. And adding a user to the docker group, which grants access to that socket, is equivalent to giving them root; do it as deliberately as you would edit the sudoers file.

Common Mistakes
  • Mounting /var/run/docker.sock into a container without understanding that it grants that container full root-equivalent control over the host's Docker — a routine footgun that turns a compromised container into a compromised host.
  • Thinking "Kubernetes dropped Docker" means images stopped working — Kubernetes dropped the dockerd shim and talks to containerd directly; the OCI images Docker builds run unchanged everywhere.
  • Assuming the docker CLI does the container work itself — it does not, and if dockerd is not running the CLI can do nothing, which is the cause of the classic "Cannot connect to the Docker daemon" error.
  • Adding a user to the docker group casually for convenience — it is equivalent to granting root, because group membership grants access to the daemon socket, and most teams under-appreciate that.
Best Practices
  • Treat access to the Docker daemon socket as equivalent to root on the host, and guard membership in the docker group with the same care you apply to the sudoers file.
  • Reach for rootless Docker or Podman, covered in Chapter 10 and Chapter 12, when a root-owned daemon is an unacceptable risk for the environment you are running in.
  • Diagnose failures by asking which layer broke — the CLI cannot reach the daemon, the daemon cannot reach containerd, or runc failed to start the process — because the error text maps onto this chain.
  • Invest in good images knowing the artifact is independent of dockerdcontainerd, Podman, and Kubernetes all run the same OCI images, so the effort is not Docker-daemon-specific.
Comparable tools Podman the same CLI with no central daemon — fork/exec, rootless by default containerd · nerdctl a daemon-but-not-Docker stack on the same runtime runc · crun · runsc the OCI runtime, with drop-in alternatives including gVisor

Knowledge Check

In the Docker architecture, which component actually creates the namespaces and cgroups and starts the container process?

  • runc, the OCI runtime that containerd invokes to do the kernel-level work
  • The docker CLI, which talks to the kernel directly to set up isolation
  • dockerd, which performs all the namespace and cgroup syscalls itself
  • The containerd-shim, which stays running to build and rebuild the container's namespaces

Why is access to /var/run/docker.sock effectively root on the host?

  • Whoever can reach the socket can tell the root daemon to run a container that mounts and edits the host filesystem
  • The socket file stores the host's root account password in plaintext that any reader can extract directly
  • It lets you read and decrypt the private inter-container network traffic of every other container currently running anywhere on the host
  • It causes every container you start afterward to run automatically in a special privileged kernel CPU mode

What did "Kubernetes deprecated Docker" actually mean?

  • Kubernetes stopped going through the dockerd shim and talks to containerd directly; Docker-built OCI images run unchanged
  • Docker images stopped working on Kubernetes and had to be rebuilt in a new format
  • The docker build command was removed and replaced by a Kubernetes-only builder
  • Kubernetes abandoned runc as its OCI runtime and adopted a completely different, mutually incompatible low-level runtime that cannot start any of the existing containers built for it

You run docker ps and get "Cannot connect to the Docker daemon." What does that tell you?

  • The CLI reached no running daemon, and since the CLI does no container work itself, nothing can happen
  • A specific container's runc process crashed and dragged all of the other running containers down with it
  • The local image store on disk is corrupted and every image must now be deleted and re-pulled
  • The CLI itself is the container runtime, and it has run out of resources to start any containers

You got correct