Skip to main content

Understanding Docker Architecture and Runtimes

Learn how the Docker CLI, dockerd, containerd, and runc work together to start containers, and why the Docker socket needs careful protection.

44 Terms

Overview and What You Will Learn

Docker is a stack of four cooperating programs, not one tool. The docker CLI sends REST requests to the dockerd daemon, dockerd delegates container lifecycle work to containerd, and containerd calls runc to create the isolated Linux process. Each layer has one job and a defined interface, which is why you can swap or debug them independently.

In this lab you will:

  • Name each layer and say what it is responsible for
  • Trace a single docker run through every layer on a real host
  • Configure /etc/docker/daemon.json with production-safe defaults
  • Understand the Docker socket and why exposing it is equivalent to giving away root
  • Explain why Docker Desktop behaves differently from Docker Engine on Linux
  • Explain why Kubernetes stopped using Docker as a runtime

Why This Matters in Production

Teams that run container fleets on EC2, such as those at Grab or Shopify, depend on the same daemon on every host. When containers will not start, builds hang, or a security review flags the Docker socket, the engineer who knows which layer owns the problem fixes it in minutes instead of guessing at docker run flags.

The architecture also explains a change that confused many teams. Kubernetes removed its built-in Docker support (dockershim) in version 1.24 and talks to containerd directly through the Container Runtime Interface. Images built with Docker still run unchanged because they follow the OCI standard.

Core Principles

The four layers form a chain. Each one only talks to its neighbour, and containerd and runc are standalone projects that work without Docker at all.

flowchart TD A([docker CLI]) -->|REST over socket| B[dockerd daemon] B -->|gRPC| C[containerd] C -->|starts| D[containerd-shim] D -->|OCI bundle| E[runc] E -->|creates| F(Container process) classDef user fill:#DBEAFE,color:#1E293B classDef tool fill:#326CE5,color:#FFFFFF classDef kernel fill:#10B981,color:#FFFFFF class A user class B,C,D,E tool class F kernel

The responsibilities break down like this:

  1. docker CLI: Parses your command and sends HTTP requests to the daemon. It holds no state.
  2. dockerd: Owns images, networks, volumes, builds, and the public Docker API.
  3. containerd: Pulls and unpacks images, manages filesystem snapshots, and supervises container processes.
  4. containerd-shim: A small per-container process that keeps the container alive and its output attached even if containerd restarts.
  5. runc: Reads the OCI bundle, creates the Linux namespaces and cgroups, starts the process, then exits.

Detailed Step-by-Step Practical Lab

How to Check That the Docker Daemon Is Healthy

The daemon is a systemd service that starts at boot and listens on a Unix socket. Checking its status, version, and active drivers is the first step in any Docker troubleshooting session.

Bash
sudo systemctl status docker
# Start it if it is stopped
sudo systemctl start docker
# One-line summary of the important runtime settings
docker info --format 'Server={{.ServerVersion}} Storage={{.Driver}} Cgroup={{.CgroupDriver}} LiveRestore={{.LiveRestoreEnabled}}'
# Server=27.3.1 Storage=overlay2 Cgroup=systemd LiveRestore=false
# Follow live daemon logs when builds fail or containers will not start
sudo journalctl -u docker.service -f
How to Configure daemon.json for Production

The daemon reads /etc/docker/daemon.json at startup, and a few settings should be on for every production host. Log rotation protects the disk, live-restore keeps containers running during daemon restarts, and explicit ulimits avoid surprising "too many open files" errors.

JSON
{
"log-driver": "local",
"log-opts": {
"max-size": "50m",
"max-file": "5"
},
"live-restore": true,
"default-ulimits": {
"nofile": {
"Name": "nofile",
"Hard": 64000,
"Soft": 64000
}
}
}
Bash
# Validate the file before restarting so a typo cannot stop the daemon
sudo dockerd --validate --config-file=/etc/docker/daemon.json
# live-restore can be applied with a reload (SIGHUP), no container restart needed
sudo systemctl reload docker
# Other changes, such as the log driver, need a full restart
sudo systemctl restart docker
Remember

live-restore keeps containers running while the daemon restarts or is upgraded, but it is not compatible with Swarm mode, and some major-version upgrades still require containers to be restarted.

How to Talk to the Docker API Through the Socket

The Docker socket is a Unix file at /var/run/docker.sock that exposes the daemon's REST API. The CLI is just one client; any tool that can speak HTTP over a Unix socket can do the same thing, which shows exactly what Docker is doing for you.

Bash
# Who can use the socket?
ls -la /var/run/docker.sock
# srw-rw---- 1 root docker 0 Oct 13 09:00 /var/run/docker.sock
# Ask the daemon for its version without the docker CLI
sudo curl --silent --unix-socket /var/run/docker.sock http://localhost/version
# List running containers through the raw API
sudo curl --silent --unix-socket /var/run/docker.sock http://localhost/containers/json

Anything that can write to this socket can start a privileged container that mounts the host filesystem. That makes socket access equal to root access on the host.

