Skip to main content
RunBook Academy

Docker & ContainersIII · Installation & DaemonComponents

Docker Engine, containerd, runc — what is in the package

Intermediate⏱ ~26 mindockerctr

What you'll learn

  • Identify which package provides which process
  • Trace a `docker run` through dockerd, containerd, the shim and runc
  • Explain why there is no runc process on a healthy host
  • Diagnose at the right layer when the daemon and the runtime disagree
  • Verify all four component versions and know what a mismatch looks like

Prerequisites

Verified against Docker Engine 29.x · Docker Engine 28.x · Docker Compose 2.x · containerd 2.x · runc 1.2.x · BuildKit 0.20+ · Linux kernel 5.15+ · Ubuntu 24.04 LTS · Debian 12 (Bookworm) · 2026-08-12

Not yet marked complete on this device.

When you install docker-ce you install five packages. They cooperate to give you the docker command. Knowing what each one does is the difference between “the daemon is broken” and “containerd is broken” — two very different troubleshooting paths, run against two different sets of logs.

The packages

PackageProvidesRole
docker-cedockerd, docker-proxy, docker-initThe daemon: API, images, networks, volumes
docker-ce-clidockerThe client. Talks to a daemon over a socket
containerd.iocontainerd, containerd-shim-runc-v2, ctr, runcThe supervisor, the per-container shim, and the OCI runtime
docker-buildx-plugindocker buildxBuildKit integration
docker-compose-plugindocker composeCompose v2

Two things about that table are worth pausing on.

runc ships in containerd.io, not in docker-ce. So does the shim. The Docker packages contain no container runtime at all. That is deliberate: a Kubernetes node can install containerd.io alone and never have Docker on it.

The CLI is a separate package from the daemon. They are versioned independently and can drift, which is why docker version reports them separately.

The call chain, and the process that is not there

Read-only / Safeversion
$ docker version
Client: Docker Engine - Community
Version:           28.5.1
API version:       1.51
Go version:        go1.24.8
OS/Arch:           linux/amd64
Context:           default

Server: Docker Engine - Community
Engine:
Version:          28.5.1
API version:      1.51 (minimum version 1.24)
OS/Arch:          linux/amd64
containerd:
Version:          v2.1.5
runc:
Version:          1.2.6
docker-init:
Version:          0.19.0

Illustrative output

Now look at what is actually running on a host with containers on it:

Read-only / Safeprocesses
$ ps -eo pid,ppid,comm --forest | grep -E 'dockerd|containerd|docker-proxy|runc'
   1180       1 containerd
 1461       1 dockerd
 1499    1461  \_ docker-proxy
 2043    1180 containerd-shim-runc-v2
 2210    1180 containerd-shim-runc-v2

Illustrative output

Read-only / SafeOCI bundle
CONTAINER=web
ID=$(docker inspect --format '{{.Id}}' "$CONTAINER")

sudo cat "/run/containerd/io.containerd.runtime.v2.task/moby/$ID/config.json" \
| jq '{ process: .process.args,
        caps: .process.capabilities.effective,
        cgroup: .linux.cgroupsPath,
        namespaces: [.linux.namespaces[].type] }'

The path itself is informative: io.containerd.runtime.v2.task is the shim’s runtime plugin, and moby is the containerd namespace Docker uses. That namespace is the reason for the next failure mode.

When the layers disagree

Each layer has its own client and its own log, and using the right one is most of the diagnosis.

Read-only / Safelayer by layer
# Layer 1: does the daemon answer at all?
docker info >/dev/null 2>&1 && echo "dockerd OK" || echo "dockerd DOWN"
journalctl -u docker -b --no-pager | tail -20

# Layer 2: does containerd answer, and does it see the same containers?
sudo ctr --namespace moby containers list
journalctl -u containerd -b --no-pager | tail -20

# Layer 3: are the tasks actually running?
sudo ctr --namespace moby tasks list
SymptomLayer at faultEvidence that confirms it
Cannot connect to the Docker daemondockerd, or the socketsystemctl status docker, journalctl -u docker -b
Daemon answers; every container operation errorscontainerd, or its socketsystemctl status containerd; ctr --namespace moby containers list
Container fails to start with a permissions or mount errorrunc, or the OCI config.State.Error in docker inspect; the bundle config.json
Container exits 126 or 127Not a layer fault at all — the imagedocker inspect --format '{{json .Config}}'
docker ps empty but the application still servesdockerd restarted and lost nothing; you are looking at live restorectr --namespace moby tasks list
Images disappeared after an upgradeThe image store switcheddocker info --format '{{.Driver}}'
Read-only / Safecontainerd down
$ docker run --rm hello-world
docker: Error response from daemon: failed to create task for container: \
failed to dial "/run/containerd/containerd.sock": \
connection error: desc = "transport: Error while dialing: dial unix \
/run/containerd/containerd.sock: connect: connection refused"

Illustrative output

Note that docker info succeeds throughout this. The daemon is fine. Restarting docker.service will not help and, on a host where containers are still running under their shims, is a strictly worse move than starting containerd. Start containerd first, then re-test; only restart dockerd if it fails to reconnect.

Why the split exists

Verification that can fail

Read-only / Safecomponent check
set -e

CLIENT=$(docker version --format '{{.Client.Version}}')
SERVER=$(docker version --format '{{.Server.Version}}')

if [ "${CLIENT%%.*}" != "${SERVER%%.*}" ]; then
  echo "major version skew: client $CLIENT, server $SERVER" >&2
  exit 1
fi

docker version --format 'engine={{.Server.Version}} containerd={{.Server.Containerd.Version}} runc={{.Server.Runc.Version}} init={{.Server.Init.Version}}'

# The runtime layer answers independently of the daemon.
sudo ctr --namespace moby containers list >/dev/null
echo "all four components reporting"

A client and server on different major versions is worth failing a check over: the API negotiates down to the server’s version, so newer client subcommands and --format fields quietly stop working rather than erroring.

Knowledge check

Knowledge check · 6 questions

  1. Q1. On a healthy host running fifty containers, how many `runc` processes should `ps` show?

  2. Q2. `docker info` succeeds, but every `docker run` fails with "failed to dial /run/containerd/containerd.sock: connection refused". What is the correct first action?

  3. Q3. Which binaries come from the `containerd.io` package rather than from `docker-ce`? Select all that apply.

  4. Q4. `ctr containers list` on a Docker host returns an empty list unless you pass `--namespace moby`.

  5. Q5. Where would you look for the exact capability set and cgroup path the kernel was configured with for a running container?

  6. Q6. Why does `systemctl restart docker` with live restore leave containers running?

Passing score: 75%. Answers are checked in this browser.