Tech Digest

Head to head

Docker vs Podman

Podman is the better-engineered container engine. Docker is the one every guide you will read assumes. This page says when that gap is worth paying and when it is not.

Last reviewed 2 tools compared

Should you use Docker or Podman for self-hosting?

Run Docker. On a single-purpose home server that you alone administer, root equivalence is not a meaningful boundary, and every compose file you will copy from a project README was tested against Docker. Switch to Podman when the host has other users, when you are on Fedora or RHEL where Podman is the packaged default, or when you want containers as real systemd units with dependency ordering, which Quadlet does natively and Docker does not.

The technical argument is settled and it favors Podman. No root daemon, no socket that is a straight path to host root, containers that become first-class systemd units, and an idle footprint of about 40 MB against Docker's 120 MB. If someone were designing a container engine for a Linux server today, they would design Podman.

The practical argument still favors Docker, and it is not close. Every project on this site publishes a compose file that was tested against Docker. Every troubleshooting thread you find at 11pm assumes docker logs. That is not a small thing when the failure you are debugging is your family's photo library.

The spec table below is worth reading for two rows. default_ports is empty on both, because neither listens on anything by itself, which is why neither appears in Port conflict checker. And both score ops 2 out of 5, which is deliberate: the ongoing attention Podman costs you in subuid ranges, linger and networking changes is roughly equal to what Docker costs you in annual major releases that move a default.

SpecificationDocker EnginePodman
LicenceApache-2.0 (Engine and Compose); Docker Desktop is proprietary (Permissive)Apache-2.0 (Permissive)
Written inGoGo
First release20132018
MaturityMatureMature
DatastoreNone; image and container metadata under /var/lib/docker, containerd image store by default since 29.0SQLite state database under ~/.local/share/containers (BoltDB support removed in 6.0)
Services to runnot container shapednot container shaped
Idle memory120 MB40 MB
Memory in use300 MB150 MB
Operational load2 / 5, Light2 / 5, Light
IdentityNot applicableNot applicable
arm64 buildsYesYes
Default portsnonenone
Backup shapeFile copySQLite backup

The recommendation, stated plainly#

Run Docker Engine if all of the following are true: you are the only administrator, the box exists to run your services, and you learn from guides rather than from source. That describes most people reading this. The docker group being root-equivalent is a genuine security property, but it is not a boundary you were relying on, because you also have sudo.

Run Podman if any of the following are true:

  • The machine has other human users, or you hand out shell access. Group membership in docker is a straight path to root, and there is no configuration that fixes it.
  • You are on Fedora, RHEL, AlmaLinux or openSUSE, where Podman is the packaged default and Quadlet ships with the distro. Fighting your distro to install Docker is its own ongoing tax.
  • You want services that start in a defined order at boot and log to journald like everything else on the box. This is the strongest reason and it deserves its own section.

Everything else is preference. Anyone telling you Docker is obsolete has not tried to follow a random self-hosted project's README on a Podman host.

Quadlet is the actual reason to switch#

Docker's answer to "start this at boot" is restart: unless-stopped and hope. There is no ordering beyond depends_on, no integration with the rest of the system's startup, no way to say "start the backup job after the database is healthy but before the reverse proxy". Podman's answer is to generate real systemd units from declarative files.

You write a .container file and systemd does the rest:

ini
# ~/.config/containers/systemd/linkding.container
[Unit]
Description=linkding bookmarks
After=network-online.target

[Container]
Image=docker.io/sissbruecker/linkding:latest
PublishPort=9090:9090
Volume=%h/srv/linkding:/etc/linkding/data:Z
Environment=LD_SUPERUSER_NAME=admin
AutoUpdate=registry

[Service]
Restart=always

[Install]
WantedBy=default.target

Then:

bash
systemctl --user daemon-reload
systemctl --user start linkding.service
loginctl enable-linger $USER
systemctl --user enable --now podman-auto-update.timer

That last line replaces a whole category of tooling. AutoUpdate=registry plus the timer gives you unattended image updates with automatic rollback if the new container fails its healthcheck, which is more than Watchtower vs Diun gets you on Docker. The :Z suffix on the volume is not optional on SELinux distributions; leave it off and the container gets permission denied on a directory that looks world-readable.

