Chapter 01Lesson 05~120 minutes

Checkpoint Lab — Containers, Virtual Machines, Linux Isolation, OCI Standards, and Docker Architecture

Complete Chapter 01 by producing a reviewable evidence packet for one disposable container: prove the selected Docker endpoint, immutable image identity, container/process lifecycle, namespace/cgroup placement, attachments, logs, platform limitations, and guarded cleanup.

CheckpointEvidence packetDigestNamespacesCleanup

Learning objectives

  • Capture a complete host/context/Engine/tool baseline without fabricating unavailable component versions.
  • Predict state changes before pull/run and verify them independently afterward.
  • Map immutable image identity to the exact container object, process, namespaces/cgroups, networks, mounts, and logs.
  • Explain what each successful Docker state proves and what it cannot prove.
  • Perform exact resource cleanup and hand off a concise Chapter 01 operating model to Chapter 02.
Chapter 01 technical baseline — verified 2026-09-20. Docker Engine 29.8.1 is the current Engine 29 patch release in Docker's release notes. Its packages update the bundled containerd static binaries to v2.3.5. Docker and OCI internals are version-sensitive, so every lab starts by recording the actual client, server, API, kernel, context, cgroup, runtime, and image identity instead of assuming this baseline.

1. Checkpoint mission and evidence standard

Your task is to run one disposable Linux container and produce an evidence map from the selected Docker context and image digest through daemon/runtime state to the process, namespace/cgroup placement, network attachment, mounts, logs, and exact cleanup. The point is not merely to make a container run; it is to explain what every observed state proves and does not prove.

Authorized disposable scope only. Use a personal lab host/VM or Docker Desktop environment. Do not change a production daemon, socket permissions, firewall, seccomp/LSM policy, user namespaces, or registry settings for this checkpoint. Native-Linux host namespace inspection is optional when your daemon is remote or Desktop-backed.

2. Record the current baseline before the lab

Do not copy version numbers from the lesson into your evidence packet. Capture your own environment. The 2026-09-20 authoring baseline is Docker Engine 29.8.1; its release notes package containerd static binaries v2.3.5. Compose, Buildx, BuildKit, containerd, runc, and Desktop release independently or may be bundled differently.

mkdir -p docker-ch01-evidence
{
  echo "captured_utc=$(date -u +%Y-%m-%dT%H:%M:%SZ)"
  echo "host_uname=$(uname -a)"
  echo "context=$(docker context show)"
} | tee docker-ch01-evidence/00-environment.txt

docker version | tee docker-ch01-evidence/01-docker-version.txt
docker info | tee docker-ch01-evidence/02-docker-info.txt
docker context inspect "$(docker context show)" > docker-ch01-evidence/03-context.json

docker compose version > docker-ch01-evidence/04-compose.txt 2>&1 || true
docker buildx version > docker-ch01-evidence/05-buildx.txt 2>&1 || true
containerd --version > docker-ch01-evidence/06-containerd.txt 2>&1 || true
runc --version > docker-ch01-evidence/07-runc.txt 2>&1 || true

If containerd or runc is not exposed on the client host—common with Docker Desktop—record that as “not directly exposed,” not as a guessed version.

3. Preflight exact lab resource identity

set -eu
LAB_CONTAINER="da-ch01-checkpoint"
LAB_LABEL="devops-academy.lab=chapter01-checkpoint"
LAB_IMAGE_TAG="alpine:3.22"

if docker inspect "$LAB_CONTAINER" >/dev/null 2>&1; then
  echo "Refusing to continue: $LAB_CONTAINER already exists."
  exit 1
fi

printf 'Target context: %s
' "$(docker context show)"
docker context inspect "$(docker context show)"

If the endpoint is not the disposable daemon you intend to use, stop. A checkpoint that accidentally targets the wrong daemon has failed its most important safety requirement.

4. Predict state changes before executing them

Write your own predictions into docker-ch01-evidence/08-predictions.txt. At minimum include these two:

Before action Prediction Verification
No container named da-ch01-checkpoint After docker run, a labeled container object exists and its main process is running. Exact docker inspect ID/name/label/state/PID.
Tag has not yet been resolved for this run After pull, alpine:3.22 resolves to a concrete registry digest used for the container. RepoDigest plus container .Image identity.
No explicit mount in the command Container inspect reports no explicit Docker mounts. .Mounts evidence.
Default network behavior The container gets an endpoint on Docker's default network unless platform policy differs. .NetworkSettings.Networks.

