Docker Architecture — The Full Picture
LEVEL 0
The Problem
You’ve been using Docker for a while now. You type docker run, and a container starts. You type docker build, and an image is created.
But what’s actually happening behind the scenes? What components are involved? When you run docker run nginx, what processes start, what talks to what, and how does it all come together?
Understanding Docker’s architecture helps you debug issues, optimize performance, and truly grasp what’s happening when you use Docker commands.
LEVEL 1
The Concept — The Restaurant Kitchen
The Concept
Imagine a restaurant with a very specific structure:
The Customer (you) sits at a table and orders food using a menu.
The Waiter (Docker CLI) takes your order and brings it to the kitchen. The waiter speaks “customer language”—you say “I’d like the salmon,” not “please grill a 6oz Atlantic salmon fillet at 400°F for 12 minutes.”
The Head Chef (Docker Daemon) receives the order from the waiter. The head chef knows how to actually prepare the food. They delegate tasks to specialists.
The Grill Master (containerd) handles the actual cooking on the grill. This is low-level work—managing heat, timing, flipping.
The Line Cook (runc) works under the grill master, handling the most basic tasks: “put this on the grill,” “take this off,” “check the temperature.”
The Pantry (Image Registry) stores all the ingredients. When the chef needs salmon, they get it from the pantry.
The Storage Room (Docker Volumes) holds long-term supplies that persist between service shifts.
This hierarchy exists so that:
- Customers don’t need to know how to cook
- The waiter doesn’t need to be a chef
- The chef can delegate low-level work
- Specialists handle their specific domains
Docker’s architecture follows the same pattern.
LEVEL 2
The Mechanics — Docker Components
The Mechanics
Docker CLI (docker command)
The CLI is what you interact with. When you run docker run, docker build, docker ps, you’re using the CLI.
The CLI is just a thin client. It packages your command into an API request and sends it to the Docker daemon.
Docker Daemon (dockerd)
The daemon is the background service that manages Docker objects: images, containers, networks, volumes.
It listens for API requests from the CLI (or other clients) and executes them.
The daemon itself doesn’t directly create containers—it delegates to lower-level tools.
containerd
containerd is a container runtime. It manages the container lifecycle: start, stop, pause, delete.
Docker daemon talks to containerd when you ask to run a container.
containerd is independent of Docker—it’s a CNCF (Cloud Native Computing Foundation) project used by Kubernetes and other tools.
runc
runc is the lowest-level component. It’s the tool that actually interacts with the Linux kernel to create namespaces, set up cgroups, and start the container process.
containerd calls runc to do the actual container creation.
runc is an implementation of the OCI (Open Container Initiative) runtime specification.
Image Registry (Docker Hub, etc.)
The registry stores Docker images. When you docker pull nginx, the daemon fetches the image from a registry.
Storage (Image and Container Layers)
Docker stores image layers and container writable layers on disk (usually /var/lib/docker/).
The storage driver (overlay2, etc.) manages these layers.
LEVEL 3
The Flow of a docker run Command
Let’s trace what happens when you run:
docker run -d -p 8080:80 nginx
Step 1: CLI parses the command
The Docker CLI parses your command:
-d= detached mode-p 8080:80= port mappingnginx= image name
Step 2: CLI sends API request to daemon
The CLI makes an HTTP POST request to the daemon:
POST /containers/create
{
"Image": "nginx",
"Detach": true,
"PortBindings": {"80/tcp": [{"HostPort": "8080"}]}
}
The daemon listens on a Unix socket (/var/run/docker.sock) or a TCP port.
Step 3: Daemon checks for the image
The daemon checks if the nginx image exists locally.
If not, it pulls the image from the registry:
- Connects to Docker Hub
- Downloads image layers
- Stores them in
/var/lib/docker/
Step 4: Daemon asks containerd to create the container
The daemon sends a request to containerd: “Create a container from this image with these settings.”
Step 5: containerd asks runc to start the container
containerd calls runc with the container configuration.
Step 6: runc creates the container
runc interacts with the Linux kernel:
- Creates namespaces (PID, network, mount, etc.)
- Sets up cgroups for resource limits
- Mounts the image filesystem layers
- Executes the container’s main process (
/usr/sbin/nginx)
Step 7: Container runs
The nginx process starts. It’s now running in isolated namespaces with cgroup limits.
The daemon monitors the container and reports its status.
Step 8: CLI reports success
The daemon responds to the CLI with the container ID.
The CLI prints it to your terminal:
a7c3f2b1e9d4...
LEVEL 4
Communication Pathways
How components talk:
You
↓ (docker run)
Docker CLI
↓ (REST API over Unix socket)
Docker Daemon (dockerd)
↓ (gRPC)
containerd
↓ (exec)
runc
↓ (Linux syscalls)
Linux Kernel
Docker Socket:
The Docker daemon listens on /var/run/docker.sock.
When you run docker ps, the CLI connects to this socket and makes an API call.
You can see this socket:
ls -la /var/run/docker.sock
srw-rw---- 1 root docker 0 Jan 31 10:00 /var/run/docker.sock
Anyone with access to this socket can control Docker (which is why the docker group has elevated privileges).
API:
The Docker API is RESTful. You can call it directly:
curl --unix-socket /var/run/docker.sock http://localhost/containers/json
This returns the same information as docker ps.
LEVEL 5
Why This Architecture?
Modularity:
By separating CLI, daemon, containerd, and runc, each component can be developed independently.
containerd can be used by other tools (like Kubernetes) without requiring the Docker daemon.
Standards compliance:
runc implements the OCI runtime spec, ensuring compatibility with other container tools.
Stability:
If the Docker CLI crashes, the daemon keeps running. If the daemon restarts, containers managed by containerd keep running.
Security:
The daemon runs as root, but the CLI doesn’t. The daemon API can be secured with TLS and authentication.