Chapter 03Lesson 02~125 minutes

Docker Engine Components: dockerd, containerd, runc, BuildKit, APIs, and Container Lifecycle: Guided Hands-On Workflow and Core Operations

Follow one disposable container and one small BuildKit build through Docker Engine using versions, inspect output, events, host-process evidence, and builder metadata without mutating Docker-managed containerd state directly.

Lifecycle evidencedocker eventsHost processBuildxSafe inspection

Learning objectives

  • Create a container object separately from starting its process and verify both states.
  • Correlate container ID, Docker-reported PID, events, logs, and host/runtime evidence where the platform permits.
  • Run a minimal BuildKit build and identify the selected builder/driver/output path.
  • Recognize Docker Desktop VM boundaries when host process evidence is not directly visible.
  • Clean only exact lab-owned resources.
Chapter 03 platform baseline — verified 2026-09-20. Docker Engine 29.8.1 is current. The 29.8 API matrix lists API 1.55 maximum / 1.40 minimum. Engine 29.8 packages BuildKit 0.33.0 and runc 1.5.1; the 29.8.1 static-binary packaging update carries containerd 2.3.5. Treat these as dated reference points only: capture docker version, docker info, docker buildx version, and platform-specific runtime evidence on the machine that actually runs the lab.

1. Lab scope and preflight

This workflow uses one disposable container and one tiny build. It does not change daemon configuration, mount the Docker socket into a container, use privileged mode, or directly modify containerd. Run it only on a personal/disposable Docker environment or an authorized lab host.

set -eu
LAB=da-ch03-runtime
LABEL='devops-academy.lab=chapter03'
WORK="${TMPDIR:-/tmp}/da-ch03-build"
mkdir -p "$WORK"

docker version
docker context show
docker info --format 'Server={{.ServerVersion}} Driver={{.Driver}} Cgroup={{.CgroupVersion}}'
docker buildx version
docker buildx ls
Expected evidence: client/server/API versions, active context, storage/image-store clues, cgroup mode, and at least one Buildx builder. If the daemon is unreachable, stop here and solve Chapter 02 connectivity before investigating runtimes.

2. Record an image input before creating a container

Use a small versioned base image and record the resolved immutable digest when the registry supplies one. The version tag remains human-readable; the digest tells you which bytes were actually selected.

docker pull busybox:1.36.1
docker image inspect busybox:1.36.1   --format 'ImageID={{.Id}} RepoDigests={{json .RepoDigests}}'

3. Separate create from start

docker create creates the Docker object without starting its process. This makes the state boundary visible.

docker create \
  --name "$LAB" \
  --label "$LABEL"   busybox:1.36.1   sh -c 'echo "container pid1=$$"; trap "echo TERM-received; exit 0" TERM; while :; do sleep 5; done'

docker inspect "$LAB" --format 'ID={{.Id}} Status={{.State.Status}} PID={{.State.Pid}} Image={{.Image}}'

Before start, the object exists but .State.Pid should not represent a running workload process. That is the first proof that Docker object lifecycle and process lifecycle are distinct.

4. Start it, then correlate Docker state with runtime state

START=$(date -u +%Y-%m-%dT%H:%M:%SZ)
docker start "$LAB"
sleep 1
PID=$(docker inspect -f '{{.State.Pid}}' "$LAB")
docker inspect "$LAB" --format 'Status={{.State.Status}} PID={{.State.Pid}} Started={{.State.StartedAt}}'
docker logs "$LAB"

# Native Linux Engine host only:
ps -fp "$PID" || true
ls -l "/proc/$PID/ns" 2>/dev/null || true
cat "/proc/$PID/cgroup" 2>/dev/null || true
ps -eo pid,ppid,comm,args | grep '[c]ontainerd-shim' || true

On Docker Desktop, the PID belongs to the Linux VM and normally will not map to an outer Windows/macOS PID. Record that limitation instead of calling the evidence “missing.”

5. Capture the lifecycle timeline with events

Current state answers “what exists now.” Events answer “what happened.” After stopping the container, query the bounded timeline.

