Adetayo Akinsanya unkletayo.dev

The Docker Toolchain Deep Dive: CLI to Daemon to containerd to runc

Tracing the execution process chain behind a single docker run command.

Part 2 in Series — Catch up on the previous article: What Docker Adds on Top of Linux Kernel Isolation Primitives (Part 1) before diving into this post.

You log into a production host and execute a simple command:

docker run -d --name web-server -p 80:80 nginx

Within 800 milliseconds, the command returns a 64-character container ID, and Nginx starts serving web traffic.

Later, an engineer performs a rolling software update on the Docker system daemon:

systemctl restart docker

While the dockerd daemon restarts, not a single HTTP request to the running Nginx container drops. The web server keeps running uninterrupted.

How does Docker restart its main control daemon without killing active running containers?

To answer this, we must examine the multi-layered process architecture that executes underneath the docker command line interface: CLI \to dockerd \to containerd \to containerd-shim \to runc.


The Architecture of the Process Chain

Modern Docker is not a single monolithic process. It is a collection of modular tools connected via UNIX sockets and gRPC interfaces:

[ User Shell ]
      |
      |  docker run nginx
      v
+-------------------------------------------------------------------+
| 1. Docker CLI (/usr/bin/docker)                                   |
+-------------------------------------------------------------------+
      |
      |  HTTP REST API payload over /var/run/docker.sock
      v
+-------------------------------------------------------------------+
| 2. Docker Daemon (/usr/bin/dockerd)                               |
|    - Handles image builds, network bridges, volume management     |
+-------------------------------------------------------------------+
      |
      |  gRPC Requests over /run/containerd/containerd.sock
      v
+-------------------------------------------------------------------+
| 3. containerd Daemon (/usr/bin/containerd)                        |
|    - Manages image snapshots, container lifecycles, content store |
+-------------------------------------------------------------------+
      |
      |  Forks a light daemonless shim process per container
      v
+-------------------------------------------------------------------+
| 4. containerd-shim (/usr/bin/containerd-shim-runc-v2)             |
|    - Holds open stdin/stdout/stderr file descriptors              |
|    - Manages container exit status codes                          |
+-------------------------------------------------------------------+
      |
      |  Executes OCI Bundle
      v
+-------------------------------------------------------------------+
| 5. runc CLI (/usr/bin/runc)                                       |
|    - Sets up Linux namespaces & cgroups via libcontainer          |
|    - Executes entrypoint binary (PID 1)                           |
|    - EXITS immediately after container creation!                  |
+-------------------------------------------------------------------+
      |
      v
+-------------------------------------------------------------------+
| 6. Containerized Process (nginx: master process)                  |
+-------------------------------------------------------------------+

1. Docker CLI (/usr/bin/docker)

The docker command is a client-side Go binary. It performs no container management or namespace creation itself.

When you type docker run, the CLI translates flags (-d, -p 80:80) into an HTTP JSON payload and transmits it across a local UNIX domain socket at /var/run/docker.sock to the Docker Daemon:

POST /v1.43/containers/create?name=web-server HTTP/1.1
Host: localhost
Content-Type: application/json

{
  "Image": "nginx",
  "ExposedPorts": { "80/tcp": {} },
  "HostConfig": {
    "PortBindings": { "80/tcp": [{ "HostPort": "80" }] }
  }
}

2. Docker Daemon (dockerd)

The dockerd process acts as the high-level orchestrator for host-level container features:

  • Build Engine: Translates Dockerfile instructions into image layers.
  • Virtual Networking: Allocates IP addresses, updates iptables rules, and sets up virtual bridge interfaces (docker0).
  • Volume Management: Mounts host directories or cloud block storage into container paths.
  • REST API Server: Authenticates requests and exposes endpoints.

Once dockerd completes high-level network and storage preparation, it hands off container execution to containerd.


3. containerd

containerd is an industry-standard, CNCF-graduated container runtime daemon.

While dockerd handles high-level user features, containerd manages low-level container lifecycle execution:

  • Pulling and pushing container images from registries.
  • Unpacking image layers into root filesystems using OverlayFS snapshotters.
  • Managing container execution state (start, stop, pause).

containerd listens on a gRPC socket at /run/containerd/containerd.sock. It can operate completely independent of Docker (for example, Kubernetes uses containerd directly via CRI without dockerd).


4. containerd-shim

When containerd is instructed to start a container, it does not launch the container process directly under its own process tree.

Instead, containerd forks an intermediate process called containerd-shim.

Why the Shim Exists:

  1. Daemonless Container Execution: The shim acts as the parent process for the container’s PID 1. If dockerd or containerd crash or undergo system upgrades, the shim keeps running, keeping the container process alive!
  2. I/O Streaming: Holds open the standard I/O pipes (stdin, stdout, stderr) so logs are captured even if the daemon restarts.
  3. Exit Code Reporting: Waits for the container PID 1 process to terminate and reports its exit code back to containerd.

5. runc

runc is the reference implementation of the Open Container Initiative (OCI) Runtime Specification.

When containerd-shim starts, it invokes runc create passing an OCI Bundle (a directory containing an config.json file and a rootfs/ directory).

How runc Creates the Container:

  1. Reads config.json to extract namespace settings, cgroup limits, capabilities, and environment variables.
  2. Invokes Linux system calls (clone(2) or unshare(2)) to create new namespaces (PID, NET, MNT, UTS, IPC).
  3. Configures cgroup resource limits in /sys/fs/cgroup/.
  4. Executes pivot_root(2) to lock the process into rootfs/.
  5. Executes execve(2) to replace its own process memory with the target container binary (e.g., nginx).

Crucially, once execve(2) replaces the process with Nginx, runc exits completely. It consumes zero system memory during container runtime!


Inspecting the Live Process Tree

You can observe this process chain directly on any Linux host using pstree:

$ pstree -plu $(pgrep containerd)
containerd(1042)─┬─containerd-shim(8920)───nginx(8945)───nginx(8990)
                 └─{containerd}(1043)

Notice the process tree hierarchy:

  • containerd (PID 1042) is the parent of containerd-shim (PID 8920).
  • containerd-shim is the parent of the nginx master process (PID 8945).
  • runc is nowhere in the process tree because it exited after setup!
  • dockerd is absent from the container process parentage, which is why restarting dockerd does not disrupt Nginx.

Summary & Next Steps

Docker’s modular toolchain separates high-level developer APIs from low-level kernel isolation execution:

  • docker CLI converts terminal commands into REST API requests sent over /var/run/docker.sock.
  • dockerd orchestrates networks, volumes, and API requests.
  • containerd manages image snapshots and container lifecycles via gRPC.
  • containerd-shim keeps container processes alive independently of daemon restarts.
  • runc executes system calls to set up namespaces/cgroups and exits.

In the next article, we examine The Open Container Initiative (OCI): Image Specification and Runtime Specification.

References & Further Reading

  1. Linux Foundation. OCI runc Runtime Spec & Command Line Interface. GitHub.
  2. Cloud Native Computing Foundation. CNCF containerd Architecture & gRPC Services Specification. containerd Docs.
  3. Docker Inc. Docker Engine Architecture and Daemon Socket Interface. Docker Docs.

Up Next in Series →

Part 3: The Open Container Initiative (OCI): Image Specification and Runtime Specification

Continue to Part 3 →