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.
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.
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.
--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?
The tag is human-readable but mutable; RepoDigests preserve the immutable content identity used by each daemon.
Why can the rootless daemon show a different image inventory from the rootful daemon?
They are separate daemons with separate sockets and data roots, so their local image/container state is independent.
Why is a high loopback port used in the lab?
It avoids privileged-port host changes and avoids exposing the training service on all host interfaces.
A resource flag fails only under rootless mode. What layer should be checked first?
The host cgroup version and systemd controller delegation, not the image or application.
Why should rootless setup not be forced through an unexplained daemon conflict?
Because a context/daemon ownership conflict is evidence that must be understood; forcing setup can create confusing parallel control planes.
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.