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.
- There are broad parallels in the
podmansub-commands and flags; thus thealias docker=podmantrick. - Podman offers
podman-compose, paralleling the old Python-baseddocker-compose. - Podman uses the same OCI image format as Docker.
- Podman can accept Docker REST API calls.
And yet, it is not a 100% drop-in replacement.
I blame most of the misunderstanding on that glib, outdated1 alias advice. It sweeps aside the fact that that there are intentional limitations to the compatibility, stemming from reasons other than the necessary lag between Docker creating a new feature and the Podman developers getting around to copying it. Increasingly, these two competing OCI engines are diverging because of differing opinions on which design choices are best. If the designers and developers of Podman introduced no significant implementation differences relative to Docker, it 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 is especially odd since these same misleading sources will often go on to list these major differences:
- rootless
- daemonless
- SELinux-first
systemdfriendly
How can Podman be both materially different and also perfectly compatible with Docker in every respect? Short answer: it can’t.
This article is not a trivial exercise in puncturing this 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
Newbies often run into trouble early in their Podman journeys by combining the top-level marketing points above and thinking they can get everything all at once, without effort. The single biggest error I see along these lines is expecting seamless compatibility and also rootless operation.
Navigating a city block presents competing choices:
OT: box width 1.2 height 0.7
RS: box width 1.8 height 0.2 color 0x606060 fill 0x606060 with .n at OT.s
line dashed color yellow from previous.w to previous.e
RN: box same with .s at OT.n
line same from previous.w to previous.e
RE: box same width 0.2 height 1.4 with .w at OT.e
line same from previous.n to previous.s
RW: box same with .e at OT.w
line same from previous.n to previous.s
P1: dot color cyan at previous.s + (0.05, 0.05)
line color cyan from previous to previous + (0, 1.05)
line same -> from previous.n to previous.n + (1.3, 0)
P2: dot color orange at RS.w + (0.05, -0.05)
line color orange from previous to previous + (1.61, 0)
line same -> from previous.e to previous.e + (0, 0.9)
P3: arrow from OT.sw to OT.ne color red
box "OCI" "Towers" fit at OT thickness 0 fill white
→ /pikchrshowThese are the primary paths available to one tasked with driving the exploration party’s vehicle from the southwest corner of OCI Towers to the northeast.2 The safe paths are the orange and cyan ones, on the city streets. Choosing to drive one’s metaphorical safari SUV along the red short-cut path through the lobby and ground-floor offices is not only unlikely to save travel time, all things considered, it is liable to result in a traffic ticket and a stiff fine.
To extend this metaphor, drivers should either:
- transition first from rootful Docker to rootful Podman, and only then plan their path to rootless mode; or
- convert their existing rootful Docker app to rootless Docker atop their familiar platform, then plan their transition to rootless Podman
Neither option gets you a zero-effort conversion, but following this advice does aid migration by reducing the magnitude of each step-change.
Podman Is Daemonless
The quibbling side details are covered elsewhere on this site, but for our purposes here, it suffices to point out that while Podman’s way of handling REST API calls has a number of benefits, it creates a few downsides when it comes to transitioning from Docker’s containerd alternative:
Because there is no background daemon to act as a service supervisor, flags like
--restart=alwaysdo not work the same as withdocker run. Underpodman, restart behavior depends on theconmonsidecar process to notice the death of the entrypoint, but ifconmondies first, you’re out of luck. Quadlets are a far better solution to this problem, sincesystemdis far more capable and durable thanconmon, but switching is not effortless.Because this REST API service is not a core requirement under Podman, certain peripheral features fail until you enable it, most commonly
podman compose. Another surprising biggie is the Podman-specificfarmsub-command.Because Podman is rootless by default, one may need to enable the API listener separately for each Podman user on a machine; bringing it up for root does not bring it up for everyone. One classic newbie error resulting from this is that a
podmancommand that succeeds as a normal user may fail when run throughsudoor vice versa because it is depending on having access to the socket.
Compose Dependence
The docker-compose command started out as a third-party plugin called Fig, which Docker acquired and then later renamed. This Python implementation is now retroactively known as Compose v1 in the official history. It was later rewritten in Go as part of Docker Engine proper, whereupon it became known as Compose v2. The current implementation is rather complex, far exceeding its humble origins.
There is now an input file format spec which Podman implements externally as podman-compose in much the same way as Compose v1, packaged separately from Podman proper.
This nominally vendor-neutral spec notwithstanding, Docker Compose v2 remains the reference implementation, with the result that we can say “Compose is as Compose does” without being glib.
Now add to this messy history a few confusing design decisions:
If both
podman-composeanddocker-composeare installed on the host machine, Podman defaults to calling the latter on the theory that you clearly desire cross-engine compatibility.3 This is true whetherdocker-composeis the old Python v1 implementation or the v2 rewrite in Go.Because
docker-composeassumes it is talking tocontainerd,podman-composelikewise presumes the Docker REST API socket is always available, contrary to Podman’s “daemonless” design choice.
The upshot of all this is that a Podman transplant’s first “podman compose up” call is likely to fail. Chasing down and fixing all possible causes is an expertise unto itself, one beyond my skill set.
The main reason I have yet to develop this facility is that I use Compose only on Docker since there is a superior (IMO) first-party alternative under Podman 4.4+ called Quadlets. Instead of writing a single YAML file that describes an entire multi-container service, Quadlets require you to factor it out into potentially many separate files which collectively express the same ideas, all managed by systemd.
We can leave the details of how all that happens to the prior link. The next relevant point here 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 and hence work great on systemd based Linuxes, whereas the third-party Compose clone will always have a certain amount of impedance mismatch.4
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 from their privileged position on high, where containerd runs as root. 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 that is likely to raise a new problem. See that link for details, but briefly, there are strong reasons to prefer running Podman on a Red Hattish distro. This then confronts partisans of non-SELinux based distros with the EL default of having SELinux both enabled and enforcing.
Thus one of the more visible extensions Podman has made to Docker: the :z and :Z mount flags for applying sensible SELinux labels to a bind-mounted --volume.
Background VM
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
- ^
The replacement is your distro’s
podman-dockerpackage. List its contents sometime to get a sense of all it does for you, which the cheap-and-cheerful alias does not. - ^ It is entirely coincidental to this metaphor that Docker, Inc. is headquartered in Palo Alto, while Podman comes out of the old Boston tech corridor that birthed IBM and DEC. Entirely…coincidental…I say!
- ^
See the
composeman page for instructions on overriding this default. - ^
Conversely, Quadlets don’t work on
systemdholdouts like Alpine Linux and Devuan.