Skip to main content

What is containerd and its role in the Docker ecosystem?

containerd is the production-grade container runtime that lives between high-level orchestrators (Docker, Kubernetes) and low-level OCI runtimes (runc). It is one of the most successful infrastructure components nobody talks about — quietly running most containers in the world.

Theory

TL;DR

  • containerd = container daemon. Manages container lifecycle: pull images, unpack, run, monitor, network setup.
  • Originally extracted from Docker in 2017 as a separate component, then donated to CNCF in 2019. CNCF graduated project.
  • Used by Docker (the daemon stack: dockerd → containerd → runc).
  • Used by Kubernetes directly (since 1.24, dockershim removed).
  • Tooling: ctr (low-level admin), nerdctl (Docker-CLI-compatible).
  • runc is below containerd — runc creates one container per invocation; containerd manages the long-running orchestration.

Where containerd sits

+----------------------------------------+ | User-facing tools | | docker / kubectl / podman / | | ctr / nerdctl | +----------------------------------------+ | | +---------v-----+ +------v-----------+ | dockerd | | kubelet | | (Docker only) | | (Kubernetes only)| +---------------+ +------------------+ | | +----+ +----+ v v +---------------+ | containerd | | (CRI server) | +---------------+ | +---------------+ | OCI runtimes | | runc / kata | | gVisor / etc | +---------------+ | +---------------+ | Container | | (process) | +---------------+

Docker users hit dockerd which delegates to containerd. Kubernetes users hit kubelet which uses containerd directly via CRI (Container Runtime Interface). Both end up at the same containerd, then runc.

Why containerd was extracted

In 2014-2016, Docker was a monolith doing everything. Two pressures emerged:

  1. Kubernetes wanted a runtime without the rest of Docker (no docker build, no Docker CLI, no Swarm). Just "manage containers".
  2. Standardization — the OCI initiative wanted a clear contract for runtimes. Docker's internal code did not have a clean abstraction.

Docker (the company) extracted containerd in 2017, donated it to CNCF, and reshaped Docker's daemon to use containerd internally. Result: same Docker, but the runtime layer is now reusable by anyone.

What containerd actually does

  • Image management: pulls from registries, stores layers, prepares rootfs.
  • Container lifecycle: create, start, stop, pause, resume, kill, delete.
  • Snapshot management: uses storage drivers (overlayfs, btrfs, etc.) to manage filesystem snapshots.
  • Process supervision: via containerd-shim (one shim per container, survives daemon restarts).
  • Networking handoff: does NOT do networking itself; defers to CNI plugins (Kubernetes) or callback APIs (Docker).
  • Metrics & events: exposes container metrics; emits events that orchestrators subscribe to.

What it does not do:

  • Build images (that is buildkit / docker build / kaniko).
  • Provide a CLI for end users (use ctr or nerdctl).
  • Network policy / orchestration (that is Kubernetes / Swarm / Cilium / Calico).
  • High-level UX (Docker provides that for Docker users).

ctr — low-level admin tool

Shipped with containerd. Speaks containerd's gRPC API directly:

bash
# List images ctr images list # Pull an image ctr images pull docker.io/library/alpine:3.21 # Run a container ctr run --rm docker.io/library/alpine:3.21 mytest /bin/sh # List running tasks ctr tasks list

Not user-friendly; intended for debugging or low-level admin. Note that containerd uses namespaces to separate clients — Docker uses moby, Kubernetes uses k8s.io. To see Docker's containers via ctr: ctr -n moby containers list.

nerdctl — Docker-compatible CLI

bash
nerdctl run --rm hello-world # like docker run nerdctl ps nerdctl images nerdctl compose up -d # Compose support too

nerdctl speaks Docker-style commands but talks to containerd directly (no dockerd). Useful in K8s nodes where Docker is not installed but containers are running.

containerd in Kubernetes (post-dockershim)

Kubernetes 1.24 (April 2022) removed the dockershim — the compatibility layer that let kubelet talk to dockerd. Now kubelet talks to containerd directly via CRI:

# /etc/crictl.yaml runtime-endpoint: unix:///run/containerd/containerd.sock

Direct, simpler, fewer moving parts. crictl is the Kubernetes-flavored CLI to debug at this layer.

Common mistakes

Confusing containerd with runc

