If you have ever built a container by hand — an overlay mount, a cgroup, a few namespaces, a pivot_root — you already understand Podman better than most Docker users. Podman is essentially those same kernel primitives, wrapped in a friendly CLI, with two of Docker’s oldest design decisions quietly removed: the root daemon and the assumption that you run as root.
New here? The clearest way to feel what a container really is comes from assembling one from scratch — see Build a Tiny Linux Container without Docker. This article picks up where that leaves off: what happens when someone productizes those primitives properly.
This is not a “Docker is dead” piece. Docker is still excellent and still everywhere. But in 2026, podman is the tool I reach for first on servers and CI, and it is worth understanding why rather than just swapping the binary.
Docker’s Two Design Decisions That Aged Badly
Docker popularized containers, but it made two early choices that look heavier today than they did in 2013.
The first is the daemon. The docker command you type is just a thin client; the real work happens in dockerd, a long-running background service. That daemon has historically run as root , and everything you launch is a child of it.
The second is that root posture. For years, running containers meant handing a root-owned daemon control over your process tree, your mounts, and your network.
Neither choice is a bug. But both concentrate risk. A single always-on process running as root is a single, permanent, high-value target — and a single point of failure. If dockerd dies, every container it supervises is affected. If it is compromised, the host is compromised.
Podman removes the daemon entirely.
What “Daemonless” Actually Buys You
When you run podman run, there is no background service to talk to. The CLI forks and execs the container directly. A small helper called conmon stays attached to monitor the container and keep its logs, and the OCI runtime (runc or crun) does the low-level work — but there is no privileged, always-on broker sitting in the middle.
Terminal window
podman run --rm -it alpine sh
That command looks identical to Docker. Under the hood it is fundamentally different: nothing was running before you typed it, and nothing lingers as a root service after the container exits.
The practical payoffs:
- A smaller attack surface. There is no root daemon socket to protect, leak, or bind-mount into a container by accident.
- Clean process supervision. Because each container is just a child process, your init system can supervise it directly — no “who restarts the daemon that restarts the container” puzzle.
- No single point of failure. Restarting or upgrading Podman does not take down running containers the way bouncing a daemon can.
The trade-off is real and worth naming: some conveniences that a daemon provides — a central event stream, always-warm state, a background API socket — now need a different mechanism. We will get to that with systemd.
Rootless: The Container Root Is Not Host Root
This is the feature that changed how I deploy.
In rootless mode, the entire container stack runs as your normal, unprivileged user. There is no root-owned daemon and no setuid binary in the hot path. Yet inside the container, processes still see themselves as root (UID 0) and behave normally — because Podman uses a user namespace to map identities.
The mapping comes from two files, /etc/subuid and /etc/subgid, which grant your user a range of subordinate IDs:
Terminal window
cat /etc/subuid
# alex:100000:65536
That line says: the user alex owns host UIDs 100000 through 165535 for use inside containers. Podman maps container UID 0 to 100000, container UID 1 to 100001, and so on.
The security consequence is the whole point. If an attacker breaks out of a rootless container, they do not land as host root. They land as UID 100000 — an account that owns nothing on the host. The blast radius shrinks from “own the machine” to “own a namespace that can’t touch anything important.”
Getting started is deliberately boring:
Terminal window
# no sudo, no daemon setup
podman info | grep -i rootless
# rootless: true
podman run -d --name web -p 8080:80 docker.io/library/nginx
curl -s localhost:8080 | head -1
Rootless port binding below 1024 is the one gotcha most people hit — unprivileged users cannot bind low ports by default, which is why the example maps 8080. You can lower the threshold with a sysctl if you truly need :80, but mapping a high port is the cleaner habit.
podman unshare: The Same Namespaces, With a Net
If you have done the hand-built-container exercise, you have typed unshare --user --mount --pid ... and then carefully wired up everything yourself. podman unshare gives you that same world — a shell inside your user namespace — but with the UID mapping already applied and the sharp edges filed off.
Terminal window
podman unshare cat /proc/self/uid_map
# 0 100000 65536
Inside that shell, you are root (UID 0) for the purpose of manipulating files a container created. This is how you fix ownership on a rootless volume without sudo:
Terminal window
podman unshare chown -R 0:0 ./data
It is the friendly, guard-railed version of the raw unshare dance — same kernel mechanism, far less chance of hurting the host.
Pods: The Idea Kubernetes Borrowed
Podman’s name is a hint. A pod is a group of containers that share a network and IPC namespace, so they talk to each other over localhost with no bridge and no published ports between them.
Terminal window
podman pod create --name app -p 8080:8080
podman run -d --pod app --name api my-api:latest
podman run -d --pod app --name cache docker.io/library/redis
The api container reaches Redis at 127.0.0.1:6379 because they share one network namespace. This is the exact model Kubernetes uses — which is not a coincidence. Podman can even emit and consume Kubernetes YAML:
Terminal window
podman kube generate app > app.yaml
podman kube play app.yaml
That makes Podman a genuinely useful local dev loop for people who ship to Kubernetes: prototype the pod on your laptop, then hand the same manifest to the cluster.
Quadlet: Let systemd Run Your Containers
Here is where the “no daemon” story resolves. Docker’s daemon restarts your containers on boot and on crash. Podman does not have a daemon to do that — so it delegates to the init system you already trust: systemd.
The modern way to wire this up is Quadlet. You write a small declarative unit file, drop it in ~/.config/containers/systemd/, and systemd turns it into a fully managed service.
~/.config/containers/systemd/web.container
[Container]
Image=docker.io/library/nginx:latest
PublishPort=8080:80
[Service]
Restart=always
[Install]
WantedBy=default.target
Reload and start it like any other service:
Terminal window
systemctl --user daemon-reload
systemctl --user start web
You now have a container that starts on boot, restarts on crash, logs to journald, and runs entirely rootless — supervised by the same tool that manages the rest of your system. No background container daemon required.
If you have used Podman before, you might remember podman generate systemd. Quadlet replaces it. The old command generated brittle, hard-to-edit unit files; Quadlet units are declarative, readable, and the officially recommended path.
Docker vs Podman at a Glance
| Docker | Podman | |
|---|---|---|
| Architecture | Client + long-running daemon | Daemonless (fork/exec + conmon) |
| Default privilege | Historically root daemon | Rootless by default |
| Container escape lands as | Potentially host root | Unprivileged host UID |
| Boot / restart supervision | Docker daemon | systemd (via Quadlet) |
| Pods | No native concept | First-class, Kubernetes-compatible |
| CLI | docker |
podman (same verbs) |
| Kubernetes YAML | Third-party tools |
generate kube / play kube built in |
Where Podman Still Isn’t Docker
An honest guide names the rough edges. Podman is not a strict superset of Docker, and a few things need adjusting:
-
macOS and Windows run containers inside a Linux VM (
podman machine), exactly like Docker Desktop does. The daemonless benefit is a Linux-host benefit; on a Mac you are still talking to a VM. -
Compose works through
podman compose(delegating to the Compose engine) orpodman-compose, and coverage is very good — but it is compatibility, not the same codebase, so exotic Compose files can surprise you. - The ecosystem of GUIs, IDE integrations, and third-party tooling still assumes Docker first. Podman Desktop has closed most of this gap, but “Docker first, Podman also” is still the common default.
-
A background API for tools that expect to poll a socket needs Podman’s optional service (
podman system service) — which, ironically, is a daemon you opt into rather than one you are forced to run.
None of these are dealbreakers on Linux servers or CI, which is exactly where daemonless and rootless pay off most.
Namespaces and cgroups are strong, but they are still a shared-kernel boundary. When you need to run genuinely untrusted code — multi-tenant workloads, arbitrary user submissions — reach for a stronger isolation layer: see microVMs: Firecracker vs gVisor for where the container boundary ends and a hardware or syscall sandbox begins.
Should You Switch?
On a Linux server or a CI runner, the case is easy: rootless, daemonless Podman removes a permanent root-owned target and hands supervision to systemd, which is already running. The CLI is close enough to Docker that most muscle memory transfers untouched, often with a single alias docker=podman.
On a developer laptop — especially macOS or Windows — the win is smaller, because you are back inside a VM and leaning on Compose compatibility. There, “switch if it helps, stay if it doesn’t” is a perfectly sane answer.
The deeper reason to learn Podman is not the logo. It is that Podman makes the shape of a container obvious again: a child process, in a user namespace, supervised by init. Once you have built one by hand and then watched Podman do the same thing cleanly, containers stop being magic — and that understanding outlasts whichever CLI you happen to type.
Originally published at alekseialeinikov.com




Top comments (0)