How to Configure Linux kernel user namespaces (userns) to Mitigate Container Privilege Escalation

In modern containerized environments (such as Docker, Podman, or Kubernetes), a critical security vulnerability exists by default: the root user inside the container is cryptographically identical to the root user on the physical host operating system. Both share the User ID (UID) of 0. If an attacker manages to exploit a vulnerability within the containerized application and achieve container-level root access, they possess the permissions necessary to mount host filesystems, manipulate kernel parameters, and ultimately execute a container breakout, seizing total control of the underlying host. To sever this dangerous relationship and enforce true isolation, Linux engineers must explicitly configure and enable User Namespaces (userns).

Understanding Linux User Namespaces

User Namespaces are a fundamental feature of the Linux kernel that allow a process to possess a completely different set of UIDs and Group IDs (GIDs) inside the namespace compared to the host operating system.

When User Namespaces are enabled, the kernel performs a mathematical translation. The user running as root (UID 0) inside the container is seamlessly mapped to an unprivileged, high-number UID (e.g., UID 100000) on the host system. The containerized application genuinely believes it has root privileges, allowing it to install packages (via apt or yum), bind to privileged ports (like port 80), and manage internal filesystem permissions.

However, from the perspective of the host operating system, that process is running as a completely unprivileged user. If the attacker escapes the container, they drop onto the host filesystem possessing UID 100000—meaning they lack the permissions to read /etc/shadow, modify kernel modules, or interact with other running containers.

Configuring User Namespace Remapping in Docker

While Podman enables User Namespaces by default (rootless containers), the widely deployed Docker daemon runs as host-root by default and requires explicit reconfiguration to utilize the userns-remap feature.

Step 1: Define the Subordinate UIDs and GIDs

First, you must define the mapping range. Create a dedicated unprivileged user on the host (e.g., dockremap). Then, populate the /etc/subuid and /etc/subgid files. These files dictate which high-number UIDs the dockremap user is authorized to utilize for the translation mapping.

# Create the unprivileged user
sudo useradd --system dockremap

# Define a block of 65,536 UIDs and GIDs starting at 100000
echo "dockremap:100000:65536" | sudo tee -a /etc/subuid
echo "dockremap:100000:65536" | sudo tee -a /etc/subgid

In this configuration, UID 0 inside the container will map to UID 100000 on the host. UID 1 inside the container maps to UID 100001, and so forth.

Step 2: Reconfigure the Docker Daemon

Next, you must instruct the Docker daemon (dockerd) to actively utilize this mapping.

Open or create the Docker daemon configuration file, typically located at /etc/docker/daemon.json.

{
  "userns-remap": "dockremap"
}

Step 3: Restart and Verify

Save the configuration file and restart the Docker service to apply the kernel namespace modifications:

sudo systemctl restart docker

To verify the configuration is functioning correctly, spawn a new interactive container as root and run the sleep command:

docker run -d --name userns-test alpine sleep 3600

Inside the container, the process is running as root. However, open a second terminal on the physical Linux host and inspect the process using ps:

ps aux | grep "sleep 3600"

In the process list, you will clearly see that the sleep command is owned by UID 100000 (or the name dockremap), not root. The container has been cryptographically isolated from the host’s privilege hierarchy, drastically reducing the blast radius of any potential application compromise.

Get the best tech tips delivered straight to your inbox.

Join thousands of readers mastering Apple, Google, Microsoft, and Linux.