Docker vs Podman is one of the most common questions once you start running containers seriously on a server. Both tools run the same images, understand almost the same commands and end up talking to the same Linux kernel. The difference lies underneath: Docker works through a central background service that runs as root. Podman does without that service and can run containers entirely without root privileges. That sounds like a detail, but it changes how secure, how lean and how convenient your server is.
We have been running Docker on our development server for months, with half a dozen containers permanently up. For this article we installed Podman right next to it and measured both under identical conditions: start time, memory per container, the constant footprint of the Docker daemon, rootless behaviour, Compose and systemd integration. Along the way we ran into two traps that no marketing comparison mentions. So what you get here is a Docker vs Podman comparison built on numbers rather than opinions, and a clear recommendation at the end for when to pick which tool.
This IBM Technology video explains the core idea behind Podman in a few minutes. Red Hat belongs to IBM and is the main developer of Podman, so this is not a neutral voice, but it explains the architecture cleanly. Our own measurements follow below.
Docker vs Podman: the short answer
If you only want three sentences:
- For beginners and for most tutorials, Docker is the more relaxed choice. Almost every guide, every Compose example and every error message online refers to Docker. You get where you want to go faster.
- For a server where security comes first, Podman is the better foundation. No service with root privileges, containers run as a normal user, and systemd integration is built in.
- Switching is not a big project. Both use the OCI image format. An image built with Docker runs unchanged under Podman, and most commands are identical.
If you are not yet sure what a container actually is, start with our primer What is Docker?, where we explain images, containers and volumes from scratch.
What is Podman, anyway?
Podman stands for Pod Manager. It is an open-source tool, developed mainly by Red Hat, for managing containers without a central service. Podman comes preinstalled on Fedora, RHEL, CentOS Stream and AlmaLinux. RHEL no longer ships Docker at all; Podman is its official container solution. On Ubuntu and Debian, Podman is a regular package, and apt install podman is all it takes.
The name reveals one special feature: Podman knows pods, groups of containers that share a network. The concept comes from Kubernetes. Docker has nothing comparable. You do not need pods to use Podman, but they are handy if you ever want to move to Kubernetes.
The most important design decision is a different one, though: Podman has no daemon. Each command starts the container directly and then exits. Why that matters is the subject of the next section.
The core difference: daemon or daemonless

When you type docker run, the docker command does not start the container itself. It sends a request to the Docker daemon dockerd, a service that runs permanently in the background. dockerd hands the job to containerd, which starts a small helper process called containerd-shim for every container. Only that shim starts the actual process.
With Podman, the upper half of this chain is missing. The podman command starts the container itself and hands it over to a tiny watchdog process called conmon. Then podman exits. There is no service running permanently that holds all the containers together.
We checked this on our server. We started the same test container with both tools (alpine, just running sleep) and looked at which process is actually the parent:
Docker: containerd-shim-runc-v2 (parent PID 1) → sleep 600
Podman: conmon (parent systemd) → sleep 600
Both containers run, both look the same from the inside. The difference is everything else that runs alongside.
What that means for memory
We measured the actual memory usage as PSS (Proportional Set Size). PSS splits memory shared between several processes proportionally. That is more honest than the usual RSS figure, which counts shared libraries in full for every process.
| What is running | Docker | Podman |
|---|---|---|
| Helper process per container | containerd-shim: 8.2 MB PSS (10.5 MB RSS) | conmon: 0.55 MB PSS (2.2 MB RSS) |
| Permanently running service | dockerd: 233 MB PSS | none |
| On top | containerd: 46 MB PSS | none |
The per-container helper process is roughly 15 times smaller with Podman. The second row matters far more, though. Our dockerd has been running for 89 days without a restart and now occupies about 233 MB of RAM, plus another 46 MB for containerd. Together that is almost 280 MB permanently reserved, whether a container is running or not.
To put it honestly: on our server with 23 GB of RAM, that is barely one percent and completely irrelevant. On a small VPS with 1 or 2 GB, the maths looks different, and 280 MB becomes a noticeable slice of the budget. We measured how much memory a server really needs in How much RAM does a server need?. Also, the dockerd figure is a snapshot after 89 days of uptime with many images built. A freshly started daemon is considerably smaller. We deliberately did not restart it for this article because test environments run on it.
What that means for reliability
The central service is not just a memory cost; it is also a shared point of failure. If dockerd is restarted, for example during an update, Docker stops all containers with it by default. There is an option called live restore that keeps containers alive across a daemon restart. On our server, as on most default installations, it is switched off (docker info shows Live Restore Enabled: false).
Podman does not have this problem, because there is nothing to restart. A Podman update does not touch running containers; each one only depends on its own conmon.
Start time: practically a tie
You often read that Podman is faster or slower than Docker. We measured it: five runs each of run --rm alpine true, meaning create, start, stop and remove a container. The image was already present locally for both.
| Run | Docker (root) | Podman (root) | Podman (rootless) |
|---|---|---|---|
| 1 | 1.78 s* | 0.81 s | 0.49 s |
| 2 | 0.58 s | 0.68 s | 0.48 s |
| 3 | 0.54 s | 0.72 s | 0.44 s |
| 4 | 0.53 s | 0.71 s | 0.49 s |
| 5 | 0.50 s | 0.74 s | 0.48 s |
*On the first Docker run, the image tagged latest was not yet present locally and had to be downloaded, so that value is not comparable.
The result: Docker as root comes in at about 0.5 seconds, Podman as root at about 0.7 seconds, and Podman without root at about 0.48 seconds. That the rootless variant was the fastest surprised us too. We suspect the difference comes from the network backend and the size of the storage directory: rootful Podman uses netavark with its own firewall rules, while the test user had an empty, fresh directory. That is a guess, not a measurement.
The honest summary: for the Docker-or-Podman question, start time does not matter. The differences are tenths of a second, for an operation you rarely run a thousand times in a row in everyday work.
Rootless: the main reason for Podman

