Overview #
Docker isn’t just a tool that accepts a docker run command and then runs a container. Behind that simple CLI is a layered platform of interacting components — daemon, REST API, container runtime, image, the container itself, and registry. Understanding Docker’s architecture isn’t an end goal; it’s the foundation for everything else you’ll build on top of it, from image build optimization to debugging a container that suddenly dies in production.
This article gives a high-level view of the entire Docker architecture. The goal isn’t to explain every component in depth (that’s what the following articles in this section do), but to show the big picture — who talks to whom, in what order, and why each layer exists. After reading this article, you’ll have a thinking framework that makes the technical articles feel connected, not like isolated fragments.
When Do You Need to Understand Docker’s Architecture? #
Not everyone needs to know Docker’s internals. But there are moments when architecture understanding shifts from “nice to know” to “critical to know” — and those moments often arrive once the problem has already occurred.
MUST understand Docker's architecture if:
✓ You deploy containers to production and have to debug
issues that don't show up in development (resource
limits, networking, IPC).
✓ You write Dockerfiles with slow builds and want to
optimize layer caching.
✓ You use Docker in CI/CD and want to know why one
pipeline is slow even though the image is small.
✓ You manage multiple Docker hosts (Swarm, K8s) and
need to understand how remote communication works.
✓ You face security issues and need to understand the
attack surface of the docker daemon.
NO need for deep understanding if:
✗ You only run `docker run` occasionally for exploration
and don't deploy anything.
✗ You use managed services (ECS, Cloud Run, Fargate)
that hide the daemon from you.
✗ You're only an end user of applications without
managing their infrastructure.
The good news: architectural understanding is cumulative. You don’t have to memorize every diagram in one go. What matters is having a reasonably accurate map in your head, then going deeper into one area when you need it. This article is that map.
How to read this section: the six articles in the Architecture section complement each other. This article gives the big picture. Engine & Daemon dives into the platform’s core. Image explains the blueprint. Container covers the runtime. Registry explains distribution. And How It Works brings everything together at the kernel level. Read them in order, or jump to the article most relevant to your problem.
The Three Layers of Docker’s Architecture #
Docker follows the classic client–server pattern, but with one extra layer in the middle: the runtime. For most interactions, you only need to know these three layers.
flowchart LR
USER[Developer / CI / Tooling]
subgraph CLIENT["Client Layer"]
CLI[docker CLI]
API[Docker SDK]
end
subgraph ENGINE["Engine Layer"]
DAEMON[dockerd]
RUNTIME[containerd + runc]
end
subgraph DELIVERY["Delivery Layer"]
REG[Registry]
IMAGES[(Image Storage)]
end
USER --> CLI
CLI -->|"REST API<br/>(unix socket / TCP)"| DAEMON
API --> DAEMON
DAEMON --> RUNTIME
DAEMON <-->|"pull / push"| REG
REG --- IMAGESThe Client Layer is what you touch every day — the docker CLI in your terminal, or SDK libraries in your applications (like the Docker SDK for Python or Go). The client has no Docker business logic; it only translates commands into REST API HTTP requests and sends them to the daemon.
The Engine Layer is where all decisions are made. This is where dockerd (the Docker Daemon) runs as a background service, receives requests from clients, decides what to do, and delegates execution to lower-level runtimes (containerd then runc). This engine layer is the most complex, and most of this article — and this section — will discuss it.
The Delivery Layer is where images are stored and distributed. Registries can be public (Docker Hub) or private (ECR, GHCR, self-hosted). An image pushed to a registry is an artifact that any host with access can pull.
This separation matters because it explains a lot of Docker behavior that looks mysterious. For example, why can docker build and docker run be executed from different machines? Because the client and engine are separate processes communicating over the network. Why is the image small but a running container big? Because an image is a package, while a container is a running instance that adds a writable layer on top. All these answers trace back to the three layers above.
Docker Engine Anatomy #
The engine is Docker’s heart. Understanding its anatomy at a high level will give all those recurring terms — daemon, runtime, API — a place in your head.
flowchart TB
subgraph ENGINE["Docker Engine"]
DAEMON["dockerd<br/>(Docker Daemon)<br/><br/>• Image management<br/>• Container lifecycle<br/>• Network & volume<br/>• REST API server"]
API["Docker REST API<br/><br/>• /v1.41/...<br/>• Default: unix:///var/run/docker.sock"]
CLI["docker CLI<br/><br/>• build, run, pull, push<br/>• ps, logs, exec"]
RUNTIME["Container Runtime<br/><br/>• containerd (high-level)<br/>• runc (low-level, OCI)"]
end
CLI -->|"HTTP request"| API
API --> DAEMON
DAEMON --> RUNTIME
RUNTIME -->|"syscall"| KERNEL[Linux Kernel]dockerd is the main process that runs continuously in the background. It’s Docker’s brain: receiving requests, managing image and container state, arranging networks and volumes, and deciding what to run. When you type docker ps, the daemon answers with the list of containers. When you run docker run nginx, the daemon prepares everything.
The Docker REST API is the bridge between client and daemon. By default this API is accessed via a Unix socket at /var/run/docker.sock, which means only processes on the same machine can talk to the daemon. But the daemon can also be configured to listen on a TCP socket — and that’s where remote management and orchestration (Swarm, K8s) come into play.
The docker CLI is the interface you use most often. It has no meaningful internal logic — all decisions happen in the daemon. The CLI only formats input, sends requests, and displays responses. That’s why all official Docker SDKs (Go, Python, Node) are essentially wrappers around the same REST API endpoints.
The Container Runtime is the execution layer. Since Docker 1.11, the daemon no longer runs containers directly. It delegates to containerd (a high-level runtime managing image snapshots and the container lifecycle), which in turn calls runc (a low-level runtime that actually creates the process in new namespaces and cgroups per the OCI standard). This split brings two big advantages: stability (if runc crashes, the daemon doesn’t crash with it) and standards (anyone can write an OCI-compliant runtime, it doesn’t have to be Docker).
Interesting detail: since Docker 1.11 (early 2016), all runtime logic used to live inside dockerd. When runc was forked into a separate project and containerd was donated to the CNCF, the daemon became much slimmer. The practical impact: Docker Engine today is more or less a lightweight orchestrator on top of industry-standard runtimes. That’s why Podman, CRI-O, and containerd can run Docker images without Docker Engine at all.
Image, Container, and Registry — Three Core Concepts #
On top of the three layers, there are three objects that always come up in Docker discussions: image, container, and registry. They’re closely related, and understanding this relationship will prevent a lot of confusion.
flowchart LR
DOCKERFILE[Dockerfile] -->|docker build| IMAGE[Docker Image]
IMAGE -->|docker push| REG[Registry]
REG -->|docker pull| IMAGE
IMAGE -->|docker run| CONTAINER[Docker Container]
CONTAINER -->|logs, exec, stop| CONTAINERA Docker Image is an immutable template. It’s a collection of filesystem layers plus metadata (default commands, exposed ports, environment variables). An image doesn’t run — it’s just a mold. Images are created with docker build from a Dockerfile and distributed through registries.
A Docker Container is a runtime instance of an image. When you run docker run nginx, Docker takes the nginx image, adds a writable layer on top, and runs the main process defined in the image. One image can be used to create many containers, and each container has its own state (written files, running processes, network interfaces).
A Docker Registry is the warehouse for images. Registries store images in repositories, and each repository can have many tags (versions). Docker Hub is the largest public registry, but there are plenty of alternatives — AWS ECR, Google Artifact Registry, GitHub Container Registry, Harbor, and self-hosted Docker Registry. The registry is the meeting point between image creators (developers, CI) and image runners (servers, Kubernetes nodes).
The relationship between the three forms the lifecycle you’ll encounter everywhere:
| Stage | Role | Example Command |
|---|---|---|
| Build | Dockerfile → local image | docker build -t myapp:1.0 . |
| Push | Local image → Registry | docker push myapp:1.0 |
| Pull | Registry → local image on another host | docker pull myapp:1.0 |
| Run | Image → live container | docker run -d -p 80:80 myapp:1.0 |
| Stop/Remove | Container stopped and deleted | docker stop + docker rm |
Notice that images stay the same, containers change. An image is a contract — what you build is what runs everywhere. A container is a temporary expression of that image on a specific host, with local state that will be lost when the container is deleted (unless data is stored in volumes).
A common anti-pattern: treating containers like VMs by storing data in the container’s filesystem. Containers are designed to be immutable and ephemeral; every restart or replacement loses the data stored in the writable layer. Always use named volumes or bind mounts for data that must survive.
End-to-End Flow: What Happens During docker run
#
One of the best ways to understand Docker’s architecture is tracing a single command from end to end. Watch what happens when you type docker run -d -p 80:80 nginx:latest in your terminal.
sequenceDiagram
participant U as User Terminal
participant CLI as docker CLI
participant D as dockerd
participant R as containerd
participant RC as runc
participant K as Linux Kernel
participant REG as Registry
U->>CLI: docker run -d -p 80:80 nginx:latest
CLI->>D: POST /containers/create<br/>(REST API, unix socket)
D->>REG: HEAD nginx:latest
REG-->>D: manifest info
D->>D: check local image
alt image missing
D->>REG: GET /v2/nginx/manifests/latest
REG-->>D: manifest
D->>REG: GET /v2/nginx/blobs/...
REG-->>D: layer data
D->>D: save image to local storage
end
D->>R: Create container spec
R->>R: snapshot writable layer
R->>RC: bundle OCI
RC->>K: clone() + unshare()
K-->>RC: namespace ready
RC->>K: execve(nginx)
K-->>U: nginx listening on port 80
D-->>CLI: container ID
CLI-->>U: print container IDLet’s break down the stages:
The client receives the command. The CLI parses the
-d -p 80:80 nginx:latestarguments, builds a JSON request per the Docker REST API, and sends it todockerdvia the Unix socket at/var/run/docker.sock.The daemon validates the request.
dockerdreceives the request, does a sanity check (is port 80 available, is thenginx:latestimage in the right format), and decides the next step.Image resolution. The daemon checks whether the
nginx:latestimage already exists in the local image cache. If not, it contacts the registry (default: Docker Hub) to fetch the required manifest and layers. Each layer is stored in local image storage and cached for future use.The container spec is assembled. The daemon creates a container specification — a complete description of which image to use, what environment variables, which ports to map, which volumes to mount, and which namespaces and cgroups to set up. This spec follows the OCI standard.
The runtime prepares execution. The spec is passed to
containerd.containerdadds a writable layer on top of the image, configures the virtual network (bridge, veth pair), and forwards the bundle torunc.runccreates the process.runcuses theclone()system call with namespace flags to create a new process running in separate namespaces (PID, NET, MNT, UTS, IPC, USER). It also registers the process with a cgroup for resource limiting.The container is alive. The main process (here
nginx) runs as PID 1 inside its own container namespace. From its point of view, it’s the only process on a freshly created “system”. Port 80 is bound in the container’s network namespace, and Docker on the host forwards traffic from host port 80 to container port 80.The response returns to the user. The daemon returns the container ID to the CLI, which prints it to the terminal. The container runs in the background (
-d), and you can interact with it viadocker logs,docker exec,docker stop, and so on.
This entire process usually happens in a matter of seconds, especially for cached images. But behind that speed is the orchestration of many components, each with its own specific responsibility.
Decision Tree — Choosing a Docker Deployment Topology #
Docker’s architecture doesn’t stand alone. How you deploy determines which components you need to understand deeply.
flowchart TD
A{Deployment<br/>target?}
A -- Dev laptop --> L[Docker Desktop / local CLI]
A -- Single server --> B[Docker Engine + Compose]
A -- Multi-host --> C[Orchestrator?]
C -- Swarm --> S[Docker Swarm]
C -- Kubernetes --> K[Kubernetes]
C -- Managed cloud --> M[ECS / Cloud Run / Fargate]
L --> LV[Bottleneck:<br/>laptop resources]
B --> BV[Bottleneck:<br/>manual configuration]
S --> SV[Learn:<br/>swarm mode, services]
K --> KV[Learn:<br/>pods, deployments, ingress]
M --> MV[Learn:<br/>IAM, scaling policies]For laptop development, you only need to understand the CLI and the local daemon. For a single server with several services, Docker Compose is your best friend. Once you have to run containers on many hosts at once, you enter the world of orchestration — Docker Swarm (native) or Kubernetes (the industry de facto standard). Managed services (ECS, Cloud Run, Fargate) hide the daemon and runtime from you entirely.
This section focuses on the Engine itself — what happens from the CLI until a container is alive. Specific deployment topologies (Swarm, Compose, K8s) are covered in other sections.
A note on versions: this section describes Docker’s architecture as relevant to Docker Engine 20.10 and above (2020–2024 releases). Since the Moby Project forked internal components, small details like process names and socket paths may differ slightly between distributions, but the architectural model stays the same.
Summary #
- Docker’s architecture has three main layers — Client Layer (CLI/SDK), Engine Layer (daemon + runtime), and Delivery Layer (registry). This separation explains why Docker can be controlled remotely, why images can be shared, and why components can be swapped (e.g. Podman for the daemon).
- Docker Engine isn’t a single process — it’s a package made up of
dockerd(daemon), the REST API, the CLI, and the container runtime (containerd+runc). Since Docker 1.11, the daemon no longer runs containers directly; it delegates to an OCI-compliant runtime.- Docker’s three central objects — image (immutable template), container (runtime instance), registry (distribution warehouse). Their lifecycle:
build→push→pull→run→remove.- Image ≠ Container — an image is a blueprint; a container is the blueprint plus a writable layer plus running processes. One image can create many containers. Deleting a container doesn’t affect the image.
- The
docker runflow passes through many components: CLI → REST API → daemon → image resolution → runtime → kernel. Understanding this flow is the basis for debugging any Docker problem.- Architecture determines tooling — for single-host deployment, Engine + Compose is enough. For multi-host, you need an orchestrator (Swarm or K8s). For managed environments, the daemon is hidden from you.
- This section covers the Engine in depth — the following articles dive into each component: engine & daemon, image, container, registry, and how it all works at the kernel level.