Run Docker Rootless
22 Sep 2026Rootless Docker runs the daemon and every container as your own user instead of root. A container escape then lands you as a normal user, not root on the host. The tradeoffs are slower networking and a few things (low ports, some volume permissions) that need extra steps.
What “rootless” actually changes
Normal Docker runs dockerd as root. Anyone who can talk to the Docker socket effectively has root on the host — that’s the whole reason “add user to the docker group” is a security warning, not just a convenience tip. A container breakout bug, or a malicious image that exploits a kernel or runtime bug, has a straight line to full host compromise.
Rootless mode runs the daemon itself as your unprivileged user, using user namespaces to remap container UID 0 to an unprivileged UID on the host. Root inside the container is not root outside it. If something escapes the container, it’s still just your user account.
Setting it up
Most distros ship the rootless setup script with Docker’s install:
dockerd-rootless-setuptool.sh install
This configures a per-user systemd service and a user-owned socket. Point your shell at it:
export DOCKER_HOST=unix:///run/user/$(id -u)/docker.sock
Add that to your shell profile so every new session picks it up. Verify:
docker info | grep -i rootless
What breaks
Ports below 1024. A rootless process can’t bind port 80 or 443 directly — that’s a kernel restriction on all unprivileged processes, not a Docker limitation. Put a reverse proxy (Caddy or nginx) in front, running with CAP_NET_BIND_SERVICE, or use sysctl net.ipv4.ip_unprivileged_port_start=80 to lower the threshold if you understand the tradeoff.
Some storage drivers. Rootless Docker defaults to fuse-overlayfs or native overlay depending on kernel support, and it’s measurably slower than the root daemon’s overlay2 on heavy build workloads. If your CI does a lot of image builds, expect longer build times.
Bind mounts and ownership. Files created inside a rootless container map to a subuid range on the host, not your literal UID. A bind-mounted directory can show up owned by a UID like 100000+ instead of your user, which breaks scripts that assume a specific owner. Check /etc/subuid for the mapping range if permissions look wrong.
cgroup resource limits. Setting memory or CPU limits (--memory, --cpus) needs cgroup v2 with delegation enabled for your user. Most current distros ship this by default, but older ones need systemd’s user@.service drop-in changes to delegate cgroup controllers to user sessions — without it, resource limit flags are silently ignored.
Comparison
| Root Docker | Rootless Docker | |
|---|---|---|
| Container escape impact | Root on host | Your user only |
| Bind ports below 1024 | Yes | No (needs proxy or sysctl) |
| Build speed | Faster (overlay2) | Slower (fuse-overlayfs) |
| Setup | Default | One extra install step |
Verdict
Run rootless on anything internet-facing or multi-tenant — the security win is real and the setup cost is one script. Keep root Docker for CI build agents where build speed matters more than isolation and the runner is already sandboxed some other way (a VM, a throwaway instance). Don’t run rootless and then sudo docker “just this once” for convenience — that defeats the entire point and you’ll forget which containers ended up under which user.