What is the Docker Engine architecture — client, daemon, containerd, and runc?

7 minadvanceddocker-daemoncontainerdruncarchitecture

Quick Answer

The Docker CLI (docker command) is a thin client that sends REST API requests to the Docker daemon (dockerd), which manages images, networks, and volumes, and delegates actual container execution to containerd (a separate, standardized container runtime manager), which in turn uses runc (a low-level OCI-compliant tool) to actually create the isolated process using Linux namespaces and cgroups. Each layer exists to separate concerns and allow components to be swapped or reused independently, following the same standardized interfaces (like the CRI) that Kubernetes itself also relies on.

Detailed Answer

The layered architecture

docker CLI  ──(REST API)──▶  dockerd (Docker daemon)
                                    │
                                    ▼
                             containerd (container lifecycle manager)
                                    │
                                    ▼
                             runc (OCI runtime — creates the actual isolated process)
                                    │
                                    ▼
                       Linux kernel (namespaces + cgroups)

Docker CLI — a thin client

docker run -d -p 8080:80 nginx

The docker command itself does almost no work directly — it constructs an HTTP request describing what you asked for and sends it to the Docker daemon's REST API (typically over a Unix socket, /var/run/docker.sock, or a TCP socket if configured for remote access). This is why remote Docker management tools, and Docker's own CLI running against a remote daemon, both work — the CLI is just one possible client of a well-defined API.

dockerd — the daemon, managing the bigger picture

The Docker daemon handles the higher-level concerns: building images, managing networks and volumes, handling the REST API, and enforcing Docker-level configuration. But it delegates the actual work of running a container to containerd, rather than doing it directly itself.

containerd — container lifecycle management

containerd is a separate, standalone component (donated to and now governed by the CNCF, the same foundation that hosts Kubernetes) responsible for the full container lifecycle: pulling images, managing storage, and supervising running containers. Notably, containerd itself has no CLI or user-facing API in the way docker does. It's designed to be used by a higher-level tool, such as dockerd, or directly by a Kubernetes node's kubelet via the CRI (see the Kubernetes stack's CRI question), rather than by an end user directly.

runc — the low-level OCI runtime

runc is the component that does the actual, final work of creating an isolated container process — setting up Linux namespaces (see that question), configuring cgroups, and then executing the container's process within that isolated environment. runc implements the OCI (Open Container Initiative) Runtime Specification (see that question). This is exactly why alternative low-level runtimes, like Kata Containers or gVisor's runtime, can be swapped in for stronger isolation without containerd or dockerd needing runtime-specific code for each one.

Why this many layers, rather than one monolithic tool

Each layer standardizes a different concern, allowing components above and below it to be swapped independently. containerd can be used directly by Kubernetes without needing dockerd at all (bypassing Docker entirely, which is exactly what happened when Kubernetes deprecated dockershim — see that stack's question). runc can be swapped for a stronger-isolation OCI-compliant runtime without containerd needing to change. This layered, standardized design is precisely why the broader container ecosystem (Docker, Kubernetes, Podman, and others) can share and interoperate around common lower-level components rather than each reimplementing container execution from scratch.

Practical relevance

When troubleshooting a Docker issue, understanding this chain tells you where to look. docker CLI errors about connecting to the daemon point at dockerd's availability. A container failing to actually start (versus the image failing to build) often points further down the stack toward containerd or runc-level issues, such as kernel feature availability or cgroup configuration. Understanding that containerd predates and outlives any specific Docker CLI experience also explains why the same container images and runtime concepts apply, whether you're using plain Docker or a Kubernetes cluster built on the same underlying containerd.