The Docker Architecture (Daemon, Client, Registry)

Docker feels like one command-line tool, but it is really a small system of cooperating parts. The Docker client sends requests, the Docker daemon does the privileged work, and registries store the images that machines pull and run. Once you understand this architecture, commands such as docker run, docker pull, and docker build become much easier to reason about and troubleshoot.

Overview: How Docker Architecture Works

The main pieces are the Docker client, the Docker daemon, the local image and container storage, the container runtime, and a registry. The client is the docker command you type. The daemon, usually called dockerd, is a long-running service that listens for Docker API requests and manages images, containers, networks, volumes, and builds. A registry is a server that stores image manifests and layer blobs, such as Docker Hub, GitHub Container Registry, Amazon ECR, or a private registry inside a company.

The client and daemon can run on the same machine, which is the normal setup on a developer workstation. They can also be separated: a local client can talk to a remote Docker Engine if it has permission and a configured Docker context. On Linux, the local client commonly talks to the daemon through a Unix socket at /var/run/docker.sock. On Windows, Docker can use a named pipe. Docker Desktop on macOS and Windows adds a managed Linux virtual machine because Linux containers need Linux kernel features. In that setup, your CLI runs on the host operating system, but the daemon and containers run inside the Desktop VM.

The daemon is the component that needs broad system access. Creating a container means creating namespaces, configuring cgroups, setting up virtual networking, mounting layered filesystems, and starting processes. A normal command-line program should not do all of that directly. Instead, the client serializes your command into an API request, the daemon checks local state, and the daemon coordinates the lower-level runtime components. Modern Docker uses containerd and runc under the hood for much of the container lifecycle, but most users interact with the higher-level Docker API through the CLI.

Images are stored as read-only layers. Each layer is a filesystem change set, and an image manifest describes which layers belong to an image plus configuration such as default command, environment, exposed ports, and target platform. A container is created from an image by mounting those read-only layers together through a union filesystem and adding a thin writable layer for that specific container. Deleting a container removes its writable layer and metadata, but it does not delete the image. Deleting an image removes local image data only when no container still depends on it.

A registry does not run your containers. It stores and serves image content. When you pull nginx:1.27-alpine, the daemon asks the registry for the tag, receives a manifest, chooses the right platform variant if the image is multi-platform, downloads missing layer blobs, verifies content digests, and records the image locally. Tags such as 1.27-alpine are human-friendly names that point to image content. Digests are content-addressed identifiers; if the content changes, the digest changes. For production, tags are convenient, but deployment pipelines often promote or pin tested images by digest for stronger reproducibility.

Syntax

The general Docker command shape is:

docker [GLOBAL_OPTIONS] COMMAND [COMMAND_OPTIONS] [ARGUMENTS]
Part Meaning
docker The client program. It parses arguments and sends a Docker API request.
GLOBAL_OPTIONS Options that affect the client connection, such as --context, --host, --config, or output behavior.
COMMAND The operation to request, such as version, pull, run, build, image, container, or compose.
COMMAND_OPTIONS Options for that operation, such as --name, -p, -d, --rm, or --platform.
ARGUMENTS Images, container names, paths, commands, or other values used by the operation.

Important architecture-related commands include:

Command What it tells you
docker version Shows both client and server versions, proving there are two sides to the Docker API connection.
docker context ls Lists configured Docker endpoints, useful when the CLI may be talking to Desktop, Linux Engine, or a remote host.
docker pull IMAGE:TAG Asks the daemon to fetch an image manifest and missing layers from a registry.
docker image inspect IMAGE:TAG Shows local image metadata, including repo digests, configuration, architecture, and layers.
docker run IMAGE:TAG COMMAND Creates a container from local image data, pulling first if needed, then starts its main process.

Examples

Example 1: See the Client and Server

docker version

Output:

Client: Docker Engine - Community
 Version:           27.1.1
Server: Docker Engine - Community
 Engine:
  Version:          27.1.1

The exact version numbers will vary, but the shape matters: Docker reports a client and a server. The client is the CLI process that exits when the command finishes. The server is the daemon that keeps running and owns the real Docker state. If the daemon is stopped, the client can still start, but commands that need the server fail because there is no API endpoint to answer.

Example 2: Pull an Image from a Registry

docker pull nginx:1.27-alpine
docker image inspect --format '{{json .RepoDigests}}' nginx:1.27-alpine

Output:

1.27-alpine: Pulling from library/nginx
Digest: sha256:3f6fb2fb8c2c5e2a1f0c7f1bb4d38f0d5ddc5c4b0d9e7a3d6c1a2b3c4d5e6f70
Status: Downloaded newer image for nginx:1.27-alpine
["nginx@sha256:3f6fb2fb8c2c5e2a1f0c7f1bb4d38f0d5ddc5c4b0d9e7a3d6c1a2b3c4d5e6f70"]

The first command asks the daemon to pull from the default registry namespace, where nginx means docker.io/library/nginx. The registry returns a manifest and the daemon downloads any layer blobs that are not already present locally. The inspect command shows the repo digest Docker recorded. The sample digest is representative; real digests depend on the current image content and platform.

Example 3: Run a Container Through the Daemon

docker run --rm --name architecture-demo alpine:3.20 sh -c "hostname && cat /etc/os-release | head -n 3"

Output:

b7a8f1c2d3e4
NAME="Alpine Linux"
ID=alpine
VERSION_ID=3.20.3

