beginner~4h

Docker Architecture, Engine, Daemon, Client & Registry

Docker is a client-server platform that builds, ships, and runs applications inside isolated Linux (or Windows) processes called containers, using OS-level virtualization instead of full hardware virt

Learning objectives

  • Name the four cooperating pieces of the Docker platform and explain what each one is actually responsible for.
  • Explain precisely how the Docker CLI talks to the daemon, and why that matters for securing a production host.
  • Trace what happens, piece by piece, when you run `docker run nginx` on a fresh machine.

📖 Story

Imagine a restaurant with four distinct roles. A waiter takes your order and relays it to the kitchen — they never cook anything themselves. The head chef runs the kitchen, decides how orders get fulfilled, and coordinates everyone in it. The line cooks are the ones with their hands actually on the food, doing the physical work of chopping, plating, and cooking. And a supplier's warehouse is where all the raw ingredients come from, delivered fresh whenever the kitchen needs something it doesn't have on hand.

Docker is built from exactly these four roles, and it helps enormously to keep them separate in your head, because most Docker confusion comes from treating "Docker" as one single thing when it's really four cooperating pieces.

The four pieces, precisely

  • Docker Client — the waiter. It's the docker command you type (docker run, docker build); it never does any actual work itself, it just packages your request and sends it onward.
  • Docker Daemon (dockerd) — the head chef. This is the long-running background process that receives requests from the client and coordinates everything: pulling images, creating containers, managing networks and volumes.
  • containerd + runc — the line cooks. containerd manages the container lifecycle day-to-day (starting, stopping, monitoring), and delegates the truly low-level work — creating the Linux namespaces and cgroups that actually isolate a process — to runc, a much smaller, more focused tool.
  • Registry — the supplier's warehouse. This is where images are stored and pulled from: Docker Hub, GitHub Container Registry (GHCR), Amazon ECR, or a private registry like Harbor or Nexus that a company runs internally.

"Docker Engine" — the umbrella term

You'll often hear "Docker Engine" used as a catch-all name for dockerd + containerd + runc + the REST API running on the host, together. The CLI you type is genuinely just a thin client talking to this engine over a Unix socket (/var/run/docker.sock) or, less commonly, a TCP socket — it holds no state of its own at all.

Why the client/daemon split matters immediately

Because the CLI is just a client, anyone (or anything) with access to that same socket can issue commands to the daemon — this single fact is the root of an entire category of security considerations covered later in this chapter and in the Container Security chapter, and it's worth internalizing now: whoever can talk to the Docker socket effectively has root on the host.

The full request path

