iwantcoding.com
🔥 Daily 👥 Rooms 🏆 Top Log in Sign up

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…