The CLI sends a create-and-start request to the daemon. The daemon ensures alpine:3.20 exists locally, creates an isolated container filesystem and process environment, starts sh, streams output back to the client, and removes the container when it exits because --rm was set. The hostname is container-specific, while the operating system files come from the Alpine image layers.

Example 4: Build an Image, Then Run It

FROM alpine:3.20
LABEL org.opencontainers.image.title="architecture-demo"
RUN adduser -D appuser
USER appuser
CMD ["sh", "-c", "echo client asked daemon to run $(whoami) in $(hostname)"]

This Dockerfile uses a pinned base image tag instead of alpine:latest. The tag latest is only a mutable name, not a promise of stability. Each instruction creates metadata or a layer in the image the daemon builds.

docker build -t architecture-demo:1.0 .
docker run --rm architecture-demo:1.0

Output:

[+] Building 1.4s (6/6) FINISHED
Successfully tagged architecture-demo:1.0
client asked daemon to run appuser in 2f8c9c0a1b5d

During the build, the client sends the build context to the daemon or BuildKit builder. The builder reads the Dockerfile, pulls the base image if needed, executes build steps in temporary containers, and records the resulting image in local storage. The later docker run command creates a new container from that image; it does not rerun the build.

How It Works Step by Step

  1. You type a command such as docker run nginx:1.27-alpine.
  2. The Docker client reads configuration, resolves the active context, and connects to the daemon over the configured API endpoint.
  3. The daemon receives the request and checks local image metadata to see whether the requested image is already available.
  4. If the image is missing, the daemon contacts the registry, resolves the tag to a manifest, selects the platform entry, and downloads missing layer blobs.
  5. The daemon verifies content digests and stores layers in Docker’s local image store.
  6. For a container, Docker mounts the image’s read-only layers together and creates a thin writable layer for that container.
  7. Docker prepares networking, environment variables, mounts, resource limits, and security settings such as capabilities and seccomp profile.
  8. The daemon asks the container runtime to start the configured process in isolated namespaces with cgroup controls.
  9. The client attaches to logs or streams output when requested. The daemon keeps tracking the container even after the original CLI command exits.

This separation explains many Docker behaviors. Restarting a terminal does not stop a detached container because the daemon owns the container. Switching Docker contexts changes which daemon receives your commands. Pulling an image on one machine does not make it available on another machine unless both pull it from a registry or you transfer it explicitly.

Common Mistakes

Confusing the Client with the Daemon

docker ps

If this reports that it cannot connect to the Docker daemon, reinstalling the CLI is usually not the fix. The client exists, but the server side is unavailable, stopped, blocked by permissions, or pointed at the wrong context. Check Docker Desktop, the Linux docker service, or docker context ls before changing application code.

Treating a Registry Like a Running Server Host

A registry stores image manifests and layer blobs. It does not know whether your application is healthy, how many containers are running, or which ports are published. Running containers belong to a daemon, a Compose project, or an orchestrator. Push images to a registry; run containers on Docker Engine, Docker Desktop, or a platform built on container runtimes.

Using latest as if It Means Newest and Safe

docker pull nginx:latest

This is a reproducibility mistake for real deployments. The latest tag is just a mutable tag and can point to different content later. Prefer a specific tag, and pin by digest when exact content matters:

docker pull nginx:1.27-alpine

Sending Too Much Build Context

docker build -t architecture-demo:1.0 .

This command is fine only if the current directory is clean. The client sends the build context to the builder, so a missing .dockerignore can send node_modules, logs, test output, credentials, or large archives. Add a .dockerignore for real projects so the daemon receives only the files needed to build the image.

Best Practices

  • Think in terms of API boundaries: the CLI requests work, the daemon performs it, and the registry stores image content.
  • Use docker version when diagnosing installation issues because it shows whether both client and server are reachable.
  • Use docker context ls before running destructive commands if you work with multiple local or remote Docker endpoints.
  • Use specific image tags instead of latest; for production promotion, consider pinning tested images by digest.
  • Keep important runtime state in volumes or external services, not in a container writable layer.
  • Keep build contexts small with .dockerignore so builds are faster and private files are not accidentally sent to the builder.
  • Remember that Docker Desktop on macOS and Windows runs Linux containers inside a managed VM, which can affect paths, file sharing, and kernel details.
  • Do not mount /var/run/docker.sock into containers casually. Access to that socket is effectively control over the Docker daemon and often the host.

Practice Exercises

  1. Run docker version and identify which lines describe the client and which describe the server. Expected end state: you can explain why both are shown.
  2. Pull alpine:3.20, inspect its repo digests, and explain the difference between the tag and the digest. Hint: use docker image inspect --format.
  3. Create a tiny Dockerfile from alpine:3.20, build it as architecture-practice:1.0, and run it. Expected end state: you can describe which work was done by the client, daemon, builder, local image store, and runtime.

Summary

  • The Docker client is the command-line program; the Docker daemon is the long-running service that manages Docker objects.
  • The client talks to the daemon through the Docker API, usually over a local socket or a configured remote context.
  • Registries store image manifests and layer blobs; they do not run containers.
  • Images are read-only layered templates, while containers add a thin writable layer and a running process.
  • docker run may involve the client, daemon, registry, local image store, networking, filesystem mounts, and the runtime.
  • Understanding the architecture makes errors about daemon connectivity, missing images, contexts, and mutable tags much easier to debug.