Now comes the point that actually decides the Docker vs Podman comparison: who is the container from the server’s point of view?
In a standard Docker installation, the daemon runs as root. The processes in your containers also run as root unless the image specifies otherwise. We checked: our Docker test container running sleep shows up in the server’s process list as user root.
There is also a detail many people underestimate. If you want to run Docker commands without sudo, you usually get added to the docker group. That group may access the socket /var/run/docker.sock. On our server it looks like this:
srw-rw---- 1 root docker 0 /var/run/docker.sock
Anyone who can write to this socket can start a container that mounts the host’s entire file system. Membership in the docker group is therefore equivalent to root access, as the official Docker documentation itself says. On our server the group happens to be empty, because we work as root. On many developer machines and servers it is not.
What rootless looks like in practice
For the test we created a fresh, ordinary user with no special permissions whatsoever. When the user was created, Ubuntu automatically added a range of 65,536 subordinate IDs to /etc/subuid and /etc/subgid. Those are exactly what rootless containers need.
Then, as that user, we started a container and ran id inside it:
On the host: uid=5002(pvdtest)
Inside container: uid=0(root) gid=0(root)
Inside the container, the process believes it is root. It can install packages, create files, everything root can do there. But what does the server see? We read the mapping with podman unshare cat /proc/self/uid_map:
0 5002 1
1 231072 65536
The first line means: root inside the container is actually user 5002 on the host, our perfectly ordinary test user. All other users in the container are mapped to the range starting at 231,072, which belongs to no real user. And the server’s process list confirms it: the sleep process runs as pvdtest, not as root.
That is the whole trick. If an attacker breaks out of a rootless container, they do not land on your server as root, but as an unprivileged user. It does not replace other safeguards, but it makes a successful escape far less damaging.
The trap: ports below 1024
Rootless comes at a price, and we walked right into it. Trying to publish a container on port 80 as an ordinary user ended like this:
Error: rootlessport cannot expose privileged port 80, you can add
'net.ipv4.ip_unprivileged_port_start=80' to /etc/sysctl.conf
(currently 1024), or choose a larger port number (>= 1024)
Linux does not allow ordinary users to bind ports below 1024, and that applies to rootless containers too. Port 8099 worked immediately. For a web server you have three options:
- Put a reverse proxy in front that listens on 80 and 443 as a system service and forwards to the high port. That is the clean solution anyway; see What is a reverse proxy?. We compare suitable proxies in Caddy vs Nginx.
- Lower the limit via sysctl, as the error message suggests. That is system-wide and then applies to all users.
- Start the container as root after all. Then you lose the main advantage.
Docker can do rootless too
To be fair: Docker can run rootless these days as well. For that you install a separate Docker daemon in the user context. It works, but it is an extra setup step, and a daemon still runs, just per user. With Podman, rootless is the normal case, not the exception. That is the real difference: not what is possible, but what happens when you configure nothing extra.
The firewall question
We have to revisit one point from our Docker Compose article here, because it regularly catches beginners out: Docker writes published ports directly into the kernel’s iptables rules and thereby bypasses a firewall such as ufw. A -p 5432:5432 makes your database reachable from the whole internet, even though ufw supposedly blocks the port.
That is why all six running Docker containers on our server explicitly bind their ports to 127.0.0.1. We double-checked this with docker ps for this article.
Rootful Podman also uses its own firewall rules via netavark and behaves similarly here. Rootless Podman, on the other hand, forwards ports through a process in the user context (in our version rootlessport with slirp4netns or pasta). That forwarding is an ordinary socket, visible to the host firewall. Our recommendation is the same for both tools, though: always bind ports that do not need to be public to 127.0.0.1. Do not rely on a firewall to handle it for you.
Commands: almost identical
The good news for anyone coming from Docker: Podman understands nearly all commands word for word.
docker pull nginx → podman pull nginx
docker run -d -p 8080:80 → podman run -d -p 8080:80
docker ps → podman ps
docker logs -f web → podman logs -f web
docker exec -it web sh → podman exec -it web sh
docker build -t app . → podman build -t app .
That is why many people simply set alias docker=podman, and Fedora even ships a dedicated package for it (podman-docker). There are still a few differences.
Short image names
With Docker, alpine always means docker.io/library/alpine. Podman does not automatically know which registry a short name should come from. On our Ubuntu system this is handled by an alias file (/etc/containers/registries.conf.d/shortnames.conf) that pins well-known names to a specific registry. For everything else we recommend: write the full name, i.e. docker.io/library/alpine instead of alpine. It is unambiguous and protects you from accidentally pulling an identically named image from a foreign registry.
Separate worlds
One thing that confuses people when switching: Docker and Podman do not share images or containers. Docker stores under /var/lib/docker, rootful Podman under /var/lib/containers, and rootless Podman in the user’s home directory. After installing Podman, your Docker images are not visible there. You have to pull or build them again.
Compose: the surprising trap
For many people, Docker Compose is the real reason to use Docker in the first place. How does that work with Podman?
Podman ships the command podman compose. We tried it with a minimal compose.yaml, and the first line of output was revealing:
>>>> Executing third-party compose provider
"/usr/libexec/docker/cli-plugins/docker-compose". <<<<
So podman compose is not a Compose implementation of its own but a forwarder. It looks for an existing Compose program and calls it. On our server it found Docker’s Compose plugin (version 5.0.2) and used that. This works because Podman offers a Docker-compatible API, so Compose talks to Podman instead of Docker.
And it did work. The container started, and here comes the crucial part:
docker ps → (empty)
podman ps → pvd-compose-web
The container was running under Podman, and Docker knew nothing about it. On a server with both installed, that is a real pitfall: your habitual docker ps does not show what is running, and the same compose command can end up in two completely separate worlds depending on how you invoke it.
Without a Docker installation you need a different Compose provider. The usual choice is the Python program podman-compose, which is a separate implementation and does not handle every Compose feature equally well. Test complex Compose files with many dependencies, health checks and custom networks before switching; do not adopt them blindly.
Cloud X Berry compares Docker and Podman in a beginner-friendly way and also tackles the question of whether you still need Docker at all. A good second look at the topic.
systemd and Quadlet: where Podman shines

