Podman Solutions

Not a Drop-In Replacement
Login

Not a Drop-In Replacement

Motivation

The most common selling point claimed for Podman is that it is a drop-in replacement for Docker. This is true in several areas:

The thing you must realize is, there are also limitations to this compatibility. If there were no significant differences from Docker, Podman would be a mere clone, yet that is not the case. It follows that there must be areas where Podman behaves differently, on purpose.

This article is not a trivial exercise in puncturing marketing hype. The motivation behind writing it is is to assist transplants by providing a list of key differences without pulling punches. It is based on a recognition that Docker has been around long enough for large swaths of the user base to have developed tight dependencies on what are proven to be inessential implementation details. Podman does certain things differently for good reasons, but if you have come to depend on the way Docker does them instead, you may find yourself needing help over these surprise obstacles.

Rootless by Default

Perhaps the second most common selling point for Podman — after the bogus claim that it is a 100% drop-in replacement — is that it is rootless by default. Where I frequently see people running into trouble is in combining these points in their head and expecting to have both simultaneously, without effort.

Podman partisans frequenltly offer the alias docker=podman trick1 but I believe this misleads Docker users into believing there is a zero-effort transition path. I recommend that explorers instead:

Neither path yields a guarantee of a zero-effort conversion, but but they do help by reducing the height of each step-change as much as possible.

Podman is Daemonless, Kinda

Another major selling point for Podman is that it is “daemonless,” but what does that really mean? Can we not puncture the claim like so:

$ systemctl --user enable --now podman

No, not really.

Yes, that is technically a Podman daemon, but the model is completely flipped. Whereas the docker command shell largely makes calls on containerd via this API to get work done, calls to the Podman implementation of this API end up calling into podman proper. If you take containerd down, docker commands start failing, but if instead you say systemctl stop podman, the only top-level subcommand that will fail is is compose for reasons covered in the next section.

Another key difference here is that the podman service isn’t an always-running daemon like containerd. Instead, it uses systemd’s socket activation feature to bring the API listener up to handle the call, but then after receiving no further calls for 5 seconds, it shuts back down again. The only reason it even has that dwell time is to save on startup overhead when there are multiple back-to-back calls.

This has a number of benefits, but also a few downsides when it comes to transitioning from Docker:

  1. There is no background daemon to act as a service supervisor. Flags like podman run --restart=always don’t have any useful effect.2

  2. This REST API service isn’t enabled by default because there is a broad class of use cases where this compatibility layer isn’t needed.

  3. Even when it is enabled, one of the knock-on effects of Podman’s rootless by default nature is that there are separate listeners per user. It is a classic newbie error to bring the listener up for your regular user and then have a command run through sudo fail for the lack, or vice versa.

A quibbler may look at all this and then say, “Isn’t systemd the daemon, then?”

Again, no, not really. Systemd only handles service management, whereas containerd does that plus all the heavy lifting for the image store on Docker’s behalf, virtual network provisioning, and more. You may then try to draw other parallels, as with .volume Quadlets and the containerd volume manager, but even then the only thing systemd is doing is generating podman commands that run in the background in the calling user’s context. You not only can run these commands by hand without the aid of any daemon, it’s one of my favorite debugging techniques for Quadlets.3

Dependence on Docker Compose

The docker-compose command started out as a third-party Python script, which was first wrapped by the first-party docker compose sub-command — no hyphen — and later rewritten in Go as part of docker proper. The modern implementation is rather complex, far exceeding its humble origins. These are sometimes distinguished as the v1 and v2 implementations.

There is a spec which Podman implements externally as podman-compose, in much the same way as Compose v1. It is typically packaged separately from Podman proper. Yet, because Docker Compose v2 is the reference implementation, we have a case where we can say “Compose is as Compose does” without being glib.

