Rootless & security¶
"Rootless is safer" gets repeated so often it turns into folklore — a vague sense that skipping sudo
somehow helps, without anyone able to say exactly why. The precise version is more interesting: rootless
doesn't remove root, it removes root's reach. The main course page lists the security defaults
(capabilities dropped, seccomp, SELinux label) as a bullet list; this page goes one layer deeper on the
mechanism underneath the single biggest one — the user namespace — why it makes rootless meaningfully
safer than "just running as root but with fewer capabilities," and the handful of cases where rootless
genuinely can't do the job.
TL;DR — in 30 seconds:
- Rootless doesn't remove root — it remaps container UID 0 to an unprivileged host UID via a user namespace, so "root inside the container" has no special standing on the host.
- Capabilities are a second, independent layer on top of the namespace: Podman starts containers with
a curated default set that excludes the dangerous, host-affecting ones (
CAP_SYS_ADMIN,CAP_NET_ADMIN, …). - Rootless is the default, not universal — a handful of cases (privileged ports without a workaround, some device mounts, legacy UID-0-assuming tooling) still genuinely need rootful Podman.
1. What a user namespace actually remaps¶
A Linux user namespace lets a process see a different UID/GID inside it than the UID/GID the host
sees outside it. Podman rootless mode puts every container in its own user namespace and maps the
container's UID 0 (root) to an unprivileged UID on the host — one from the range your account was
allocated in /etc/subuid and /etc/subgid (typically 100000–165535, a 64k-UID block per user).
flowchart LR
subgraph ctr["Inside the container"]
R0["UID 0 (root)"]
R1["UID 1"]
R33["UID 33 (e.g. www-data)"]
end
subgraph host["On the host"]
H0["UID 1000<br/>(your unprivileged login)"]
H100000["UID 100000<br/>(mapped from container root)"]
H100001["UID 100001"]
H100033["UID 100033"]
end
R0 -.->|"mapped to"| H100000
R1 -.->|"mapped to"| H100001
R33 -.->|"mapped to"| H100033
The container process believes it is root — it can chown, bind low-numbered ports inside its own
namespace, install packages, do anything root normally does — inside that namespace only. The
kernel enforces the remap at every syscall boundary: a write that "root" makes inside the container
lands on the host as a write by UID 100000, a UID with no special standing on the host at all. If a
process somehow escaped the container, it would find itself running as an ordinary unprivileged user,
not as the root that started podman.
This is the core security win, and it's structurally different from just dropping capabilities off a root process: a capability drop can be re-added or bypassed by a kernel bug that grants root back its usual powers, but a UID remap means there is no root on the host side to grant powers back to.
2. Capabilities — the second layer¶
Even inside its own user namespace, a container doesn't get the full set of Linux capabilities —
the ~40 discrete privileges that used to be bundled into "is this process root or not" (CAP_NET_ADMIN
to configure networking, CAP_SYS_ADMIN for a large grab-bag of admin operations, CAP_CHOWN to
change file ownership arbitrarily, and so on). Podman starts every container with a curated default
set that leaves out the dangerous, host-affecting ones, and lets you tune it explicitly:
podman run --cap-drop=ALL --cap-add=NET_BIND_SERVICE myimage # start from nothing, add back one
podman run --cap-drop=SYS_ADMIN myimage # keep defaults, remove one
| Capability | What it allows | Default (rootless) |
|---|---|---|
CAP_CHOWN |
Change file ownership to any UID/GID | Included |
CAP_NET_BIND_SERVICE |
Bind ports below 1024 | Included |
CAP_SYS_ADMIN |
Broad admin operations (mount, namespaces, more) | Excluded |
CAP_SYS_MODULE |
Load/unload kernel modules | Excluded |
CAP_SYS_PTRACE |
Trace/inspect other processes | Excluded |
CAP_NET_ADMIN |
Configure interfaces, routes, firewall rules | Excluded |
The user namespace and the capability set answer two different questions: the namespace asks "root of what, exactly?" and capabilities ask "root can do what, exactly?" Rootless mode narrows both at once — root of an unprivileged namespace, holding only a safe capability subset.
3. Why this is safer than "rootful, but careful"¶
A rootful container (podman run as your host root, or with sudo) still benefits from dropped
capabilities and SELinux confinement, but its container-root is host UID 0. A container escape —
a kernel bug in a namespace or cgroup implementation, a misconfigured volume mount that exposes a host
path writable as root — hands the attacker real root on the host. In rootless mode, the equivalent
escape hands the attacker an ordinary unprivileged host account. The blast radius of the single
scariest failure mode (a full container-to-host escape) is categorically smaller, not just harder to
reach.
That's also why the two Containerfile lines you'll see repeated everywhere — USER 1001 and
--cap-drop=ALL --cap-add=<only what's needed> — matter even on top of rootless: rootless narrows what
"root inside the container" can do to the host, but a process running as container-root can still do
plenty of damage inside its own container and its own mounted volumes (overwrite another
service's files sharing that volume, read secrets mounted for a different user). Running as a named
non-root UID inside the image is a second, independent layer of the same idea.
4. When you still need root¶
Rootless is the right default, not a universal one. A handful of cases genuinely need rootful Podman
(sudo podman run …, or a rootful systemd Quadlet unit):
- Binding a privileged port without a workaround. Rootless can bind ports below 1024 via a sysctl change or a granted capability (see the networking page), but a rootful container does it natively, with no extra configuration.
--network hostcombined with low-level network configuration — some CNI plugins and advanced networking setups (certain VLAN/macvlan configurations, raw socket access) assume the host's real network namespace and root's networking capabilities.- Mounting host devices or filesystems that require root to mount at all — a raw block device, a kernel module-backed filesystem — the mount syscall itself needs privilege the user namespace can't grant.
- Some legacy tooling that hard-codes an expectation of UID 0 and hasn't been adapted to run correctly under a remapped namespace.
The 80/20 rule: default to rootless; reach for rootful only when a specific, nameable requirement above forces it, and document why when you do — a rootful container is worth noticing in a review, not a routine choice.
5. How this connects¶
- The UID remap here is what makes
--userns=keep-id(covered on the storage page's SELinux context and the main course page) worth having: it's the same remapping machinery, just aimed so container UID matches your host UID instead of a subordinate range — convenient for dev, not what you'd want for an untrusted workload. - Capabilities and SELinux labels are two independent confinement layers stacked on top of the
namespace boundary — losing one (say, disabling SELinux with
--security-opt label=disablefor a stubborn bind mount) doesn't remove the others; you're trading away one layer, not all of them. - The same "narrow root's scope with a namespace" idea reappears at a larger scale in Kubernetes' Pod
Security Standards and
securityContext.runAsNonRoot— a cluster-wide policy enforcing the same discipline this page describes at the single-host level.
Common gotchas
- Treating "rootless" and "no capabilities" as the same thing. Fix: they're two independent layers — the user namespace answers "root of what," capabilities answer "root can do what." Rootless narrows both, but neither replaces the other.
- Assuming rootless means a container escape is harmless. Fix: it's a categorically smaller blast radius (an unprivileged host account, not real root), not zero risk — still worth defending against.
- Skipping a non-root
USERin the image because "it's rootless anyway." Fix: rootless narrows what container-root can do to the host; container-root can still damage its own container and mounted volumes — set a non-rootUSERas a second, independent layer. - Reaching for
sudo podmanas a first move when something doesn't work. Fix: rootless is the correct default — only fall back to rootful for a specific, nameable requirement (a privileged port with no workaround, a raw device mount), and document why.
Check yourself — a process escapes its container. Why is the outcome different under rootless vs. rootful Podman?
Under rootful, container-root is host UID 0, so an escape hands the attacker real root on the host. Under rootless, container UID 0 is mapped to an ordinary unprivileged host UID, so the equivalent escape only hands the attacker an unprivileged host account — the blast radius is categorically smaller.
Check yourself — name one case where rootful Podman is still the right call.
Any of: binding a privileged port with no workaround available, --network host combined with
certain advanced networking setups, mounting a host device/filesystem that needs root to mount at
all, or legacy tooling that hard-codes an expectation of UID 0.
You can defend this when you can explain what a user namespace actually remaps (and why that's different from just running as root with fewer capabilities), what capabilities and SELinux each add on top of it, and name at least one concrete case where rootful is still the right call.