Docker, containerd, and runc: How They Relate

The relationship in one sentence

If we had to summarize it in a single table:

ComponentRoleAnalogy
DockerA full-stack tool for developers, providing the CLI, image building, and container managementA car dealership: you just say “I want to buy a car” and it handles the rest
containerdManager of the container lifecycle, handling images, storage, networking, and processesA car assembly plant: schedules the production line and manages parts inventory
runcThe low-level tool that actually creates and starts the container process, then exitsThe 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:

  1. The docker CLI sends the request to dockerd (the Docker daemon)
  2. 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:

StandardContentCorresponding implementation
OCI Runtime SpecDefines “how to run a container”: how Namespaces are configured, how Cgroups are set up, how the root filesystem is mountedrunc is the reference implementation
OCI Image SpecDefines “the format of an image”: how filesystem layers are organized and how metadata is describedFollowed 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:

  1. 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.
  2. 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

Dockercontainerdrunc
For whomDevelopersPlatforms / orchestration systemsA lower-level runtime
What it doesBuild images, CLI interaction, full toolchainManage image transfer and container lifecycleCreate a container process conforming to the OCI standard
How it runsDaemon (dockerd)DaemonOne-shot process (exits after starting)
DependenciesDepends on containerdDepends on runcOnly depends on the Linux kernel
Resident?YesYesNo

Understanding the division of labor among these three is the first step to understanding the modern container technology stack.