5. Resolve the image and start the bounded workload

docker pull "$LAB_IMAGE_TAG" | tee docker-ch01-evidence/09-pull.txt
ALPINE_REF="$(docker image inspect "$LAB_IMAGE_TAG" --format '{{index .RepoDigests 0}}')"
IMAGE_ID="$(docker image inspect "$LAB_IMAGE_TAG" --format '{{.Id}}')"
printf 'repo_digest=%s
image_id=%s
' "$ALPINE_REF" "$IMAGE_ID"   | tee docker-ch01-evidence/10-image-identity.txt

docker run -d \
  --name "$LAB_CONTAINER" \
  --label "$LAB_LABEL" \
  "$ALPINE_REF" \
  sh -c 'echo "checkpoint-started $(date -u +%Y-%m-%dT%H:%M:%SZ)"; exec sleep 900' \
  | tee docker-ch01-evidence/11-container-id.txt

You have now created exactly one Docker object with one long-running process. No ports, host mounts, extra devices, capabilities, secrets, or privileged mode are required.

6. Capture object, process, network, mount, and log evidence

docker inspect "$LAB_CONTAINER" > docker-ch01-evidence/12-container-inspect.json

docker inspect "$LAB_CONTAINER" --format \
'id={{.Id}} name={{.Name}} image={{.Image}} status={{.State.Status}} running={{.State.Running}} pid={{.State.Pid}} exit={{.State.ExitCode}}' \
  | tee docker-ch01-evidence/13-container-state.txt

docker inspect "$LAB_CONTAINER" --format 'networks={{json .NetworkSettings.Networks}}' \
  | tee docker-ch01-evidence/14-networks.txt

docker inspect "$LAB_CONTAINER" --format 'mounts={{json .Mounts}}' \
  | tee docker-ch01-evidence/15-mounts.txt

docker top "$LAB_CONTAINER" | tee docker-ch01-evidence/16-docker-top.txt
docker logs "$LAB_CONTAINER" | tee docker-ch01-evidence/17-container-logs.txt

Explain every result. A running state proves the main process has not exited. A network endpoint proves attachment, not host publication. An empty mount list proves no explicit mount attachments, not absence of a root filesystem. A log line proves stdout/stderr collection, not health.

7. Capture the container's process, kernel, namespace, and cgroup view

docker exec "$LAB_CONTAINER" sh -c '
  echo "=== kernel ==="
  uname -a
  echo "=== os-release ==="
  cat /etc/os-release
  echo "=== processes ==="
  ps -o pid,ppid,user,comm
  echo "=== namespaces ==="
  for n in pid mnt net uts ipc; do printf "%s -> " "$n"; readlink "/proc/1/ns/$n"; done
  echo "=== cgroup ==="
  cat /proc/1/cgroup
' | tee docker-ch01-evidence/18-container-view.txt

Your report must explicitly distinguish userspace identity from kernel identity. “Alpine” comes from image userspace files. The kernel comes from the daemon host (native Linux) or Docker Desktop VM.

8. Native Linux Engine only — correlate the daemon-host process

If and only if your selected context points to a native Linux Engine host you control, map Docker's reported PID to host namespace/cgroup evidence. Otherwise write not directly observed because daemon host is remote/Desktop-backed in the evidence packet.

PID="$(docker inspect "$LAB_CONTAINER" --format '{{.State.Pid}}')"
{
  echo "pid=$PID"
  ps -fp "$PID"
  for n in pid mnt net uts ipc; do printf "%s -> " "$n"; readlink "/proc/$PID/ns/$n"; done
  echo "cgroup:"
  cat "/proc/$PID/cgroup"
  echo "host-pid-namespace:"
  readlink /proc/1/ns/pid
  echo "host-net-namespace:"
  readlink /proc/1/ns/net
} | tee docker-ch01-evidence/19-native-linux-host-view.txt

This correlation is the strongest Chapter 01 proof that Docker's container abstraction resolves to an ordinary host-kernel process with a different namespace view.

9. State the component assumptions and limitations

