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.
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.
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.
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
- 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?
Because it identifies which Engine endpoint received the side effect. Without it, a container ID alone does not prove which daemon/host you changed.
The image tag is alpine:3.22. Why must the packet
also capture a digest?
Tags are mutable references. The digest records the immutable registry content selected at pull time and is a stronger reproducibility anchor.
A container is running and has a network IP. Does that prove users can reach an application on the host?
No. Network attachment is separate from port publication, application listening, firewall/routing behavior, and end-to-end health.
Why can “not observed because Docker Desktop hides the daemon host PID namespace” be a correct checkpoint result?
Because honest evidence must respect platform boundaries. Fabricating host namespace values is worse than explicitly documenting that the daemon host is inside a managed VM and using portable Docker inspection instead.
What would be wrong with finishing cleanup using
docker system prune -a --volumes?
It is far broader than the lab ownership boundary and can remove unrelated images, networks, caches, and volumes. The checkpoint requires exact resource-identity cleanup.
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.
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.
- Docker documentation — product documentation and current operating guidance.
- Docker Engine — Engine architecture, daemon, objects, and administration.
- Docker Engine 29 release notes — current 29.x changes; 29.8.1 was released 2026-09-15.
- Docker Engine API — API negotiation and current client/server compatibility guidance.
- Docker Engine security — namespaces, cgroups, daemon attack surface, and kernel hardening.
- Seccomp security profiles — default syscall filtering and why disabling it is not a routine fix.
- Runtime metrics — current cgroup v2 behavior and container cgroup inspection.
- Rootless mode — daemon/container privilege reduction and user-namespace model.
-
Docker Desktop on Linux
— the VM-backed Desktop execution boundary and
desktop-linuxcontext. - Open Container Initiative — Image, Runtime, and Distribution specifications.
- OCI Runtime Specification — runtime configuration, execution environment, and lifecycle; OCI Runtime Spec v1.3.0 was released in November 2025.
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.
0x716c4Ab160C4B66F31a28AE2448BfF68fc3a2ef0
Send only Ethereum/ERC-20 compatible assets to this
address.