Podman Rootless and Rootful Capabilities
CapEff: 00000000800405fb in a rootless container, and the identical value under sudo. Eleven capabilities either way, no CAP_SYS_ADMIN in either, and mount fails in both - the difference is reach, not the bitmask.
Security and Operations Guide 41 of 47 Intermediate
- OSUbuntu 26.04 LTS
- Podman5.7.0
- Runtimecrun 1.21
- Networknetavark 1.16.1
- TimeAbout 14 min
Podman follows the distribution here rather than a vendor repository, so the version you get is the one Ubuntu shipped. The commands are stable across 5.x.
| Server Name | IP Address | OS | Roles | CPU | RAM | HDD |
|---|---|---|---|---|---|---|
| PODMAN01 | 192.168.0.24 | Ubuntu 26.04 LTS | Primary Container Host | 2 Core | 4 GB | 50 GB |
This guide includes
Use this when you expect rootless to be more restricted than rootful and want to check rather than assume. This matters because the capability mask is identical in both - so whatever rootful is buying you, it is not capabilities.
- reading the capability mask twice, rootless and rootful, and finding them identical
- listing the three things that genuinely do fail rootless
- finding where the difference actually lives, since it is not in the mask
Before you start
- guide 3 - the user namespace is what makes identical capabilities mean different things.
-
Read the mask twice
/proc/self/statusinside a container reports what it actually holds. Rootless:CapPrm: 00000000800405fb CapEff: 00000000800405fb CapBnd: 00000000800405fbNow the same thing under
sudo:CapEff: 00000000800405fbIdentical. This is the thing worth correcting: rootful Podman does not hand a container a bigger capability set. Both get the same eleven, because the default set is a property of Podman's configuration, not of who ran it.
capsh --decodeturns the hex into names:cap_chown, cap_dac_override, cap_fowner, cap_fsetid, cap_kill, cap_setgid, cap_setuid, cap_setpcap, cap_net_bind_service, cap_sys_chroot, cap_setfcapRead what is absent: no
cap_sys_admin, nocap_net_admin, nocap_net_raw, nocap_sys_module, nocap_sys_ptrace. Podman drops those by default for everyone. A container is not "root on the host minus namespaces" - it starts from a deliberately reduced set.bash Example session podman run --rm docker.io/library/alpine grep -E '^Cap(Prm|Eff|Bnd)' /proc/self/statusCapPrm: 00000000800405fbCapEff: 00000000800405fbCapBnd: 00000000800405fbsudo -n podman run --rm docker.io/library/alpine grep -E '^Cap(Prm|Eff|Bnd)' /proc/self/statusTrying to pull docker.io/library/alpine:latest...Getting image source signaturesCopying blob sha256:55afa1ecc21d2bb5e5045f32dafee56272ffd89860bac26f6c32123439af26a4Copying config sha256:d529dd0c6e5597ac7e4a3e2dea65c3fcc6173f4cae713c409265c1dd9914a11bWriting manifest to image destinationCapPrm: 00000000800405fbCapEff: 00000000800405fbCapBnd: 00000000800405fbpodman run --rm docker.io/library/alpine sh -c 'apk add -q libcap 2>/dev/null; capsh --decode=$(grep CapEff /proc/self/status | cut -f2)'0x00000000800405fb=cap_chown,cap_dac_override,cap_fowner,cap_fsetid,cap_kill,cap_setgid,cap_setuid,cap_setpcap,cap_net_bind_service,cap_sys_chroot,cap_setfcapExpected resultThe same three-line mask in both containers, and eleven decoded capability names.
Success conditionYou have the same capability mask from a rootless and a rootful container.
-
So what does rootless actually cost you
Three things that fail rootless, and the fourth line is the interesting one:
$ mount -t tmpfs none /mnt mount: permission denied (are you root?) $ sysctl -w kernel.hostname=nope sysctl: error setting key 'kernel.hostname': Read-only file system $ echo 1 > /proc/sys/net/ipv4/ip_forward sh: can't create /proc/sys/net/ipv4/ip_forward: Read-only file systemNow the same
mount, rootful:$ sudo podman run --rm alpine mount -t tmpfs none /mnt mount: mounting none on /mnt failed: Permission deniedIt fails there too. Not because of the user namespace - because
cap_sys_adminis not in the default set, andmountneeds it.sudodid not help, and reaching for it would not have helped.Note also the two
/proc/sysfailures say Read-only file system, not permission denied. Podman mounts/proc/sysread-only regardless of capabilities, so there is no capability to add that would let a container change a host sysctl. That is a mount option, not a privilege - a different mechanism reaching the same end.So the honest summary: the default capability set is the main constraint, and it is the same either way. What rootless additionally costs you is narrower than people assume, and it is specific: no privileged ports below 1024 (guide 51), no UIDs past your subuid range (guide 34), no macvlan (guide 54), and a userspace network stack (guide 53).
bash Example session podman run --rm docker.io/library/alpine mount -t tmpfs none /mntmount: permission denied (are you root?)[exit 1]podman run --rm docker.io/library/alpine sysctl -w kernel.hostname=nopesysctl: error setting key 'kernel.hostname': Read-only file system[exit 1]podman run --rm docker.io/library/alpine sh -c 'echo 1 > /proc/sys/net/ipv4/ip_forward'sh: can't create /proc/sys/net/ipv4/ip_forward: Read-only file system[exit 1]sudo -n podman run --rm docker.io/library/alpine mount -t tmpfs none /mntmount: mounting none on /mnt failed: Permission denied[exit 255]Expected resultThree rootless failures, and the same
mountfailing rootful as well.Success conditionYou can name something
sudo podmandoes not fix. -
Where the difference does live
If the capabilities match, what is rootful actually buying? Two things, and neither is a capability:
1. The namespace those capabilities apply in. Rootless, your eleven capabilities are held in *your* user namespace, so
cap_chowncan only chown to UIDs mapped into it - which is exactly the ceiling measured in guide 34. Rootful,cap_chownmeans the whole UID space. Same capability, different reach.2. What you are allowed to ask for.
--privileged,--cap-add=SYS_ADMIN,--device, macvlan, a real bridge - a rootful Podman can grant these because root has them to give. Rootless cannot grant what it does not hold, so the flags exist and quietly do less.The practical consequence is a good habit: when something fails in a container, read whether the message names a capability or a namespace.
Operation not permittedon a syscall is usually a missing capability, and--cap-addmay fix it in either mode.Invalid argumenton a UID, orRead-only file systemon/proc/sys, is structural - no capability will help, andsudois a different machine (guide 14) rather than a bigger one.bash Example session podman run --rm --cap-drop=ALL --security-opt no-new-privileges docker.io/library/alpine 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)Expected result
uid=0(root)in a container holding no capabilities at all.Success conditionYou can say what rootful gives you without saying "more capabilities".
Troubleshooting
Operation not permittedon a syscall you expected to work.Why: A capability outside Podman's default eleven.
Fix:Identify it from the syscall -
mountisSYS_ADMIN, raw sockets areNET_RAW,ptraceisSYS_PTRACE- then--cap-add=. Rootless can only add what your namespace holds.--privilegeddoes not behave as documented rootless.Why: It grants everything *available*, and rootless has far less available.
Fix:Expected.
podman run --rm --privileged alpine grep CapEff /proc/self/statusshows what you actually got.pingfails inside a container.Why:
cap_net_rawis not in the default set.Fix:
--cap-add=NET_RAW, or rely onnet.ipv4.ping_group_rangewhich many distributions set permissively.You need to set a sysctl for the container.
Why:
/proc/sysis mounted read-only inside.Fix:
--sysctl net.core.somaxconn=1024on the run command - Podman sets it namespaced at creation. Only namespaced sysctls can be set this way.