Rootless Docker & User Namespaces
By default, the Docker daemon (dockerd) runs as root. This is a massive security risk. If an attacker escapes a container (via a kernel exploit), they become root on the host, gaining full control over your server.
Rootless Docker mitigates this by running the daemon and containers inside a User Namespace (USERNS).
1. The “Root” Problem
In a standard Docker installation:
- Daemon: Runs as
root(UID 0) on the host. - Container: Processes inside often run as
root(UID 0). - Kernel: The kernel sees both as the same user (UID 0).
[!WARNING] If a containerized process exploits a vulnerability like Dirty COW or CVE-2019-5736 (runc overwrite), it breaks out of the container and lands on the host as
root. Game over.
2. The Solution: User Namespaces (USERNS)
User Namespaces allow you to map a range of UIDs on the host to a different range of UIDs inside the container.
- Inside the container: The process thinks it is
root(UID 0). - On the Host: The process is actually an unprivileged user (e.g., UID 1000).
This is achieved via /etc/subuid and /etc/subgid.
How Mapping Works
We allocate a range of “subordinate UIDs” to a user.
Example /etc/subuid:
alice:100000:65536
This means: User alice can use UIDs starting from 100,000 up to 165,535.
When alice runs a container:
- Container UID
0→ Host UID100000 - Container UID
1→ Host UID100001
If the attacker breaks out, they land on the host as UID 100000 (a nobody), not root.
3. Interactive: User Namespace Visualizer
See how User Namespaces map IDs between the Container and the Host.
Inside Container
Host Perspective
Configuration (/etc/subuid)
alice:100000:65536
4. Installing Rootless Docker
Run Docker as a standard user without sudo.
1. Prerequisites
Ensure uidmap is installed:
sudo apt-get install -y uidmap
2. Installation Script
Run the installation script as a non-root user:
curl -fsSL https://get.docker.com/rootless | sh
3. Environment Setup
Add the following to your ~/.bashrc:
export PATH=/home/alice/bin:$PATH
export DOCKER_HOST=unix:///run/user/1000/docker.sock
4. Running the Daemon
Use systemd (user mode) to start the daemon:
systemctl --user start docker
systemctl --user enable docker
5. Configuration: daemon.json
Even in rootless mode, you might need to configure the daemon. The config file lives in ~/.config/docker/daemon.json.
{
"storage-driver": "overlay2",
"log-driver": "json-file",
"log-opts": {
"max-size": "10m",
"max-file": "3"
}
}
[!NOTE] Overlay2 Support: Rootless Overlay2 requires Linux Kernel 5.11+ or
fuse-overlayfsinstalled. Without it, Docker falls back to the slowervfsdriver.
6. Trade-offs: Root vs Rootless
| Feature | Rootful Docker | Rootless Docker |
|---|---|---|
| Security | Low (Daemon runs as root) | High (Daemon runs as user) |
| Port Binding | Can bind ports < 1024 | Cannot bind ports < 1024 (requires sysctl tweak) |
| Performance | Native | Slight overhead (USERNS translation) |
| Cgroups | Full Support | Limited (Requires Cgroup v2) |
| Complexity | Easy (Default) | Medium (Env vars, limited drivers) |
When to use Rootless?
- Always, if possible.
- Multi-tenant environments (Shared CI runners).
- Production servers where security is paramount.
[!TIP] Port 80/443: To bind privileged ports in rootless mode, allow it via sysctl:
sudo sysctl net.ipv4.ip_unprivileged_port_start=0