Skip to main content
RunBook Academy

Docker & ContainersI · FoundationsArchitecture

Docker architecture at a glance

Foundation⏱ ~28 mindocker

What you'll learn

  • Map every daemon and runtime that participates in a container lifecycle
  • Explain that the docker CLI is an HTTP client, and what follows from that
  • Explain why the shim exists
  • Predict what a restart of each component does to running containers
  • Identify which component to investigate for which class of failure

Prerequisites

None — start here.

Verified against Docker Engine 29.x · Docker Engine 28.x · Docker Compose 2.x · containerd 2.x · runc 1.2.x · BuildKit 0.20+ · Linux kernel 5.15+ · Ubuntu 24.04 LTS · Debian 12 (Bookworm) · 2026-08-12

Not yet marked complete on this device.

Five processes run on a Docker host for every container that runs. Knowing which one is responsible for which failure is half of production Docker troubleshooting.

The five processes

sequenceDiagram
  participant CLI as docker CLI
  participant Dockerd as dockerd (daemon)
  participant Containerd as containerd
  participant Shim as containerd-shim
  participant Runc as runc
  participant Kernel as Linux kernel
  CLI->>Dockerd: docker run nginx (REST over socket)
  Dockerd->>Containerd: Create container (gRPC)
  Containerd->>Containerd: Pull image (if needed)
  Containerd->>Shim: Start container
  Shim->>Runc: create + start (OCI)
  Runc->>Kernel: clone(2) + execve(2)
  Kernel-->>Runc: PID of init process
  Runc-->>Shim: container PID
  Shim-->>Containerd: container running
  Containerd-->>Dockerd: container running
  Dockerd-->>CLI: container started

1. docker (the CLI)

A thin client. Does not store state; every call is request/response to dockerd. Talks to the daemon over the configured context (DOCKER_HOST), defaulting to /var/run/docker.sock.

“Thin client” understates it. The docker binary is an HTTP client, and the Docker Engine API is an ordinary REST API — the documentation says it can be reached with “an HTTP client such as wget or curl, or the HTTP library which is part of most modern programming languages”, with the API version as the first path element: “if the endpoint is /containers/ you can use /v1.55/containers/”.

You can prove this in one command:

Read-only / Safethe API, without the CLI
# curl -s --unix-socket /var/run/docker.sock http://localhost/v1.49/containers/json | jq -r '.[].Names[0], .[].State'
/web
/redis
/postgres
running
running
running

Illustrative output

Read-only / Safewhich daemon am I talking to
docker context ls
docker context show
printenv DOCKER_HOST || echo 'DOCKER_HOST is unset; the default socket is in use'

2. dockerd (the daemon)

Long-running process. Manages images, volumes, networks, builds, and the userland proxy (docker-proxy, one process per published port on the default configuration). Forwards container lifecycle requests to containerd. Reads /etc/docker/daemon.json for configuration.

What dockerd does not do:

  • Create namespaces.
  • Apply cgroups.
  • Run containers.

On Engine 28.x with the classic image store, dockerd owns the image store itself and hands containerd only the job of running things. From Engine 29.0 the containerd image store is the default for fresh installations, which moves image storage down to containerd too. Which of the two you are on determines whether ctr -n moby images ls shows your images or an almost-empty list — a genuinely confusing difference if you do not know it exists.

3. containerd (the supervisor)

Long-running process, started as its own systemd unit. Manages container lifecycle independently of dockerd: it holds the content store and snapshotter, the container metadata, and the task service that starts and supervises processes. Docker’s containers live in containerd’s moby namespace, which is why every ctr command in this course carries --namespace moby.

4. containerd-shim-<id> (one per container)

A small process that exists so that containerd can be restarted without taking containers down. The shim holds the parent of the container’s PID 1, so if containerd dies and is restarted, the shim is still around to keep the container alive.

You will see one shim per container in ps auxf.

5. runc (or crun, or youki)

Short-lived. This is the part people picture as “the container runtime” and then imagine as a daemon; it is neither long-running nor a service. It is a binary the shim executes, once per operation.

Given a bundle — a directory containing a config.json written to the OCI runtime spec and a rootfs/runc does the actual isolation work: creates the namespaces, applies the cgroup limits, sets the capability bounding set, loads the seccomp filter and the LSM label, pivot_roots into the rootfs, and execves the entrypoint as PID 1 of the new PID namespace. Then it exits. The shim takes over.

Read-only / Safethe bundle runc was handed
CONTAINER=web
ID=$(docker inspect --format '{{.Id}}' "$CONTAINER")
runc --root /run/docker/runtime-runc/moby list
cat /run/containerd/io.containerd.runtime.v2.task/moby/"$ID"/config.json | jq '.linux.namespaces'

What a restart of each component costs

This is the table to internalise, because it is the difference between a change you can make at 14:00 and one that needs a window.

RestartRunning containersNetworkingdocker exec sessionsLogs
docker CLINothing — it is a clientNothingNothingNothing
containerdSurvive — shims are separate processesUntouchedDroppedBuffered by the shim
dockerd without live restoreAll stoppedRules re-appliedDroppedGap
dockerd with live restoreSurviveRules re-appliedDroppedBuffered, then drained

Why this architecture matters for ops

SymptomFirst place to look
CLI errors with “Cannot connect to the Docker daemon”dockerd (is it running? socket permissions?)
CLI errors with “connection refused” but daemon is runningsocket path, DOCKER_HOST, or the selected docker context
Container exists in docker ps but won’t startcontainerd (image pull, shim, runtime)
Container exists but docker exec failsshim (process supervisor issue)
Kernel-level errors (cgroup, capability, namespace)runc (and the kernel)
Containers vanished after a package upgradelive-restore was off when dockerd restarted
docker images empty but containers runclassic vs containerd image store mismatch in your tooling

Inspecting the running stack

Read-only / Safeverify the stack
ps -eo pid,comm | grep -E 'dockerd|containerd|runc|shim' | head -20
Read-only / Safeshims
ps -eo pid,ppid,comm | awk '$3 ~ /containerd-shim/'
Read-only / Safecontainerd introspection
ctr --namespace moby containers ls
ctr --namespace moby tasks ls
Read-only / Safeversion
$ docker version --format '{{.Server.Version}} containerd={{.Server.Components}}'
28.3.2 containerd=[{Engine 28.3.2 map[...]} {containerd 1.7.27 map[...]} {runc 1.2.6 map[...]} {docker-init 0.19.0 map[...]}]

Illustrative output

Knowledge check

Knowledge check · 6 questions

  1. Q1. Which component actually creates the Linux namespaces that isolate a container?

  2. Q2. On a default Docker installation with no daemon.json changes, what happens to running containers when you run `systemctl restart docker`?

  3. Q3. Why is adding a user to the `docker` group equivalent to granting them root on that host?

  4. Q4. The `docker` command performs container creation itself and needs its own privileges to do so.

  5. Q5. Which of these survive a restart of `containerd` alone? Select all that apply.

  6. Q6. Why does each container have its own containerd-shim process?

Passing score: 75%. Answers are checked in this browser.