Skip to main content

What are the main components of Docker architecture?

Docker is not one binary but a layered stack of components, each with a clear responsibility and a standard contract between them. Knowing what each piece does is what separates someone who uses Docker from someone who can debug it.

Theory

TL;DR

  • Five layers: client → daemon → containerd → runc → container process. Plus a registry off to the side for images.
  • docker CLI is just a REST client. The intelligence is in the daemon.
  • dockerd receives requests, manages images, networks, volumes; delegates container lifecycle to containerd.
  • containerd is the production-grade container runtime. Manages container state, pulls images, talks to lower-level runtimes.
  • runc is the OCI-spec implementation that actually creates the process with namespaces and cgroups. Then exits.
  • Registry stores and serves images. Docker Hub is the default; ECR, GCR, GHCR, Harbor are common alternatives.

Quick example

bash
$ docker run hello-world

That one line touches every layer. Walk-through:

  1. docker CLI parses the command, sends POST /containers/create then POST /containers/{id}/start to dockerd over /var/run/docker.sock.
  2. dockerd pulls hello-world if not cached: hits the registry, downloads layers, stores them.
  3. dockerd calls containerd over gRPC: "create a container with this rootfs and config".
  4. containerd calls runc (via a containerd-shim process): "start a process here".
  5. runc sets up namespaces, cgroups, mounts, and execs the container's CMD. Then runc exits.
  6. The container process is now alive, parented by a containerd-shim.

Four processes were involved (CLI, dockerd, containerd, containerd-shim) plus your container.

The five components in detail

1. Docker client (docker CLI)

Thin wrapper. Parses commands, formats them as REST calls, sends them to the daemon. Has zero state of its own. Connects via:

  • Unix socket (default on Linux/Mac): /var/run/docker.sock
  • TCP socket (configured via DOCKER_HOST env var) for remote daemons

You can swap the client - nerdctl mimics the docker CLI but talks directly to containerd, skipping dockerd.

2. Docker daemon (dockerd)

The brain of the system. Long-running process, usually started by systemd. Owns:

  • REST API (Engine API) - what the client talks to.
  • Image management - pulls, builds, tags, stores under /var/lib/docker/.
  • Networking - bridge, overlay, host networks; iptables rules.
  • Volumes - named volumes, bind mounts.
  • Build engine - BuildKit subprocess for docker build.
  • Plugin system - storage, network, log drivers.

The daemon does NOT itself run containers - it delegates that to containerd.

3. containerd

Production-grade container runtime, originally extracted from Docker, now a CNCF graduated project. The thing that actually manages container lifecycle:

  • Pulls and unpacks images.
  • Creates container objects (rootfs + config).
  • Starts and stops containers via lower runtimes.
  • Tracks state, exposes events to its clients.

Kubernetes also uses containerd directly as its container runtime. Same component, different orchestrators on top.

4. runc

The OCI Runtime Specification reference implementation. Tiny binary (~10 MB). Its only job: given a JSON config and a rootfs directory, set up namespaces and cgroups and exec() the entry process. Then runc exits.

The long-running process you see for a container is not runc - it is your CMD, parented by a containerd-shim (which keeps holding the container alive even after containerd is restarted).

5. Registry

Image storage and distribution. Implements the OCI Distribution Spec. The daemon talks to it over HTTPS to pull and push images.

  • Docker Hub - default. docker pull nginx actually means docker pull docker.io/library/nginx.
  • AWS ECR, Google Artifact Registry, GitHub GHCR - private registries used in production.
  • Harbor / Distribution - self-hosted registries.

How docker run nginx flows through everything

+--------+ 1. CLI | docker | --- REST/socket ---+ +--------+ | v +-------------------+ | dockerd | | - parses request | | - pulls image ---+--> registry | - configures net | (Docker Hub, +-------------------+ ECR, etc.) | gRPC v +-------------------+ | containerd | | - prepares rootfs | | - tracks state | +-------------------+ | shim v +-------------------+ | containerd-shim | | (one per container)| +-------------------+ | spawns v +-------------------+ | runc | | - sets namespaces | | - sets cgroups | | - exec()s process | +-------------------+ | becomes v +-------------------+ | nginx process | +-------------------+

The shim is what allows you to restart dockerd without killing your containers - the shim survives the daemon restart and reconnects when daemon is back.

Why this layered design

Docker did not start this way. Original Docker (pre-2015) was one monolithic binary. The split into containerd + runc happened for two reasons:

  1. Standardization (OCI) - extract the runtime piece into a spec everyone could implement. Now Podman, Kata, gVisor, Firecracker all implement the same OCI runtime contract.
  2. Reusability - Kubernetes wanted a container runtime without the rest of Docker (no docker build, no docker network, no daemon REST API). It picked containerd.

