Containers·5 min read·

Hardening Docker Daemon Security with Rootless Mode and User Namespaces

Configure rootless Docker and Linux user namespaces to eliminate root daemon vulnerabilities, restrict container capabilities, and secure your VPS workloads.

NB

Netbay Engineering

Netbay Engineering

On this page

Running containers under a standard Docker installation presents an inherent architectural risk: the Docker daemon itself runs as host root. If an attacker achieves remote code execution within a container and exploits a kernel privilege escalation or misconfigured bind mount, they can compromise the entire host operating system. Rootless Docker solves this threat model by executing both the container engine daemon and user containers inside an unprivileged Linux user namespace.

In rootless mode, the daemon runs under an ordinary Linux user account (such as UID 1001). Container root (UID 0 inside the container) is mapped to an unprivileged subordinate user ID on the host (for example, UID 100000), while the host root account (UID 0) is completely inaccessible from within the container execution context. This boundary ensures that even if a containerized process breaks out of its namespace through a container escape vulnerability, it possesses only the limited permissions of the non-root user on the host system.

The core technology powering rootless containers is the Linux user namespace (user_namespaces(7)). User namespaces isolate security-related identifiers, specifically user IDs and group IDs. A process inside a user namespace can have full root privileges (UID 0) inside its own namespace while mapping to an arbitrary non-zero UID on the parent system. When combined with subordinate UID ranges (/etc/subuid and /etc/subgid), the container runtime can allocate a pool of 65,536 distinct unprivileged host IDs to represent all internal container accounts cleanly.

Expand
Rootless Docker Security Architecture
Rootless Docker execution isolation on Linux VPS

Prerequisites and Kernel User Namespace Configuration

Before enabling rootless Docker on modern distributions such as Ubuntu 24.04 or Debian 12, verify that unprivileged user namespaces and subordinate UID/GID mappings are configured in the kernel. In some enterprise distributions, unprivileged user namespaces are disabled by default to restrict unauthenticated local privilege escalation vectors.

Verify that user namespaces are supported by your kernel and configure the subordinate UID and GID ranges for a dedicated deployment user:

Rootless Docker Daemon: User Namespace Privilege Boundary Traditional Root Daemon (dockerd) - Runs as Host UID 0 (root) - Full host kernel capabilities - Container breakout = Host root compromise - Socket: /var/run/docker.sock High Attack Surface / Escalation Risk Rootless Daemon (dockerd-rootless) - Runs as unprivileged UID 1001 (deploy) - Subordinate UIDs: 100000-165535 - Container breakout = Restricted user on host - Socket: /run/user/1001/docker.sock Zero Host Root Privileges Enforced
bash
# Verify user namespace support in the Linux kernel
sysctl kernel.unprivileged_userns_clone

# Create a dedicated deployment user if not already present
sudo useradd -m -s /bin/bash deploy || true

# Allocate 65,536 subordinate UIDs and GIDs for deploy user
sudo usermod --add-subuids 100000-165535 deploy
sudo usermod --add-subgids 100000-165535 deploy

# Verify the subordinate mappings are correctly registered
grep deploy /etc/subuid /etc/subgid

In addition to user namespaces, rootless networking relies on user-space network emulation through RootlessKit and slirp4netns or VPNKit. These tools create a virtual tap interface inside the container network namespace and translate TCP and UDP packets into unprivileged host socket syscalls, removing the requirement for root-level iptables manipulation.

Installing and Starting the Rootless Docker Engine

Switch to the unprivileged deployment user and execute the official rootless setup tool. The installation script inspects your system prerequisites, sets up the rootless systemd unit under ~/.config/systemd/user/docker.service, and initializes the rootless Docker socket:

bash
# Switch to the dedicated deployment user account
su - deploy

# Run the rootless Docker setup tool
dockerd-rootless-setuptool.sh install

# Enable systemd user session lingering so daemons persist after logout
loginctl enable-linger deploy

# Export the user Docker socket environment variables in user profile
export DOCKER_HOST=unix:///run/user/1001/docker.sock
echo 'export DOCKER_HOST=unix:///run/user/1001/docker.sock' >> ~/.bashrc

# Verify that the daemon is active without root privileges
systemctl --user status docker
docker info | grep -E 'Rootless|Security Options'

Notice that the Docker daemon socket is located at /run/user/1001/docker.sock rather than the traditional /var/run/docker.sock. This ensures that only processes running as the deploy user (or members of the deploy user session) can interact with the Docker API, eliminating the notorious security vulnerability where granting group access to docker is equivalent to granting passwordless host root access.

Hardening the Rootless Daemon Configuration

To harden your rootless daemon beyond default settings, create a user-level configuration file at ~/.config/docker/daemon.json. This configuration restricts inter-container communication (ICC), enforces strict process limits, and prevents privilege escalation flags:

json
{
  "userns-remap": "default",
  "icc": false,
  "no-new-privileges": true,
  "log-driver": "local",
  "log-opts": {
    "max-size": "25m",
    "max-file": "3"
  },
  "default-ulimits": {
    "nofile": {
      "Name": "nofile",
      "Hard": 65535,
      "Soft": 32768
    }
  }
}

The no-new-privileges flag prevents container processes from gaining additional privileges via setuid or setgid binaries inside the container image, blocking common escalation payloads. The icc: false setting prevents containers on the default bridge network from snooping on each other's traffic or establishing unauthorized inter-container connections unless explicitly linked.

After updating the configuration file, reload the systemd user daemon with systemctl --user restart docker.

Operational Caveats and Low Port Binding

Because rootless Docker does not possess the CAP_NET_BIND_SERVICE capability on the host network, containers cannot bind to privileged ports below 1024 by default. If your web application container attempts to listen directly on port 80 or 443, the kernel will reject the bind syscall with a permission denied error.

Rather than running an external reverse proxy as root, you can configure the Linux kernel to lower the unprivileged port threshold to port 80:

bash
# Allow unprivileged processes to bind ports from 80 upwards
echo 'net.ipv4.ip_unprivileged_port_start=80' | sudo tee -a /etc/sysctl.d/99-rootless.conf
sudo sysctl --system

# Verify the new threshold is active
sysctl net.ipv4.ip_unprivileged_port_start

Another consideration is storage drivers: rootless Docker defaults to overlay2 when supported by your kernel and filesystem. On Ubuntu 24.04 with ext4 or XFS, native unprivileged OverlayFS is fully supported. If you encounter errors relating to fuse-overlayfs, ensure your kernel has unprivileged overlay mounts enabled.

By shifting your container runtime from the global system context to an unprivileged user namespace, you effectively neutralize an entire class of container escape exploits on your server.

Docker Rootless Mode Tutorial
YouTube

You can spin up an isolated Ubuntu 24.04 instance on Netbay in under 60 seconds with Intel Xeon Platinum compute and high-speed SSD storage in Lucknow — netbayhosts.in.

Keep reading

Follow along on a real VPS

Deploy Linux in under 60 seconds

These guides are written against Ubuntu, Debian, and RHEL-family images — the same ones on NetBay.

Deploy an instance