| Tested on | Red Hat Enterprise Linux 10.2 (Coughlan) |
|---|---|
| Package | podman-5.8.2-5.el10_2.x86_64shadow-utils-4.15.0-11.el10.x86_64shadow-utils-subid-4.15.0-11.el10.x86_64 |
| Applies to | Any Linux host with Podman installed |
| Privilege | Normal user podtest for rootless examples; administrator access for /etc/subuid and /etc/subgid |
| Scope | Rootless setup, subuid/subgid verification, newuidmap/newgidmap, uid_map inspection, first container, storage paths, XDG_RUNTIME_DIR, su vs su -, linger scope, podman system migrate, NFS/noexec storage, RHEL IdM/SSSD subID notes, and setup troubleshooting. Does not cover --userns modes, volume permission diagnosis, pasta internals, or privileged-port workarounds. |
| Related guides | Fix Podman volume permission denied |
Rootless Podman lets a normal Linux account run containers without executing the podman command as root. Setup failures usually trace back to subordinate UID/GID ranges, stale user namespaces, or a broken login environment — not missing container images. This guide walks through verification on the lab user podtest and the errors you are most likely to hit before the first successful podman run.
What is rootless Podman?
Rootless Podman runs the Podman process and containers under a normal Linux account instead of requiring the Podman CLI itself to run as root.
The core mechanism looks like this:
Host user
│
▼
Rootless Podman user namespace
│
├── host user's UID → container/rootless namespace UID 0
└── subordinate UIDs/GIDs → additional container identitiesRoot inside a rootless container is not host root. The container cannot gain privileges the user running Podman does not possess.
Rootless and rootful Podman also keep separate:
- containers
- images
- volumes
- configuration
- storage
A container started with podman run ... as podtest does not appear in sudo podman ps, and the reverse is also true.
Check whether Podman is running rootless
Confirm the active context before changing subordinate ID files.
As the normal user:
podman info --format '{{.Host.Security.Rootless}}'On the lab account:
trueYou can also spot the same flag in a full podman info transcript under the security section as rootless: true.
Establish the host identity next:
iduid=1014(podtest) gid=1015(podtest) groups=1015(podtest) context=unconfined_u:unconfined_r:unconfined_t:s0-s0:c0.c1023When rootless: true and id shows your unprivileged account, you are exercising the rootless storage and namespace path.
Rootless Podman requirements
Before debugging obscure errors, confirm the baseline:
- Podman installed — see Install Podman on RHEL, Ubuntu, Debian, or Rocky Linux
- normal Linux login user, not only a shared service account invoked with broken environment inheritance
newuidmapandnewgidmaponPATH- subordinate UID range in
/etc/subuid - subordinate GID range in
/etc/subgid - writable local storage for the user's graph root
- supported rootless network helper such as pasta on Podman 5+
- valid user runtime/session environment (
HOME,XDG_RUNTIME_DIRwhen required)
Modern kernels can use native rootless OverlayFS. Rootless Podman does not always require fuse-overlayfs.
Check newuidmap and newgidmap
These helpers construct the subordinate UID/GID portions of the rootless user namespace.
Verify they exist:
command -v newuidmap/usr/bin/newuidmapCheck the GID mapper the same way:
command -v newgidmap/usr/bin/newgidmapOn RHEL-family systems they normally ship with shadow-utils. If either command is missing, install the distribution package that provides them before editing subordinate ID files.
Check /etc/subuid and /etc/subgid
Read the ranges assigned to the lab user:
grep '^podtest:' /etc/subuidpodtest:983040:65536Repeat for the GID file — the range should match:
grep '^podtest:' /etc/subgidpodtest:983040:65536The file format is:
USERNAME:START:COUNTSo podtest:983040:65536 means:
- first subordinate ID =
983040 - count =
65536 - last subordinate ID =
1048575
That differs from usermod syntax, which uses a start-end range instead of a count.
Subordinate ranges must not overlap between users.
The usermod --add-subuids range trap
/etc/subuid uses START:COUNT, but usermod uses START-END.
To allocate the same 65536 IDs as the lab entry, an administrator would run:
sudo usermod --add-subuids 983040-1048575 podtestAllocate the GID range with the same start-end pair:
sudo usermod --add-subgids 983040-1048575 podtestThe incorrect form --add-subuids 100000-65536 does not mean “start at 100000 with size 65536.” The second number is an ending ID, not a count.
After any change, verify the files:
grep '^podtest:' /etc/subuid /etc/subgid/etc/subuid:podtest:983040:65536
/etc/subgid:podtest:983040:65536If ranges were added after Podman already initialized rootless state, continue to the podman system migrate section below.
Use getsubids to verify subordinate IDs
On current shadow-utils builds, getsubids is often clearer than reading the flat files alone:
getsubids podtest0: podtest 983040 65536This becomes especially useful when subordinate IDs are not sourced directly from /etc/subuid, such as SSSD-backed ranges on IdM-managed hosts.
Inspect the actual rootless UID/GID mapping
File entries alone do not prove the active namespace is using them. Read the live map as podtest:
podman unshare cat /proc/self/uid_map0 1014 1
1 983040 65536Then the GID map:
podman unshare cat /proc/self/gid_map0 1015 1
1 983040 65536Interpretation:
- namespace ID
0maps to host UID1014(thepodtestaccount) - namespace IDs
1through65536map to host subordinate UIDs starting at983040
That second line must exist for rootless image extraction and multi-entry container ID maps. Custom --uidmap tuning belongs in Podman user namespaces, not in this setup guide.
Run your first rootless container
Pull and run the upstream hello image:
podman run --rm quay.io/podman/hello!... Hello Podman World ...!Run a shell-capable image and inspect IDs inside the container namespace:
podman run --rm docker.io/library/alpine:3.20 iduid=0(root) gid=0(root) groups=0(root),1(bin),2(daemon),3(sys),4(adm),6(disk),10(wheel),11(floppy),20(dialout),26(tape),27(video)uid=0(root) here does not mean host UID 0. Compare with the host-side unshare view:
podman unshare iduid=0(root) gid=0(root) groups=0(root) context=unconfined_u:unconfined_r:container_runtime_t:s0-s0:c0.c1023Both show namespace-local root mapped to the unprivileged host account.
Where rootless Podman stores its data
Rootless state is private to the user. Read the configured paths:
podman info --format 'graphRoot={{.Store.GraphRoot}} runRoot={{.Store.RunRoot}} volumePath={{.Store.VolumePath}}'On the lab account:
graphRoot=/home/podtest/.local/share/containers/storage runRoot=/run/user/1014/containers volumePath=/home/podtest/.local/share/containers/storage/volumesTypical defaults when XDG_DATA_HOME is unset:
- persistent storage:
$HOME/.local/share/containers/storage - runtime state:
$XDG_RUNTIME_DIR/containers(here/run/user/1014/containers)
Moving graphroot to local disk when $HOME is NFS-backed is covered in Podman storage location. This article does not walk through every storage.conf key.
What is XDG_RUNTIME_DIR?
XDG_RUNTIME_DIR holds user-specific runtime state such as the rootless runRoot directory. A broken value is a common setup failure when operators switch users incorrectly.
On a healthy systemd login session it is typically /run/user/$UID. Check ownership when troubleshooting:
stat -c '%u %U %n' /run/user/10141014 podtest /run/user/1014The directory owner must match the user running Podman. Do not tell readers to permanently hard-code export XDG_RUNTIME_DIR=/run/user/1014 in shell profiles as a universal fix. Fix the login session instead.
su vs su - with rootless Podman
Switching users incorrectly from root is a frequent source of XDG_RUNTIME_DIR errors.
A non-login su can inherit the previous user's environment:
su podtest -c 'echo HOME=$HOME XDG_RUNTIME_DIR=$XDG_RUNTIME_DIR'HOME=/home/podtest XDG_RUNTIME_DIR=/run/user/0/run/user/0 belongs to root, not podtest.
A login shell resets more of the environment:
su - podtest -c 'echo HOME=$HOME XDG_RUNTIME_DIR=$XDG_RUNTIME_DIR'HOME=/home/podtest XDG_RUNTIME_DIR=Even when XDG_RUNTIME_DIR is empty in a non-interactive su - snippet, Podman on the lab host still uses /run/user/1014/containers because that path exists for the user. The danger is inheriting another user's runtime directory, not an empty variable by itself.
Prefer SSH or su - with a full login session, and verify stat on the runtime directory before blaming subordinate IDs.
Fix XDG_RUNTIME_DIR is not owned by the current user
When Podman reports that error, compare the current UID with the runtime directory owner:
id -uPrint the runtime directory Podman inherited from your session:
echo "$XDG_RUNTIME_DIR"Confirm which user owns that path:
stat -c '%u %U %n' "$XDG_RUNTIME_DIR"The UID column must match id -u. If you inherited /run/user/0 from a root su session, open a proper login session for podtest instead of recursively changing ownership under /run/user.
Do not delete /run/user/$UID as a standard repair. That directory is managed by the user's login session.
When loginctl enable-linger is required
loginctl enable-linger is not required merely to run podman run interactively in an SSH or desktop session.
Enable linger when:
- user services must continue after logout
- rootless Quadlet or systemd user units must run without an active login
- rootless containers need to survive session termination
- user services should start during boot without an interactive login
Enable it for the target account:
sudo loginctl enable-linger podtestVerify:
loginctl show-user podtest -p LingerLinger=yesLogout and reboot behavior for long-running containers belongs in the container lifecycle guides. Rootless Quadlet units are covered separately.
Fix there might not be enough IDs available in the namespace
This error usually means the active rootless user namespace does not expose enough subordinate IDs, even when /etc/subuid looks correct on disk.
Inspect the live map first:
podman unshare cat /proc/self/uid_mapA broken map might show only the single host-user line and omit the subordinate range starting at 983040.
Confirm the files:
grep '^podtest:' /etc/subuid /etc/subgidIf ranges were added or changed after Podman initialized the rootless pause namespace, refresh Podman's persistent rootless state:
- stop the user's running containers
- run
podman system migrate - confirm
/etc/subuidand/etc/subgidstill contain the intended mappings - start Podman again
- re-read
uid_mapandgid_map
Run the migrate step as the affected user:
podman system migratepodman system migrate exits silently on success on the lab host. Verify the map afterward:
podman unshare cat /proc/self/uid_map0 1014 1
1 983040 65536Do not jump straight to podman system reset for this error.
Why podman system migrate matters after subuid/subgid changes
Rootless Podman keeps a pause process alive to hold the user's unprivileged namespaces open. Editing /etc/subuid or /etc/subgid does not automatically rewrite a namespace that is already running.
podman system migrate stops the necessary rootless state so the next Podman operation can recreate the namespace with the updated subordinate ranges. That is why migrate belongs in the normal repair path and reset belongs only after deliberate data-loss review.
Fix newuidmap or newgidmap errors
When Podman reports failures involving newuidmap or newgidmap, verify the helpers and the user's ranges before changing permissions.
Check the binaries:
ls -l "$(command -v newuidmap)"-rwxr-xr-x. 1 root root 43144 Feb 23 05:30 /usr/bin/newuidmapInspect the GID helper permissions too:
ls -l "$(command -v newgidmap)"-rwxr-xr-x. 1 root root 43160 Feb 23 05:30 /usr/bin/newgidmapConfirm subordinate ranges exist:
getsubids podtestDo not manually change helper capabilities unless distribution packaging is genuinely broken and official remediation supports it.
Whitespace in /etc/subuid or /etc/subgid
Trailing whitespace can prevent Podman from recognizing an otherwise valid entry. Inspect the raw file:
cat -A /etc/subuid | grep '^podtest:'podtest:983040:65536$The line should end cleanly with $ and no stray spaces before the newline.
Rootless Podman and NFS home directories
Rootless Podman does not fail simply because $HOME lives on NFS. The problem is container storage on a filesystem that does not support the UID mapping semantics rootless storage needs.
If default graph root resolves to NFS:
$HOME/.local/share/containers/storagecontainer layers and metadata can fail in ways that look like permission errors.
Identify where $HOME is mounted:
findmnt -T "$HOME"On the lab host, $HOME is on local XFS:
TARGET SOURCE FSTYPE OPTIONS
/ /dev/mapper/rhel-root xfs rw,relatime,seclabel,attr2,inode64,logbufs=8,logbsize=32k,noquotaWhen $HOME is remote, move rootless graphroot to local storage through storage.conf as described in Podman storage location. That is separate from bind-mounting application data from NFS into a container.
Home or storage mounted noexec / nodev
Rootless storage must live on a filesystem and mount combination that can execute and extract container content.
Check the mount options for the storage path:
findmnt -T "$HOME"A noexec home or graph root can produce permission denied when launching container processes. Point Podman storage at an appropriate local path instead of asking administrators to weaken corporate mount policy.
Rootless Podman with FreeIPA / RHEL IdM / SSSD subordinate IDs
Plain LDAP and RHEL IdM/SSSD subID integration are not the same thing. On current RHEL systems, centrally managed subordinate ranges can flow through SSSD when the platform is configured for it.
RHEL-oriented pieces include:
- IdM assigning user subID ranges
shadow-utils-subidproviding lookup supportnsswitch.confdelegatingsubidto one sourceauthselectfeatures such aswith-subidon supported releases
Example IdM-oriented enablement on RHEL:
sudo authselect enable-feature with-subidVerify delegation:
getsubids idmuserThe subid nsswitch database supports one delegation source at a time, such as:
subid: filesor:
subid: sssDo not maintain conflicting duplicate ranges in both files and SSSD views.
The lab host uses local files only and does not demonstrate IdM lookup. Treat the IdM path as an enterprise configuration note, not the default workstation workflow.
Rootless Podman configuration paths
| Purpose | Rootless location |
|---|---|
| containers config | $HOME/.config/containers/containers.conf |
| storage config | $HOME/.config/containers/storage.conf |
| registry config | $HOME/.config/containers/registries.conf |
| persistent storage | $HOME/.local/share/containers/storage by default |
| runtime state | $XDG_RUNTIME_DIR/containers |
| registry auth | $XDG_RUNTIME_DIR/containers/auth.json by default |
Each file has its own dedicated guide when you need more than path awareness.
Do not use podman system reset as the first fix
podman system reset --force is destructive. It removes the user's local Podman state, including containers, pods, images, networks, and related storage metadata for that account.
Use it only when:
- existing rootless storage is intentionally disposable
- normal mapping and session repair have failed
- you understand the data loss
Before considering reset, inspect what would disappear:
podman ps -aList local images that reset would delete:
podman imagesInclude named volumes in the inventory:
podman volume lsBack up persistent data first. Do not present deleting ~/.config/containers or /run/user/$UID as a routine intermediate repair. Diagnose the specific failure instead.
Rootless Podman setup verification checklist
Work through these commands as the target user when rootless Podman fails before the first successful run.
Confirm identity:
idConfirm helpers:
command -v newuidmapThe GID mapper should resolve the same way:
command -v newgidmapConfirm subordinate ranges:
getsubids "$(id -un)"Or read the files directly:
grep "^$(id -un):" /etc/subuid /etc/subgidConfirm the active namespace map:
podman unshare cat /proc/self/uid_mapConfirm Podman sees rootless mode:
podman info --format '{{.Host.Security.Rootless}}'Finish with a real container:
podman run --rm quay.io/podman/helloWhen that hello container prints successfully and uid_map shows both the host user line and the subordinate range, rootless setup is in good shape. Networking behavior, volume permissions, and user-namespace modes are separate topics.
References
- Podman rootless tutorial
- Podman rootless documentation
- containers.conf man page
- Red Hat — Managing subordinate ID ranges
Summary
Rootless Podman runs under a normal Linux account through user namespaces and subordinate UID/GID ranges from /etc/subuid and /etc/subgid. The host user maps to namespace UID 0; additional container identities draw from the subordinate pool. That namespace-local root is not host root, and rootless storage is separate from anything created with sudo podman.
Setup debugging should follow a fixed order: confirm newuidmap and newgidmap, read subordinate ranges with getsubids, prove the live map with podman unshare cat /proc/self/uid_map, and run podman system migrate after administrators change ranges. Watch for the usermod start-end trap, inherited XDG_RUNTIME_DIR values from careless su, and NFS or noexec storage under the default graph root.
loginctl enable-linger matters for services that must outlive login sessions, not for a one-off interactive podman run. Reserve podman system reset --force for intentional wipes after you have listed containers, images, and volumes. Once hello-world succeeds, continue with Rootless Podman networking for pasta defaults and with Podman storage location if graph root must move off NFS.

