Chapter 22Lesson 05~120 minutes

Checkpoint Lab — CPU, Memory, PIDs, Devices, ulimits, cgroups v2, Reservations, and Container Resource Governance

Profile a synthetic workload, apply bounded resource controls, capture an evidence packet, and justify a reproducible container resource envelope.

Resource governancecgroups v2CPU & memoryPIDs & ulimitsEvidence-first

Learning objectives

  • Profile a synthetic workload before choosing limits.
  • Predict and verify CPU, memory, PID, and ulimit effects independently.
  • Capture Docker, cgroup, stats, and exit evidence in a reproducible packet.
  • Select a justified resource envelope and document uncertainty, host capacity, and platform limitations.
Checkpoint rule. This lab is successful only if you can explain the chosen envelope from evidence. “The container stayed up” is not enough. Keep every workload bounded, use only disposable names, and never run the lab against a production or shared daemon.

1. Scenario and deliverable

You own a small synthetic worker. Your task is to measure its baseline, predict the effect of CPU/memory/PID/ulimit controls, apply those controls one at a time, and produce an evidence packet that another operator can use to reproduce the decision. The final recommendation must include host/VM capacity assumptions and at least one limitation.

2. Preflight and evidence directory

Use a dedicated local directory for text evidence. Do not store secrets. The lab uses alpine:3.22 plus python:3.13-alpine only for the bounded memory probe.

mkdir -p dkr22-evidence

docker context show | tee dkr22-evidence/context.txt
docker version | tee dkr22-evidence/docker-version.txt
docker info | tee dkr22-evidence/docker-info.txt
docker compose version | tee dkr22-evidence/compose-version.txt 2>&1 || true
docker buildx version | tee dkr22-evidence/buildx-version.txt 2>&1 || true

docker info --format 'cgroup={{.CgroupVersion}} driver={{.CgroupDriver}} cpus={{.NCPU}} memory={{.MemTotal}}'   | tee dkr22-evidence/capacity.txt

docker pull alpine:3.22
docker pull python:3.13-alpine
docker image inspect alpine:3.22 --format '{{json .RepoDigests}}' | tee dkr22-evidence/alpine-digest.txt
docker image inspect python:3.13-alpine --format '{{json .RepoDigests}}' | tee dkr22-evidence/python-digest.txt

3. Write predictions before execution

Record at least two predictions before running the controlled workloads. A good packet states the expected mechanism and the evidence that will confirm or falsify it.

Prediction Expected evidence
A 0.50 CPU ceiling will create a quota in cgroups v2. HostConfig plus cpu.max; stats should show bounded use, though exact samples vary.
64 MiB memory with equal memory-swap will not permit extra swap. HostConfig.Memory/MemorySwap; failure state and memory.events if the probe exceeds the working set.
A PID ceiling will reject process/thread creation before unbounded host exhaustion. PidsLimit, pids.max, bounded spawn errors/logs.
An explicit nofile rlimit will be visible to PID 1. HostConfig.Ulimits and ulimit -n//proc/1/limits.

4. Baseline and CPU-controlled workload

First run a short unrestricted CPU worker, capture one stats sample, then run the same loop under a half-CPU ceiling. This is not a scientific benchmark; it proves the policy/evidence chain. For performance conclusions, collect repeated samples under representative contention.

docker rm -f dkr22-base dkr22-cpu 2>/dev/null || true

docker run -d --name dkr22-base --label academy=docker-ch22 alpine:3.22 sh -c 'while :; do :; done'
sleep 2
docker stats --no-stream dkr22-base | tee dkr22-evidence/stats-baseline.txt
docker rm -f dkr22-base

docker run -d --name dkr22-cpu --label academy=docker-ch22 --cpus 0.50 alpine:3.22 sh -c 'while :; do :; done'
sleep 2
docker inspect dkr22-cpu > dkr22-evidence/cpu-inspect.json
docker stats --no-stream dkr22-cpu | tee dkr22-evidence/stats-cpu.txt
docker exec dkr22-cpu sh -c 'if [ -f /sys/fs/cgroup/cpu.max ]; then echo cpu.max=$(cat /sys/fs/cgroup/cpu.max); cat /sys/fs/cgroup/cpu.stat; fi'   | tee dkr22-evidence/cpu-cgroup.txt
docker rm -f dkr22-cpu

5. Memory-controlled workload and exit evidence

The allocation loop is intentionally small and bounded by a 64 MiB cgroup. Preserve the failed container until after inspection. If the process raises allocation failure rather than being OOM-killed, record that result instead of forcing a different outcome.

docker rm -f dkr22-mem 2>/dev/null || true
docker run --name dkr22-mem --label academy=docker-ch22   --memory 64m --memory-swap 64m python:3.13-alpine   python -c 'blocks=[]
while True: blocks.append(bytearray(8*1024*1024))'   >dkr22-evidence/memory-stdout.txt 2>dkr22-evidence/memory-stderr.txt || true

docker inspect dkr22-mem > dkr22-evidence/memory-inspect.json
docker inspect dkr22-mem \
  --format 'exit={{.State.ExitCode}} OOMKilled={{.State.OOMKilled}} memory={{.HostConfig.Memory}} memorySwap={{.HostConfig.MemorySwap}}'   | tee dkr22-evidence/memory-summary.txt
docker rm dkr22-mem

6. PID and nofile controls

