Running Podman Rootless: User Namespace Mapping, Configuration, and Practical Limits
Podman runs containers rootless by default using Linux user namespaces. This guide covers subordinate UID/GID setup, a worked nginx example, networking limits, storage drivers, systemd integration, and the common permission trap with host volumes.
03 Oct 2026, 20:45 UTC

The Problem and the Takeaway
You need to run containers without root privileges—on a shared server, in CI, or on a developer laptop—while keeping the host secure. Podman does this by default when a non-root user starts it: it maps container UID 0 (root inside the container) to an unprivileged host UID range using Linux user namespaces. The container believes it runs as root; the host sees only an ordinary user's processes.
This works out of the box if your user has subordinate ID ranges configured in /etc/subuid and /etc/subgid. Without those ranges, Podman falls back to a single UID mapping that breaks many container images. The practical takeaway: allocate a subordinate range once, then run containers normally—no sudo, no daemon, no rootless kit installation.
How User Namespace Mapping Works
When you run podman run as a non-root user, Podman asks the kernel to create a new user namespace. Inside that namespace, UID 0 maps to a host UID drawn from your subordinate range. For example, if your range starts at 100000, container root becomes host UID 100000, container UID 1 becomes 100001, and so on. The mapping is defined in /proc//uid_map for each container process.
This mapping is per-user, not per-container. All your rootless containers share the same subordinate range, but each gets its own user namespace instance. The kernel enforces that a process in one user namespace cannot affect processes in another unless capabilities allow it.
Worked Example: Rootless Nginx on Port 8080
Assume you're logged in as user alice on a system with kernel 5.11+ and Podman 4.0+. First, ensure subordinate ranges exist (run as root or via sudo):
# Run as root or with sudo
usermod --add-subuids 100000-165535 --add-subgids 100000-165535 alice
Log out and back in so the new ranges take effect. Then run the container:
# Run as alice (no sudo)
podman run -d --name web -p 8080:80 docker.io/library/nginx:alpine
What happens:
- Podman pulls the image to
$HOME/.local/share/containers/storage(overlayfs viafuse-overlayfsif the kernel lacks unprivileged overlay support). - The container starts in a new user namespace. Its root (UID 0) maps to host UID 100000.
- Port 80 inside the container is published to host port 8080 via
slirp4netns(user-mode networking). Because 8080 > 1024, no kernel sysctl is needed.
Verify it works:
curl localhost:8080
# Should return nginx's welcome page
Check the mapping from the host side:
# Find the container's PID on the host
podman inspect --format '{{.State.Pid}}' web
# Suppose it prints 12345
cat /proc/12345/uid_map
# Expected output shows: 0 100000 65536
Networking: Ports Below 1024 and Host Network Mode
Rootless containers cannot bind privileged ports (< 1024) by default. Two options exist:
- Sysctl change (kernel 5.11+): Run as root once:
Thensysctl -w net.ipv4.ip_unprivileged_port_start=0 # Persist: echo 'net.ipv4.ip_unprivileged_port_start=0' > /etc/sysctl.d/99-unprivileged-ports.confpodman run -p 80:80 ...works rootless. - Host network mode:
podman run --net=host ...shares the host network namespace. The container still runs rootless (user namespace isolates UIDs), but it sees all host interfaces and can bind any port the user could bind. Use when you need raw socket access or low ports without sysctl changes.
Default networking uses slirp4netns (or pasta on newer Podman). It translates container traffic to host user-space sockets—no CAP_NET_ADMIN, no TAP devices. Expect slightly higher latency and no inbound connections unless you publish ports with -p.
Storage Location and Drivers
Rootless images and containers live under $HOME/.local/share/containers/storage. The default driver is overlay backed by fuse-overlayfs (a FUSE userspace implementation) because most kernels disallow unprivileged overlay mounts. On kernels 5.11+ with CONFIG_OVERLAY_FS and CONFIG_USER_NS, native overlay may work if /etc/containers/storage.conf enables it.
Check your driver:
podman info --format '{{.Store.GraphDriverName}}'
# Typical output: overlay (with fuse-overlayfs)
Performance note: fuse-overlayfs adds context-switch overhead. For I/O-heavy workloads (databases, builds), consider a rootful Podman instance or a dedicated volume mounted with -v that bypasses the overlay layer.
Systemd Integration: Running at Boot Without Login
To start containers on boot without an active user session, enable lingering for the user and generate a systemd user unit:
# Run once as root or with sudo
loginctl enable-linger alice
# As alice, generate the unit
podman generate systemd --name web --files --new
# Creates container-web.service in ~/.config/systemd/user/
# Enable and start
systemctl --user daemon-reload
systemctl --user enable --now container-web.service
Verify:
systemctl --user status container-web.service
# Should show active (running)
This works on systemd 247+ with cgroup v2. On cgroup v1 systems, resource limits (memory, CPU) may be unavailable for rootless containers.
Common Mistake: Host Volume Permissions
A frequent surprise: files created inside the container as root appear on the host owned by the mapped UID (e.g., 100000), not your login UID (e.g., 1000). Example:
# As alice
mkdir -p /home/alice/data
podman run --rm -v /home/alice/data:/data:Z alpine touch /data/from-container
ls -l /home/alice/data/
# Output: -rw-r--r-- 1 100000 100000 0 ... from-container
The :Z suffix relabels the volume for SELinux (required on Fedora/RHEL). Without it, the container may be denied access. But ownership remains mapped.
Fixes:
- Chown the host directory to the mapped UID:
sudo chown 100000:100000 /home/alice/data(requires root). - Use a bind mount with UID mapping:
--uidmap 0:1000:1 --gidmap 0:1000:1maps container root to your login UID. This reduces isolation—container root becomes your user. - Run the container process as a non-root UID inside the container (e.g.,
--user 1000) and ensure the image supports it.
Hard Limits You Cannot Work Around
- Kernel modules: Rootless containers cannot load modules (
modprobe,insmod). If your workload needs WireGuard, eBPF, or special filesystems, use rootful Podman or a VM. - Filesystem mounts: NFS, CIFS, FUSE (except
fuse-overlayfs) require a privileged helper or rootful mode. The kernel blocks unprivileged mounts of these types. - Sysctls and capabilities: Even with
--cap-add=ALL, the user namespace lacks capabilities in the parent namespace. You cannot setnet.ipv4.tcp_congestion_controlor useCAP_SYS_ADMINfor mount operations. - Subordinate range conflicts: Ranges in
/etc/subuidand/etc/subgidmust not overlap across users. Check withcat /etc/subuidbefore adding.
Verification Checklist
After setup, confirm rootless operation:
podman info --format '{{.Host.Security.Rootless}}'→truepodman top web husershows UID 0 inside;cat /proc//uid_mapshows 0 100000 65536 on hostcurl localhost:8080returns nginx pagesystemctl --user status container-web.serviceshows activels -la $HOME/.local/share/containers/storage/shows overlay directories
If any check fails, review subordinate ranges, kernel version, and SELinux status (sestatus).
When to Choose Rootful Instead
Rootless Podman covers most developer and CI workloads. Switch to rootful (or a VM) when you need:
- Kernel module loading
- Privileged port binding without sysctl changes on older kernels
- Native overlayfs performance for heavy I/O
- Full capability set including
CAP_SYS_ADMINfor mounts - NFS/CIFS volume mounts without helper daemons
Podman lets you run both modes side by side—rootless for daily work, rootful for the exceptions.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.