Chapter 26Lesson 02~150 minutes

Rootless Docker, RootlessKit, User Namespaces, Networking, Storage, Limitations, and Threat Reduction: Guided Hands-On Workflow and Core Operations

Build an evidence-first rootless workflow: verify prerequisites, inspect or create a user daemon safely, switch contexts, examine mappings, publish a bounded port, and compare rootful versus rootless state.

Rootless labContextsUID/GID mappingPublished portsData root

Learning objectives

  • Verify rootless prerequisites and existing contexts without destructive host changes.
  • Run a bounded rootless workload, inspect UID/GID mappings, and verify a loopback published port.
  • Compare rootless socket/data-root/security state with an existing rootful daemon without mutating either control plane.
  • Clean only the exact training container and preserve rootless daemon data unless an explicit lab-account retention decision says otherwise.
Lab principle. Prefer inspection of an existing rootless daemon. Install only on a dedicated authorized Linux lab where prerequisites are already satisfied; never disable host security controls to make the exercise pass.

1. Lab scope and safety model

This workflow is for an authorized disposable Linux host, VM, or training account. If a rootless daemon already exists, use the inspection path and do not reinstall it. If the host is Docker Desktop, do not modify Desktop internals; use the comparison notes instead.

No broad host mutation. The lab does not disable the system daemon, rewrite sysctls, grant privileged ports, expose a Docker API over TCP, use --privileged, or remove unrelated Docker data. Any package/subuid provisioning needed for a new rootless account is a host-administrator prerequisite, not a container workaround.

2. Preflight: capture client, host, subordinate IDs, and current context

mkdir -p "$HOME/da-ch26-evidence"
date -Is | tee "$HOME/da-ch26-evidence/time.txt"
uname -a | tee "$HOME/da-ch26-evidence/uname.txt"
id | tee "$HOME/da-ch26-evidence/id.txt"
docker version | tee "$HOME/da-ch26-evidence/docker-version.txt"
docker context ls | tee "$HOME/da-ch26-evidence/contexts-before.txt"
docker context show | tee "$HOME/da-ch26-evidence/context-before.txt"
command -v newuidmap || true
command -v newgidmap || true
grep "^$(whoami):" /etc/subuid 2>/dev/null || true
grep "^$(whoami):" /etc/subgid 2>/dev/null || true
stat -fc %T /sys/fs/cgroup 2>/dev/null || true
systemctl --user show-environment >/dev/null 2>&1 && echo 'user-systemd: available' || true

A new installation should proceed only if newuidmap, newgidmap, and suitable subordinate ranges are present. Docker requires at least 65,536 subordinate UIDs and GIDs.

3. Install only when the host is a dedicated Linux lab; otherwise inspect

If docker context ls already contains a working rootless context, skip installation. On a dedicated Linux lab with Docker rootless extras already installed, the supported setup command is:

dockerd-rootless-setuptool.sh check
dockerd-rootless-setuptool.sh install

The setup tool normally creates a user-level systemd service and a rootless Docker context. It may instruct the administrator to satisfy prerequisites if they are absent. Do not use --force merely to silence an unexplained conflict with another daemon.

4. Switch context deliberately and prove the endpoint

docker context use rootless
docker context show
docker context inspect rootless
docker info --format '{{json .SecurityOptions}}'
docker info --format 'Root={{.DockerRootDir}} Driver={{.Driver}} Cgroup={{.CgroupVersion}}'
printf 'XDG_RUNTIME_DIR=%s
' "$XDG_RUNTIME_DIR"
ls -l "$XDG_RUNTIME_DIR/docker.sock" 2>/dev/null || true

The important state change is the client target. The image/container inventory visible now belongs to the rootless daemon; it is not expected to match the rootful daemon inventory.

5. Run a pinned human-readable test image and record immutable identity

Use a versioned small image tag, then immediately record the resolved digest. The tag is convenient; the digest is the evidence.

