Skip to main content
RunBook Academy

Docker & ContainersII Β· Linux InternalsStartup deep-dive

Container startup from CLI to running process

Advanced⏱ ~32 min

What you'll learn

  • Trace `docker run nginx` from CLI invocation to running process
  • Identify which component is responsible for which delay
  • Diagnose common startup failures at the right layer

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-09

Not yet marked complete on this device.

This lesson traces the full lifecycle of a single command:

docker run --name web -p 8080:80 -d nginx:1.27

You should be able to look at any layer of this stack and answer β€œwhat is happening right now and why is it slow?”

The high-level sequence

sequenceDiagram
  participant U as User shell
  participant CLI as docker CLI
  participant D as dockerd
  participant CT as containerd
  participant SH as containerd-shim
  participant R as runc
  participant K as Linux kernel
  U->>CLI: docker run nginx
  CLI->>D: POST /containers/create (REST, unix socket)
  D->>CT: Create container (gRPC)
  CT->>CT: Resolve image locally
  alt image not cached
    CT->>CT: Pull layers from registry (HTTPS)
  end
  CT->>CT: Construct OCI bundle
  D-->>CLI: container ID
  CLI->>D: POST /containers/<id>/start
  D->>CT: Start container (gRPC)
  CT->>SH: Create shim, start container
  SH->>R: runc run <bundle>
  R->>K: clone(2) with CLONE_NEWNS|NEWPID|NEWNET|NEWUTS|NEWIPC
  R->>K: mount overlayfs (upperdir, lowerdir, workdir, merged)
  R->>K: write cgroup v2 limits
  R->>K: apply seccomp, AppArmor
  R->>K: apply capability set
  R->>K: execve("/docker-entrypoint.sh")
  K-->>R: PID 1 of the new namespace
  R-->>SH: container running, PID
  SH-->>CT: container started
  CT-->>D: container started
  D-->>CLI: container started

Phase 1 β€” CLI parsing and request

Read-only / Safeis it the daemon or is it the container?
curl -s --max-time 5 --unix-socket /var/run/docker.sock \
  -w '\nhttp_code=%{http_code}\n' http://localhost/_ping
echo '--- are the workloads still running regardless? ---'
ps -eo pid,ppid,etime,comm | grep -E 'containerd-shim' | head
echo '--- would a daemon restart stop them? ---'
docker info --format 'live-restore={{.LiveRestoreEnabled}}' 2>/dev/null || echo 'daemon not answering'
OK
http_code=200
--- are the workloads still running regardless? ---
 1971       1    05:12:44 containerd-shim
 1978       1    05:12:44 containerd-shim
 1992       1    05:12:43 containerd-shim
--- would a daemon restart stop them? ---
live-restore=false

That capture is from a healthy host, and every line is load-bearing:

  • OK / http_code=200 β€” the daemon is answering. A hang in docker ps after this is not a daemon problem.
  • No output, or http_code=000 after five seconds β€” the daemon is not answering. Now it is a daemon problem, and the next stop is journalctl -u docker --since '10 min ago', not a restart.
  • PPID of 1 on every shim β€” the shims are children of init, not of dockerd. The containers do not depend on the daemon to keep running.
  • live-restore=false β€” the default, and the reason a restart would stop all of them. Turn it on before you need it.
Configuration changemake the next daemon restart survivable
sudo tee /etc/docker/daemon.json <<'EOF'
{
  "live-restore": true
}
EOF
sudo systemctl reload docker
docker info --format 'live-restore={{.LiveRestoreEnabled}}'
live-restore=true

Illustrative output

Use reload, not restart: reloading applies the change without the stop you are trying to protect against. And read the value back from docker info rather than trusting the file β€” a JSON syntax error in daemon.json leaves the daemon running on its previous configuration, and nothing on the command line says so.

Phase 2 β€” dockerd: create the container record

dockerd receives the create request, validates the spec, and asks containerd to create a container record. dockerd does not run anything yet β€” it just records β€œa container with this spec should exist”.

If the image is not local, dockerd asks containerd to pull. Pull is the single largest source of latency on a cold cache:

  • DNS resolution for the registry.
  • TLS handshake.
  • Authentication (OAuth bearer token from the registry).
  • Manifest fetch.
  • Layer fetch (the actual bytes).

