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-dockerif you’re keeping Docker. That package provides a/usr/bin/dockershim which forwards to Podman, and it conflicts with Docker’s own CLI. For the same reason, skip thealias 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:8080at once. podman pswon’t show Docker’s containers (and vice versa) - each only knows about the containers it started itself. Same withpodman imagesanddocker 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 dockerevery time, or join thedockergroup - 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 singledocker run -v /:/host, no exploit required. - Rootless Docker: safe, but it’s a manual setup, and a single
apt install docker.ioor a strayusermod -aG dockerlater 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.