Overview and What You Will Learn
When you type docker run nginx and a container appears in seconds, it looks like magic. It is not. That single command travels through four distinct software layers before a container process starts on your machine. In this guide you will learn exactly what those four layers are, what each one does, and why Docker was designed this way. You will also understand the Docker socket — the most important and most dangerous file on any Docker host — and what happens if it is exposed incorrectly.
By the end of this guide you will be able to explain how Docker works at the architecture level, understand why Docker Desktop behaves differently from Docker Engine on Linux, and know how to configure the Docker daemon for production use.
Why This Matters in Production
At Swiggy, every EC2 instance running containers has a Docker daemon running on it. When containers fail to start, when builds mysteriously hang, or when a security team raises a concern about the Docker socket — engineers who understand the architecture can diagnose and fix these problems in minutes. Engineers who only know docker run commands are stuck waiting for someone else to help.
Understanding the Docker daemon also explains why Kubernetes deprecated Docker in version 1.24 and switched to containerd directly — a change that confused thousands of engineers who did not understand the architecture underneath.
Core Principles
Docker's architecture has four layers. Each layer has a single responsibility and communicates with the next layer through a well-defined interface.
+------------------------------------------+| docker CLI || (the command you type in your terminal) || Sends REST API calls to the daemon |+------------------------------------------+ | | REST API over Unix socket | /var/run/docker.sock v+------------------------------------------+| dockerd (Docker Daemon) || The background service managing objects || Handles: images, containers, networks, || volumes, builds, and the API |+------------------------------------------+ | | gRPC calls v+------------------------------------------+| containerd || Container lifecycle management || Pulls images, manages snapshots, || starts and stops container processes |+------------------------------------------+ | | OCI runtime spec v+------------------------------------------+| runc || The actual container runner || Creates Linux namespaces and cgroups || Starts the container process || Exits immediately after container starts |+------------------------------------------+This separation of concerns is intentional. containerd and runc are independent projects that can be used without Docker. Kubernetes uses containerd directly — skipping dockerd entirely — which is why Docker was deprecated as a Kubernetes runtime.
Detailed Step-by-Step Practical Lab
Milestone 1: Understanding the Docker Daemon
The Docker daemon (dockerd) is a background service that starts when your system boots and runs continuously. It listens for API requests and manages all Docker objects on the host.
# Check if the Docker daemon is runningsudo systemctl status docker # Output you want to see:# docker.service - Docker Application Container Engine# Loaded: loaded (/lib/systemd/system/docker.service; enabled)# Active: active (running) since Mon 2024-01-15 09:00:00 IST # If it is not running, start itsudo systemctl start docker # Check the daemon version and runtime infodocker info# Output includes:# Server Version: 24.0.7# Storage Driver: overlay2# Logging Driver: json-file# Cgroup Driver: systemd# Runtimes: runc# containerd version: v1.7.8 # View daemon logs to diagnose startup problemssudo journalctl -u docker.service -f# Follow live daemon logs — useful when builds fail or containers won't startThe daemon configuration file lives at /etc/docker/daemon.json. This is where you configure production settings:
{ "log-driver": "json-file", "log-opts": { "max-size": "100m", "max-file": "3" }, "storage-driver": "overlay2", "default-ulimits": { "nofile": { "Name": "nofile", "Hard": 64000, "Soft": 64000 } }, "live-restore": true}The live-restore: true setting is critical for production — it tells the daemon to keep containers running if the daemon itself is restarted or upgraded. Without it, every daemon restart kills all running containers.
# After editing daemon.json, reload without stopping containerssudo systemctl reload docker # Or do a full restart (kills containers unless live-restore is set)sudo systemctl restart dockerMilestone 2: Understanding the Docker Socket
The Docker socket is a Unix socket file at /var/run/docker.sock. The Docker CLI sends REST API calls to the daemon through this socket. Whoever can read and write to this socket has full control over Docker — which means full control over the host.
# Check the socket file and its permissionsls -la /var/run/docker.sock# srw-rw---- 1 root docker 0 Jan 15 09:00 /var/run/docker.sock# Owner: root, Group: docker# Only root and members of the docker group can use Docker # See what API calls your docker commands actually make# Run this in one terminal:sudo socat -v UNIX-LISTEN:/tmp/docker-debug.sock,fork \ UNIX-CONNECT:/var/run/docker.sock & # Then in another terminal:DOCKER_HOST=unix:///tmp/docker-debug.sock docker ps# Watch the raw API calls appear in the first terminal# You will see: GET /v1.43/containers/json HTTP/1.1The Docker socket is the most common Docker security vulnerability. Mounting it inside a container gives that container root access to the host:
# NEVER DO THIS IN PRODUCTIONdocker run -v /var/run/docker.sock:/var/run/docker.sock \ some-image # Any process inside that container can now:# - Run new privileged containers# - Mount the host filesystem# - Read all secrets from other containers# - Escalate to full root on the host # If you need Docker inside Docker for CI, use:# - Docker-in-Docker (dind) with --privileged flag in a controlled environment# - kaniko for building images without Docker socket access# - buildah as a Docker socket free alternativeMilestone 3: Understanding containerd and runc
containerd was split out of Docker in 2016 and donated to the CNCF. It handles the actual container lifecycle — pulling images, managing filesystem snapshots, and starting container processes. The Docker daemon talks to containerd through a gRPC socket.
# containerd socket (separate from Docker socket)ls -la /run/containerd/containerd.sock # Check containerd statussudo systemctl status containerd # List containers directly through containerd (bypasses Docker)# Useful for debugging when Docker daemon is having issuessudo ctr containers listsudo ctr tasks list # containerd uses namespaces — Docker containers are in the 'moby' namespacesudo ctr --namespace moby containers listrunc is even simpler — it reads an OCI bundle (a filesystem and a config.json file) and creates the Linux namespaces and cgroups that isolate the container. It then executes the container process and exits immediately. runc does not run continuously — it is a one-shot binary that sets up isolation and hands off.
# See runc in action during a container start# In one terminal, watch for runc processes:watch -n 0.1 'ps aux | grep runc' # In another terminal, start a container:docker run -d nginx # You will briefly see:# root /usr/bin/runc --root /var/run/docker/runtime-runc/moby --log ...# It appears for less than a second — runc starts the container and exitsMilestone 4: Tracing a docker run Command End-to-End
Here is the complete sequence of what happens when you run docker run nginx:
Step 1: docker CLI You type: docker run nginx CLI parses: image=nginx, no command specified, no flags CLI calls: POST /v1.43/containers/create to Docker daemon Step 2: Docker daemon receives the request Checks: does nginx image exist locally? If not: pulls from registry (calls containerd to pull) Creates: container configuration object Calls: containerd to start the container Step 3: containerd receives the request Prepares: container filesystem snapshot using OverlayFS Creates: OCI bundle (rootfs + config.json) Calls: runc to start the container process Step 4: runc executes Creates: new PID namespace (container gets PID 1) Creates: new network namespace (container gets its own network) Creates: new mount namespace (container gets its own filesystem view) Creates: new UTS namespace (container gets its own hostname) Sets up: cgroups for CPU and memory limits Executes: the container's entrypoint process (nginx) Exits: runc process exits, container keeps running Step 5: containerd monitors the container process Watches: container process health Reports: container status back to Docker daemon Step 6: Docker daemon updates its state Records: container is now running Returns: container ID to CLI CLI prints: container ID to your terminal# Verify this sequence yourselfdocker run --name=trace-test -d nginx # Check which process is actually runningdocker top trace-test# UID PID PPID CMD# root 12345 1 nginx: master process nginx -g daemon off # That PID is on the HOST — containers are just processes with isolationps aux | grep nginx# You will see the same PID — containers are not separate machinesMilestone 5: Docker Desktop vs Docker Engine
Docker Engine is the native Linux installation — the daemon runs directly on the Linux host. Docker Desktop (on macOS and Windows) runs a lightweight Linux VM and runs Docker Engine inside it. This explains several behaviours that confuse engineers:
# On Linux — Docker runs natively# /var/run/docker.sock is on the actual host filesystem # On macOS — Docker Desktop runs a hidden Linux VM# /var/run/docker.sock is actually inside the VM# Port bindings (-p 8080:80) work through VM port forwarding# Volume mounts (-v /home/user/code:/app) go through VM filesystem sharing# This is why volume performance is slower on macOS than Linux # Check which context you are usingdocker context ls# NAME DESCRIPTION DOCKER ENDPOINT# default Current DOCKER_HOST unix:///var/run/docker.sock# desktop Docker Desktop ... # Switch contexts if you have multiple Docker environmentsdocker context use desktopCommon Mistakes
| Mistake | Why It Is Dangerous | Fix |
|---|---|---|
Mounting /var/run/docker.sock into containers |
Full host root access from inside container | Use kaniko or buildah for CI builds instead |
Running daemon without live-restore: true |
All containers die when daemon restarts | Add "live-restore": true to daemon.json |
| Not setting log rotation in daemon.json | Logs fill the disk over days/weeks | Always set max-size and max-file |
| Adding users to the docker group without thinking | Docker group = root equivalent access | Only add users who genuinely need Docker access |
| Using Docker Desktop file mounts for database volumes | Slow I/O causes database performance issues | Use named Docker volumes for databases on macOS |
Troubleshooting Reference
| Problem | Symptom | Diagnostic Command | Fix |
|---|---|---|---|
| Daemon not running | Cannot connect to Docker daemon |
systemctl status docker |
systemctl start docker |
| Permission denied | Got permission denied while trying to connect |
groups |
Add user to docker group: usermod -aG docker $USER |
| Daemon config invalid | Daemon fails to start after editing daemon.json | dockerd --validate |
Fix JSON syntax error in daemon.json |
| Socket not found | docker: /var/run/docker.sock: no such file |
ls /var/run/docker.sock |
Restart Docker daemon |
| containerd issues | Containers won't start despite daemon running | systemctl status containerd |
systemctl restart containerd |
| High disk usage | Disk filling up on build machines | docker system df |
docker system prune -f |
PLACEMENT PRO TIP**Tip:** On production Linux hosts, always add `"live-restore": true` to `/etc/docker/daemon.json` before running any workloads. Without it, every Docker daemon upgrade or crash kills all running containers. With it, containers keep running while the daemon restarts — zero downtime for daemon maintenance.
REMEMBER THIS**Remember:** The Docker socket at `/var/run/docker.sock` is functionally equivalent to root access on the host. Treat it with the same care as an SSH private key. Never mount it into untrusted containers, never expose it over TCP without TLS, and audit which processes have access to it on every production host.
COMMON MISTAKE / WARNING**Common Mistake:** Thinking that containers are separate virtual machines. Containers are Linux processes running on the host kernel with isolation provided by namespaces and cgroups. When you run `ps aux` on the host, you can see all container processes with their real PIDs. This is why a compromised container can be a serious security risk — it is running on the same kernel as everything else on the host.
COMMON MISTAKE / WARNING**Security:** The members of the `docker` group on a Linux host have effective root access. Running `docker run -v /:/host alpine chroot /host` gives any docker group member a root shell on the host filesystem. Never add untrusted users to the docker group. On multi-tenant servers, use rootless Docker or Podman instead, which do not require a privileged daemon.