When you type docker run nginx, here's what actually happens, piece by piece:

  1. The CLI parses your command and makes an HTTP request to the daemon's REST API, over the Unix socket.
  2. The daemon checks whether the nginx image already exists locally. If not, it contacts the configured registry, authenticates if needed, and pulls the image's layers.
  3. The daemon hands off to containerd, which prepares the container's filesystem (a new, thin writable layer on top of the pulled image's read-only layers — covered in depth in the next chapter) and constructs an OCI (Open Container Initiative) runtime specification describing exactly what the container should look like.
  4. containerd invokes runc, which does the actual, one-time work of creating Linux namespaces (PID, network, mount, UTS, IPC — each hiding a different kind of system resource from the container) and cgroups (which cap how much CPU/memory/IO the container's processes can consume), then starts the container's main process inside that isolated environment.
  5. Once the process is running, runc's job is done — it exits — and containerd takes over ongoing supervision (restart policies, exposing logs, reporting status back up to the daemon).

Why namespaces and cgroups, specifically

A namespace controls what a process can see — a container in its own PID namespace sees itself as PID 1, with no visibility into any other process on the host, even though from the host's perspective it's just an ordinary process among many. A cgroup controls what a process can use — how much CPU time, how much memory, how much disk I/O — independent of what it can see. Together, these two Linux kernel features are the entire technical foundation containers are built on; Docker doesn't invent isolation, it packages kernel features that already existed into a convenient, portable interface.

The OCI standard, briefly

The Open Container Initiative standardizes the image format and the runtime interface, specifically so that runc isn't the only option — other OCI-compliant runtimes (like crun or gVisor, used for extra sandboxing) can be swapped in underneath containerd without changing anything above it. This is also exactly why Kubernetes can run containers without Docker installed at all — it only needs something that speaks the OCI runtime interface, and containerd (or another CRI-compliant runtime) is sufficient on its own.

  1. Install Docker, which installs and starts the dockerd daemon as a background service, along with the docker CLI.
  2. Run docker run nginx. The CLI sends a "create and start a container from image nginx" request to the daemon.
  3. The daemon checks its local image cache. On a fresh machine, nginx isn't there yet, so it reaches out to the default registry (Docker Hub) and downloads the image's layers.
  4. Each downloaded layer is stored locally and cached — a second docker run nginx on the same machine skips the download step entirely, since the image is already present.
  5. The daemon delegates to containerd, which prepares the container's filesystem and OCI spec, then hands off to runc to actually create the namespaces/cgroups and start the process.
  6. nginx's main process starts inside its new, isolated environment, and the container is now "running" — visible to docker ps, with its own logs, its own (initially unreachable-from-outside) network namespace, and its own tiny writable layer for any files it creates.
  7. When the container stops, its writable layer isn't automatically deleted — docker rm is what actually removes it, which is why a stopped container can still be inspected, restarted, or have its logs read before being cleaned up.
  • A CI pipeline building and pushing an image — the CLI (or an SDK talking to the same API) issues docker build and docker push; the daemon does the actual layer construction; the finished image lands in a registry like ECR, ready for a deployment step to pull from.
  • A local developer running docker-compose up — Compose is itself just a client that translates a YAML file into a sequence of ordinary Docker Engine API calls (create network, create volumes, create and start each service's container) — it has no special access the CLI doesn't also have.
  • Kubernetes running containers without Docker installed — a kubelet on each node talks directly to containerd (or another CRI-compliant runtime) via the Container Runtime Interface, completely bypassing dockerd — a direct, practical consequence of the OCI/CRI standardization covered in Internal Working.
  • A security team restricting who can run docker commands on a shared build server — because the Docker socket effectively grants root, this is usually solved by adding only trusted users to the docker group, or routing all builds through a controlled CI system instead of direct developer socket access.
  • A private registry (Harbor) mirroring Docker Hub internally — common in regulated environments where pulling directly from the public internet on every build isn't acceptable; the registry piece of the architecture is what makes this substitution possible without changing anything else.
  • Treat access to the Docker socket as equivalent to root access on that machine, and restrict the docker group membership accordingly — this single fact should shape every access-control decision involving Docker.
  • Prefer a private, access-controlled registry for production images rather than pulling directly from public registries on every deploy — it's both faster (often geographically closer) and safer (no dependency on a third party's uptime or unexpected image changes).
  • Keep the daemon and CLI versions reasonably in sync across your fleet — a large version skew between client and daemon can surface confusing, hard-to-diagnose API compatibility errors.
  • Understand that docker-compose, Kubernetes' kubelet, and any other Docker-adjacent tool are just callers of the same underlying API — there's no hidden, more-privileged path into the engine that bypasses the same security model.
  • When troubleshooting "docker isn't working," check the daemon's own logs (journalctl -u docker) before assuming the problem is in your Dockerfile or command — a surprising number of issues are actually daemon-level (disk space, permissions, networking) rather than image-level.

⚠️ Why this keeps happening

Because the Docker CLI feels like a normal, harmless command-line tool, it's easy to forget that every single thing it does is actually a request to a privileged background service — the security implications of that design only become obvious once someone points them out, or after an incident makes it obvious the hard way.

  • Adding a user to the docker group without realizing this is functionally equivalent to giving them root. A user in that group can mount the host's entire filesystem into a container and read or modify anything on it.
  • Exposing the Docker socket over an unauthenticated TCP port for "convenience" (remote Docker access), effectively handing root access on that machine to anyone who can reach the port.
  • Assuming docker is a single monolithic program, which leads to confusing troubleshooting — a networking issue might actually be a containerd problem, an image-pull issue might actually be a registry authentication problem, and conflating them wastes debugging time.
  • Not realizing images are cached locally, and being surprised that a docker run of an image you've never explicitly pulled works instantly online, but fails when offline — the image was already cached from an earlier pull you didn't consciously notice.
  • Forgetting that a stopped container still occupies disk space (its writable layer isn't deleted until docker rm), leading to disk space slowly filling up on build servers that run many short-lived containers without cleanup.
  • Rely on the local image cache deliberately — structure CI pipelines so repeated builds on the same runner can reuse previously pulled base-image layers, rather than always pulling from scratch.
  • Use a registry mirror or pull-through cache close to your build infrastructure (geographically and network-wise) to reduce image-pull latency, especially for large base images pulled frequently across many CI jobs.
  • Avoid unnecessary daemon restarts in automation scripts — restarting dockerd briefly disrupts every running container's ability to be managed (though running containers themselves typically survive a daemon restart, since containerd continues supervising them independently).
  • Monitor daemon-level resource usage (docker system df) periodically — accumulated unused images, stopped containers, and dangling volumes all consume disk space that isn't always obvious from docker ps alone.
  • For latency-sensitive CI, consider a registry with layer-level deduplication and fast pulls (many managed registries offer this) rather than a self-hosted registry with no such optimization.
  • Never expose the Docker daemon's API over the network without TLS client-certificate authentication — an unauthenticated remote API is one of the most severe, most common Docker misconfigurations found in security audits.
  • Use a private registry with access controls and image-scanning integration for anything running in production, rather than pulling untrusted public images directly.
  • Monitor the daemon's own health and logs as a first-class part of your infrastructure observability — a wedged or crashed daemon can prevent new deployments even while existing containers keep running.
  • Pin specific image tags (or, better, image digests) in production deployments rather than floating tags like latest, so a registry-side change can't silently alter what gets pulled on the next restart.
  • Periodically audit who has access to the Docker socket on every host, including CI runners and build agents — this list tends to grow quietly over time as new automation is added.
  1. Run docker version and compare the "Client" and "Server" sections of the output — confirm they're reported as genuinely separate components with their own version numbers.
  2. Run docker run hello-world on a fresh machine (or after docker system prune -a), and time how long the first run takes versus a second, repeated run — observe the registry-pull cost disappear on the cached second run.
  3. Inspect ps aux on the Docker host while a container is running, and identify the containerd-shim process associated with it — this is the process that keeps supervising the container after runc's one-time setup work is done.
  4. Check which group your user account needs to be in to run docker commands without sudo, and explain in your own words why that group membership is effectively root-equivalent.

✓ Quick recap

  • Docker is four cooperating pieces, not one program: the CLI (client), dockerd (daemon), containerd + runc (the actual container lifecycle and low-level isolation), and a registry (where images live).
  • The CLI is a thin client talking to the daemon's REST API over a Unix socket — whoever can reach that socket has effective root access on the host.
  • containerd handles day-to-day container lifecycle; runc does the one-time work of creating Linux namespaces and cgroups, then exits, leaving containerd to supervise the running process.
  • The OCI standard is what lets other runtimes (and Kubernetes' kubelet) talk to containerd directly, without needing dockerd installed at all.
  • Treat Docker socket access as equivalent to root, and restrict docker group membership and any remote API exposure accordingly.

Want a visual for this concept?

Generate a diagram tailored to “Docker Architecture, Engine, Daemon, Client & Registry” — the AI picks whichever visual (flowchart, comparison, sequence, etc.) best fits.

Sign in to generate a visual →

Practice quiz

Next Step

Continue to Images, Layers, Caching & Container Lifecycle← Back to all Docker chapters