Docker Rootless Mode Configuration
Docker rootless mode enables running containers without requiring root privileges, significantly reducing the attack surface and improving security. This comprehensive guide covers prerequisites, installation, cgroup v2 configuration, networking with slirp4netns, limitations, troubleshooting, and production best practices. Rootless mode is essential for multi-tenant environments and organizations with strict security requirements.
Table of Contents
- Understanding Rootless Mode
- Prerequisites and System Setup
- Installing Rootless Docker
- Cgroup v2 Configuration
- Networking in Rootless Mode
- Container Operations
- Limitations and Workarounds
- Troubleshooting
- Integrating with System Services
- Conclusion
Understanding Rootless Mode
Rootless Docker runs the Docker daemon as an unprivileged user instead of root, with containers also running as non-root processes. This eliminates the root daemon risk while maintaining full container functionality.
Security benefits:
- No privileged daemon running as root
- Container breakout cannot gain root on host
- User namespace isolation prevents privilege escalation
- Eliminates classic Docker privilege escalation vectors
- Suitable for untrusted workloads
# Check current Docker daemon privilege
ps aux | grep dockerd | grep -v grep
# Rootless Docker runs as user process, not root
# Regular Docker daemon runs as root
# Verify rootless mode
docker info | grep -i rootless
Rootless vs Regular Docker:
- Regular: Single privileged daemon serves all users
- Rootless: Each user runs own daemon instance
- Regular: Better resource sharing between containers
- Rootless: Better isolation and security
Prerequisites and System Setup
Prepare your system for rootless Docker installation.
Check system requirements:
# Verify Linux kernel version (need 4.18+)
uname -r
# Check for cgroup v2
ls -la /sys/fs/cgroup/ | grep cgroup2
# Verify cgroup2 mounted
mount | grep cgroup2
# Check for systemd (recommended)
systemctl --version
# Verify user namespace support
cat /proc/sys/user/max_user_namespaces
# Should show non-zero value (typically 1-16384)
Configure user namespaces:
# Check current subuid/subgid range
cat /etc/subuid
cat /etc/subgid
# If empty, configure user IDs
sudo usermod --add-subuids 100000-165535 $USER
sudo usermod --add-subgids 100000-165535 $USER
# Verify configuration
grep $USER /etc/subuid
grep $USER /etc/subgid
# Example output:
# username:100000:65536
# username:100000:65536
Install system dependencies:
# Ubuntu/Debian
sudo apt-get update
sudo apt-get install -y \
uidmap \
dbus-user-session \
slirp4netns \
fuse-overlayfs
# CentOS/RHEL
sudo dnf install -y \
uidmap \
dbus-x11 \
slirp4netns \
fuse-overlayfs
# Fedora
sudo dnf install -y \
shadow-utils \
dbus-x11 \
slirp4netns \
fuse-overlayfs
# Alpine (if applicable)
apk add shadow fuse-overlayfs slirp4netns
Installing Rootless Docker
Install and configure rootless Docker daemon.
Official installation script:
# Download rootless installation script
curl -fsSL https://get.docker.com/rootless | sh
# Script output shows environment setup
# Add to .bashrc or .profile:
# export PATH=/home/username/.docker/bin:$PATH
# export DOCKER_HOST=unix:///run/user/UID/docker.sock
# Add to your shell profile
echo 'export PATH=/home/'"$USER"'/.docker/bin:$PATH' >> ~/.bashrc
echo 'export DOCKER_HOST=unix:///run/user/'"$(id -u)"'/docker.sock' >> ~/.bashrc
# Apply changes
source ~/.bashrc
# Verify Docker installation
docker --version
docker ps
Manual installation steps:
# Stop and disable regular Docker daemon (if installed)
sudo systemctl stop docker
sudo systemctl disable docker
# Verify daemon not running
ps aux | grep dockerd | grep -v grep
# Install Docker for rootless (from official repository)
sudo apt-get install -y docker.io
# Run rootless installation
dockerd-rootless-setuptool.sh install
# Start rootless Docker daemon
systemctl --user enable docker
systemctl --user start docker
# Verify daemon running as user
ps aux | grep dockerd | grep -v grep
# Output should show daemon running as your user, not root
Configure rootless Docker:
# Create Docker config directory
mkdir -p ~/.docker
# Create daemon configuration
cat > ~/.docker/daemon.json <<'EOF'
{
"storage-driver": "overlay2",
"log-driver": "json-file",
"log-opts": {
"max-size": "10m",
"max-file": "3"
},
"userland-proxy": false
}
EOF
# Restart Docker daemon to apply config
systemctl --user restart docker
# Verify configuration applied
docker info | grep -E "Storage Driver|Log Driver"
Cgroup v2 Configuration
Configure cgroup v2 for enhanced resource management in rootless mode.
Check cgroup version:
# Check mounted cgroup version
mount | grep cgroup
# Check default cgroup version
cat /sys/fs/cgroup/cgroup.controllers
# Verify cgroup2 support
ls -la /sys/fs/cgroup/cgroup2
Enable cgroup v2:
# Check boot parameters
cat /etc/default/grub | grep GRUB_CMDLINE_LINUX
# Add cgroup_no_v1 to boot parameters
sudo sed -i 's/GRUB_CMDLINE_LINUX="/GRUB_CMDLINE_LINUX="cgroup_no_v1=all /' /etc/default/grub
# Update GRUB
sudo update-grub
# Reboot system
sudo reboot
# After reboot, verify cgroup v2
mount | grep cgroup
Configure cgroup limits for rootless containers:
# Verify cgroup2 mounted
mount | grep cgroup2
# Restart Docker to apply cgroup v2
systemctl --user restart docker
# Run container with resource limits
docker run -d \
--name test-cgroup \
--memory 512m \
--cpus 0.5 \
alpine sleep 1000
# Check cgroup v2 limits applied
cat /sys/fs/cgroup/user.slice/user-$(id -u).slice/user@$(id -u).service/docker-*/memory.max
# Verify limits
docker stats test-cgroup
Networking in Rootless Mode
Configure networking for rootless Docker containers.
slirp4netns networking:
# Verify slirp4netns installed
which slirp4netns
# Check version
slirp4netns --version
# Docker uses slirp4netns for rootless networking
# Creates virtual network without requiring privileged mode
# Run container with port mapping
docker run -d \
--name web \
-p 8080:80 \
nginx:latest
# Test port mapping works
curl localhost:8080
# View network configuration
docker network ls
docker network inspect bridge
Network driver configuration:
# Check available network drivers in rootless
docker network ls
# Create custom network
docker network create mynet
# Run containers on custom network
docker run -d \
--name app1 \
--network mynet \
myapp:latest
docker run -d \
--name app2 \
--network mynet \
myapp:latest
# Containers communicate via network name
docker exec app1 ping app2
Expose services with port mapping:
# Port mapping in rootless mode
# Ports < 1024 cannot be exposed (no root privileges)
# Expose high-numbered port (works)
docker run -d \
--name web \
-p 8080:80 \
nginx:latest
# Verify accessible
curl localhost:8080
# Cannot expose port 80 directly
# Must use reverse proxy or higher port
# Setup reverse proxy for port 80
sudo tee /etc/nginx/sites-available/rootless <<'EOF'
upstream docker {
server 127.0.0.1:8080;
}
server {
listen 80;
server_name _;
location / {
proxy_pass http://docker;
}
}
EOF
# Enable reverse proxy
sudo ln -sf /etc/nginx/sites-available/rootless /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginx
Container Operations
Perform container operations in rootless mode.
Running containers:
# Run container in rootless mode
docker run -d \
--name myapp \
-p 8080:5000 \
myapp:latest
# Verify running as unprivileged user
ps aux | grep myapp
# Inside container, check UID
docker exec myapp id
# Rootless process mapping:
# Container UID 0 -> Host UID 100000 (from subuid range)
# Volume mounting
docker run -d \
--name vol-test \
-v /tmp/data:/app/data \
myapp:latest
# Verify volume accessible
docker exec vol-test ls -la /app/data
Building images:
# Build image in rootless Docker
docker build -t myapp:latest .
# Push to registry
docker login
docker push myregistry.com/myapp:latest
# Build with specific context
docker build -f Dockerfile.rootless -t myapp:rootless .
# Verify image created
docker images | grep myapp
Limitations and Workarounds
Understand rootless Docker limitations and solutions.
Common limitations:
# Limitation 1: Cannot use ports < 1024
# Workaround: Use higher ports, add reverse proxy
# Limitation 2: Some volume mounts may have permission issues
# Workaround: Check subuid/subgid mapping
# Limitation 3: Performance slightly lower than root mode
# Workaround: Use fuse-overlayfs for better performance
# Limitation 4: No access to /dev/fuse might affect some workloads
# Workaround: Enable fuse support in kernel
Configure storage driver for better performance:
# Check current storage driver
docker info | grep "Storage Driver"
# Configure fuse-overlayfs for rootless
cat > ~/.docker/daemon.json <<'EOF'
{
"storage-driver": "overlay2",
"storage-opts": [
"overlay2.override_kernel_check=true"
]
}
EOF
# Restart Docker
systemctl --user restart docker
# Performance should improve
docker run -d --name perf-test busybox
time docker exec perf-test find / > /dev/null
Permission issues and fixes:
# Check subuid/subgid configuration
cat /etc/subuid
cat /etc/subgid
# If issues mounting volumes
# Ensure subuid range includes the UID you need
# Reconfigure user IDs if needed
sudo usermod --add-subuids 100000-165535 $USER
sudo usermod --add-subgids 100000-165535 $USER
# Reinitialize rootless setup
dockerd-rootless-setuptool.sh install
# Remove any conflicting Docker images/containers first
docker system prune -a
Troubleshooting
Diagnose and resolve common rootless Docker issues.
Daemon not starting:
# Check daemon status
systemctl --user status docker
# View daemon logs
journalctl --user -u docker -f
# Common error: cannot allocate memory
# Solution: Check subuid/subgid setup
grep $USER /etc/subuid /etc/subgid
# If missing, reconfigure
sudo usermod --add-subuids 100000-165535 $USER
sudo usermod --add-subgids 100000-165535 $USER
# Reinitialize
dockerd-rootless-setuptool.sh uninstall
dockerd-rootless-setuptool.sh install
Connection issues:
# Cannot connect to Docker socket
# Verify DOCKER_HOST variable
echo $DOCKER_HOST
# Set correctly
export DOCKER_HOST=unix:///run/user/$(id -u)/docker.sock
# Test connection
docker ps
# Verify socket exists
ls -la /run/user/$(id -u)/docker.sock
# Check daemon running
systemctl --user is-active docker
Port mapping issues:
# Cannot access exposed port
# Remember: can only use ports > 1024
# Verify port mapping
docker port myapp
# Test connection
telnet localhost 8080
# Check if process listening
netstat -tlnp | grep 8080
# Enable userland proxy if needed
docker run -d -p 8080:80 --name web nginx:latest
Integrating with System Services
Set up rootless Docker to start automatically with user session.
Automatic daemon startup:
# Enable rootless Docker for current user
systemctl --user enable docker
# Verify autostart enabled
systemctl --user is-enabled docker
# Enable linger to start without user login (optional)
loginctl enable-linger $USER
# Verify linger enabled
loginctl show-user $USER | grep Linger
# Daemon will now start automatically when user logs in
# Or immediately if linger enabled
Create systemd user services:
# Create service unit directory
mkdir -p ~/.config/systemd/user
# Create application service
cat > ~/.config/systemd/user/myapp.service <<'EOF'
[Unit]
Description=My Application (Rootless)
Requires=docker.service
After=docker.service
[Service]
Type=simple
ExecStart=/usr/bin/docker run \
--rm \
--name myapp \
-p 8080:5000 \
myapp:latest
ExecStop=/usr/bin/docker stop myapp
Restart=on-failure
RestartSec=5
[Install]
WantedBy=default.target
EOF
# Load and start service
systemctl --user daemon-reload
systemctl --user enable myapp.service
systemctl --user start myapp.service
# Check service status
systemctl --user status myapp.service
# View logs
journalctl --user -u myapp.service -f
Use Docker socket activation:
# Use Docker socket for service activation
cat > ~/.config/systemd/user/myapp.service <<'EOF'
[Unit]
Description=My Application (Socket Activated)
Requires=docker.service
After=docker.service
BindsTo=docker.service
[Service]
Type=notify
ExecStart=/usr/bin/docker run \
--rm \
--name myapp \
-p 8080:5000 \
--env DOCKER_HOST=unix:///run/user/%U/docker.sock \
myapp:latest
ExecStop=/usr/bin/docker stop myapp
Restart=always
[Install]
WantedBy=default.target
EOF
systemctl --user daemon-reload
systemctl --user enable myapp.service
systemctl --user start myapp.service
Conclusion
Docker rootless mode provides a significant security improvement by eliminating the root daemon while maintaining full container functionality. By properly configuring user namespaces, cgroup v2, and networking with slirp4netns, you create a secure container environment suitable for multi-tenant systems and sensitive workloads. While some limitations exist (port mapping, permission considerations), workarounds make rootless mode practical for production deployments. Start by understanding system requirements and prerequisites, carefully follow installation steps, and test thoroughly before deploying to production. As container security remains paramount, rootless Docker represents a best practice for organizations prioritizing defense-in-depth architecture. Integrate with systemd for reliable service management and implement monitoring to track rootless container performance and health.


