rootless docker per member — provisioning works, running a container does not yet

Not finished. Committed because the diagnosis is worth more than the code.

WHY ROOTLESS AND NOT THE DOCKER GROUP. `usermod -aG docker <user>` is the one-line version
and it is root: `docker run -v /:/host -it alpine chroot /host` is a root shell, which reads
.env, every other member's home and the wallet seed. Every boundary from today, bypassed by
one documented command. Rootless gives what was actually asked for — a daemon per account,
containers in that account's user namespace, images in their own home.

VERIFIED on this host: provisioning succeeds, the server reports 29.5.0, the daemon runs as
the member, `docker pull` puts 403 MB under their own home, and `docker ps -a` shows nothing
while the owner has four containers. That last line is the isolation, measured.

NOT VERIFIED: actually running a container. It failed, and the cause is an interaction
between two things built today:

  failed to copy xattrs: failed to set xattr "system.posix_acl_default" on …/volumes/…/_data

Creating a volume copies xattrs, and the DEFAULT ACLs on a member's home — added so the file
browser could read their files — are inherited by Docker's storage, where a mapped id inside
a user namespace is not a valid id to set. Both features correct alone. The fix here strips
default ACLs from ~/.local/share/docker only, leaving the access ACLs the file browser needs.

That fix is UNPROVEN. The re-test failed for a different, environmental reason: probe users
recycle uid 1001, and a stale lingering systemd user manager from a previous probe answered
`systemctl --user`, so the unit appeared not to exist. Cleaned with `loginctl terminate-user`.
Retest on a machine that has not had a uid-1001 user, or on a fresh uid.

Also worth knowing before this ships: uid reuse after deleting a member is a real hazard, not
just a test artefact — the next member gets the previous member's uid, and anything left
lingering belongs to them.

setup.sh gains uidmap and dbus-user-session as core packages; the shell template exports
DOCKER_HOST from $XDG_RUNTIME_DIR when the socket exists.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-11 20:01:17 +00:00
co-authored by Claude Opus 5
parent 71589aee99
commit 3bea46f2d7
4 changed files with 246 additions and 5 deletions
+16
View File
@@ -230,6 +230,22 @@ case $PM in
;;
esac
# uidmap — newuidmap/newgidmap, needed for a member's own rootless Docker.
#
# Rootless containers map subordinate uid ranges, and those two setuid helpers are the only way to do it
# unprivileged. Without them `dockerd-rootless-setuptool.sh` fails at the first step. Core rather than a
# profile extra for the same reason acl is: the alternative to a member having their own daemon is adding
# them to the `docker` group, which is root on the host — see src/servers/os-user-docker.ts.
case $PM in
apt)
if dpkg -s uidmap &>/dev/null 2>&1; then skip "uidmap"; else CORE_PKGS+=(uidmap); fi
if dpkg -s dbus-user-session &>/dev/null 2>&1; then skip "dbus-user-session"; else CORE_PKGS+=(dbus-user-session); fi
;;
pacman)
if has newuidmap; then skip "uidmap (shadow)"; else CORE_PKGS+=(shadow); fi
;;
esac
# acl — setfacl/getfacl, needed by per-user Linux accounts.
#
# A member's home is 700 and owned by them, which is right for a shell and locks the platform out of the