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.


In standard mode, dockerd runs as root. A vulnerability in the daemon, or an escaped container, gives the attacker root on the host. Rootless mode eliminates this entire threat class by running the daemon and all container processes entirely within an unprivileged user account.

Rootless Docker uses two components to emulate the privileges normally required:

ComponentRole
rootlesskitCreates a user namespace where the current user appears as root (UID 0) inside the namespace, mapped to their real UID outside. Acts as the “outer” process that launches dockerd.
slirp4netnsProvides networking without root — implements a user-space TCP/IP stack that tunnels traffic without requiring CAP_NET_ADMIN.

The result: dockerd, containerd, runc, and every container process all run as the invoking user on the host. Inside their namespaces they appear to have root. Outside, they have no elevated privileges.

Host OS Rootless user namespace
─────────────────────── ───────────────────────
uid=1000 (alice) ───▶ uid=0 (root inside namespace)
├─ dockerd (as alice on host)
├─ containerd (as alice on host)
└─ container process (as alice on host)
Terminal window
# Install rootless Docker (must be run as a non-root user)
# Requires: uidmap package, kernel user namespace support
dockerd-rootless-setuptool.sh install
# The daemon socket moves to the user's runtime directory
export DOCKER_HOST=unix://$XDG_RUNTIME_DIR/docker.sock
docker ps
# Start the rootless daemon as a systemd user service
systemctl --user start docker
systemctl --user enable docker # auto-start on login
# Verify it is running rootless
docker info | grep "rootless"
Standard DockerRootless Docker
Daemon privilegeRoot (uid=0)Non-root (invoking user)
Container isolationKernel namespacesKernel namespaces + user namespaces
Daemon compromise impactFull host rootLimited to invoking user’s permissions
PerformanceFull nativeSmall overhead (~3-5%) from user-namespace UID mapping
Privileged ports (<1024)SupportedNot supported by default
Overlay networksSupportedNot supported (use bridge networks)
AppArmor / SELinuxFull supportLimited (profile loading requires root)
--privileged containersWorksNot supported
Best forServers, CI/CD runnersShared dev machines, security-sensitive environments

The core security argument is daemon compromise isolation:

  • In standard mode: dockerd exploited → attacker has root on the host
  • In rootless mode: dockerd exploited → attacker has the invoking user’s permissions only (no /etc/shadow, no sudo, no kernel module loading)

This also protects against container escape — if a container process breaks out of its namespace, it runs as an unprivileged user on the host rather than as root.

These are two different mechanisms that are often confused:

userns-remapRootless mode
What runs as rootdockerd still runs as rootdockerd runs as the invoking user
What is remappedContainer UIDs → unprivileged host UIDsEverything — daemon + containers
Configurationdaemon.json option, applied by a root-owned daemonPer-user installation, no daemon.json
Use caseShared server where daemon must be rootFully unprivileged development/CI environments

userns-remap is “root daemon, unprivileged containers.” Rootless mode is “unprivileged daemon + unprivileged containers.” They can coexist but target different environments.

  • Shared developer machines — multiple users each run their own daemon instance; no shared root process
  • Security-sensitive environments — minimizes the blast radius of daemon vulnerabilities
  • CI runners on shared infrastructure — Kubernetes pods running Docker-in-Docker without privileged mode
  • Avoid it when: you need overlay networks, privileged containers, AppArmor profile loading, or full-speed I/O on storage drivers

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.