With Docker, the daemon is responsible for bringing containers back up after a reboot (restart: unless-stopped). Podman has no daemon. So the job goes to something that runs on every modern Linux system anyway: systemd.
The modern way to do this is called Quadlet. You write a small file that looks like a systemd unit, and Podman automatically generates a proper service from it. That is exactly what we tested as a rootless user. The file ~/.config/containers/systemd/pvdsleep.container consisted of just these lines:
[Container]
Image=docker.io/library/alpine
Exec=sleep 1000
[Install]
WantedBy=default.target
After that, two commands were enough:
systemctl --user daemon-reload
systemctl --user start pvdsleep
The result: systemctl --user is-active pvdsleep reported active, and podman ps showed a container named systemd-pvdsleep. The container is now a perfectly ordinary systemd service. Logs go to the journal, dependencies on other services can be declared as usual, and systemctl enable makes it start at boot.
Our stumbling block: exit code 137
After stopping the service, it did not show inactive but failed, with exit code 137. At first glance that looked like an error. The cause is mundane: sleep in an Alpine container ignores SIGTERM, because it runs as process number 1 and has no handler for it. Podman waits ten seconds and then kills the process hard with SIGKILL. 137 means exactly that: 128 plus signal 9.
We had already seen the same warning while cleaning up our other Podman test containers: StopSignal SIGTERM failed to stop container ... resorting to SIGKILL. It is not a Podman problem but a property of the image. Docker follows the same pattern when stopping (SIGTERM first, SIGKILL after ten seconds), although we did not specifically measure that here. For real services: the program inside the container must handle SIGTERM properly, or you start the container with --init so a tiny init process forwards the signals.
One more tip if you want rootless containers to keep running without a logged-in user: lingering must be enabled for that user (loginctl enable-linger USERNAME). Otherwise systemd terminates all user services as soon as the last session ends, and your containers with them.
Versions: what to watch out for
We tested Podman 4.9.3, the version from the official Ubuntu 24.04 LTS repositories. That is not the latest release. The 5.x series has been available since 2024 and, among other things, made pasta the default network for rootless containers. On Fedora you always get a current version; on Ubuntu and Debian you get one that matches the distribution and receives its security updates there.
Docker was running version 29.2.1 on our machine, installed from Docker’s own repository. That is a typical pattern: Docker is usually installed straight from the vendor, Podman from the distribution. Both have pros and cons. The vendor repository is more current, the distribution repository more stable and maintained long-term. Which distribution suits a server is covered in Linux server distributions compared.
Docker vs Podman: the big comparison table
| Criterion | Docker | Podman |
|---|---|---|
| Architecture | central daemon (dockerd + containerd) | daemonless, one conmon per container |
| Default privileges | daemon as root | rootless possible and common |
| Constant memory footprint (measured on our server) | ~280 MB for daemon and containerd | 0 MB with no containers running |
| Helper process per container (PSS) | 8.2 MB | 0.55 MB |
Start time run --rm alpine true | ~0.5 s | ~0.7 s (root), ~0.48 s (rootless) |
| Commands | the reference | nearly identical |
| Image format | OCI | OCI |
| Compose | official docker compose | podman compose calls an third-party provider |
| systemd integration | via restart policies in the daemon | native via Quadlet |
| Pods | no | yes |
| Desktop app | Docker Desktop (paid licence for larger companies) | Podman Desktop (open source) |
| Docs and tutorials online | a huge amount | considerably fewer |
| Preinstalled on | no major distribution | Fedora, RHEL, CentOS Stream, AlmaLinux |
When Docker, when Podman?

