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: ```console $ 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 [Run in the secure runtime](/docs/hetzner/apalla/workloads/secure/run-in-the-secure-runtime). ## 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 [security context](/docs/hetzner/apalla/workloads/secure/set-security-contexts).