docker

Rootless Docker: Setup, Configuration and Limitations (2026 Guide)

By Shubhankar Tripathi • • 5 min read

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:

ID inside the container

ID on the host

Example

0 (root)

Your own user ID

Container root = testuser (UID 1000)

1 to 65536

Your subordinate range, starting at 231072

Container UID 1 = host UID 231072

101 (for example the nginx user)

231072 + 100

Host UID 231172


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

Component

Role

RootlessKit

Creates the user namespace and starts dockerd inside it as "fake root". It also handles networking and port forwarding between the namespace and the host.

dockerd-rootless.sh

Wrapper script that launches dockerd through RootlessKit.

dockerd-rootless-setuptool.sh

Installs or removes the per-user systemd service and the rootless Docker CLI context.

User-mode networking

A user-space network stack (such as slirp4netns or pasta) gives containers network access without root.

systemd user service

Runs the daemon as ~/.config/systemd/user/docker.service under your own account.


Where Rootless Docker Keeps Its Files

Item

Rootful (default)

Rootless

Daemon socket

/var/run/docker.sock

$XDGRUNTIMEDIR/docker.sock (for example /run/user/1000/docker.sock)

Data (images, containers, volumes)

/var/lib/docker

~/.local/share/docker

Daemon configuration

/etc/docker/daemon.json

~/.config/docker/daemon.json

Service

docker.service (system)

docker.service (user, via systemctl --user)


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:

Option

Daemon runs as

Containers run as (on host)

Protects against

Default (rootful)

root

root, unless the image sets a user

Nothing beyond normal container isolation

Non-root USER in the image

root

A non-root UID

Exploits that need root inside the container; the daemon and docker group are still root-equivalent

userns-remap

root

Remapped unprivileged UIDs

Container escapes landing as root; the daemon itself is still root

Rootless mode

Your unprivileged user

Your user or your subordinate UIDs

Container escapes and daemon compromise both land as an unprivileged user


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:

Limitation

What it means in practice

Workaround

Ports below 1024

-p 80:80 fails by default

Use high ports, setcap on RootlessKit, or the ipunprivilegedportstart sysctl

Resource limits

--cpus, --memory and --pids-limit work only with cgroup v2 and systemd; CPU limits need delegation

Use cgroup v2 and the delegate.conf file shown above

Network performance

User-mode networking is slower than kernel networking

Use --net=host on Engine 29.5 or later where appropriate, or the experimental lxc-user-nic driver

Source IP addresses

Not preserved by default with -p

Disable userland-proxy and load brnetfilter (RootlessKit 3.0+)

Container IP addresses

The IPAddress from docker inspect is inside RootlessKit's namespace and not reachable from the host

Publish ports with -p instead of connecting to container IPs

Host networking on older Engines

Before Engine 29.5, --net=host stayed inside RootlessKit's namespace

Upgrade to Engine 29.5 or later, or use -p

Ping

May fail without extra configuration

Set net.ipv4.pinggrouprange

Unsupported features

AppArmor, checkpoint/restore, overlay networks (and therefore Swarm overlay networking) and SCTP port exposure do not work

Use rootful Docker or Kubernetes for these workloads

Capabilities

--cap-add and --privileged grant powers only inside your user namespace, not on the host

Tasks that need real host privileges must run rootful

Storage

Supported drivers are overlay2 (kernel 5.11+), fuse-overlayfs, btrfs and vfs; the data directory cannot be on NFS

Keep ~/.local/share/docker on local disk, or set data-root in daemon.json

Per-user data

Each user has a separate image store, so images may be downloaded and stored several times

Use a registry mirror or pull-through cache on shared machines

Service lifetime

The daemon stops when you log out unless lingering is on

Run sudo loginctl enable-linger $(whoami)


Troubleshooting Common Rootless Docker Errors

Error or symptom

Cause

Fix

systemd not detected / Failed to connect to bus

You switched user with sudo su or sudo -iu

Log in directly, use ssh user@localhost, or sudo machinectl shell user@

No subuid ranges found for user

No entry in /etc/subuid or /etc/subgid

Add a range with sudo usermod --add-subuids ... --add-subgids ...

lchown <file>: invalid argument during docker pull

Too few subordinate IDs

Give the user at least 65,536 IDs

lchown <file>: operation not permitted during docker pull

Docker data directory is on NFS

Set a local data-root in ~/.config/docker/daemon.json

OCI runtime create failed ... /run/systemd/private ... connection reset by peer

The user's D-Bus session is not running

Install dbus-user-session, log in again; if needed run systemctl --user enable --now dbus

--cpus or --memory has no effect

cgroup v1, or controllers not delegated

Use cgroup v2 and add delegate.conf

cannot expose privileged port

Port below 1024

Use a port above 1024 or configure privileged ports

Cannot connect to the Docker daemon

Rootless service not running, or the CLI is pointing at the rootful socket

Run systemctl --user start docker and check docker context ls and DOCKERHOST

Daemon does not start after reboot

Lingering is disabled

Run sudo loginctl enable-linger $(whoami)


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