docker stop --time 10 "$LAB"
END=$(date -u +%Y-%m-%dT%H:%M:%SZ)
docker events --since "$START" --until "$END" --filter "container=$LAB"
docker inspect "$LAB" --format 'Status={{.State.Status}} Exit={{.State.ExitCode}} Finished={{.State.FinishedAt}}'

Look for create/start/stop/die-related events and correlate timestamps with the inspect state and logs. Event vocabulary can vary with operation and platform, so preserve raw output rather than forcing a prewritten story onto it.

6. Run one BuildKit build and identify the builder path

The Dockerfile is intentionally trivial because this chapter is about build ownership, not Dockerfile instruction mastery.

cat > "$WORK/Dockerfile" <<'EOF'
FROM busybox:1.36.1
RUN printf 'chapter03-build-evidence\n' > /evidence.txt
CMD ["cat", "/evidence.txt"]
EOF

cd "$WORK"
docker buildx ls
docker buildx inspect --bootstrap
docker buildx build --progress=plain --load -t da-ch03-build:local .
docker image inspect da-ch03-build:local --format 'ImageID={{.Id}} Created={{.Created}}'

With the default docker driver, the BuildKit backend is integrated with Docker Engine and a single-platform result is loaded locally. If your selected builder uses another driver, --load is the explicit contract that makes a Docker Engine image-store result part of this lab.

7. Inspect daemon/runtime evidence without mutating lower-level state

# Native Linux Engine examples; read-only.
systemctl status docker --no-pager 2>/dev/null || true
journalctl -u docker --since "$START" --no-pager 2>/dev/null | tail -n 80 || true
containerd --version 2>/dev/null || true
runc --version 2>/dev/null || true
Boundary: do not turn this into ctr -n moby ... mutation. Docker owns the managed runtime state. Direct lower-level changes can make the Engine's object model diverge from runtime/content state.

8. Layer-selection challenge

Classify each symptom before proposing a fix: (a) docker version cannot reach the selected endpoint; (b) daemon accepts create but start fails with an OCI runtime error; (c) Buildx reports success but the image is absent locally; (d) container PID exists but application health fails. The likely first layers are respectively context/API transport, runtime/process creation, builder/exporter contract, and application/runtime health—not one generic “Docker problem.”

9. Exact cleanup

docker rm -f "$LAB" 2>/dev/null || true
docker image rm da-ch03-build:local 2>/dev/null || true
rm -rf "$WORK"

docker ps -a --filter "label=$LABEL"
docker image inspect da-ch03-build:local >/dev/null 2>&1 && echo 'image still present' || echo 'lab image removed'

Do not substitute docker system prune. Exact cleanup is part of the evidence chain because it proves which resources the lab owned.

Next lesson

Next: Docker Engine Components: dockerd, containerd, runc, BuildKit, APIs, and Container Lifecycle: Configuration, Design Choices, and Tradeoffs

Continue through the same Engine evidence chain while adding the next layer of operational reasoning.

Knowledge check

Why capture docker events in addition to docker inspect?

On Docker Desktop, why might the PID from docker inspect not exist in the Windows or macOS host process table?

Why should the lab avoid ctr or nerdctl as a normal repair tool for Docker-managed containers?

What proves which BuildKit backend handled a build?

Official references and version notes

Current baseline, not a frozen requirement

Verified 2026-09-20: Docker Engine 29.8.1 is current; the Engine 29.8 API matrix lists maximum API 1.55 and minimum API 1.40. Engine 29.8 packaging includes BuildKit 0.33.0 and runc 1.5.1; 29.8.1 updates the static-binary containerd package to 2.3.5. Package-managed distributions and Docker Desktop can bundle or expose components differently. Record the actual client/server/API/component/builder versions on the learner's environment before diagnosing compatibility.

Keep the academy open

Support free, practical DevOps education.

Every lesson is designed to remain readable in a browser, downloadable from GitHub, and usable without a paid learning platform. Contributions help expand and maintain the curriculum.

Ethereum / ERC-20
0x716c4Ab160C4B66F31a28AE2448BfF68fc3a2ef0 Send only Ethereum/ERC-20 compatible assets to this address.