The cost is that you now maintain unit files instead of compose files, and nobody publishes Quadlet units. You will translate every project's compose file by hand. For a stack of five services that is an afternoon; for thirty it is a project.

Where docker-compose compatibility actually breaks#

This is the part that gets waved away with "Podman supports compose". Here is what that sentence means in practice.

podman compose sets up the environment and then executes an external provider. It prefers docker-compose if it is installed, falls back to podman-compose, and prints a warning that it is doing so. Your compose behavior is therefore whatever that provider does, and the two providers do not agree. Pin it deliberately:

bash
export PODMAN_COMPOSE_PROVIDER=/usr/bin/docker-compose
export PODMAN_COMPOSE_WARNING_LOGS=false

The specific breakages worth knowing before you commit:

  • Healthcheck-gated depends_on. condition: service_healthy is honored by docker compose and handled inconsistently by podman-compose. Stacks where an app must wait for Postgres to accept connections start racing.
  • Profiles. --profile selection is not reliably implemented in podman-compose, so optional services either always start or never do.
  • Swarm-era keys. Anything under deploy: (replicas, resource limits, placement) is silently ignored. Docker ignores most of it outside Swarm too, but people copy those files expecting the limits to apply.
  • Socket mounts. Containers that bind /var/run/docker.sock (management UIs, Traefik's Docker provider, update watchers) need the Podman socket instead: systemctl --user enable --now podman.socket, then mount /run/user/1000/podman/podman.sock. Under rootless, the socket only sees that user's containers.
  • Source IPs in logs. Rootless bridge networking goes through rootlessport, a userspace proxy that does not preserve client addresses. Every request appears to come from the gateway. That breaks fail2ban-style banning and any per-IP rate limiting your app does, and it is not fixable without moving the proxy outside the container.
  • pasta as the default network. Since Podman 5.0 rootless networking uses pasta, which copies your main interface address. A container therefore cannot reach the host on that same IP, so container-to-container traffic that worked through published ports needs explicit pasta options in containers.conf.

None of these are dealbreakers. All of them are an evening you did not plan for, multiplied by the number of stacks you run. Keep Docker Compose conventions in mind either way: the compose files that survive translation are the boring ones.

Rootless: both engines do it, and both have sharp edges#

Podman is rootless by default. Docker's rootless mode is an explicit install path (dockerd-rootless-setuptool.sh install). The constraints are almost identical, which is the point most Podman advocacy misses.

ConstraintRootless DockerRootless Podman
Bind ports below 1024setcap cap_net_bind_service=ep $(which rootlesskit) and restart the daemonsysctl net.ipv4.ip_unprivileged_port_start=443
Survives logoutloginctl enable-linger $USERloginctl enable-linger $USER
CPU and memory limitsIgnored without cgroup v2 plus systemd delegationSame, and same Delegate= drop-in needed
NFS or network home directoryNot supportedNot supported, move graphroot to a local path
Storage location~/.local/share/docker~/.local/share/containers

For cgroup delegation on either engine you need a drop-in, or --memory and --cpus are decoration:

ini
# /etc/systemd/system/user@.service.d/delegate.conf
[Service]
Delegate=cpu cpuset io memory pids

Check docker info or podman info for Cgroup Driver: none. If you see it, your limits are not being applied and nothing warned you.

The uncomfortable part about each#

Docker's is the firewall. Docker writes its own iptables chains, so a container published with -p 8080:80 is reachable from the internet even when ufw reports the port denied. People firewall the host, assume they are finished, and are wrong in a way that produces no error message. Bind to an interface (-p 127.0.0.1:8080:80) and put Caddy in front, as Reverse proxy and TLS lays out, or use a firewall that sits outside the host entirely.

Docker 29.0.0 also raised the minimum client API to v1.44 in November 2025, which broke every management tool compiled against an older API, and 29.3.0 walked that floor back to v1.40 in March 2026. Compose went from v2 to v5 in the same window and dropped its internal BuildKit builder. Docker is boring infrastructure, but 29.x is where that stopped being entirely true, and An update strategy that does not lose data applies to the engine itself, not just to what runs on it.

Podman's is churn of a different shape. Version 6.0 dropped BoltDB and migrates state to SQLite on first launch, which is a one-way trip you should snapshot before. The GitHub organization moved from containers to podman-container-tools in 2026, so pinned raw URLs and vendored module references need updating. And rootless containers running on an NFS home directory simply fail, because the server enforces ownership outside the user namespace.

Verdict by situation#

Your situationPickWhy
First home server, following guidesDockerEvery README you will read was written and tested against it
Fedora, RHEL or openSUSE hostPodmanPackaged default, Quadlet included, no fighting the distro
Shared box with other shell usersPodman rootlessThe docker group cannot be made safe
You want ordered startup and journald logsPodman with QuadletReal systemd units with dependencies, not restart: unless-stopped
1 GB VPS running two containersPodmanNo daemon saves you roughly 120 MB you do not have
You use Portainer or Komodo to manage stacksDockerBoth assume Docker semantics; Podman support is partial at best
Running CI with Docker-in-Docker imagesDockerNested Docker and Swarm tooling expect the daemon
Hardening a public-facing single-purpose VPSEither, rootlessThe engine matters less than not publishing ports you did not mean to

What to do next#

If you are starting out, install Docker Engine from Docker's own apt or dnf repository rather than the distro package, set log rotation in /etc/docker/daemon.json before you accumulate gigabytes of JSON, and move on to the things that actually decide whether your server survives its second year: Docker Compose conventions and Backups that actually restore.

If you are switching to Podman, do it one stack at a time. Translate your smallest service to a Quadlet unit, confirm it survives a reboot with linger enabled, then translate the next. Do not migrate thirty containers in one weekend and then try to work out which of the pasta, SELinux and cgroup differences broke the one that matters. A security baseline for a home server covers the parts of the threat model that neither engine addresses on its own.

Questions#

Can Podman run docker-compose files?

Mostly, with caveats. podman compose is not an implementation: it detects an external provider, preferring docker-compose if installed and otherwise podman-compose, and prints a warning telling you so. The two providers disagree on healthcheck-gated depends_on, profiles and build context handling. Set PODMAN_COMPOSE_PROVIDER explicitly so you know which one is actually running your stack.

Is rootless Podman actually more secure than Docker?

Yes, in one specific way: a container breakout lands you as an unprivileged user rather than as root. That is a real difference on a shared machine. It is close to meaningless on a box where you are the only account and you already have sudo. Rootless Docker exists and closes most of the same gap, but it is a non-default install path that few guides account for.

Does Portainer work with Podman?

Partially. Enable the socket with systemctl --user enable --now podman.socket and point tooling at unix:///run/user/$(id -u)/podman/podman.sock. Portainer will list and control containers, but features that assume Docker semantics (Swarm, some stack operations, image build behaviour) misbehave. If you are running Podman, systemd and Quadlet are the management surface, not a web UI.

Should you migrate an existing Docker host to Podman?

Not for its own sake. You will rewrite every compose file as Quadlet units or accept a compose shim, re-derive your volume paths under ~/.local/share/containers, and debug pasta networking. Migrate when a concrete requirement forces it: an audit that rejects the docker group, a distro where Podman is the supported engine, or a need for real boot-time service ordering.

Do Podman containers start automatically after a reboot?

Only if you do two things. Write Quadlet units so systemd owns them, and run loginctl enable-linger $USER so your user manager starts without a login session. Skipping linger is the classic Podman failure: everything works until the first unattended reboot, then nothing comes back and there is no error anywhere because nothing tried to start.

Which uses less memory?

Podman, measurably. There is no resident daemon, so an idle Podman host costs a couple of conmon processes and a pasta helper per running container, against roughly 120 MB for dockerd plus containerd before a single container starts. On a 1 GB VPS that is worth something. On a 16 GB server it is noise next to the applications.

Sources#

Published . Last reviewed . Found something out of date? Tell us and we will fix it and log the change.