Architecture
Docker’s architecture splits into a CLI client, a daemon that does the work (dockerd), an image registry, and an OCI runtime (containerd + runc) that talks to the kernel. Knowing the pieces makes troubleshooting — networking, storage, permissions — much faster.
Daemon, containerd, runc, registries
EXAMPLE
# 1) High-level pieces
#
# docker CLI ──REST──> dockerd ──gRPC──> containerd ──> runc ──> kernel namespaces + cgroups
# │ │
# └─ talks to registries (Docker Hub, ECR, GHCR)
#
# • CLI (docker) — your client; sends commands to the daemon over a UNIX socket or TCP
# • dockerd — the daemon; image management, build, networks, volumes, swarm
# • containerd — container runtime; handles container lifecycle and pulls images
# • runc — low-level OCI runtime; actually spawns the container process
# • Kernel features — namespaces (isolation) + cgroups (limits) + capabilities + seccomp + AppArmor
# 2) Where the daemon lives
# Linux: /var/run/docker.sock (UNIX socket, root-owned)
# Docker Desktop (mac/win): VM running Linux + socket forwarded to host
# Rootless: ~/.docker/run/docker.sock
# Anyone with read+write on docker.sock = effectively root on the host.
# DO NOT add untrusted users to the 'docker' group.
# 3) Inspect what's running
docker version # client + server versions
docker info # runtime, kernel, storage driver, networks, plugins
docker context ls # which daemon are you connected to?
docker system df # disk usage by images / containers / volumes
docker system events # live event stream — pulls, starts, dies
# 4) Containers — running processes wrapped in namespaces
docker run --rm -it alpine sh
docker ps # running
docker ps -a # all (including exited)
docker logs <id> # stdout + stderr
docker exec -it <id> sh # shell into a running container
docker top <id> # processes inside (via the host PID namespace)
docker inspect <id> # full JSON — labels, mounts, network, env
# 5) Images — read-only layers + metadata
docker images
docker pull nginx:1.27-alpine
docker history nginx:1.27-alpine # layer breakdown
docker image rm nginx:1.27-alpine
docker image prune -f # remove dangling images
# 6) Image registries
# Public: docker.io (Docker Hub), ghcr.io, quay.io
# Private: AWS ECR, GCP Artifact Registry, Azure ACR, Harbor, Nexus
docker login ghcr.io
docker tag myapp:1.0.0 ghcr.io/me/myapp:1.0.0
docker push ghcr.io/me/myapp:1.0.0
docker pull ghcr.io/me/myapp:1.0.0
# 7) Storage drivers (Linux)
# overlay2 — the modern default; copy-on-write layered FS
# zfs / btrfs — alternatives
# vfs — slow fallback, used in some sandboxed envs
# Check what you're on:
docker info | grep 'Storage Driver'
# 8) Volumes vs bind mounts vs tmpfs
docker volume create app-data
docker run -v app-data:/var/lib/app myapp # named volume — managed by daemon
docker run -v /home/me/code:/app myapp # bind mount — host path
docker run --tmpfs /run myapp # tmpfs — RAM only
# Volumes are the right default for data you want to outlive the container.
# Bind mounts are great for development (live code reload).
# 9) Networks
docker network ls
# bridge — default; isolated subnet, NAT to host
# host — share the host network namespace (Linux only)
# none — no networking at all
# overlay — multi-host, used by swarm/k8s
docker network create app-net
docker run --network app-net --name db postgres
docker run --network app-net --name web -p 8080:8080 myapp
# Inside 'web', the host 'db' resolves to db's container IP — DNS is automatic on user-defined networks.
# 10) Port publishing
docker run -p 8080:80 nginx # host:container
docker run -p 127.0.0.1:8080:80 nginx # bind to localhost only
docker run -P nginx # publish all EXPOSE'd ports on random host ports
# 11) Resource limits
docker run --memory=512m --cpus=1.5 myapp
docker run --pids-limit=100 myapp
# Maps to kernel cgroups; the container can't exceed even under load.
# 12) Security knobs
docker run --read-only --tmpfs /tmp myapp # read-only root FS
docker run --user 1000:1000 myapp # drop root
docker run --cap-drop ALL --cap-add NET_BIND_SERVICE myapp # drop most capabilities
docker run --security-opt no-new-privileges myapp
docker run --security-opt seccomp=profile.json myapp
# 13) Daemon configuration
# /etc/docker/daemon.json
{
"log-driver": "json-file",
"log-opts": { "max-size": "10m", "max-file": "3" },
"default-address-pools": [{ "base": "172.30.0.0/16", "size": 24 }],
"insecure-registries": [],
"live-restore": true,
"userns-remap": "default"
}
# After editing: sudo systemctl restart docker
# 14) Rootless mode
# Run dockerd as your user — kernel UID mapping isolates from root.
# Trade-offs: no privileged ports < 1024 without setcap, slightly slower I/O, fewer features.
dockerd-rootless-setuptool.sh install
systemctl --user start docker
export DOCKER_HOST=unix:///run/user/$(id -u)/docker.sock
# 15) Compose — declarative multi-container apps
# docker-compose.yml
services:
web:
build: .
ports: ['8080:8080']
depends_on: [db]
environment:
DATABASE_URL: postgres://app:app@db:5432/app
db:
image: postgres:16-alpine
environment:
POSTGRES_PASSWORD: app
volumes: ['db-data:/var/lib/postgresql/data']
volumes:
db-data:
docker compose up -d
docker compose logs -f
docker compose down
# 16) Swarm and Kubernetes
# Swarm — Docker's own clustering; simple, fewer features
# Kubernetes — industry standard; uses containerd directly (not Docker)
# In K8s the 'docker' is just the build tool; runtime is containerd or CRI-O.
# 17) Plugins
docker plugin install vieux/sshfs
docker plugin ls
# Storage + network + log drivers are pluggable.
# 18) Cleanup commands
docker container prune -f
docker image prune -af # all unused images
docker volume prune -f
docker network prune -f
docker system prune -af --volumes # everything not currently in use
# Run periodically on CI runners and dev laptops — disk fills FAST.
# 19) Build internals (buildx + BuildKit)
DOCKER_BUILDKIT=1 docker build -t myapp .
docker buildx build --platform linux/amd64,linux/arm64 --push -t me/myapp:1.0.0 .
# buildx supports multi-platform builds, cache backends, secrets, named contexts.
# 20) Common bugs
# • 'permission denied: docker.sock' — your user isn't in the docker group (add carefully)
# • Container can't reach the host — use 'host.docker.internal' (Mac/Win) or the docker bridge gateway IP (Linux)
# • Disk full after a year — docker system prune you've been postponing
# • Network conflict with VPN — change default-address-pools
# • Build cache misses constantly — copy package.json before source; check .dockerignore
# • iptables rules left over after uninstall — install/uninstall via the official script
# • Slow file mounts on Mac — use Mutagen or named volumes, not bind mounts, for dev DB data
Why it matters
The CLI talks to the daemon; the daemon delegates to containerd and runc; runc speaks kernel. Most weird Docker bugs boil down to the daemon (permissions on the socket, disk-full storage), the kernel (network rules, namespace clashes), or BuildKit caching — docker info + docker system df + docker context ls are your first three diagnostics.
Tip: Tweak the snippet with Try it Yourself », then sit the quiz at the bottom of the page.
Example
Example
# Client (docker CLI) -> Daemon (dockerd) -> Engine -> Containers + Images + Networks + Volumes.Try it Yourself »
Discussion
Loading…