Suggest an editImprove this articleRefine the answer for “What are the main components of Docker architecture?”. Your changes go to moderation before they’re published.Approval requiredContentWhat you’re changing🇺🇸EN🇺🇦UAPreviewTitle (EN)Short answer (EN)**Docker architecture** is a client-server stack: the `docker` CLI talks to the `dockerd` daemon over a REST API. The daemon delegates container lifecycle to `containerd`, which uses `runc` to actually start the process. Images come from a registry (Docker Hub or other). ``` docker (client) → REST → dockerd → containerd → runc → container process ↓ registry (image storage) ``` **Key:** five components - client, daemon, containerd, runc, registry. Each has a clear job and standard contracts (OCI, REST). That separation lets you swap pieces (Podman replaces dockerd; nerdctl talks straight to containerd).Shown above the full answer for quick recall.Answer (EN)Image**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.For the reviewerNote to the moderator (optional)Visible only to the moderator. Helps review go faster.