CertGrid CertGrid
Troubleshooting·Podman

Podman Volume Ownership and Permissions

A container running as its own user 1000 writes files owned by host UID 100999. A single file you can still delete; a directory tree you cannot - `rm -rf` fails with Permission denied and `podman unshare rm -rf` works.

Storage Guide 33 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. A non-root container user, and where its files land

    --user 1000 makes the container's process uid 1000 inside. id confirms uid=1000(sysadmin) gid=0(root), and the write succeeds.

    Worth saying plainly: the write is not denied. That is what this guide was planned around and it is wrong - the directory is group-writable and the container's gid 0 maps to your own gid 1000, so the process has permission.

    The problem is the ownership. Compare the two views of the same directory:

    inside     -rw-r--r--  1 1000    0     by-container-user.txt
    outside    -rw-r--r--  1 100999  1000  by-container-user.txt

    100999. Not 1000. Read it off /etc/subuid, which says sysadmin:100000:65536: container uid 1 is host uid 100000, so container uid 1000 is host uid 100999.

    Notice that by-container-root.txt from the previous guide is still owned by 1000. Container root maps to you; every other container user maps into the subordinate range. That is the whole asymmetry.

    bash Example session
    podman run --rm --user 1000 -v ~/src:/src docker.io/library/alpine sh -c 'id; echo hi > /src/by-container-user.txt; ls -ln /src'uid=1000(sysadmin) gid=0(root) groups=0(root)total 12-rw-r--r--    1 0        0                3 Aug 22 12:10 by-container-root.txt-rw-r--r--    1 1000     0                3 Aug 22 12:10 by-container-user.txt-rw-rw-r--    1 0        0               14 Aug 22 12:10 host.txtrm -rf ~/src && mkdir -p ~/src && echo "from the host" > ~/src/host.txt && ls -ln ~/srctotal 4-rw-rw-r-- 1 1000 1000 14 Aug 22 12:10 host.txtcat /etc/subuidsysadmin:100000:65536

    Expected resultuid=1000 inside, 100999 outside, and a subuid base of 100000.

    Success conditionYou can compute the host UID a container user will write as.

  2. One file is fine. A directory is not.

    The single file can be removed:

    $ rm -f ~/src/by-container-user.txt      # succeeds

    Which surprises people who expect ownership to decide. It does not - POSIX unlink permission comes from write access to the containing directory, and you own that. The file's owner is irrelevant.

    Now have the container create a tree, which is what a real application does:

    $ stat -c 'dir owner uid=%u %n' ~/src/tree ~/src/tree/deep
    dir owner uid=100999 /home/sysadmin/src/tree
    dir owner uid=100999 /home/sysadmin/src/tree/deep
    
    $ rm -rf ~/src/tree
    rm: cannot remove '/home/sysadmin/src/tree/deep/f.txt': Permission denied
    
    $ touch ~/src/tree/newfile
    touch: cannot touch ...: Permission denied

    Now the directories are owned by 100999, so you have no write access inside them - you can neither delete their contents nor add to them. rm -rf fails part-way through and leaves a partial tree.

    This is the shape of the real complaint: a build container wrote node_modules or a target/ directory, and your own account cannot clean it up.

    bash Example session
    podman run --rm --user 1000 -v ~/src:/src docker.io/library/alpine sh -c 'mkdir -p /src/tree/deep && echo x > /src/tree/deep/f.txt; ls -ln /src/tree'total 4drwxr-xr-x    2 1000     0             4096 Aug 22 12:10 deepls -ln ~/src/treetotal 4drwxr-xr-x 2 100999 1000 4096 Aug 22 12:10 deepstat -c 'dir owner uid=%u %n' ~/src/tree ~/src/tree/deepdir owner uid=100999 /home/sysadmin/src/treedir owner uid=100999 /home/sysadmin/src/tree/deeprm -rf ~/src/treerm: cannot remove '/home/sysadmin/src/tree/deep/f.txt': Permission denied[exit 1]touch ~/src/tree/newfiletouch: cannot touch '/home/sysadmin/src/tree/newfile': Permission denied[exit 1]

    Expected resultDirectories owned by 100999, and Permission denied from both rm -rf and touch.

    Success conditionYou have a directory on your own machine that you cannot delete.

  3. podman unshare is the way out

    $ podman unshare rm -rf ~/src/tree

    Silent success. podman unshare runs a command inside your user namespace, where host 100999 appears as an ordinary uid you have authority over - the same namespace whose uid_map you read in guide 3.

    This is the correct tool and it is worth learning instead of the alternatives:

    • sudo rm -rf works, but reaching for root to clean up after an unprivileged container is the wrong instinct, and on a shared host you may not have it.
    • chown -R needs root for the same reason.

    podman unshare needs neither. Anything you can run, you can run in there - podman unshare ls -ln, podman unshare chown -R 0:0 ., a whole shell with podman unshare bash.

    bash Example session
    podman unshare rm -rf ~/src/treels ~/srcby-container-root.txthost.txt

    Expected resultNo output, and the tree gone.

    Success conditionYou removed files your own account could not touch, without root.

  4. Prevent it with --userns=keep-id

    Better than cleaning up is not creating the problem. --userns=keep-id maps your uid to the same number inside instead of mapping container root to you:

    $ podman run --rm --userns=keep-id ... id
    uid=1000(sysadmin) gid=1000(sysadmin) groups=1000(sysadmin)

    Both uid and gid, unlike the --user 1000 case where gid was 0. And the file it writes is owned by 1000 1000 on both sides - the inside and outside listings agree for the first time in this guide.

    When to use which:

    • --userns=keep-id whenever a container writes to a bind mount of yours. Development containers, build containers, anything touching your source tree. This should be a habit.
    • The default for containers writing only to named volumes, where nothing on the host reads the files by hand.
    • --userns=keep-id:uid=1000,gid=1000 when the image expects a specific non-root uid and you need to line it up with yours.

    The trade-off: with keep-id the container has no root at all, so an image that wants to apt-get install at startup will fail. That is usually a sign the image should have done it at build time.

    bash Example session
    podman run --rm --userns=keep-id -v ~/src:/src docker.io/library/alpine sh -c 'id; echo hi > /src/with-keep-id.txt; ls -ln /src'uid=1000(sysadmin) gid=1000(sysadmin) groups=1000(sysadmin)total 12-rw-r--r--    1 1000     1000             3 Aug 22 12:10 by-container-root.txt-rw-rw-r--    1 1000     1000            14 Aug 22 12:10 host.txt-rw-r--r--    1 1000     1000             3 Aug 22 12:10 with-keep-id.txtrm -rf ~/src && mkdir -p ~/src && echo "from the host" > ~/src/host.txt && ls -ln ~/srctotal 4-rw-rw-r-- 1 1000 1000 14 Aug 22 12:10 host.txt

    Expected resultuid=1000 gid=1000 inside, and a new file owned by 1000 on the host.

    Success conditionA container wrote a file you can delete without help.

Troubleshooting

Official sources