Docker from First PrinciplesPost 16 of 18 · 18 of 18 written

16. Hands-on learning path

Docker · 16 of 184 min readOct 2026

Fourteen labs, each proving one idea from the guide with your own hands. Labs 1 to 10 run on your Mac; 11 and 12 need a rented Linux GPU machine; 13 and 14 run anywhere. Keep a short lab notebook: the command, what you expected, what happened.

  • Lab 1 - A container is a process. Get a Linux shell with namespaces you can inspect: on the Mac, docker run --rm -it --privileged --pid=host ubuntu:24.04 bash shows the VM’s processes. Start docker run -d --name s alpine sleep 1000 in another terminal and find the sleep process in the first one. Compare uname -r in an Ubuntu and an Alpine container. Done when you can explain why the kernel version is identical.
  • Lab 2 - Build a container by hand. Export a filesystem with docker export $(docker create alpine) -o alpine.tar. In a privileged Ubuntu container with the tar mounted, run mkdir rootfs && tar -xf alpine.tar -C rootfs, then unshare --mount --uts --ipc --net --pid --fork chroot rootfs /bin/sh. Inside, mount -t proc proc /proc, then try ps, hostname foo and ip addr. Done when you’ve seen PID 1, a private hostname and an empty network: a container with no Docker involved.
  • Lab 3 - Cgroups kill. Run docker run --name oom --memory 100m --memory-swap 100m python:3.12-slim python -c "x = bytearray(300_000_000)", then echo $? and docker inspect -f '{{.State.OOMKilled}}' oom. Done when you’ve seen exit 137 and true.
  • Lab 4 - Layers don’t forget. Write one Dockerfile that creates a 500 MB file in one RUN and deletes it in the next, and another that does both in one RUN. Compare sizes with docker image ls and inspect with docker history and dive. Done when you can explain the difference by pointing at a layer.
  • Lab 5 - The cache. Build a small FastAPI image with the slow ordering from Part 6, edit one .py file, and time the rebuild; then reorder and repeat. Add a uv or pip cache mount and time a dependency change. Done when a code-only rebuild takes seconds.
  • Lab 6 - PID 1 and signals. Write a Python loop that prints a message and exits on SIGTERM. Build it once with CMD ["python", "app.py"] and once with CMD echo start && python app.py (shells often hand a single command straight over, so the second command keeps the shell as PID 1). Time docker stop for each. Done when you’ve seen the 10-second hang and the clean exit.
  • Lab 7 - Networking. Run two containers on the default bridge and try to reach one by name; repeat on a user-defined network. Run a server bound to 127.0.0.1, publish its port and fail to reach it; rebind to 0.0.0.0. Done when you can predict each result before running it.
  • Lab 8 - Storage. Create a named volume for a Hugging Face cache, download a small model in one container and load it offline in a second. Bind-mount a dataset read-only and confirm a write fails. Done when the second container loads the model with no network (--network none).
  • Lab 9 - A Compose RAG stack. Build the stack from Part 9 with FastAPI, pgvector and Redis, with Ollama running natively on the Mac. Add an ingestion command run with docker compose run --rm. Done when docker compose down && docker compose up -d brings everything back, data included.
  • Lab 10 - Multi-architecture. Build your API image for linux/amd64,linux/arm64, push it to GHCR, and check the index with docker buildx imagetools inspect. Done when the same tag runs on your Mac and on an amd64 cloud VM.
  • Lab 11 - GPU basics. On a rented GPU VM, install the driver and the NVIDIA Container Toolkit and run the nvidia-smi smoke test from Part 10, driven from your Mac through a Docker context. Train a small model with num_workers=4 without --shm-size, watch it fail, then fix it. Done when you can explain each of the four pieces in the GPU table.
  • Lab 12 - Serving. On the GPU VM, run compose.gpu.yaml with vLLM, add a readiness healthcheck, put Caddy in front with TLS, and keep the model port bound to 127.0.0.1. Send a request during docker compose restart and observe what happens. Done when a redeploy drops no requests.
  • Lab 13 - Hardening. Take the API image: non-root user, --read-only with a tmpfs, --cap-drop ALL, no-new-privileges. Scan it with Trivy or Docker Scout and generate an SBOM. Done when the service still works and the scan has no critical findings you can’t explain.
  • Lab 14 - Beyond Docker. Either convert your training image to Apptainer and run it on a cluster you have access to, or deploy the API to a local kind cluster with a Deployment, a Service and readiness probes. Done when you can map each Kubernetes object to its Docker equivalent from Part 15.