Create docker-ch01-evidence/20-assumptions.md and record:

  • Actual Docker client/server versions and negotiated API printed by docker version.
  • Whether the daemon is native Linux Engine, Docker Desktop, rootless, or remote.
  • Actual Compose/Buildx versions if installed; note that neither is required to run this Chapter 01 container.
  • containerd/runc versions if directly observable; otherwise state that Docker manages them and the client host does not expose their binaries.
  • The resolved Alpine RepoDigest and local image configuration ID.
  • Whether native host namespace/cgroup evidence was observable or intentionally omitted.

Do not fabricate missing fields. “Not observed” is valid evidence.

10. Explain what each green state proves—and does not prove

Observed state What it proves What it does not prove
docker version reaches server Client can communicate with the selected Engine endpoint and negotiate an API. Any image/container/application is healthy.
RepoDigest captured Registry content resolved to an immutable digest for this pull. That the content is vulnerability-free or trusted.
.State.Running=true Main container process is currently running. Application readiness or external reachability.
Network endpoint exists Container is attached to a Docker network. Host port publication or user reachability.
Namespace IDs differ from host Those namespace views are isolated for the process. A complete hostile-security boundary.
Cleanup removes the container The lab container object/process no longer exists. That the pulled image or unrelated Docker resources were removed.

11. Guarded cleanup and post-cleanup proof

Verify exact resource identity before deletion. Do not use wildcard or “latest” cleanup.

docker inspect "$LAB_CONTAINER" --format \
'name={{.Name}} label={{index .Config.Labels "devops-academy.lab"}} image={{.Image}} status={{.State.Status}}' \
  | tee docker-ch01-evidence/21-pre-cleanup.txt

# Continue only if name/label match the checkpoint resource.
docker rm -f "$LAB_CONTAINER" | tee docker-ch01-evidence/22-cleanup.txt

docker ps -a --filter "name=^/${LAB_CONTAINER}$" --no-trunc   | tee docker-ch01-evidence/23-post-cleanup.txt

Leave the pulled image unless you have a specific reason to remove it. This avoids turning a container-cleanup exercise into a host-wide image-management side effect.

12. Evidence packet checklist

Your checkpoint is complete when the packet contains:
  • UTC capture time, host/platform, active context, and endpoint identity.
  • Docker client/server/API evidence plus docker info.
  • Tool/runtime version observations or explicit “not exposed/not observed” notes.
  • Resolved RepoDigest and local image ID.
  • Container ID, state, PID, command/process evidence, logs.
  • Network and mount evidence.
  • Container-side namespace/cgroup evidence; host-side correlation when safely available.
  • Predictions with a statement of whether each was confirmed.
  • Guarded cleanup proof.
  • A short paragraph explaining what a running container proves and does not prove.

Knowledge check

Why is the active Docker context part of the evidence packet?

The image tag is alpine:3.22. Why must the packet also capture a digest?

A container is running and has a network IP. Does that prove users can reach an application on the host?

Why can “not observed because Docker Desktop hides the daemon host PID namespace” be a correct checkpoint result?

What would be wrong with finishing cleanup using docker system prune -a --volumes?

13. Chapter 01 operational review

You can now explain the complete first-principles chain: developer intent goes through the selected Docker client/context to the Engine API and daemon; Docker manages image/container metadata and delegates runtime work through its containerd/runtime stack; the result is a Linux process isolated by namespaces, cgroups, mounts, and security controls while sharing the daemon host kernel. You can identify that process, preserve its immutable image identity, inspect attachments, and remove only the resource you created.

Chapter 02 moves from architecture to installation and environment setup: choosing Docker Engine versus Desktop, validating supported platforms, understanding daemon connectivity and contexts, and creating a reproducible local operating baseline.

Next chapter

Next: Installing Docker Engine and Desktop, CLI Basics, Daemon Connectivity, Contexts, and Environment Setup: Concepts, Architecture, and Mental Model

Continue with the next lesson in the course sequence and carry forward the evidence-first Docker operating model.

Official references and version notes

Version-sensitive statements in this chapter were rechecked against primary documentation on 2026-09-20. Docker Engine, Docker Desktop, containerd, runc, OCI specifications, security defaults, and platform integration continue to evolve. Record the versions actually reported by your own environment before treating an example as production policy.

Current-version note

Docker Engine 29.8.1 is the current Engine 29 patch release as of this lesson's verification date. The current Engine API page contains an example for 29.8.1 reporting API 1.56 while its version matrix lists Docker 29.8 with maximum API 1.55. This chapter therefore treats the output of docker version on the actual client/daemon pair as authoritative lab evidence and does not hard-code an expected negotiated API number.

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.