Normally the Docker daemon runs as root, and anyone in the docker group effectively has root on the server. Rootless mode runs the daemon and every container under an ordinary user account instead. If a container is compromised, the attacker lands in that user's account, not root's. This guide sets it up on Ubuntu and explains what you give up in return.
Before you start
- A Linux VPS running Ubuntu 22.04 or 24.04, with a
sudouser. - Docker Engine installed from Docker's official apt repository, including the
docker-ce-rootless-extraspackage, which the standard install pulls in. See Install Docker and Docker Compose. - A dedicated non-root user that will own the rootless daemon, for example
deploy. Create one with Create a sudo user on Linux if needed, but rootless Docker itself does not need sudo rights.
Warning: The steps below stop the system-wide Docker daemon. Containers running under it will stop. Plan a maintenance window, or test on a fresh server first.
Install the prerequisites
Rootless mode maps a range of user IDs into containers, which needs the uidmap tools:
sudo apt update
sudo apt install -y uidmap dbus-user-sessionCheck that your user has a subordinate ID range:
grep deploy /etc/subuid /etc/subgidEach file should have a line such as deploy:100000:65536. Ubuntu adds one when the user is created. If it is missing, add it with sudo usermod --add-subuids 100000-165535 --add-subgids 100000-165535 deploy.
Stop the root daemon
If you do not need the root daemon any more, disable it:
sudo systemctl disable --now docker.service docker.socket
sudo rm -f /var/run/docker.sockYou can keep both running side by side, but it is easy to talk to the wrong one, so most people disable the root daemon.
Run the setup tool
Log in directly as the rootless user over SSH. Do not use sudo su - deploy, because the user's systemd session and XDG_RUNTIME_DIR would not be set up.
ssh deploy@203.0.113.10
dockerd-rootless-setuptool.sh installThe tool checks requirements, creates a user-level systemd service called docker.service and starts it. If a requirement is missing, it prints the exact command to fix it. On Ubuntu 24.04, it may ask you to add an AppArmor profile for rootlesskit, because that release restricts unprivileged user namespaces. Follow the commands it prints, then run the setup again.
At the end it prints two environment lines. Add them to ~/.bashrc so every shell uses the rootless daemon:
echo 'export DOCKER_HOST=unix:///run/user/$(id -u)/docker.sock' >> ~/.bashrc
source ~/.bashrcStart on boot
User services normally stop when you log out. Enable lingering so the daemon starts at boot and keeps running:
systemctl --user enable docker
sudo loginctl enable-linger deployRun the loginctl command from an account with sudo rights if deploy has none.
Allow ports below 1024
Unprivileged processes cannot bind ports below 1024, so publishing -p 80:80 fails. You have three options:
- Publish high ports (
8080,8443) and let a reverse proxy or firewall rule forward 80 and 443. - Allow unprivileged users to bind from port 80 upwards, system-wide:
echo 'net.ipv4.ip_unprivileged_port_start=80' | sudo tee /etc/sysctl.d/99-rootless.conf
sudo sysctl --system- Give the
rootlesskitbinary the capability to bind low ports. The Docker documentation describes this withsetcap; check it for the current command.
The sysctl change lets any local user open ports 80 and up, so use it only on a server where you trust every account.
Storage and other differences
- Images, containers and volumes live under
~/.local/share/docker, not/var/lib/docker. Make sure the home partition has room. - The root daemon's images are not visible. Pull or build them again as the rootless user.
- Resource limits such as
--memoryand--cpusneed cgroup v2 with delegation, which Ubuntu 22.04 and 24.04 use by default. - Rootless networking is slower than the root bridge for heavy traffic, and the source IP seen by containers can differ. Check the Docker rootless documentation for the port driver options if you need the client's real IP.
- Some features need real root. For example,
--network hostgives a container the network of the rootless sandbox, not the real host network, and AppArmor profiles and some storage drivers are not available.
Rootless mode versus the docker group
Adding a user to the docker group is simpler, but that user can start a container that mounts / and so has full root access. Rootless mode removes that risk at the cost of the limitations above. For a single-purpose VPS where only you log in, the docker group with other hardening is often enough. For shared servers or untrusted images, rootless is the stronger choice.
Check it works
docker info --format '{{.SecurityOptions}}'
docker run --rm hello-worldThe first line should include name=rootless. Then confirm containers run as your user on the host:
docker run -d --name sleeper alpine sleep 300
ps -o user,cmd -C sleep
docker rm -f sleeperThe sleep process should belong to deploy, not root.
Troubleshooting
Cannot connect to the Docker daemon at unix:///var/run/docker.sock:DOCKER_HOSTis not set in this shell. Source~/.bashrcor export it again.systemctl --userfails withFailed to connect to bus: you switched user withsuorsudo. Log in directly over SSH as that user.- Containers stop when you log out: lingering is not enabled. Run
sudo loginctl enable-linger deploy. newuidmaperrors:uidmapis not installed or/etc/subuidhas no entry for the user.
Next steps
- Add more hardening on top: Docker security checklist for a public VPS.
- Compare with a daemonless alternative: Install Podman and run rootless containers.
- Run rootless containers on a Linux VPS.