Atop all this, we have a few confusing design decisions:

  1. If both podman-compose and docker-compose are installed on the host machine, podman compose defaults to calling the latter on the theory that you clearly desire cross-engine compatibility.4

  2. Because docker-compose assumes it is talking to containerd, podman-compose is written to presume the Docker REST API socket is available, which it is not by default per the “daemonless” design choice.

The upshot of all this is that it is common for a “podman compose up” call to fail. Chasing down and fixing all of the possible causes is an expertise unto itself, one beyond my skill set.

The main reason I have yet to develop expertise in the ways Podman Compose fails is that I don’t use it, because it isn’t as valuable under Podman as with Docker. Beginning with Podman 4.4, there is a first-party alternative to Compose called Quadlets which works better in this context, especially on Red Hat OSes. Instead of writing a single YAML file that describes the entire service, the Quadlet alternative has you write one or more files in file formats related to that of systemd units, which express the same ideas. These are then transformed automatically in the background to actual systemd unit files. See the linked man page for more details.

The point is, when you bring a Compose incompatibility to the Podman team, you must not only fight through NIH syndrome but also answer the question, “Why don’t you just move to Quadlets?”

And that’s a good question! Quadlets are a first-party solution which work great. No matter what, Compose will always have a certain amount of impedance mismatch for reasons covered in the previous section.

SELinux First

Docker has always tried to pretend SELinux doesn’t exist, either by hosting on Debian and friends or by banging things into place by using their privileged (rootful) position. Contrast Podman, which comes from Red Hat, where until recently the core development team included Mr. SELinux.

You may then say, “Okay, I will run Podman on Debian, then,” but now you have a new problem, because with Podman, Red Hat makes a notable exception to their famous stability promises. Versions of most software included in their Enterprise Linux offering are nailed in place roughly six months before a new major release of the OS, then patched for the next ten-ish years without adding any new features. Being a first-party product, Podman isn’t treated in that same hands-off manner; every point release of RHEL has a chance of including a new version of Podman with new features. It isn’t guaranteed, but consider this history:

RHEL Version Podman Version
8.0 1.6.4
8.3 2.0
8.4 3.0
8.5 3.3
8.6 4.0
8.7 4.2
8.8/9.0 4.6.1
8.9/9.3 ??
8.10 4.9.4

Were Red Hat to treat Podman the same as they do the bulk of the software they include with their distribution, they would have stuck with 1.6.4 clear through the end of the RHEL 8 lifetime, then 4.6.1 through the end of RHEL 9, requiring users to upgrade to RHEL 10 in order to get Podman 5.x.

I chose this section to point all this out because competing stability-focused distros like Debian and Ubuntu Server do the opposite: they pick one version at LTS release time and stick with it for years and years. This then pushes people toward Red Hattish distros to get a more up-to-date version of Podman, which then confronts them with SELinux being enabled and enforcing by default.

Background VM on Non-Linuxes

On macOS and Windows, both Podman and Docker need a background Linux VM to provide the kernel, without which they cannot do LXC-type things. These VMs are not set up precisely the same way, which produces migration issues when someone is depending on exact details of the underlying VM. One common case is that they differ in how they handle file sharing with the host.

And That’s Not All!

There are more differences, some of which are too niggly to deal with here, but others which I’ve previously covered in other articles, which do not benefit from being repeated here:

License

This work is © 2025 by Warren Young and is licensed under CC BY-NC-SA 4.0


  1. ^ The modern truth is a smidge more complicated, a fact you can verify locally by listing the contents of your distro’s podman-docker package sometime.
  2. ^ This is one of the reasons we recommend switching to Quadlets when moving to Podman: systemd is first and foremost a service supervisor.
  3. ^ Start by saying “systemctl status --user my-broken-service” and then copy-paste the ExecStart command it gives into my shell prompt. Before hitting Enter, change the -d flag to -it. The resulting output shows what the failing call is trying to do and where it’s getting tripped up, which is often enough to diagnose the problem and design a fix.
  4. ^ See the man page for instructions on overriding this default.