For a multi-layer image on a fresh host, pull can take 5–30 seconds depending on bandwidth and image size.

Phase 3 β€” containerd: build the OCI bundle

containerd prepares the OCI bundle. An OCI bundle is a directory containing:

  • config.json β€” the runtime spec (namespaces, cgroups, capabilities, mounts, etc.).
  • rootfs/ β€” the container’s root filesystem, mounted via OverlayFS.

containerd does not run anything; it just creates the bundle on disk.

Phase 4 β€” start request

The CLI sends POST /containers/<id>/start. dockerd forwards it to containerd. containerd spawns a containerd-shim-<id> process which is the parent of the future container.

The shim invokes runc run --bundle /var/run/docker/containerd/.../<id> ....

Phase 5 β€” runc: create namespaces and start the process

runc does the heavy lifting:

  1. Apply capabilities. Set the capability bounding set for the new namespaces.
  2. Apply seccomp. Load the BPF filter that will be evaluated on every syscall.
  3. Apply AppArmor / SELinux. Apply the LSM profile.
  4. Write cgroup limits. Set memory.max, cpu.max, etc.
  5. Mount OverlayFS. Compose the lower/upper/work/merged tree.
  6. Mount /proc, /sys, /dev. Inside the new mount namespace.
  7. clone(2) with namespace flags. The new process is the container’s PID 1. The old PID namespace is hidden.
  8. chdir to rootfs, execve the entrypoint.

At this point the entrypoint is running as PID 1 in the container.

Phase 6 β€” entrypoint runs

The entrypoint is whatever the image’s ENTRYPOINT and CMD combine to. For nginx:1.27, it’s /docker-entrypoint.sh which execs nginx -g 'daemon off;'.

nginx reads its configuration, binds to port 80 (the container port), and starts serving.

Phase 7 β€” port publishing

The -p 8080:80 flag asks dockerd to publish container port 80 to host port 8080. dockerd configures the iptables (or nftables) rules for this. There are two implementations: the legacy userland-proxy and the modern iptables-nat (default on Linux 5.x+).

Where time is spent

A cold-cache docker run nginx on a fresh host typically takes:

PhaseTime
CLI parsing< 10 ms
dockerd β†’ containerd RPC~5 ms
Image pull (5 layers Γ— ~25 MB at 100 MB/s)~10 s
Bundle creation~50 ms
Shim + runc spawn~30 ms
OverlayFS mount~50 ms
cgroup write~5 ms
clone + execve~20 ms
nginx startup~100 ms

The image pull dominates. Subsequent runs hit local cache and complete in well under a second.

What can go wrong at each phase

SymptomLikely cause
β€œCannot connect to Docker daemon”dockerd is not running, or socket perms wrong
β€œimage not found”Wrong image reference, or registry auth issue
β€œpermission denied” on volumeBind mount ownership, SELinux label mismatch, or a seccomp denial surfacing as EPERM
Container exits immediatelyEntrypoint error; check docker logs
docker stop always takes exactly 10 s and exits 137PID 1 ignores SIGTERM β€” usually a shell-form CMD. Not an OOM: check State.OOMKilled before reaching for memory
Container exits 137 with State.OOMKilled trueGenuine OOM kill; check memory.events and dmesg
docker ps / docker exec / docker logs all hangThe daemon, not the container. curl --max-time 5 --unix-socket /var/run/docker.sock http://localhost/_ping before you restart anything
Port not reachableFirewall (host), iptables (Docker), proxy upstream, or app bound to 127.0.0.1 inside the container

Knowledge check

Knowledge check Β· 6 questions

  1. Q1. Which phase of `docker run nginx` typically dominates the total wall-clock time on a cold host?

  2. Q2. After the container is running, what is runc's role?

  3. Q3. Name the order of the first three processes spawned by `docker run`, from the inside out.

  4. Q4. `docker ps`, `docker logs` and `docker exec` all hang with no output. What does this tell you?

  5. Q5. Restarting the Docker daemon on a stock host leaves running containers untouched.

  6. Q6. Which process is the parent of a running container, once runc has exited?

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