Skip to main content

Run a workload in a user namespace

Inspect 1.36

By default, UID 0 inside a container is UID 0 on the node, so a process that escapes the container while running as root is root on the host, able to reach other pods and host files. A user namespace breaks that link: it maps the container's user IDs onto a different, unprivileged range of host UIDs. The container still sees itself as root; on the node, that same process is an ordinary user with no privileges.

Take that trade whenever a container runs as root you cannot change, or parses untrusted input, and you want a compromised pod's blast radius kept small. It works on every node, costs almost nothing, and takes a single field.

Turn it on#

Set hostUsers: false on the pod spec. There is nothing to install.

yaml
		spec:
  hostUsers: false # remap container UIDs onto an unprivileged host range
  containers:
    - name: app
      image: your/image
	

The kubelet hands the pod its own block of 65536 host UIDs and maps the container's 0-65535 onto it. Volume file ownership is translated for you with idmapped mounts, a kernel feature that rewrites ownership between the pod's range and the host's, so files created inside the pod still read correctly from either side. User namespace support is on by default in 1.36, with no feature gate to set.

What root sees versus what the host sees#

The point of the remap is that the two views disagree. Inside, nothing changed; on the node, the same process has been demoted:

Inside the container On the node
Runs as UID 0, root Runs as an unprivileged UID the kubelet assigns
Creates files owned by root Creates files owned by that unprivileged user
Holds capabilities like cap_sys_admin Those capabilities apply only inside the namespace, powerless against host resources
An escape starts as root An escape lands as a user who owns nothing on the host

Verify#

Inside, the process still sees root; the kernel's map shows what actually happened:

		$ kubectl exec userns-app -- cat /proc/self/uid_map
         0  348258304      65536
	

Read it as: container UID 0 maps to host UID 348258304, for a range of 65536 IDs. The kubelet picks a different range for each pod. A pod without hostUsers: false shows 0 0 4294967295, meaning container root is host root, straight through.

Three tiers, weakest to strongest#

A user namespace is the middle of three boundaries, each stronger than the last:

Tier Kernel Container root on the host Works on
Standard runtime (default) shared with host host root (UID 0) every node
+ user namespace (hostUsers: false) shared with host unprivileged host user every node
Secure runtime (runtimeClassName: secure) own guest kernel isolated in a VM bare metal only

When the kernel itself must not be shared, step up to .

Where it does not work#

A user namespace cannot be combined with hostNetwork: true, hostPID: true, or hostIPC: true: sharing a host namespace and remapping users are mutually exclusive. It also does not fit a workload that must own host files as a real host UID, such as a node agent, which is why platform DaemonSets that need host access do not use it.

Warning

Do not set hostUsers: false cluster-wide. Platform components need host access and break when forced into a user namespace. Set it per workload, or default it only inside namespaces that hold your own applications.

If a workload needs even finer control over what its remapped root can do, pair it with a .