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 runthrough every layer on a real host - Configure
/etc/docker/daemon.jsonwith 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.
The responsibilities break down like this:
- docker CLI: Parses your command and sends HTTP requests to the daemon. It holds no state.
- dockerd: Owns images, networks, volumes, builds, and the public Docker API.
- containerd: Pulls and unpacks images, manages filesystem snapshots, and supervises container processes.
- containerd-shim: A small per-container process that keeps the container alive and its output attached even if
containerdrestarts. - 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.
sudo systemctl status docker # Start it if it is stoppedsudo systemctl start docker # One-line summary of the important runtime settingsdocker 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 startsudo journalctl -u docker.service -fHow 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.
{ "log-driver": "local", "log-opts": { "max-size": "50m", "max-file": "5" }, "live-restore": true, "default-ulimits": { "nofile": { "Name": "nofile", "Hard": 64000, "Soft": 64000 } }}# Validate the file before restarting so a typo cannot stop the daemonsudo dockerd --validate --config-file=/etc/docker/daemon.json # live-restore can be applied with a reload (SIGHUP), no container restart neededsudo systemctl reload docker # Other changes, such as the log driver, need a full restartsudo systemctl restart dockerRemember
live-restorekeeps 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.
# 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 CLIsudo curl --silent --unix-socket /var/run/docker.sock http://localhost/version # List running containers through the raw APIsudo curl --silent --unix-socket /var/run/docker.sock http://localhost/containers/jsonAnything 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.
# NEVER do this on a shared or production hostdocker 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.
Now verify it on your own host:
# Start a containerdocker run -d --name trace-test nginx:stable # Ask Docker for the host PID of the container's main processPID=$(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 runcps -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 inodesudo ls -l /proc/$PID/ns # List the same container directly through containerd, bypassing dockerdsudo ctr --namespace moby containers listThe 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.
# List the endpoints your CLI can talk todocker 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 TCPdocker context create mumbai-prod --docker "host=ssh://rahul@10.0.1.50"docker --context mumbai-prod psProduction Best Practices & Common Pitfalls
- Enable
live-restore: Daemon upgrades and crashes then no longer kill running containers. - Rotate logs by default: Set
max-sizeandmax-fileindaemon.jsonso 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
dockergroup as root: Any member can rundocker run -v /:/host alpine chroot /hostand 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 --validateso a JSON typo does not take down the daemon.
TipWhen Docker misbehaves, test containerd directly with
sudo ctr --namespace moby containers list. If containerd is healthy, the problem is indockerd; if not, checksystemctl status containerd.
Common MistakeThinking containers are small virtual machines. They share the host kernel, so a kernel vulnerability or an over-privileged container affects everything on the host.
SecurityAccess to
/var/run/docker.sockis 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 |