People often think they are interchangeable. They are not.

  • runc is a one-shot CLI that creates one container per invocation, then exits. It implements the OCI Runtime Spec.
  • containerd is a long-running daemon that manages many containers' lifecycle. It calls runc as a tool.

Treating containerd as Docker-specific

It is not. Kubernetes uses containerd without Docker. So does podman (via crun, similar but different). containerd is independent infrastructure that Docker happens to also use.

Trying to use Docker images on Kubernetes by manipulating /var/lib/docker

Kubernetes' containerd uses /var/lib/containerd/, NOT Docker's /var/lib/docker/. They are isolated stores. To move images, push to a registry and pull on the other side, or use nerdctl save/load.

Confusing ctr -n moby and ctr -n k8s.io

containerd's namespaces (moby, k8s.io, default) are isolation between clients. Docker's containers live in moby, K8s in k8s.io. Default ctr commands query default and find nothing.

Real-world usage

  • All major cloud Kubernetes (EKS, GKE, AKS) use containerd as the runtime.
  • Docker Engine uses containerd internally (since Docker 18.09).
  • Edge / IoT: containerd's lightweight footprint makes it popular on resource-constrained devices.
  • CRI-O (alternative to containerd, used by some OpenShift deployments) — same role, different implementation. Both implement Kubernetes' CRI.

Follow-up questions

Q: What is the difference between containerd and Docker?


A: Docker is a full platform: CLI, daemon (dockerd), build tools, registry client, networking, image management. containerd is just the container-runtime piece — pulls images, runs containers, manages lifecycle. Docker uses containerd internally.

Q: What is the difference between containerd and CRI-O?


A: Both implement Kubernetes' Container Runtime Interface (CRI). containerd is broader, used by Docker too. CRI-O is K8s-specific, lighter. Functionally equivalent for K8s; different communities and design philosophies.

Q: How does containerd interact with the OCI runtime?


A: Via the runtime-spec interface. containerd writes a config.json (OCI Runtime Spec) and asks the runtime (runc, kata, gVisor) to create the container. The runtime is invoked once per container start; containerd-shim then supervises.

Q: What is a containerd-shim?


A: A small process per container that maintains the container's stdin/stdout/stderr and survives containerd restart. If containerd dies, the shim keeps the container alive; when containerd comes back, it reconnects via the shim.

Q: (Senior) How would you migrate a workload from Docker to containerd-direct (no Docker daemon)?


A: Kubernetes-managed: change the kubelet config to point at containerd; remove dockershim binaries (modern K8s already does). Image format is OCI, no conversion needed. Image pulls work identically (same registry protocol). Volumes via CSI plugins. Networking via CNI plugins. The migration is mostly an audit: what depended on Docker-specific behavior (Docker socket mounts in pods, /var/lib/docker references)? Refactor those. For non-K8s scenarios, swap docker run for nerdctl run or podman run — same flags, different daemon model.

Examples

Inspecting Docker's containerd

bash
# List Docker's containers via ctr sudo ctr -n moby containers list CONTAINER IMAGE RUNTIME a3f9d2b8c1e4f3a2... - io.containerd.runc.v2 # Same for K8s sudo ctr -n k8s.io containers list

Namespace selection (-n) routes the query to the right slice of containerd's state.

Using nerdctl instead of docker

bash
# nerdctl works exactly like docker nerdctl run -d --name web -p 8080:80 nginx nerdctl ps nerdctl logs web nerdctl compose up -d # But under the hood, no dockerd — just containerd. ps -ef | grep dockerd # (no result) ps -ef | grep containerd containerd ...

Useful in environments where Docker is intentionally not installed (some K8s nodes, hardened hosts).

Examining the containerd → runc handoff

bash
# When a container starts, you can see the chain $ ps -ef | grep -E 'containerd|runc' | grep -v grep root 1235 1 /usr/bin/containerd root 4567 1235 containerd-shim-runc-v2 -id abc123 ... # runc itself is not in the list — it ran briefly and exited # The container's process is parented by the shim (4567)

runc invocation is short; the shim is what sticks around. containerd is the long-running daemon orchestrating the whole thing.

Short Answer

Interview ready
Premium

A concise answer to help you respond confidently on this topic during an interview.

Comments

No comments yet