Bash
# NEVER do this on a shared or production host
docker run -v /var/run/docker.sock:/var/run/docker.sock some-image
# A process in that container can now launch privileged containers,
# mount the host root filesystem, and read secrets from every other container.

For CI jobs that need to build images, use a daemonless builder such as BuildKit in rootless mode, kaniko, or buildah instead of mounting the socket.

How to Trace a docker run Command Through Every Layer

A single docker run becomes two API calls and then a hand-off through containerd to runc. Following the sequence once makes every later debugging session easier.

sequenceDiagram participant CLI as docker CLI participant D as dockerd participant C as containerd participant R as runc CLI->>D: create, then start D->>C: pull and snapshot image C->>R: start with OCI bundle R-->>C: process started, runc exits C-->>D: container running D-->>CLI: print container ID

Now verify it on your own host:

Bash
# Start a container
docker run -d --name trace-test nginx:stable
# Ask Docker for the host PID of the container's main process
PID=$(docker inspect -f '{{.State.Pid}}' trace-test)
echo "Container main process PID on the host: $PID"
# Show the process chain: the shim is the parent, not dockerd and not runc
ps -o pid,ppid,cmd -p "$PID"
ps -o pid,cmd -p "$(ps -o ppid= -p "$PID")"
# PID CMD
# 2210 /usr/bin/containerd-shim-runc-v2 -namespace moby -id <container-id> ...
# Show the isolation runc set up: each namespace is a separate inode
sudo ls -l /proc/$PID/ns
# List the same container directly through containerd, bypassing dockerd
sudo ctr --namespace moby containers list

The container's main process shows up in the host process list with a real PID. A container is an ordinary Linux process with isolated namespaces and cgroup limits, not a virtual machine.

How to Tell Docker Desktop From Docker Engine

Docker Engine runs the daemon directly on a Linux host, while Docker Desktop on macOS and Windows runs the same engine inside a hidden Linux VM. This explains why bind-mounted files are slower on macOS and why localhost ports work through forwarding.

Bash
# List the endpoints your CLI can talk to
docker context ls
# NAME DESCRIPTION DOCKER ENDPOINT
# default Current DOCKER_HOST unix:///var/run/docker.sock
# desktop Docker Desktop unix:///Users/rahul/.docker/run/docker.sock
# Manage a remote host over SSH instead of exposing the API over TCP
docker context create mumbai-prod --docker "host=ssh://rahul@10.0.1.50"
docker --context mumbai-prod ps

Production Best Practices & Common Pitfalls

  • Enable live-restore: Daemon upgrades and crashes then no longer kill running containers.
  • Rotate logs by default: Set max-size and max-file in daemon.json so no single container can fill the disk.
  • Never expose the API over plain TCP: Port 2375 has no authentication. Use SSH contexts or TLS on 2376.
  • Treat the docker group as root: Any member can run docker run -v /:/host alpine chroot /host and get a root shell. Prefer rootless Docker or Podman on multi-tenant hosts.
  • Do not mount the socket into application containers: It hands the container control of the host.
  • Use named volumes for databases on macOS: Bind mounts cross the VM boundary and are much slower.
  • Validate before restarting: Run dockerd --validate so a JSON typo does not take down the daemon.
Tip

When Docker misbehaves, test containerd directly with sudo ctr --namespace moby containers list. If containerd is healthy, the problem is in dockerd; if not, check systemctl status containerd.

Common Mistake

Thinking containers are small virtual machines. They share the host kernel, so a kernel vulnerability or an over-privileged container affects everything on the host.

Security

Access to /var/run/docker.sock is equal to root on the host. Audit which users and containers can reach it on every production node.

Quick Reference & Troubleshooting Commands

Command Description
sudo systemctl status docker Check the daemon service
docker info Show server version, drivers, and runtimes
sudo journalctl -u docker.service -f Follow daemon logs
sudo dockerd --validate --config-file=/etc/docker/daemon.json Validate daemon config
sudo ctr --namespace moby containers list List containers through containerd
docker context ls Show configured Docker endpoints
Symptom Likely cause Fix
Cannot connect to the Docker daemon Daemon stopped sudo systemctl start docker
permission denied on the socket User not in docker group sudo usermod -aG docker $USER, then log in again
Daemon fails after editing config Invalid JSON in daemon.json Run dockerd --validate and fix the syntax
Containers will not start, daemon is up containerd unhealthy sudo systemctl restart containerd
Disk filling up on build hosts Unused images and build cache docker system df, then docker system prune

Resources

Explore More in Docker Fundamentals and Container Lifecycle

All 4 Topics

Frequently Asked Questions

Is Understanding Docker Architecture and Runtimes free to learn on DevOps Network?

Yes - this topic, like everything on DevOps Network, is 100% free with no paywall or sign-up gate.

What does the Understanding Docker Architecture and Runtimes topic cover?

Learn how the Docker CLI, dockerd, containerd, and runc work together to start containers, and why the Docker socket needs careful protection.