LAB_IMAGE=busybox:1.37.0
docker pull "$LAB_IMAGE"
docker image inspect "$LAB_IMAGE" --format '{{json .RepoDigests}}'
docker run \
  --rm "$LAB_IMAGE" sh -c 'echo inside-id:; id; echo uid-map:; cat /proc/self/uid_map; echo gid-map:; cat /proc/self/gid_map' | tee "$HOME/da-ch26-evidence/mapping.txt"

6. Inspect a long-running container, rootless port forwarding, and ownership

docker run -d \
  --name da-ch26-web \
  --label academy.chapter=26   -p 127.0.0.1:18080:8080 busybox:1.37.0   sh -c 'while true; do printf "HTTP/1.1 200 OK\r\nContent-Length: 8\r\n\r\nrootless" | nc -l -p 8080; done'

docker inspect da-ch26-web \
  --format 'User={{.Config.User}} PID={{.State.Pid}} IP={{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}} Ports={{json .NetworkSettings.Ports}}'
curl -fsS http://127.0.0.1:18080 || true
docker port da-ch26-web
docker stats --no-stream da-ch26-web

Port 18080 is intentionally non-privileged and bound to loopback. Rootless IP addresses may live inside RootlessKit’s network namespace and are not a durable host-reachability contract; the published loopback port is the intended access path.

7. Compare rootless and rootful daemon identity without mutating either

If a separate rootful context exists, record it; do not start/stop system services just for this comparison.

docker context ls
ROOTLESS_CTX=$(docker context show)
printf 'rootless-context=%s
' "$ROOTLESS_CTX" | tee "$HOME/da-ch26-evidence/rootless-context.txt"

# Optional: replace "default" only if it is already your known rootful context.
docker context inspect default >/dev/null 2>&1 && {
  docker \
  --context default info \
  --format 'rootful-root={{.DockerRootDir}} security={{json .SecurityOptions}}'     | tee "$HOME/da-ch26-evidence/rootful-summary.txt"
}

docker \
  --context "$ROOTLESS_CTX" info \
  --format 'rootless-root={{.DockerRootDir}} security={{json .SecurityOptions}}'   | tee "$HOME/da-ch26-evidence/rootless-summary.txt"

8. Bounded cleanup and optional rootless uninstall

docker --context rootless rm -f da-ch26-web 2>/dev/null || true
docker context use rootless

# Optional only if THIS lab installed the rootless daemon on a dedicated account:
# dockerd-rootless-setuptool.sh uninstall

The uninstall tool removes the user service but intentionally does not imply that all user-owned Docker data should be erased. Preserve or remove the rootless data root only under an explicit retention decision for the dedicated lab account. Do not run a broad recursive delete against a personal home directory.

9. Challenge: identify the failing layer before changing it

A learner can run ordinary containers rootlessly, but --memory is rejected and a bind-mounted file appears owned by an unexpected host UID. Which layers should be investigated first?

Expected reasoning: resource-control failure points to cgroup v2/systemd delegation; ownership points to subordinate-ID mapping and bind-mount semantics. Neither problem is evidence that the image needs to be rebuilt, the network needs changing, or rootless mode should be abandoned.

Knowledge check

Why does the lab record RepoDigests after pulling a versioned image tag?

Why can the rootless daemon show a different image inventory from the rootful daemon?

Why is a high loopback port used in the lab?

A resource flag fails only under rootless mode. What layer should be checked first?

Why should rootless setup not be forced through an unexplained daemon conflict?

Next lesson

Next: Rootless Docker, RootlessKit, User Namespaces, Networking, Storage, Limitations, and Threat Reduction: Configuration, Design Choices, and Tradeoffs

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

Official references and version notes

Version/platform baseline, verified 2026-09-22.

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.

Ethereum / ERC-20
0x716c4Ab160C4B66F31a28AE2448BfF68fc3a2ef0Send only Ethereum/ERC-20 compatible assets to this address.