Skip to content
Go back

Rootless Docker: Run Without Root

By SumGuy 6 min read
Rootless Docker: Run Without Root

Your containers are running as root and you probably don’t care — until you do

Most people install Docker, run the post-install steps that add their user to the docker group, and move on. Feels fine. Works fine. But here’s the thing: adding yourself to the docker group is functionally the same as giving yourself passwordless sudo. If a container escapes — or if someone finds a daemon exploit — you’ve handed them root on your host. No questions asked.

Rootless Docker flips that on its head. The daemon, the containers, everything runs under your user account. No privileged daemon sitting on a Unix socket owned by root. Your audit team will love you, and honestly, your 2 AM self will appreciate not having to explain a breach to anyone.

Rootless vs Regular Docker: What’s actually different?

With standard Docker, the dockerd daemon runs as root. Always. Even when you run docker run as your own user, you’re talking to a root-owned socket at /var/run/docker.sock. The docker group membership is just a shortcut to reach that socket — it doesn’t change what’s running underneath.

Regular DockerRootless Docker
Daemon runs asrootyour user
Socket location/var/run/docker.sock/run/user/$UID/docker.sock
Container escape impactfull host compromiselimited to your user’s permissions
Ports below 1024works out of the boxneeds extra config
Performancebaselineslight overhead (user namespaces + slirp4netns)
Privileged containerssupportedrestricted

The container escape scenario is the big one. If something nasty gets out of a rootless container, it’s running as you — not as root. It can mess with your home directory, sure, but it can’t touch system files, install kernel modules, or pivot to other users. That’s a meaningful security boundary.

This makes rootless an excellent choice for CI runners, shared servers, multi-user homelabs, and anywhere compliance is a real concern rather than a checkbox someone emails you about.

Prerequisites

You need Ubuntu 22.04+ or Debian 11+ (earlier versions work but require more manual setup). You’ll also need a regular Docker install already in place — if you haven’t done that yet, follow this guide first.

Now install the two packages rootless mode actually depends on:

Terminal window
sudo apt-get install -y uidmap slirp4netns docker-ce-rootless-extras

uidmap handles user namespace UID/GID mapping. slirp4netns is the userspace networking stack — it’s what lets containers reach the internet without needing raw socket access. Without these, the setup script will bail on you.

One more thing: make sure your user has subuid and subgid entries. Check:

Terminal window
grep $(whoami) /etc/subuid /etc/subgid

You should see something like youruser:100000:65536 in each file. If those lines are missing, add them:

Terminal window
sudo usermod --add-subuids 100000-165535 --add-subgids 100000-165535 $(whoami)

Install Docker rootless

This next step is important: run it as the user you want to use Docker as. Not root, not via sudo, not via su. SSH in as that user directly, or su -l youruser (the -l matters — it sets up a proper login environment).

Terminal window
dockerd-rootless-setuptool.sh install

At the end of the output you’ll see something like:

[INFO] Make sure the following environment variables are set (or add them to ~/.bashrc):
export PATH=/usr/bin:$PATH
export DOCKER_HOST=unix:///run/user/1000/docker.sock

Grab those export lines and drop them into your shell config. The UID in the socket path will match your actual user ID — don’t blindly copy 1000 if that’s not you.

~/.bashrc
export PATH=/usr/bin:$PATH
export DOCKER_HOST=unix:///run/user/1000/docker.sock

Then apply it:

Terminal window
source ~/.bashrc

The DOCKER_HOST variable is what tells the Docker CLI to talk to your rootless daemon instead of the system socket. Forget this and you’ll be staring at “permission denied” errors wondering what went wrong.

Verify it’s actually working

Check the service status:

Terminal window
systemctl --user status docker

You want active (running). If it’s not started yet:

Terminal window
systemctl --user start docker

Then do the sanity check:

Terminal window
docker run --rm hello-world

“Hello from Docker!” means you’re in business. If it hangs or errors, double-check that DOCKER_HOST is set in your current shell — it’s the most common trip-up.

Make it survive a reboot

Two things need to happen for rootless Docker to start automatically:

Terminal window
systemctl --user enable docker
sudo loginctl enable-linger $(whoami)

The first one enables the user systemd service. The second one — loginctl enable-linger — is the one people miss. By default, user systemd services only run while you’re logged in. Linger tells the system to keep your user’s systemd session alive even when you’re not connected. Skip it and your containers will die the moment the server reboots and nobody logs in.

Ports below 1024

By default, rootless containers can’t bind to privileged ports (anything under 1024). So no port 80, no port 443 directly. You’ve got options:

Option 1: Map to a high port and reverse proxy it. Honestly, this is what you should be doing anyway. Run Caddy or nginx on the host (or rootful), have it proxy to your container on 8080/8443.

Option 2: Grant rootlesskit the capability. If you really need the container itself to bind to port 80:

Terminal window
sudo setcap cap_net_bind_service=ep $(which rootlesskit)
systemctl --user restart docker

This is surgical — it grants that one capability to rootlesskit without blowing up the whole security model.

When should you NOT use rootless?

Honest answer: it’s not always the right call.

Skip rootless if you need privileged containers. docker run --privileged does work in some rootless configurations, but you lose most of the security benefit and gain complexity. If you’re running containers that need to manipulate kernel parameters, load modules, or do things like modify network namespaces, you’re better off with rootful Docker in a tightly controlled environment.

Skip it on single-user servers where you’re already root. If you’re the only person on a box and you manage it as root, rootless adds ceremony without much benefit.

Consider it carefully for high-throughput workloads. The slirp4netns networking layer has overhead compared to the veth pairs rootful Docker uses. For most homelab workloads it’s invisible, but if you’re benchmarking something network-intensive you’ll notice.

For everything else — shared servers, CI, anything facing the internet, anything that needs to pass a security audit — rootless is the right default.

Your containers are sandboxed. Now go run something interesting.

That’s the full setup. You’ve got a Docker daemon running as your user, containers that can’t reach root if they escape, and a system that survives reboots. The tradeoffs are real but narrow, and for most use cases you’ll never hit them.

If you’re running a homelab and you’re not using rootless, this is the one change that actually moves your security posture. Everything else is configuration tweaks — this is architecture.


Share this post on:

Send a Webmention

Written about this post on your own site? Send a webmention and it'll show up above once verified.


Previous Post
LUKS Full Disk Encryption on Linux
Next Post
PostgreSQL + Linux: Kernel Tuning That Actually Matters

Discussion

Powered by Garrul . Sign in with GitHub or Google, or post anonymously.

Related Posts