How to Run Rootless Docker (Containers That Can't Become Root on the Host)

Several tutorials noted it: docker.sock is root on the host, and the docker group is effectively sudo. Normally that's accepted. Rootless mode changes the deal — the Docker daemon itself runs as your user, inside user namespaces, so even a container that breaks out of its sandbox finds itself as john, not root. It costs some convenience (low ports, a few features) and is worth it on machines that run untrusted or internet-facing containers.
Quick reference
| Task | What | |
|---|---|---|
| Install | dockerd-rootless-setuptool.sh install (from docker-ce-rootless-extras) | |
| Socket | unix:///run/user/1000/docker.sock — set DOCKER_HOST | |
| Start at boot | systemctl --user enable docker + loginctl enable-linger john | |
| Ports under 1024 | sysctl net.ipv4.ip_unprivileged_port_start=80 | |
| Won't work | host networking details, --privileged device access, some cap_add, macvlan, swap limits | |
| Check | `docker info | grep -i rootless → rootless` in Security Options |
1. Prerequisites
sudo apt install uidmap dbus-user-session docker-ce-rootless-extras -y
grep ^john /etc/subuid /etc/subgid
Both files need a line like john:100000:65536 — the range of IDs your containers map into. Ubuntu adds it when the user is created; add it with sudo usermod --add-subuids 100000-165535 --add-subgids 100000-165535 john if missing.
If the rootful daemon is running and you're migrating fully, stop it: sudo systemctl disable --now docker.service docker.socket.
2. Install the rootless daemon
As your user, not root:
dockerd-rootless-setuptool.sh install
It sets up a user-level systemd service and prints two lines to add to ~/.bashrc:
export PATH=/usr/bin:$PATH
export DOCKER_HOST=unix:///run/user/1000/docker.sock
systemctl --user enable --now docker
sudo loginctl enable-linger john
docker info | grep -i rootless
loginctl enable-linger keeps your user services running when you're not logged in — otherwise containers stop when you log out.
3. Ports below 1024
The rootless daemon can't bind 80 or 443 by default:
echo "net.ipv4.ip_unprivileged_port_start=80" | sudo tee /etc/sysctl.d/99-rootless.conf
sudo sysctl --system
systemctl --user restart docker
Or keep the reverse proxy (Nginx/Caddy) on the host and proxy to high ports — which many setups already do.
4. Migrate a stack
Images and volumes live in ~/.local/share/docker now, separate from the rootful /var/lib/docker. For each stack: docker compose pull && docker compose up -d as your user; bind-mounted ./data folders carry over unchanged (they're yours already). Named volumes need the tar trick (65) once. Then remove the rootful data when you're sure: sudo rm -rf /var/lib/docker.
Files written by a container's root user now appear on the host as UID 100000 (the mapped range) — ls -ln makes sense of it, and podman unshare-style tools or chown through the mapping if you need to touch them.
5. What doesn't work (and the workarounds)
- Host networking works but can't bind low ports and sees the user namespace's view; Home Assistant and Netdata mostly behave, Pi-hole DHCP (144) and macvlan (101) don't — keep those rootful, on a Pi, or in Incus (142).
- --privileged and device passthrough (Frigate's Coral, Zigbee sticks, /dev/dri) need the device readable by your user (udev rules, render/dialout groups) and often still fail — run those stacks rootful on a separate host or in a VM.
- Memory/swap limits need cgroup v2 delegation (Ubuntu 22.04+ has it) — mem_limit works, memswap_limit may not.
- ufw-docker (105) isn't needed: rootless publishes via a userspace proxy that does respect UFW.
- Watchtower, Dozzle, Portainer work with the socket path changed to /run/user/1000/docker.sock.
6. Podman instead?
Podman is rootless by design, daemonless, and runs most compose files via podman-compose or podman compose. Systemd integration (Quadlet) is excellent for "a container as a service". If you're starting fresh on a security-first box, Podman is a fine choice; if you've got sixty tutorials' worth of Docker habits, rootless Docker is the smaller step.
Where it's worth it
Internet-facing hosts (a VPS with a public site, the Matrix server), the box that runs other people's images you haven't audited, and anything exposed via Cloudflare Tunnel (109). The home media box with a Coral stick: probably not — the hardware access fights you and the exposure is low.
Troubleshooting
docker: command not found / connects to the wrong daemon. DOCKER_HOST not exported in this shell; source ~/.bashrc.
Containers stop when you log out. loginctl enable-linger.
"permission denied" binding port 443. The sysctl in step 3.
Slow networking. The default userspace proxy (slirp4netns) is slower; install passt/pasta and set it as the network driver for near-native speed.
Wrapping up
Install the rootless extras, run the setup tool as yourself, linger on, low ports via sysctl, migrate the stacks that fit. Root is no longer one container escape away on the hosts that face the world — and the stacks that need raw hardware stay where they are.
