# Docker Architecture: What Actually Runs When You Type docker run

> The pieces behind the docker command: the CLI, the daemon, containerd, runc, the image layers and the kernel features that do the actual isolating.

Source: https://latchkey.dev/learn/ci-explained/docker-architecture  
Updated: 2026-08-31

The docker command does almost nothing. It sends an HTTP request to a daemon, which delegates to a container runtime, which asks the kernel for the isolation. Knowing which layer is which is what makes Docker failures diagnosable.

Docker is usually drawn as one box. It is at least four, and they fail differently: a CLI that only formats requests, a daemon that holds all the state, a runtime that creates the container, and kernel features that provide the isolation.

The reason this matters practically is that error messages come from different layers, and knowing the source narrows the cause immediately.

## The layers, top to bottom

| Layer | What it is | What it does |
| --- | --- | --- |
| Docker CLI | The `docker` binary | Turns your command into an HTTP request. Holds no state; can talk to a daemon on another machine. |
| Docker daemon (`dockerd`) | A long-running service | Owns images, containers, volumes and networks. Listens on `/var/run/docker.sock`. |
| containerd | A container supervisor | Pulls images, manages the container lifecycle, and supervises running containers. |
| runc | An OCI runtime | The piece that actually creates the container, then exits. A shim process stays behind as its parent. |
| Linux kernel | Namespaces, cgroups, union filesystems | Provides the isolation and the limits. Docker configures these; it does not implement them. |

> A container is not a virtual machine and there is no hypervisor in this list. On macOS and Windows there is one, because a Linux kernel has to come from somewhere, and everything above runs inside that VM.

## What happens on docker run

- The CLI POSTs to the daemon over the Unix socket. If this fails you get "Cannot connect to the Docker daemon", which means the daemon is not running, not that anything is wrong with your image.
- The daemon checks for the image locally and asks containerd to pull it if it is missing. Registry authentication and rate limits surface here.
- containerd unpacks the layers into a union filesystem and prepares a writable layer on top.
- runc creates the namespaces and cgroups, pivots into the new root filesystem, and executes your process. Then runc exits and a shim holds the container.
- The daemon streams logs and your exit code back to the CLI.

## Images are layers, and that is the whole caching story

An image is an ordered stack of read-only filesystem layers plus a JSON manifest, addressed by the hash of its contents. Two images built from the same base share those layers on disk and over the network, which is why pulling a second image from the same base is fast. A running container adds one writable layer on top, and copy-on-write means a file is only duplicated into it when the container modifies it.

> This is also why layer order in a Dockerfile matters so much. Changing a file invalidates its layer and every layer after it, so putting the dependency install before the source copy is not style, it is the difference between a cached build and a full one.

## BuildKit builds, and it is separate

Since Docker v23 `docker build` runs through BuildKit rather than the old sequential builder. BuildKit parses the Dockerfile into a dependency graph, so independent stages build in parallel, and it keeps its own cache that is not part of the image store. That last point is the source of a common surprise: pruning images does not free the build cache, because they are different stores.

## Why the layering matters when something breaks

| Symptom | Layer | What it usually means |
| --- | --- | --- |
| `Cannot connect to the Docker daemon` | CLI to daemon | The daemon is not running, or your user is not in the docker group. |
| `toomanyrequests` on pull | Daemon to registry | A registry rate limit, not a Docker problem. |
| `exec format error` | runc | The image was built for a different CPU architecture. |
| Container killed with exit code 137 | Kernel cgroups | The memory limit was hit and the OOM killer acted. |
| `no space left on device` | Filesystem | The image store or the build cache filled the disk. |

## Applying this to your pipeline

- Measure before changing. Most CI optimisation targets the wrong step because the slow one is assumed rather than timed.
- Cache what is expensive to produce and cheap to validate, and key the cache to the exact tool version.
- Fail fast: run the cheapest checks that can reject a change first, so an expensive job never starts on code that cannot pass.
- Prefer determinism over speed when they conflict. A fast pipeline nobody trusts gets re-run, which is slower than a slow one that is believed.

## FAQ

### What is the architecture of Docker?

A client-server design: the `docker` CLI sends requests to the `dockerd` daemon over a socket, the daemon delegates to containerd for image and lifecycle management, containerd calls runc to create the container, and runc configures Linux namespaces and cgroups. The kernel does the actual isolating.

### What is the difference between Docker, containerd and runc?

Docker is the whole product and the daemon that manages state. containerd is the supervisor that pulls images and manages container lifecycle. runc is the low-level runtime that creates a single container and then exits. Kubernetes talks to containerd directly and skips the Docker daemon entirely.

### Is a Docker container a virtual machine?

No. Containers share the host kernel and are isolated with namespaces and cgroups rather than virtualised hardware, which is why they start in milliseconds. On macOS and Windows there is a Linux VM in the picture, but only because a Linux kernel has to come from somewhere.

### What does the Docker daemon actually do?

It holds all the state: images, containers, volumes, networks and the build cache. The CLI is stateless and only formats requests, which is why `docker` can point at a daemon on another machine by setting `DOCKER_HOST`.

### Why does my build cache survive deleting all my images?

Because BuildKit keeps its cache in a separate store from the image store. `docker image prune -a` empties one and leaves the other untouched, which is why `docker system df` can still report tens of gigabytes after you have deleted every image.

---

Latchkey runs CI/CD that repairs its own failures. Agent entry points: https://latchkey.dev/agent.txt, https://latchkey.dev/openapi.json, https://latchkey.dev/llms.txt
