Installing and Configuring Rootless Docker: Run Docker Without Root to Protect Your Host

Docker tutorial - IT technology blog
Docker tutorial - IT technology blog

Quick start: Set Up Rootless Docker in 5 Minutes

With just an Ubuntu 22.04 or Debian 12 server and a standard user account, you can launch the Docker daemon immediately without granting persistent root privileges.

Step 1: Install prerequisite packages

Rootless Docker requires uidmap for UID/GID mapping and slirp4netns to create a user-space virtual network stack. Run the following command to install them:

sudo apt-get update
sudo apt-get install -y uidmap dbus-user-session slirp4netns

Verify whether the subuid range configuration is ready for your user:

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

If the output matches a format like ubuntu:100000:65536, the system is ready to allocate 65,536 subordinate UIDs to your containers.

Step 2: Download and run the installation script

Drop sudo privileges entirely. Run the following command as a regular user:

curl -fsSL https://get.docker.com/rootless | sh

Step 3: Define the socket and enable the service

The script creates a dedicated socket in the user’s runtime directory. Add the environment variables to ~/.bashrc so the Docker CLI can detect it:

echo 'export PATH=$HOME/bin:$PATH' >> ~/.bashrc
echo 'export DOCKER_HOST=unix://$XDG_RUNTIME_DIR/docker.sock' >> ~/.bashrc
source ~/.bashrc

Enable and start the background service via the systemd user instance:

systemctl --user enable --now docker
systemctl --user status docker

Step 4: Verify the setup

Test the installation with a sample container:

docker run --rm hello-world

Once the welcome message appears, the daemon is up and running properly. This entire process executes strictly within an isolated user namespace.

The Core Risk: Why Is Default Docker Dangerous?

The Docker Group Is Root in Disguise

Many sysadmins routinely add users to the docker group for convenience to run commands without sudo. This creates a severe security risk. Anyone with access to the Docker socket can mount the host’s root filesystem / into a container and alter /etc/shadow with a single command.

The Threat of Container Escapes

Web applications constantly face the threat of remote code execution (RCE) vulnerabilities. At times, security flaws exist directly in the container runtime layer, such as the well-known runc vulnerability CVE-2019-5736 or the file descriptor leak CVE-2024-21626 (Leaky Vessels).

If the Docker daemon runs with host root privileges, an attacker who breaks out of a container immediately gains full control of the entire server. Consequently, malicious code can seize the system and compromise sensitive data.

The Defense Mechanism of Rootless Docker

Rootless Docker completely flips this paradigm using Linux User Namespaces. Inside the container, the application still operates as root (UID 0). However, from the host’s perspective, that process only runs with an unprivileged UID (for example, UID 1001).

Even if an attacker breaches the container and escapes onto the host, they remain restricted to UID 1001 privileges. The host remains safe because the attacker cannot overwrite system files or interfere with other users’ processes.

Production Configuration: Ports, Linger, and Resource Limits

1. Exposing Privileged Ports Below 1024

By default, Linux prevents unprivileged users from binding to ports below 1024, such as 80 or 443. To run Nginx or Traefik directly, you must lower the kernel’s privileged port threshold:

echo "net.ipv4.ip_unprivileged_port_start=80" | sudo tee /etc/sysctl.d/50-rootless-docker.conf
sudo sysctl --system

After applying this setting, Rootless Docker can bind ports 80 and 443 externally without triggering permission denied errors.

2. Keeping Containers Alive After SSH Logout (Linger)

By default, systemd terminates all user processes as soon as the SSH session disconnects. To keep containers running 24/7 as standalone background services, enable user lingering:

loginctl enable-linger $(whoami)

3. Managing RAM and CPU with cgroup v2

To enforce resource limits on containers in rootless mode, the host must have cgroup v2 enabled. Quickly verify this with:

docker info | grep -i cgroup

If the output returns Cgroup Version: 2 with warnings about missing controllers, configure systemd delegation so unprivileged users can manage CPU and memory resources:

sudo mkdir -p /etc/systemd/system/[email protected]
cat <<EOF | sudo tee /etc/systemd/system/[email protected]/delegate.conf
[Service]
Delegate=cpu cpuset io memory pids
EOF
sudo systemctl daemon-reload

You can now set container resource limits as usual:

docker run -d --name cache-redis --memory="512m" --cpus="1.0" redis:alpine

Field Notes: Running Rootless in Production

Seamlessly Switching Between Rootful and Rootless

If your server runs both Docker installations side-by-side (a system-wide root daemon and an isolated rootless instance), use Docker Contexts to switch between them rather than constantly exporting environment variables:

# Create a context for rootless
docker context create rootless-local --description "User daemon" --docker "host=unix://$XDG_RUNTIME_DIR/docker.sock"

# Switch working context
docker context use rootless-local

Debugging the Docker Engine API via Unix Socket

In internal CI/CD pipelines, querying the Docker API directly through its socket streamlines task automation. You can inspect the container list using curl:

curl --unix-socket $XDG_RUNTIME_DIR/docker.sock http://localhost/containers/json

JSON payloads returned by the Docker API can be lengthy and difficult to inspect. You can paste the payload into toolcraft.app JSON Formatter to clearly visualize volume mount hierarchies and network settings. This approach is intuitive and helps you spot misconfigurations without needing heavy browser extensions.

Network Performance Trade-offs

Because the slirp4netns virtual network stack processes packets entirely in user space, it introduces slightly higher latency than traditional bridge networking. Network throughput may decrease by 15% to 30%, with an additional 5% to 10% CPU overhead under heavy traffic.

If your workload consists of high-throughput microservices handling tens of thousands of requests per second, consider switching to the pasta driver (the default in Docker Engine 24.0+ when the package is installed) or using --net=host when maximum performance is necessary.

Share: