Docker Rootless Mode: Running Containers Without Root Privileges

Docker runs as root by default, giving containers dangerous host access. Learn how to run Docker in rootless mode, what breaks, and how to fix common compatibility issues.

Y
Yash Pritwani
11 min read read

Docker Rootless Mode: Running Containers Without Root Privileges

By default, the Docker daemon runs as root. Every container it spawns inherits access to the host kernel with root-level capabilities. This means a container escape — through a kernel vulnerability, a misconfigured mount, or a compromised image — gives the attacker root access to your host.

Docker rootless mode eliminates this by running the entire Docker daemon and all containers as an unprivileged user. No root. No sudo. No elevated capabilities.

Why Docker Runs as Root (and Why That Is a Problem)

Docker needs root for three things:

  1. Network namespace creation — setting up virtual networks requires CAP_NET_ADMIN
  2. User namespace mapping — mapping container UIDs to host UIDs requires /etc/subuid
  3. cgroup management — resource limits (CPU, memory) require cgroup access

Historically, these operations required root. Docker chose convenience over security by running the daemon as root.

The consequences:

  • A container breakout gives root access to the host
  • Any process that can talk to the Docker socket (/var/run/docker.sock) effectively has root
  • Mounting the Docker socket into a container (common for CI/CD) gives that container full host control
# This gives the container full root access to the host
docker run -v /var/run/docker.sock:/var/run/docker.sock ...
# The container can now: create privileged containers, read any file, modify the host

Setting Up Docker Rootless Mode

Prerequisites

# Install uidmap (provides newuidmap/newgidmap)
sudo apt-get install -y uidmap dbus-user-session

# Configure subordinate UID/GID ranges
echo "youruser:100000:65536" | sudo tee -a /etc/subuid
echo "youruser:100000:65536" | sudo tee -a /etc/subgid

# Enable lingering (keeps user services running after logout)
sudo loginctl enable-linger youruser

Installation

Get more insights on Security

Join 2,000+ engineers who get our weekly deep-dives. No spam, unsubscribe anytime.

# Install rootless Docker (as your regular user, NOT root)
dockerd-rootless-setuptool.sh install

# Set environment variables (add to ~/.bashrc)
export PATH=/home/youruser/bin:$PATH
export DOCKER_HOST=unix:///run/user/$(id -u)/docker.sock

# Start the rootless daemon
systemctl --user start docker
systemctl --user enable docker

Verify It Works

# Check Docker is running rootless
docker info 2>/dev/null | grep -i "rootless\|security"
# Security Options: rootless

# Check the daemon process
ps aux | grep dockerd
# youruser  12345  ... dockerd-rootless
# Note: NO root process

# Run a container
docker run --rm alpine id
# uid=0(root) gid=0(root)
# The container THINKS it is root, but on the host it maps to youruser

What User Namespaces Actually Do

The key technology behind rootless Docker is user namespaces. Inside the container, processes see themselves as root (UID 0). On the host, they map to an unprivileged UID (e.g., 100000).

Container view:     root (UID 0)
                      |
User namespace map:   UID 0 → UID 100000 on host
                      |
Host view:          youruser:100000 (unprivileged)

This means:

  • A container breakout lands you as UID 100000 — an unprivileged user with no special access
  • Files created by root inside the container appear as UID 100000 on host volumes
  • The container cannot access files owned by actual root (UID 0) on the host

What Breaks in Rootless Mode (and How to Fix It)

Port Binding Below 1024

Rootless containers cannot bind to ports below 1024 (privileged ports):

# This fails in rootless mode
docker run -p 80:80 nginx
# Error: bind: permission denied

# Fix 1: Use a higher port
docker run -p 8080:80 nginx

# Fix 2: Allow unprivileged port binding (Linux kernel 4.11+)
sudo sysctl net.ipv4.ip_unprivileged_port_start=80
# Add to /etc/sysctl.d/rootless.conf for persistence

Volume Permissions

Files written by the container appear as the mapped UID on the host:

docker run -v /home/youruser/data:/data alpine touch /data/test
ls -la /home/youruser/data/test
# -rw-r--r-- 1 100000 100000 0 Mar 23 12:00 test

# Fix: Use --userns-remap or chown inside the container

Overlay2 Storage Driver

