Checkpoint Lab — Rootless Docker, RootlessKit, User Namespaces, Networking, Storage, Limitations, and Threat Reduction
Compare one synthetic workload under rootful and rootless contexts, preserve socket/data-root/UID/network/resource evidence, and state exactly which threat boundary improved and which did not.
Learning objectives
- Run a controlled rootful-versus-rootless comparison using the same immutable image identity.
- Capture socket/data-root/UID/network/resource/storage evidence and verify predictions independently.
- State the improved threat boundary and the risks that remain shared or external.
- Produce an auditable evidence packet and bridge the result to UID/GID ownership work in Chapter 27.
1. Checkpoint objective and prerequisites
Run the same synthetic workload under a known rootful context and a known rootless context, then compare control-plane authority and observable behavior. If the host has no authorized rootful context, record only the rootless side and use the documented rootful expected-state table; do not create a privileged daemon merely for the exercise.
2. Create a clean evidence directory and capture assumptions
E="$HOME/da-ch26-checkpoint"
mkdir -p "$E"
date -Is | tee "$E/time.txt"
uname -a | tee "$E/uname.txt"
id | tee "$E/host-id.txt"
docker version | tee "$E/docker-version.txt"
docker context ls | tee "$E/contexts.txt"
grep "^$(whoami):" /etc/subuid 2>/dev/null | tee "$E/subuid.txt" || true
grep "^$(whoami):" /etc/subgid 2>/dev/null | tee "$E/subgid.txt" || true
stat -fc %T /sys/fs/cgroup 2>/dev/null | tee "$E/cgroup-fs.txt" || true
3. Predict before execution
Write predictions into $E/predictions.txt before
running containers. At minimum:
-
The rootless context will use a user-owned socket/data root and
report
rootlessin SecurityOptions; the rootful context will not. - Container UID 0 under rootless will map through a user namespace rather than to host UID 0.
- A loopback published high port such as 18081 will be reachable through the rootless port-forwarding path even though the container endpoint IP may not be directly reachable from the real host namespace.
- Resource limits will only be reliable in rootless mode if cgroup v2/systemd delegation is available.
4. Record both daemon identities before running the workload
ROOTLESS_CTX=rootless
ROOTFUL_CTX=default
docker \
--context "$ROOTLESS_CTX" info \
--format 'Security={{json .SecurityOptions}} Root={{.DockerRootDir}} Driver={{.Driver}} Cgroup={{.CgroupVersion}}' | tee "$E/rootless-info.txt"
docker \
--context "$ROOTFUL_CTX" info \
--format 'Security={{json .SecurityOptions}} Root={{.DockerRootDir}} Driver={{.Driver}} Cgroup={{.CgroupVersion}}' | tee "$E/rootful-info.txt"
If default is not your authorized rootful daemon, stop
and replace it with the correct existing context or omit that half
of the comparison.
5. Pull once per daemon and preserve immutable image evidence
IMG=busybox:1.37.0
for C in "$ROOTLESS_CTX" "$ROOTFUL_CTX"; do
docker --context "$C" pull "$IMG"
docker --context "$C" image inspect "$IMG" --format '{{json .RepoDigests}}' | tee "$E/$C-repodigests.txt"
done
Separate pulls are intentional because each daemon has its own image store. Matching RepoDigests prove content identity; they do not imply identical daemon authority or networking/storage state.
6. Run the same identity workload in each context
for C in "$ROOTLESS_CTX" "$ROOTFUL_CTX"; do
docker --context "$C" run --rm "$IMG" sh -c 'id; cat /proc/self/uid_map; cat /proc/self/gid_map' | tee "$E/$C-mapping.txt"
done
Interpret the maps, not just id. Inside both containers
the process may print UID 0; the outer mapping and daemon privilege
model are the security-relevant difference.
7. Run a bounded service and capture rootless network/resource evidence
docker \
--context "$ROOTLESS_CTX" run -d \
--name da-ch26-cp \
--label academy.chapter=26 -p 127.0.0.1:18081:8080 "$IMG" sh -c 'while true; do printf "HTTP/1.1 200 OK\r\nContent-Length: 2\r\n\r\nOK" | nc -l -p 8080; done'
docker \
--context "$ROOTLESS_CTX" inspect da-ch26-cp \
--format 'ID={{.Id}} PID={{.State.Pid}} Networks={{json .NetworkSettings.Networks}} Ports={{json .NetworkSettings.Ports}}' | tee "$E/rootless-container.txt"
docker --context "$ROOTLESS_CTX" stats --no-stream da-ch26-cp | tee "$E/rootless-stats.txt"
curl -fsS http://127.0.0.1:18081 | tee "$E/rootless-http.txt"
8. Optional resource-control proof when cgroup v2/systemd delegation is present
Only run this if the preflight confirms cgroup v2 and the rootless daemon accepts delegated controls:
docker \
--context "$ROOTLESS_CTX" run \
--rm \
--memory 64m \
--pids-limit 64 "$IMG" sh -c 'echo resource-controls-active; cat /proc/self/cgroup' | tee "$E/rootless-resource-proof.txt"
If it fails because controllers are not delegated, preserve that failure as platform evidence; do not switch to privileged mode.
9. Threat-boundary conclusion
Create $E/conclusion.txt answering four questions:
- Improved: Which host-root authority did the rootless daemon/runtime no longer possess?
- Unchanged: Which shared-kernel/application/credential risks still exist?
- Different behavior: Which networking/storage/cgroup behaviors differed from rootful execution?
- Decision: Is rootless appropriate for this workload, or is a rootful feature/VM/sandbox boundary required?
10. Verification checklist and exact cleanup
- Rootless SecurityOptions captured.
- Rootless and rootful data roots compared.
- RepoDigests recorded separately per daemon.
- UID/GID maps captured and interpreted.
- Published port verified without exposing all host interfaces.
- cgroup/storage/network limitations recorded rather than hidden.
- No daemon config, firewall, LSM, or privileged-mode workaround used.
docker --context rootless rm -f da-ch26-cp 2>/dev/null || true
# Keep $HOME/da-ch26-checkpoint as the evidence packet, or archive it deliberately:
tar -C "$HOME" -czf "$HOME/da-ch26-checkpoint.tgz" da-ch26-checkpoint
Review the evidence packet before sharing it: context endpoints, host usernames, paths, and system metadata can be sensitive in real environments.
11. What Chapter 26 adds—and the bridge to Chapter 27
Chapter 26 adds a daemon-level least-authority option: the Docker daemon and runtime themselves can execute without host-root privilege, with explicit user-namespace, networking, storage, and cgroup tradeoffs. Chapter 27 narrows the lens to UID/GID mapping, userns-remap, non-root images, filesystem ownership, and least-privilege identity—concepts that remain important whether the daemon is rootful or rootless.
Knowledge check
Why pull the same image separately in rootful and rootless contexts?
Each daemon has its own local image store; separate pulls plus matching RepoDigests prove content identity without conflating daemon state.
What two predictions must be verified independently in the checkpoint?
At minimum the daemon/socket/data-root rootless boundary and the UID/GID namespace mapping; networking/resource predictions are also captured.
If the rootless memory-limit proof fails because cgroup controllers are not delegated, what should you do?
Preserve the failure as platform evidence and fix/choose an appropriate host architecture; do not use privileged mode to bypass it.
What threat does rootless mode reduce most directly?
The daemon/runtime no longer starts with host-root authority, reducing the blast radius of that control-plane compromise class.
What threat remains after switching to rootless?
Shared-kernel vulnerabilities, application flaws, stolen credentials, deliberately exposed host mounts, and network attacks still exist.
Official references and version notes
- Docker Engine rootless mode — architecture, prerequisites, setup, contexts, and subordinate UID/GID requirements.
- Rootless mode tips — socket/data-root locations, systemd user service, cgroup resource controls, ports, and advanced operation.
- Rootless troubleshooting — supported storage drivers, cgroup requirements, unsupported features, networking drivers, source-IP behavior, and current host-network behavior.
- User namespace remapping — contrast between a rootful daemon with remapped containers and fully rootless daemon execution.
- Storage driver selection — rootless overlay2/fuse-overlayfs guidance and backing-filesystem considerations.
- Docker Engine 29 release notes — Engine 29.5 rootless networking changes and Engine 29.8 RootlessKit updates.
- Docker Desktop for Linux FAQ — why Docker Desktop uses a VM instead of ordinary Rootless Docker.
- Docker Desktop networking — VM/backend networking boundary on Desktop platforms.
- RootlessKit upstream — user-namespace networking and port-forwarding implementation used by Rootless Docker.
Docker Engine 29.8.1 is the current Engine release. Engine 29.5
changed packaged rootless networking so
gvisor-tap-vsock is the preferred/default path when
slirp4netns is not separately installed, and rootless
host networking now reaches the real host network namespace. Engine
29.8 updates RootlessKit to 3.1.0 and adds the optional
pesto port driver when paired with
pasta (IPv4 only). Rootless resource limits require
cgroup v2 plus systemd delegation; rootless storage support is
host/kernel dependent. Docker Desktop uses an Engine inside a
managed Linux VM and must not be described as ordinary Rootless
Docker. Every lab therefore records actual
docker version, context, security options,
cgroup/storage/network state rather than assuming upstream defaults.
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.
0x716c4Ab160C4B66F31a28AE2448BfF68fc3a2ef0Send only Ethereum/ERC-20 compatible assets to this
address.