After all the measurements, our recommendation is clear, but not dogmatic.
Choose Docker if …
- you are just getting started. The sheer number of tutorials, Stack Overflow answers and ready-made Compose files is unbeatable. Every hurdle you do not have to clear is an hour saved.
- you rely heavily on Compose, especially with complex files from other projects. The official Docker Compose is the reference.
- your team or CI pipeline already uses Docker. Switching tools without a concrete reason costs more than it gains.
- you develop on Windows or macOS and your Docker Desktop licence allows it.
Choose Podman if …
- you run a server with several users or services, where a single breakout must not cost you the whole machine.
- you use Fedora, RHEL or AlmaLinux. Podman is the default there and very well integrated; see our Fedora deep dive.
- you want to run containers as proper systemd services. Quadlet is the cleanest solution we know.
- you need every megabyte of RAM, for example on a small VPS. No daemon means no baseline consumption.
- you want to move to Kubernetes later. Pods and
podman kube generatemake the transition easier.
And what do we do ourselves?
Honestly: our development server keeps running Docker, because it hosts test environments for shops and databases whose guides and Compose files are built around Docker. Rebuilding a working setup just for a comparison article would be exactly the kind of optimisation that breaks more than it fixes. For a new server with a few, clearly defined services, we would pick Podman with Quadlet today. There you get the memory savings and privilege separation for free.
Migrating from Docker to Podman: how to go about it
If you want to switch, do it step by step:
- Install Podman alongside Docker without removing Docker. The two do not get in each other’s way, as our test showed. That is precisely what can confuse you, though; see the Compose section.
- Move a non-critical service first. Pull the image by its full name, start it with
podman run, verify. - Back up your data. Docker volumes live under
/var/lib/docker/volumes, and Podman cannot see them. You have to copy data deliberately or mount it as a bind mount. Always take a backup first. - Adjust ports if you go rootless: anything below 1024 goes through a reverse proxy.
- Set up autostart with Quadlet instead of relying on restart policies.
- Only once everything runs, remove Docker, and check first whether any script or cron job still calls
docker.
If you are still setting up the server, our guide Linux server setup helps. For key-based SSH access, read What is SSH?.
What we deliberately do not claim
So you can put our numbers into context:
- One server, not lab conditions. All measurements come from our development server under normal load. Other containers were running at the same time.
- No load test. We measured start time and memory, not the throughput of an application inside the container. Since both use the same runtime (runc) and the same kernel, we do not expect meaningful differences there, but we did not verify it.
- dockerd after 89 days. The Docker daemon’s memory figure is a snapshot of a long-running service, not the value right after startup.
- Podman 4.9.3. Newer versions behave differently in some respects, especially networking.
- Rootless start time. We did not investigate why rootless was faster on our machine. The explanation above is a guess.
After testing we cleaned everything up: deleted the test user along with its subordinate IDs, removed all Podman containers and images (the storage directory is back to 248 KB) and deleted the extra Docker image we had pulled. Podman itself stays installed. The six production Docker containers kept running undisturbed the whole time.
Conclusion: Docker vs Podman in one sentence
Docker is the more convenient standard, Podman the more secure and leaner architecture, and because both understand the same images, the choice is not a decision for life. If you are starting out, Docker is a fine place to start. If you are setting up a new server and care about privilege separation and systemd, give Podman a serious try. The biggest difference we measured was not speed, but the question of who your containers run as: with Docker as root, with Podman, if you want, as a perfectly ordinary user.
Frequently asked questions about Docker vs Podman
What is the difference between Docker and Podman?
Docker manages containers through a central background service that runs as root. Podman has no such service and starts each container directly, optionally without root privileges. Commands and image format are largely the same.
Is Podman better than Docker?
Not across the board. Podman has a more secure design, needs no permanent service and fits better with systemd. Docker has the larger community, more tutorials and the more mature Compose. For beginners Docker is usually more convenient; for new servers with a security focus, Podman is the better choice.
Is Podman faster than Docker?
Not meaningfully in our measurement. A container start took about 0.5 seconds with Docker, about 0.7 seconds with rootful Podman and about 0.48 seconds rootless. Podman does save noticeably on memory, though, because no daemon runs permanently.
Can I use Docker images with Podman?
Yes. Both use the OCI format, and every image from Docker Hub runs under Podman. The safest way is to specify the full name, such as docker.io/library/nginx.
Does Docker Compose work with Podman?
Yes, with limitations. podman compose is a forwarder that calls an existing Compose program, in our case Docker’s plugin. Without Docker you need podman-compose, which does not support every feature equally well. Test complex files first.
What does rootless mean in Podman?
The container runs as an ordinary user. Inside the container the process appears to be root, but on the server it is just an unprivileged user. If an attacker breaks out, they do not have root on the host.
Can Docker run rootless as well?
Yes, via a separate rootless mode in which a dedicated daemon runs in the user context. That requires extra setup. With Podman, rootless works without any extra steps.
Why can’t my rootless container open port 80?
Linux forbids ordinary users from binding ports below 1024. Use a high port and put a reverse proxy in front, or lower the limit via net.ipv4.ip_unprivileged_port_start. The first option is cleaner.
Is Podman free?
Yes. Podman and Podman Desktop are open source and free for companies too. Docker Engine is also free; only Docker Desktop requires a paid licence above a certain company size.
Can I install Docker and Podman at the same time?
Yes, they store images and containers separately. That is exactly what can be confusing, though: a container you start with Podman does not appear in docker ps. In the long run you should settle on one.
What is Quadlet?
Quadlet is Podman’s integration with systemd. You describe a container in a small file, and Podman generates a systemd service from it that starts at boot, logs to the journal and can be controlled with systemctl.
