Podman - rootless containers, out of the box

In an earlier post I explained why being in the docker group is equivalent to having passwordless root, and walked through switching Docker to rootless mode. It works, but it’s ten steps of setup: leaving the group, disabling the system daemon, finding helper scripts, installing rootlesskit, fixing up DOCKER_HOST or a CLI context. There’s a simpler way to get the same safety for most day-to-day container use: Podman.

What is Podman?

Podman is a container engine developed by Red Hat. It runs the very same OCI images as Docker, pulls them from the very same registries (Docker Hub included), and its CLI deliberately mirrors Docker’s: podman run, podman ps, podman build, podman images, podman exec all take the flags you already know. Many people simply alias docker=podman and don’t look back.

How is it different from Docker?

Two differences matter:

1. No daemon. Docker is a client-server design: the docker CLI is a thin client that asks a long-running dockerd daemon to do the actual work. That’s exactly why the docker group is dangerous - the daemon runs as root and does whatever anyone with access to its socket asks it to. Podman has no daemon at all. podman run forks the container directly, as a child process of your shell. There’s no socket to talk to and no privileged process to trick.

2. Rootless is the default. When you run podman as a regular user, it runs rootless - no setup needed. Containers live in a user namespace: root inside the container maps to your UID on the host, and other container UIDs map to a range of subordinate UIDs allocated to you in /etc/subuid. Your images and containers are stored under ~/.local/share/containers/, separate from everyone else’s. You can still run rootful Podman via sudo podman, but you have to ask for it explicitly.

Installing

On Ubuntu, Podman is in the official repo:

sudo apt install podman

That’s it. No group to join, no service to enable, no socket to export. Ubuntu’s adduser already allocates subordinate UIDs and GIDs to every regular user; verify with:

grep $USER /etc/subuid /etc/subgid

You should see something like mavi:100000:65536 in both files.

Podman and Docker side by side

You don’t have to choose: Podman and Docker install and run side by side just fine. They are completely independent - separate binaries, separate runtimes, separate storage. Docker keeps its images in /var/lib/docker (or ~/.local/share/docker in rootless mode), Podman in ~/.local/share/containers. The flip side is that they don’t share images: an image pulled by docker is invisible to podman and vice versa, so you’ll download it once for each.

A few things to keep in mind:

  • Don’t install podman-docker if you’re keeping Docker. That package provides a /usr/bin/docker shim which forwards to Podman, and it conflicts with Docker’s own CLI. For the same reason, skip the alias docker=podman.
  • Ports are still shared. Both publish ports on the same host, so a Docker container and a Podman container can’t both bind -p 8080:8080 at once.
  • podman ps won’t show Docker’s containers (and vice versa) - each only knows about the containers it started itself. Same with podman images and docker images.

On my Ubuntu 26.04 box, rootless Docker 29.1 and Podman 5.7 run happily next to each other: installing Podman didn’t touch Docker’s setup at all.

Hello, world

podman run --rm hello-world

Surprise - you don’t get Docker’s hello-world at all:

