Docker, containerd, and runc: How They Relate
The relationship in one sentence
If we had to summarize it in a single table:
| Component | Role | Analogy |
|---|---|---|
| Docker | A full-stack tool for developers, providing the CLI, image building, and container management | A car dealership: you just say “I want to buy a car” and it handles the rest |
| containerd | Manager of the container lifecycle, handling images, storage, networking, and processes | A car assembly plant: schedules the production line and manages parts inventory |
| runc | The low-level tool that actually creates and starts the container process, then exits | The robot that tightens the final screw on the line, then walks away |
Their call-chain relationship is:
docker CLI → dockerd → containerd → containerd-shim → runc
(your command) (API gateway) (container butler) (process nanny) (container starter)
Let’s unpack each layer below.
1. Docker: the developer’s all-in-one toolkit
Docker is a complete platform tool. The everyday commands docker build, docker run, and docker push all come from it.
When you run docker run -d nginx:
- The docker CLI sends the request to dockerd (the Docker daemon)
- dockerd orchestrates everything that follows: pulling the image, configuring the network, and starting the container
Docker’s job is to wrap image building (BuildKit), image distribution, network configuration, and volume storage into simple commands. A developer’s daily work only requires interacting with Docker.
Worth noting: dockerd does not directly operate the container process — it delegates container startup and lifecycle management to containerd, focusing itself on higher-level work such as the API gateway, image-build scheduling, and network/storage orchestration.
2. containerd: the container’s “butler”
containerd is a CNCF graduated project and a piece of infrastructure in the container ecosystem. It is an independent daemon dedicated to managing the complete lifecycle of containers. Upper-layer platforms such as Docker and Kubernetes all manipulate containers through it.
containerd’s core responsibilities:
- Image management: pull, push, storage, and layered management
- Container lifecycle: create, start, stop, delete, pause, resume
- Snapshot storage: managing the container filesystem through the snapshotter plugin
- Task execution: calling runc to create the actual process and monitoring it continuously through the shim process
Within the Docker system, the division of labor is:
- dockerd handles image building (BuildKit), network orchestration, volume management, and the user API
- containerd handles pulling images, storing images, managing image layers, and starting/stopping containers
containerd doesn’t care who the upper layer is (Docker or Kubernetes); it just manages containers well. This decoupling means: you can build images with Docker and run them directly against containerd with Kubernetes — one shared image system fits all.
3. runc: the container’s “starter”
If containerd is the butler, then runc is the executor under the butler.
3.1 runc does exactly one thing
runc’s responsibility is extremely single-minded: it reads a JSON config file that conforms to the OCI Runtime Spec, then according to that config calls clone() to create a Namespace, sets up Cgroups, and starts the container process. Once that is done, runc exits on its own.
It is not a daemon and does not stay resident in the background. Every time a container is started, containerd spawns a new runc process; once the container is up, runc exits. The container process is then taken over by another role — shim.
3.2 The relationship between the OCI standard and runc
OCI (Open Container Initiative) defines industry specifications for container formats and runtimes, consisting of two core standards:
| Standard | Content | Corresponding implementation |
|---|---|---|
| OCI Runtime Spec | Defines “how to run a container”: how Namespaces are configured, how Cgroups are set up, how the root filesystem is mounted | runc is the reference implementation |
| OCI Image Spec | Defines “the format of an image”: how filesystem layers are organized and how metadata is described | Followed by all Registries and build tools |
This means: as long as a program can produce a config that conforms to the OCI Runtime Spec, it can use runc (or any compatible OCI runtime) to start a container. Docker, containerd, and Kubernetes all decouple from the low-level runtime through this standard.
4. The full journey of one docker run
Having understood each component’s responsibilities, let’s look at the complete process of a container from creation to running:
1. docker run nginx
│
▼
2. dockerd receives the request and parses the parameters
│
▼
3. dockerd calls containerd: pull the nginx image for me, then start it
│
▼
4. containerd connects to the Registry and downloads each layer of the image to the local snapshotter
│
▼
5. containerd starts a new process — containerd-shim (the process nanny)
│
▼
6. shim calls runc: here is the OCI config, create the container process for me
│
▼
7. runc executes clone() to create the Namespace, sets up Cgroups,
loads the image filesystem, and starts the nginx main process
│
▼
8. runc exits. The nginx process is handed over to shim
│
▼
9. shim continuously monitors the nginx process:
- collects stdout/stderr
- reports the exit status to containerd
- cleans up after the container exits
Why shim matters
You might ask: after runc starts the container and exits, who watches the container process? The answer is shim.
shim’s design solves two problems:
- containerd restarts don’t affect the container: if containerd is upgraded or crashes and restarts, the shim process is unaffected and the container keeps running.
- Collecting exit status: after runc exits, if it were still the parent process of the container, the container would become a zombie process on exit. As the parent process, shim can correctly reap it and report the exit information.
5. The Kubernetes perspective: Docker is optional
In a Kubernetes cluster, the container runtime chain is:
Kubernetes → containerd → runc
Notice there is no dockerd here. Kubernetes talks directly to containerd to manage containers, because:
- Kubernetes already manages networking via CNI plugins and storage via CSI plugins, so it doesn’t need dockerd as another layer
- containerd is lighter and more focused, with smaller resource overhead
- Kubernetes only needs to “manage container processes well”, not “build images for developers”
But this doesn’t mean Docker is obsolete — the image format is the OCI standard, so it runs anywhere. Developers still use Docker to build and test images, push them to a Registry, and Kubernetes pulls them and runs them directly through containerd. The division between Docker and Kubernetes is: Docker owns the development phase, and containerd owns production runtime.
6. Summary
| Docker | containerd | runc | |
|---|---|---|---|
| For whom | Developers | Platforms / orchestration systems | A lower-level runtime |
| What it does | Build images, CLI interaction, full toolchain | Manage image transfer and container lifecycle | Create a container process conforming to the OCI standard |
| How it runs | Daemon (dockerd) | Daemon | One-shot process (exits after starting) |
| Dependencies | Depends on containerd | Depends on runc | Only depends on the Linux kernel |
| Resident? | Yes | Yes | No |
Understanding the division of labor among these three is the first step to understanding the modern container technology stack.