What's the difference between the Docker CLI and the Docker daemon (dockerd)?

5 minintermediatedocker-clidocker-daemonarchitecture

Quick Answer

The Docker CLI (docker) is a lightweight client program that translates commands you type into REST API calls — it holds no state itself and does none of the actual work. dockerd (the Docker daemon) is the long-running background process that actually does the work: managing images, containers, networks, and volumes, and maintaining all of Docker's state. This split means the CLI can connect to and manage a daemon running on a completely different, remote machine, not just the one the CLI itself is running on.

Detailed Answer

The CLI is a thin, stateless client

docker ps
docker run -d nginx
docker images

Every one of these commands does the same basic thing internally: the docker binary formats an HTTP request describing the action, sends it to the daemon's REST API, and formats whatever JSON response comes back into the human-readable output you see. The CLI itself holds no persistent state about running containers, images, or anything else — ask it something without a daemon to talk to, and it has nothing to report.

docker ps
# Cannot connect to the Docker daemon at unix:///var/run/docker.sock.
# Is the docker daemon running?

This exact error — extremely common for anyone new to Docker — makes the client/daemon split concrete: the CLI genuinely can't do anything on its own without a running daemon to actually talk to.

dockerd holds and manages all real state

The daemon is a persistent background process (typically started as a system service) that:

  • Manages the actual container lifecycle (working with containerd/runc underneath — see that question)
  • Builds and stores images, and manages their layers on disk
  • Manages networks and volumes
  • Exposes the REST API that the CLI (and any other client) communicates with
  • Persists all of this state across CLI invocations — the daemon keeps running and tracking everything even when no docker command is currently executing

The API socket

ls -la /var/run/docker.sock

By default, the CLI and daemon communicate over a Unix socket on the local machine (/var/run/docker.sock). This is also exactly why membership in the docker group, which grants read/write access to that socket, is functionally equivalent to root access on the host — a detail covered more fully in the security topic's Docker-socket question.

Why this split matters: remote Docker management

docker -H tcp://remote-server:2376 ps
# or via the DOCKER_HOST environment variable
export DOCKER_HOST=tcp://remote-server:2376
docker ps

Because the CLI is just a client speaking a well-defined REST API, it can connect to a daemon running on an entirely different machine (over TCP, ideally secured with TLS client certificates) just as easily as it connects to a local Unix socket. This is what lets tools and workflows manage remote Docker hosts, and what lets a CI runner build images against a Docker daemon running on a separate build server.

The same pattern shows up elsewhere in the ecosystem

This client/daemon separation is a recurring architectural pattern well beyond Docker. The Kubernetes API server plays a directly analogous role (kubectl is a thin client, exactly the way docker is). Recognizing this pattern — a thin CLI translating commands into API calls against a stateful backend service — transfers directly to understanding how most modern infrastructure tooling is built.

Practical troubleshooting relevance

"Cannot connect to the Docker daemon" errors point specifically at the daemon not running, not being reachable at the expected socket/address, or a permissions issue accessing the socket. They do not point at anything wrong with the CLI itself, since the CLI has essentially nothing of its own that can fail independently of its ability to reach the daemon.