CertGrid CertGrid
Concepts·Podman

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

Written against the versions above. 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.

Every command on this page ran on podman01.
Server NameIP AddressOSRolesCPURAMHDD
PODMAN01192.168.0.24Ubuntu 26.04 LTSPrimary Container Host2 Core4 GB50 GB

Before you start

  1. Read the mask twice

    /proc/self/status inside a container reports what it actually holds. Rootless:

    CapPrm:	00000000800405fb
    CapEff:	00000000800405fb
    CapBnd:	00000000800405fb

    Now the same thing under sudo:

    CapEff:	00000000800405fb

    Identical. 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 --decode turns 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_setfcap

    Read what is absent: no cap_sys_admin, no cap_net_admin, no cap_net_raw, no cap_sys_module, no cap_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_setfcap

    Expected 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.

  2. 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 system

    Now the same mount, rootful:

    $ sudo podman run --rm alpine mount -t tmpfs none /mnt
    mount: mounting none on /mnt failed: Permission denied

    It fails there too. Not because of the user namespace - because cap_sys_admin is not in the default set, and mount needs it. sudo did not help, and reaching for it would not have helped.

    Note also the two /proc/sys failures say Read-only file system, not permission denied. Podman mounts /proc/sys read-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 mount failing rootful as well.

    Success conditionYou can name something sudo podman does not fix.

  3. 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_chown can only chown to UIDs mapped into it - which is exactly the ceiling measured in guide 34. Rootful, cap_chown means 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 permitted on a syscall is usually a missing capability, and --cap-add may fix it in either mode. Invalid argument on a UID, or Read-only file system on /proc/sys, is structural - no capability will help, and sudo is 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 resultuid=0(root) in a container holding no capabilities at all.

    Success conditionYou can say what rootful gives you without saying "more capabilities".

Troubleshooting

Official sources