Rootless Docker uses fuse-overlayfs instead of the kernel overlay2 driver. Performance is slightly lower for I/O-heavy workloads:

# Check storage driver
docker info | grep "Storage Driver"
# Storage Driver: fuse-overlayfs (rootless)
# vs: overlay2 (rootful)

# For better performance, use overlay2 with idmapped mounts (kernel 5.19+)

Docker Compose

Docker Compose works with rootless Docker, but ensure DOCKER_HOST points to the rootless socket:

export DOCKER_HOST=unix:///run/user/$(id -u)/docker.sock
docker compose up -d  # Works normally

Networking Limitations

Rootless containers use slirp4netns or pasta for networking instead of iptables:

  • No bridge networking — containers communicate via port mapping, not direct IP
  • Slower than rootful networking — slirp4netns adds overhead
  • No --net=host — host networking requires root capabilities
# Check which network driver is used
docker info | grep "Network"
# pasta (or slirp4netns)

Rootless Docker in Production

Free Resource

Infrastructure Security Audit Template

The exact audit template we use with clients: 60+ checks across network, identity, secrets management, and compliance.

Get the Template

Docker Compose Example

# docker-compose.yml (runs under rootless Docker)
services:
  web:
    image: nginx:alpine
    ports:
      - "8080:80"    # Use port above 1024
    read_only: true   # Extra hardening
    tmpfs:
      - /tmp
      - /var/cache/nginx

  api:
    image: node:20-slim
    ports:
      - "3000:3000"
    environment:
      - NODE_ENV=production
    volumes:
      - ./app:/app:ro
    read_only: true
    tmpfs:
      - /tmp

  db:
    image: postgres:16-alpine
    ports:
      - "5432:5432"
    volumes:
      - pgdata:/var/lib/postgresql/data
    environment:
      POSTGRES_PASSWORD_FILE: /run/secrets/db_password

volumes:
  pgdata:

Systemd Service

# ~/.config/systemd/user/docker-compose-app.service
[Unit]
Description=My App (rootless Docker)
After=docker.service

[Service]
Type=oneshot
RemainAfterExit=yes
WorkingDirectory=/home/youruser/app
ExecStart=/usr/bin/docker compose up -d
ExecStop=/usr/bin/docker compose down

[Install]
WantedBy=default.target

Rootless vs Other Hardening Approaches

Approach Protects Against Overhead Compatibility
Rootless Docker Container breakout → root access Low Some features break
Podman (daemonless) Same as rootless + no daemon socket Low Different CLI
gVisor (runsc) Kernel exploits Medium Syscall compatibility
Kata Containers Full VM isolation High Near-full compatibility
Seccomp profiles Specific syscall attacks None Per-container config

Rootless Docker is the lowest-friction hardening you can apply. It does not replace seccomp or AppArmor — stack them together.

Migration Checklist

Moving from rootful to rootless Docker:

  1. Audit port usage — identify anything binding below 1024
  2. Check volume permissions — files will be owned by mapped UIDs
  3. Test networking — ensure services can communicate via port mapping
  4. Update CI/CD — runners need DOCKER_HOST pointing to rootless socket
  5. Benchmark I/O — fuse-overlayfs may be slower for database workloads
  6. Update monitoring — cAdvisor and Docker metrics need rootless socket path

The Bottom Line

Docker running as root is a design decision from 2013 that prioritized ease of use over security. Rootless mode, available since Docker 20.10, eliminates the most dangerous consequence of container escapes: landing as root on the host.

The compatibility issues are real but manageable. Port binding, volume permissions, and networking all have straightforward fixes. For most workloads, rootless Docker is a drop-in replacement that dramatically reduces your blast radius.

If you are running Docker in production and have not switched to rootless mode, you are one container escape away from full host compromise. The migration takes an afternoon. The security improvement is permanent.

#docker#rootless#container-security#devsecops#linux#security-hardening

Related Service

Security & Compliance

Zero-trust architecture, compliance automation, and incident response planning.

Need help with security?

TechSaaS provides expert consulting and managed services for cloud infrastructure, DevOps, and AI/ML operations.

We Will Build You a Demo Site — For Free

Like it? Pay us. Do not like it? Walk away, zero complaints. You will spend way less than hiring developers or any agency.

47+ companies trusted us
99.99% uptime
< 48hr response

No spam. No contracts. Just a free demo.