Rootless Docker: Setup, Configuration and Limitations (2026 Guide)
Rootless Docker: Setup and Limitations
A standard Docker installation has a hidden security cost. The Docker daemon (dockerd) runs as root, and any user who can send it commands, including every member of the docker group, effectively has root access to the host. A single compromised account, CI job or vulnerable container can turn into full control of the machine.
Rootless Docker removes that risk by running both the Docker daemon and your containers as an ordinary, unprivileged user. If an attacker escapes a container, they land on the host as that unprivileged user instead of as root.
In this guide you will learn how rootless mode works, how to set it up on Ubuntu step by step, how to configure the features that need extra work (low ports, resource limits, ping and source IPs), and which limitations to plan for before using it in production. The guide is written for Docker Engine 29 on Linux, as of October 2026.
Quick Answer
What it is: Rootless mode runs the Docker daemon and containers inside a Linux user namespace, so neither has root privileges on the host.
How to enable it on Ubuntu: install uidmap and dbus-user-session, stop the system-wide daemon, then run dockerd-rootless-setuptool.sh install as your normal user.
Main limitations: ports below 1024, ping and resource limits need extra configuration; networking is slower by default; AppArmor, checkpoint, overlay networks and SCTP port exposure are not supported; NFS cannot be used as the data directory.
Best for: shared development machines, CI runners and security-sensitive hosts where nobody should get root through Docker.
Why the Docker Group Is a Security Risk
In a normal ("rootful") installation, the daemon listens on /var/run/docker.sock, which is owned by root and the docker group. Anyone who can write to that socket can ask the root-owned daemon to do anything, including mounting the host's root filesystem into a container:
docker run --rm -it -v /:/host alpine chroot /host
That single command gives a root shell on the host to any member of the docker group, without a password. This is why Docker's own documentation warns that the docker group grants root-level privileges.
In rootless mode, the same command runs, but the "root" inside the container is mapped to your own unprivileged user. You can only read and change files on the host that your user could already access.
How Rootless Docker Works
User Namespaces and ID Mapping
Rootless mode is built on Linux user namespaces. A user namespace gives a process its own private range of user and group IDs. Inside the namespace a process can be UID 0 (root), while on the host that same process is an ordinary user.
Each user who runs rootless Docker is given a range of subordinate IDs in /etc/subuid and /etc/subgid. A typical entry looks like this:
testuser:231072:65536
This means the user testuser owns 65,536 host IDs starting at 231072. Rootless Docker maps container IDs onto host IDs as follows:
Two small helper programs, newuidmap and newgidmap, set up this mapping. They are the only privileged (setuid) binaries rootless mode relies on.
The Main Components
Where Rootless Docker Keeps Its Files
Because data is stored per user, each user who runs rootless Docker has a separate set of images, containers and volumes.
Rootless Docker vs Other Hardening Options
Rootless mode is often confused with two other features. They solve different problems:
These options combine well. Running non-root containers inside rootless Docker gives two layers of protection.
When Should You Use Rootless Docker?
Rootless mode is a good fit for:
Shared development servers where several people need Docker but should not get root.
CI/CD runners that build untrusted pull requests.
Security-sensitive or compliance-driven hosts where giving root through the docker group is not acceptable.
Workstations where you simply want a smaller blast radius.
It is a weaker fit when you need Docker Swarm overlay networks, maximum network throughput, NFS-backed storage for Docker data, or features that rely on real host privileges such as some monitoring agents and low-level networking tools.
Prerequisites
This guide assumes Docker Engine is already installed on Ubuntu from Docker's official apt repository, as described in our guide How to Install Docker on Ubuntu, Windows (WSL2) and macOS. You also need:
A regular (non-root) user account. Rootless mode is configured per user.
The uidmap package, which provides newuidmap and newgidmap.
The dbus-user-session package, so systemd can manage your user's services and cgroups.
At least 65,536 subordinate UIDs and GIDs for your user in /etc/subuid and /etc/subgid.
cgroup v2 (the default on Ubuntu 22.04 and later) if you want CPU, memory and PID limits to work.
Linux kernel 5.11 or later to use the fast overlay2 storage driver. All supported Ubuntu releases meet this.
Check your subordinate ID ranges and cgroup version:
grep ^$(whoami): /etc/subuid
grep ^$(whoami): /etc/subgid
stat -fc %T /sys/fs/cgroup/
Each grep should print a line like testuser:231072:65536. The stat command prints cgroup2fs on cgroup v2. Ubuntu normally creates subordinate ID ranges automatically when a user is created. If yours are missing, add a range for the user:
sudo usermod --add-subuids 231072-296607 --add-subgids 231072-296607 $(whoami)
Pick a range that does not overlap with ranges already listed in /etc/subuid for other users.
Ubuntu 24.04 and later: Recent Ubuntu releases restrict unprivileged user namespaces with AppArmor. The docker-ce-rootless-extras package installed from Docker's apt repository includes the AppArmor profile that allows RootlessKit to work, so the package-based setup in this guide needs no extra AppArmor steps.
Step-by-Step: Set Up Rootless Docker on Ubuntu
Step 1: Install the Required Packages
sudo apt update
sudo apt install -y uidmap dbus-user-session docker-ce-rootless-extras
The docker-ce-rootless-extras package provides RootlessKit and the setup scripts. It is usually installed already together with docker-ce; the command above makes sure.
If dbus-user-session was newly installed, log out and log back in before continuing so your user session starts its own D-Bus instance.
Step 2: Stop the System-Wide (Rootful) Daemon
Rootless and rootful Docker can technically run side by side, but beginners are less likely to get confused if only one daemon is running. Disable the rootful daemon and remove its socket:
sudo systemctl disable --now docker.service docker.socket
sudo rm /var/run/docker.sock
If you decide to keep the rootful daemon running, the setup tool in the next step requires the --force flag.
Step 3: Run the Rootless Setup Tool
Run the setup tool as your normal user, without sudo:
dockerd-rootless-setuptool.sh install
Important: Log in directly as the user, through a desktop session or ssh user@host. Switching users with sudo su or sudo -iu does not create a proper systemd user session and causes errors such as "systemd not detected" or "Failed to connect to bus". If you must switch from another account, use sudo machinectl shell user@ (from the systemd-container package).
The tool creates a systemd user service, starts the rootless daemon, and creates a Docker CLI context named rootless that it selects automatically. If a prerequisite is missing, it prints instructions instead of installing.
Step 4: Configure Your Shell
The Docker CLI already uses the rootless context. Some third-party tools ignore CLI contexts and read the DOCKERHOST variable instead, so it is good practice to set it too. Add these lines to ~/.bashrc (or your shell's equivalent):
export PATH=/usr/bin:$PATH
export DOCKERHOST=unix://$XDGRUNTIMEDIR/docker.sock
Reload your shell with source ~/.bashrc.
Step 5: Start Rootless Docker at Boot
By default, systemd stops a user's services when that user logs out. Enable the service and turn on lingering so the daemon starts at boot and keeps running after you log out:
systemctl --user enable docker
sudo loginctl enable-linger $(whoami)
Manage the daemon with the same systemctl commands you already know, adding --user:
systemctl --user status docker
systemctl --user restart docker
systemctl --user stop docker
Step 6: Verify That Docker Is Running Rootless
docker info
Look for these lines in the output:
Context: rootless in the Client section.
rootless listed under Security Options, together with seccomp and cgroupns.
Docker Root Dir pointing to ~/.local/share/docker.
Now run a real container and check which host user its processes run as:
docker run -d --name web -p 8080:80 nginx
ps -eo user,cmd | grep "nginx:"
The Nginx master process, which is root inside the container, appears under your own username. The worker processes, which run as the nginx user inside the container, appear as a high numeric UID such as 231172, from your subordinate range. Nothing runs as root on the host. Confirm the web server works, then clean up:
curl -I http://localhost:8080
docker rm -f web
Alternative: Install Without Packages
On systems where you cannot install packages, Docker provides a script that downloads static rootless binaries into ~/bin:
curl -fsSL https://get.docker.com/rootless | sh
With this method, set export PATH=$HOME/bin:$PATH instead of /usr/bin. On Ubuntu 24.04 and later you must also create an AppArmor profile for $HOME/bin/rootlesskit yourself, because only the deb package ships one; follow the "Distribution-specific hints" section of Docker's rootless troubleshooting documentation. The script method is not available on the s390x architecture. For most Ubuntu users the package method is simpler and easier to keep updated.
Configuring Rootless Docker
Daemon Settings
Rootless Docker reads its configuration from ~/.config/docker/daemon.json, not from /etc/docker/daemon.json. For example, to turn on log rotation with the local logging driver:
mkdir -p ~/.config/docker
cat > ~/.config/docker/daemon.json <<EOF
{
"log-driver": "local"
}
EOF
systemctl --user restart docker
Enable CPU, Memory and PID Limits
Flags such as --memory, --cpus and --pids-limit work in rootless mode only with cgroup v2 and systemd. By default systemd delegates only the memory and pids controllers to user services, so --cpus is silently ignored. To delegate CPU and I/O control as well, create a systemd drop-in file:
sudo mkdir -p /etc/systemd/system/user@.service.d
sudo tee /etc/systemd/system/user@.service.d/delegate.conf <<EOF
[Service]
Delegate=cpu cpuset io memory pids
EOF
sudo systemctl daemon-reload
Log out and back in (or reboot), then restart the rootless daemon. Delegating cpuset requires systemd 244 or later, which all supported Ubuntu releases include. Test that limits apply:
docker run --rm --cpus 0.5 --memory 256m alpine cat /sys/fs/cgroup/cpu.max /sys/fs/cgroup/memory.max
The output should show 50000 100000 (half a CPU) and 268435456 (256 MB).
Expose Privileged Ports (Below 1024)
An unprivileged user cannot normally bind ports below 1024, so docker run -p 80:80 fails with "cannot expose privileged port". The simplest workaround is to use a high port such as -p 8080:80 and put a reverse proxy or load balancer in front. If you really need low ports, choose one of these options:
Option 1: give only RootlessKit the capability to bind low ports:
sudo setcap capnetbindservice=ep $(which rootlesskit)
systemctl --user restart docker
Option 2: allow all unprivileged processes to bind low ports, system-wide:
echo "net.ipv4.ipunprivilegedportstart=0" | sudo tee /etc/sysctl.d/99-rootless-ports.conf
sudo sysctl --system
Option 1 is narrower and generally preferable. Option 2 affects every user and process on the machine.
Allow Ping From Containers
Many distributions do not allow unprivileged users to send ICMP echo (ping) packets. If docker run --rm alpine ping -c 1 8.8.8.8 fails, allow it with:
echo "net.ipv4.pinggrouprange = 0 2147483647" | sudo tee /etc/sysctl.d/99-rootless-ping.conf
sudo sysctl --system
Preserve Client Source IP Addresses
By default, port forwarding in rootless mode hides the real client IP: applications see connections coming from an internal address. This matters for access logs, rate limiting and IP allow-lists. With current releases (RootlessKit 3.0 or later), disable the userland proxy and load the brnetfilter kernel module:
cat > ~/.config/docker/daemon.json <<EOF
{
"log-driver": "local",
"userland-proxy": false
}
EOF
echo brnetfilter | sudo tee /etc/modules-load.d/docker.conf
sudo modprobe brnetfilter
systemctl --user restart docker
This example keeps the local logging driver from earlier, because daemon.json is a single file and the command replaces its contents. Older releases need a different port driver configured through a systemd override; see Docker's rootless troubleshooting documentation.
Switch Between Rootful and Rootless Docker
If both daemons are installed, Docker CLI contexts let you switch between them without changing environment variables:
docker context ls
docker context use rootless
docker context use default
The default context points to the rootful daemon at /var/run/docker.sock. If you set DOCKERHOST in your shell, it overrides the selected context, so unset it when switching.
Limitations of Rootless Docker
Rootless mode is mature and stable, but running without root has real trade-offs. Plan for these before you adopt it:
Troubleshooting Common Rootless Docker Errors
To inspect the daemon's own network and mount namespaces while debugging, enter them with:
nsenter -U --preserve-credentials -n -m -t $(cat $XDGRUNTIMEDIR/docker.pid)
Rootless Docker in CI Pipelines
Rootless mode is especially valuable on CI runners that build code from many contributors. For "Docker in Docker" builds, use the docker:<version>-dind-rootless image instead of docker:<version>-dind. The inner daemon runs as UID 1000.
Note that this image still needs the --privileged flag on the outer container, because the inner daemon must disable seccomp, AppArmor and mount masks to set up its own namespaces. It reduces risk compared to a rootful inner daemon, but it is not equivalent to running on an unprivileged host. Rootless Docker on the runner host itself, or daemonless builders, give stronger isolation.
How to Uninstall Rootless Docker
Remove the user service and CLI context. This does not delete binaries or data:
dockerd-rootless-setuptool.sh uninstall
Delete your rootless images, containers and volumes. RootlessKit is used because some files belong to your subordinate UIDs, which your normal user cannot delete directly:
rootlesskit rm -rf ~/.local/share/docker
Remove the PATH and DOCKERHOST lines you added to ~/.bashrc. To return to rootful Docker, re-enable the system-wide daemon:
sudo systemctl enable --now docker.service docker.socket
Rootless Docker Security: What It Does and Does Not Protect
Rootless mode significantly reduces the impact of a container escape or a compromised Docker client, but it is not a complete security solution on its own:
It protects the host's system files, other users' data and root-only resources from anything running in Docker.
It does not protect files your own user can access. A compromised container can still read or change anything in your home directory that you mount into it.
It relies on user namespaces, a kernel feature that has had its own vulnerabilities. Keep the kernel and Docker Engine patched.
It still benefits from other hardening: run containers as non-root users, drop capabilities, use read-only filesystems and scan images for vulnerabilities.
Frequently Asked Questions
Is rootless Docker ready for production?
Yes. Rootless mode has been a stable Docker Engine feature since version 20.10. It is a good choice for production workloads that fit its limitations, especially on multi-user hosts and CI runners. Check the limitations table before migrating workloads that need overlay networks, low ports, NFS storage or maximum network throughput.
Does Docker Compose work with rootless Docker?
Yes. docker compose talks to whichever daemon the current context or DOCKER_HOST points to, so it works the same way in rootless mode. The same port, networking and storage limitations apply to Compose services.
Can I use rootless mode with Docker Desktop?
Rootless mode is a feature of Docker Engine on Linux. Docker Desktop on Windows and macOS already runs the Docker Engine inside a separate virtual machine, which isolates it from your host operating system in a different way.
What is the difference between rootless Docker and userns-remap?
Both use user namespaces so that root inside a container is not root on the host. With userns-remap, the Docker daemon itself still runs as root. With rootless mode, the daemon also runs as an unprivileged user, so a compromise of the daemon does not give root either.
Is rootless Docker the same as running a container with a non-root user?
No. A USER instruction in a Dockerfile only changes the user inside the container; the daemon still runs as root and the docker group is still root-equivalent. Rootless mode changes how the daemon itself runs. Using both together is best practice.
How does rootless Docker compare with Podman?
Podman is daemonless and designed to run rootless by default, while rootless Docker keeps Docker's familiar daemon-based architecture and full Docker tooling. Both rely on the same Linux building blocks: user namespaces, subordinate IDs and user-mode networking.
Conclusion and Next Steps
Rootless Docker closes one of the biggest security gaps in a default Docker installation: the root-owned daemon and the root-equivalent docker group. With a few prerequisites and a single setup command, the daemon and its containers run as an ordinary user, so a container escape no longer means root on the host.
The trade-offs are real but manageable. Use high ports or a reverse proxy, enable cgroup delegation for resource limits, configure source IP propagation when you need it, and keep rootful Docker for the few workloads that genuinely require host privileges or overlay networks.
Continue the Docker course with these tutorials on BitCodeMatrix:
How to Install Docker on Ubuntu, Windows (WSL2) and macOS — the prerequisite for this guide
Docker Security Fundamentals — the wider container security picture
Container Isolation and Linux Namespaces — the kernel features rootless mode builds on
Docker Contexts: Manage Remote Docker Hosts (next in the course) — switching between local, rootless and remote Docker engines