Use a small fixed number of child-process attempts and a short lifetime. Then prove a separate nofile rlimit. These tests demonstrate two different resource mechanisms.

docker rm -f dkr22-pids 2>/dev/null || true
docker run --name dkr22-pids --label academy=docker-ch22 --pids-limit 12 alpine:3.22 sh -c '
  i=1
  while [ "$i" -le 18 ]; do sleep 4 & echo "attempt=$i rc=$?"; i=$((i+1)); done
  wait
' >dkr22-evidence/pids-stdout.txt 2>dkr22-evidence/pids-stderr.txt || true
docker inspect dkr22-pids > dkr22-evidence/pids-inspect.json
docker rm dkr22-pids

docker run \
  --rm \
  --name dkr22-ulimit \
  --label academy=docker-ch22 \
  --ulimit nofile=128:128 alpine:3.22 sh -c 'ulimit -n; cat /proc/1/limits | grep -i "open files" || true'   | tee dkr22-evidence/ulimit.txt

7. Capture cgroup-v2 evidence when available

Use one short-lived inspection container with the intended final envelope. Reading the mounted cgroup controller files from inside the container avoids guessing the host cgroup path and works across systemd/cgroupfs layouts when the files are visible.

docker rm -f dkr22-envelope 2>/dev/null || true
docker run -d \
  --name dkr22-envelope \
  --label academy=docker-ch22 \
  --cpus 0.50 \
  --memory 128m \
  --memory-swap 128m \
  --pids-limit 64 \
  --ulimit nofile=256:256 alpine:3.22 sleep 60

docker inspect dkr22-envelope > dkr22-evidence/envelope-inspect.json
docker stats --no-stream dkr22-envelope > dkr22-evidence/envelope-stats.txt
docker exec dkr22-envelope sh -c '
  echo "proc-cgroup:"; cat /proc/self/cgroup
  if [ -f /sys/fs/cgroup/cgroup.controllers ]; then
    for f in cpu.max cpu.weight memory.max memory.high memory.swap.max pids.max memory.events; do
      printf "%s=" "$f"; cat "/sys/fs/cgroup/$f" 2>/dev/null || echo unavailable
    done
  else
    echo "cgroups-v2-files=unavailable"
  fi
' > dkr22-evidence/envelope-cgroup.txt

docker rm -f dkr22-envelope

8. Choose and justify a resource envelope

Your conclusion must connect policy to evidence. A defensible statement looks like: “For this synthetic worker on a daemon reporting N CPUs and M bytes of memory, use 0.5 CPU, 128 MiB RAM with no extra swap, 64 PIDs, and 256 open files. This is a lab envelope, not a production sizing result; representative load and concurrency testing are still required.”

Do not claim a CPU or memory reservation from a standalone quota unless the target platform actually provides reservation semantics. Do not infer production capacity from one developer-laptop sample.

9. Verification checklist

  • Exact Docker context, Engine/CLI versions, cgroup version/driver, host-or-VM CPU count, and memory capacity are recorded.
  • Image repository digests for every workload image are recorded.
  • CPU policy is proved by both Docker configuration and cgroup evidence where available.
  • Memory failure preserves exit code, OOMKilled, logs, and configured memory/swap values.
  • PID and ulimit tests are bounded and their configuration is preserved.
  • No privileged container, Docker socket mount, daemon mutation, broad prune, host firewall change, or production endpoint is used.
  • The final resource envelope includes assumptions, uncertainty, and rollback criteria.

10. Cleanup and evidence archive

Remove only exact lab containers. Keep the evidence directory for review. If you archive it, inspect the files before sharing because docker info can contain host/runtime metadata even though this lab uses no secrets.

docker rm -f dkr22-base dkr22-cpu dkr22-mem dkr22-pids dkr22-ulimit dkr22-envelope dkr22-device 2>/dev/null || true
docker ps -a --filter label=academy=docker-ch22

tar -czf dkr22-evidence.tgz dkr22-evidence/

11. What this adds to a production Docker operating model

You can now treat CPU, memory, PIDs, rlimits, and devices as measurable operating contracts rather than arbitrary flags. Production readiness means the image identity, host/VM capacity, resource policy, runtime evidence, and failure response are independently verifiable. Chapter 23 adds the next layer: logs, logging drivers, rotation, events, stats, and observability contracts.

Knowledge check

Why does the checkpoint record host/VM capacity before choosing a container envelope?

The memory probe exits but OOMKilled=false. Is the lab invalid?

Why record both Docker HostConfig and cgroup files?

Can this synthetic envelope be copied directly to production?

A production container needs a GPU. What principle from this chapter applies?

Next lesson

Next: Logs, Logging Drivers, Rotation, stdout/stderr Contracts, Events, stats, and Container Observability: 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 baseline, verified 2026-09-21.

Docker Engine 29.8.1 is the current Engine baseline used here. Earlier Engine 29 packaging moved to containerd 2.x; one important consequence documented in Engine 29 release notes is that the default container nofile limit can follow systemd's default instead of older very-high Docker defaults. Docker Desktop runs Linux containers inside a VM, so host capacity, cgroup paths, devices, and swap evidence belong to that VM boundary. Record docker version, docker info, and the actual cgroup mode on your host rather than assuming these values.

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
0x716c4Ab160C4B66F31a28AE2448BfF68fc3a2ef0Send only Ethereum/ERC-20 compatible assets to this address.