Skip to content

Docker Architecture

Docker uses a client-server architecture: the Docker CLI (client) converts commands into API requests and sends them to the Docker daemon (dockerd), which coordinates the actual work through a chain of specialized runtimes. The client and daemon can be on the same host or connected over a network.

Docker Architecture

No single component creates a container alone. Responsibility is delegated down a chain:

Docker CLI → dockerd → containerd → shim → runc → container process
ComponentRole
Docker CLITranslates user commands into Docker API requests
dockerdReceives API calls, coordinates the rest — no longer creates containers directly
containerdHigh-level runtime — manages container lifecycle, converts images to OCI bundles
shimPer-container process that outlives runc, keeps streams open, enables daemonless containers
runcLow-level OCI runtime — calls the kernel to build namespaces and cgroups, then exits

For the full deep dive on each component — including history, internals, the step-by-step startup sequence, and Linux binaries — see Docker Engine.


The separation of client and daemon is a deliberate design choice with real consequences:

  • The daemon runs as root. dockerd needs elevated privileges to create namespaces, manage cgroups, and manipulate network interfaces. The CLI does not need to run as root — it just sends API requests.
  • Remote management is a first-class feature. The daemon listens on a socket; any authorized client anywhere on the network can manage it. CI runners, Portainer, VS Code extensions — they all speak the same Docker API.
  • Crash isolation. Because the daemon and the container processes are decoupled via shims, a dockerd crash or restart does not kill running containers. The shim keeps the container alive and reconnects the daemon when it comes back.

A context is a named configuration that points the Docker CLI at a specific Docker daemon. This lets you manage multiple Docker environments (local, remote server, Docker Desktop, cloud) from the same terminal without changing environment variables.

Terminal window
# List all configured contexts — the active one is marked with *
docker context ls
# Switch to a different context
docker context use my-remote-server
# Run any Docker command against the active context
docker ps
# Create a new context pointing at a remote daemon over SSH
docker context create my-remote-server \
--docker "host=ssh://user@remote-host"
# Create a context for a TLS-secured remote daemon
docker context create prod \
--docker "host=tcp://prod-host:2376,cert=~/.docker/certs/prod"
# Inspect a context
docker context inspect my-remote-server
# Remove a context
docker context rm my-remote-server
Use CaseContext setup
Local developmentDefault context (Docker Desktop or Engine)
Remote production serverSSH context: host=ssh://deploy@prod-host
Multiple cloud environmentsSeparate context per region/account
CI/CD runnerSet DOCKER_CONTEXT environment variable
Terminal window
# Override context for a single command without switching
docker --context my-remote-server ps
# Or use the DOCKER_CONTEXT environment variable
DOCKER_CONTEXT=my-remote-server docker ps

A key architectural property of Docker’s shim-based design: running containers are not children of the daemon. The shim process (containerd-shim-runc-v2) becomes the parent of each container’s PID 1 after runc exits.

This means:

  • dockerd can be stopped, updated, and restarted without touching running containers
  • containerd can be upgraded independently of the containers it manages
  • The container survives daemon crashes — the shim holds the I/O streams and re-reports status when the daemon reconnects

This design is what makes zero-downtime Docker daemon upgrades possible on production hosts.


Docker can be run entirely without root — both the daemon and all container processes run as a non-privileged user. This is called rootless mode.

Terminal window
# Install rootless Docker (run as a non-root user)
dockerd-rootless-setuptool.sh install
# The daemon socket is now in the user's runtime dir
export DOCKER_HOST=unix://$XDG_RUNTIME_DIR/docker.sock
docker ps
Standard DockerRootless Docker
Daemon privilegeRootNon-root user
Container isolationKernel namespacesKernel namespaces + user namespaces
PerformanceFullSlight overhead from user namespace mapping
Feature parityFullSome features limited (e.g., binding privileged ports < 1024)
Best forServers, CI runnersShared developer machines, security-sensitive environments

Docker Desktop is the developer-friendly distribution for macOS and Windows. It wraps Docker Engine in a Linux VM (via Apple Hypervisor / WSL2) and adds:

Docker Desktop architecture
ComponentPurpose
CLIStandard docker commands
GUIManage images, containers, resource limits (CPU/memory/disk)
Credential HelperSecure credential storage for private registries
ExtensionsThird-party tools (e.g., Dive, Portainer, Trivy)
Optional KubernetesSingle-node K8s cluster alongside Docker
Docker EngineDocker Desktop
OSLinux onlymacOS, Windows, Linux
LicenseFree (Apache 2.0)Free for personal use; paid for large orgs
KubernetesNot includedOptional, single-node
GUINoneIncluded
VM overheadNoneYes (Linux VM layer)
  • On Linux, prefer Docker Engine for servers and CI runners — no VM overhead, full performance.
  • On macOS/Windows, Docker Desktop is the practical choice. The VM boundary means bind mounts have some performance overhead compared to native Linux.

Client and daemon communicate over a local socket by default:

  • Linux: /var/run/docker.sock
  • Windows: \\.\pipe\docker_engine
Terminal window
# Connect CLI to a remote Docker daemon
docker -H remote-host:2375 ps
export DOCKER_HOST=tcp://remote-host:2375
# The CLI is just a REST API wrapper — you can call it directly:
curl --unix-socket /var/run/docker.sock http://localhost/containers/json

Any tool that speaks the Docker REST API — Portainer, VS Code Docker extension, CI runners — can manage Docker. The CLI is not special.