Resolved "hello-world" as an alias (/etc/containers/registries.conf.d/shortnames.conf)
Trying to pull quay.io/podman/hello:latest...
Getting image source signatures
Copying blob sha256:81df7ff16254...
Copying config sha256:5dd467fce50b...
Writing manifest to image destination
!... Hello Podman World ...!

         .--"--.
       / -     - \
      / (O)   (O) \
   ~~~| -=(,Y,)=- |
    .---. /`  \   |~~
 ~/  o  o \~~~~.----. ~~
  | =(X)= |~  / (O (O) \
   ~~~~~~~  ~| =(Y_)=-  |
  ~~~~    ~~~|   U      |~~

Project:   https://github.com/containers/podman
...

That’s the first difference from Docker in action. Docker silently assumes Docker Hub for any short image name. Podman doesn’t: a short name is only resolved through an explicit alias table - on Ubuntu, /etc/containers/registries.conf.d/shortnames.conf, which maps popular names to full ones. ubuntu maps to docker.io/library/ubuntu, while hello-world maps to Podman’s own quay.io/podman/hello, hence the seal. A short name that isn’t in the table is refused outright:

$ podman pull nginxdemos/hello
Error: short-name "nginxdemos/hello" did not resolve to an alias and no unqualified-search registries are defined in "/etc/containers/registries.conf"

This is a security feature: it removes the risk of pulling a lookalike image from a registry you didn’t intend. Get into the habit of using fully-qualified image names. If you want Docker’s own hello-world, ask for it:

podman run --rm docker.io/library/hello-world
Trying to pull docker.io/library/hello-world:latest...
Getting image source signatures
Copying blob sha256:4f55086f7dd0...
Copying config sha256:e2ac70e7319a...
Writing manifest to image destination

Hello from Docker!
This message shows that your installation appears to be working correctly.

To generate this message, Docker took the following steps:
 1. The Docker client contacted the Docker daemon.
 2. The Docker daemon pulled the "hello-world" image from the Docker Hub.
...

Yes, it says “Docker” - that’s simply the text baked into the image. There was no Docker client and no Docker daemon involved, of course.

An interactive Ubuntu shell

podman run --rm -ti docker.io/library/ubuntu

You get a root prompt inside the container:

root@4b1e2c9a7d3f:/# id
uid=0(root) gid=0(root) groups=0(root)

But this “root” is only root inside the user namespace. Ask the kernel how UIDs are mapped:

root@4b1e2c9a7d3f:/# cat /proc/self/uid_map
         0       1000          1
         1     100000      65536

The first line says: UID 0 in the container is UID 1000 on the host - that’s you. The second line maps container UIDs 1..65536 to your subordinate range from /etc/subuid. Container root has your privileges on the host, and not one bit more.

Now try the trick from the docker-rootless post - mount the host’s root filesystem into the container:

podman run --rm -ti -v /:/host docker.io/library/ubuntu
root@9c0d5e1f2a3b:/# cat /host/etc/shadow
cat: /host/etc/shadow: Permission denied
root@9c0d5e1f2a3b:/# touch /host/etc/evil
touch: cannot touch '/host/etc/evil': Permission denied
root@9c0d5e1f2a3b:/# ls -l /host/etc/shadow
-rw-r----- 1 nobody nogroup 1361 Oct  2 04:42 /host/etc/shadow

Note the owner: the host’s real root isn’t mapped into the container’s user namespace at all, so root-owned host files show up as nobody:nogroup, and container root gets only the “others” permissions on them.

With rootful Docker, that same command hands you the whole machine. With Podman, it hands you exactly what you could already read and write as yourself. The privilege escalation simply isn’t there.

Why Podman is the safer default

Think about what the alternatives require:

  • Rootful Docker: you either sudo docker every time, or join the docker group - and then any process running under your account (a malicious npm package, a compromised IDE plugin, a curl-to-bash installer) can become root with a single docker run -v /:/host, no exploit required.
  • Rootless Docker: safe, but it’s a manual setup, and a single apt install docker.io or a stray usermod -aG docker later can quietly put the rootful daemon back.
  • Podman: rootless out of the box, nothing to configure, no group, no daemon, no root-owned socket. The safe path is the default path.

When I still use Docker

Podman’s daemonless model is great for interactive and short-lived work: trying out an image, poking around a distro, running a build in a container, spinning up a throwaway PostgreSQL for a test run. The container lives as long as the command you typed.

Where Docker still earns its keep for me is long-running services - a container that should start on boot, restart when it crashes, and keep running whether or not I’m logged in. That’s what the Docker daemon (together with docker compose and restart policies) is built for. For those, I run Docker in rootless mode, with lingering enabled.

For everything else - which, on a developer workstation, is most of it - I reach for Podman. Same images, same commands, no root.

Written on October 21, 2026