The layered design lets you mix and match: Kubernetes + containerd + runc; Docker + containerd + Kata; Podman + crun; etc.

Common mistakes

Confusing dockerd with containerd

They are both daemons but at different layers. dockerd is the user-facing one (REST API, build engine, image management). containerd is one layer down (pure container lifecycle). Stopping dockerd does not stop your containers; stopping containerd does.

Thinking docker CLI does the work

bash
$ docker stop dockerd # Stops the daemon. CLI is now useless. $ docker ps Cannot connect to the Docker daemon at unix:///var/run/docker.sock

The CLI is just a frontend. All state lives in the daemon. Without dockerd running, no docker command works.

Assuming the architecture matters for application code

It does not. Your app inside a container just sees a Linux kernel, namespaces, cgroups. It does not know about Docker, containerd, or runc. The architecture matters for operators, not for the code running inside.

Forgetting that Mac and Windows hide a Linux VM

Docker Desktop on macOS or Windows runs the entire stack (dockerd, containerd, runc) inside a small Linux VM, because everything below dockerd needs a Linux kernel. The CLI on your Mac talks to dockerd via the VM's exposed socket. You see this VM as memory usage in Docker Desktop's settings.

Real-world usage

  • Kubernetes: uses containerd directly via the CRI (Container Runtime Interface) - dockerd is not in the picture. The kubelet on each node talks to containerd, which talks to runc.
  • GitHub Actions self-hosted runners: typically run dockerd inside a VM; jobs use docker run for builds.
  • CI/CD pipelines (Jenkins, GitLab, CircleCI): dockerd available on each runner; jobs build and push images.
  • Docker Desktop: all components bundled, plus a small Linux VM and the GUI. The same dockerd/containerd/runc stack underneath.

Follow-up questions

Q: What is the difference between containerd and runc?


A: containerd is the long-running daemon that manages container lifecycle (pulls images, tracks state, handles many containers). runc is a one-shot CLI binary that creates one container according to OCI spec, then exits. Containerd calls runc as a tool; runc does not run continuously.

Q: What is a containerd-shim?


A: A small process that sits between containerd and your container. It owns the container's stdio (stdout, stderr) and waits for it to exit. The shim's purpose is to keep the container alive across containerd restarts: you can upgrade containerd without killing every running container.

Q: Can I use Docker without dockerd at all?


A: Yes. Podman is API-compatible with Docker but daemonless - no dockerd, each podman command runs as your user. nerdctl keeps a daemon (containerd) but skips dockerd. Both produce/consume OCI images, so existing images and registries work unchanged.

Q: Where does BuildKit fit?


A: BuildKit is Docker's modern build engine. When you run docker build, the daemon spawns a BuildKit subprocess that handles the actual build (parsing the Dockerfile, scheduling stages, mounting caches). BuildKit can also run standalone for rootless or daemonless builds.

Q: (Senior) What does the OCI specification standardize and why does it matter?


A: Three specs: Image (the format - manifests, configs, layers), Runtime (how to actually run a container from an unpacked rootfs - what runc implements), and Distribution (how registries serve images over HTTP). Standardization means an image you build with Docker runs on Podman, Kubernetes, Kata, Firecracker - and a registry built once works for all. Without OCI, every runtime would be incompatible with every other.

Examples

Inspecting the running stack

bash
# On a Linux host with Docker installed $ ps -ef | grep -E 'dockerd|containerd|runc' | grep -v grep root 1234 1 dockerd root 1235 1 containerd root 4567 1235 containerd-shim-runc-v2 -id abc123 ... # runc is not in this list - it ran briefly and exited.

The shim is what stays running for each container. dockerd and containerd are the long-lived daemons.

Direct containerd interaction with ctr

bash
# Talk to containerd directly, bypassing dockerd $ ctr images pull docker.io/library/nginx:1.27-alpine $ ctr run --rm docker.io/library/nginx:1.27-alpine my-nginx

No dockerd involved. Useful for debugging when dockerd is broken but containerd is healthy. This is also exactly how Kubernetes interacts with containers.

Comparing Docker and Podman flow

bash
# Docker - daemon-based $ docker run hello-world # CLI -> dockerd -> containerd -> runc # Podman - daemonless $ podman run hello-world # CLI -> conmon (per-container monitor) -> crun/runc

Same OCI image. Same end result (a container running). No dockerd, no client-server split. Each podman invocation is its own process. Useful in CI, in rootless setups, and on hosts where you do not want a long-running root daemon.

Short Answer

Interview ready
Premium

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

Comments

No comments yet