Docker Architecture: What Actually Runs When You Type docker run
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
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.
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
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.
Frequently asked questions
What is the architecture of Docker?
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?
Is a Docker container a virtual machine?
What does the Docker daemon actually do?
docker can point at a daemon on another machine by setting DOCKER_HOST.Why does my build cache survive deleting all my images?
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.