The short answer
Pick Docker if the software you want to run needs it, such as Coolify, Dokploy or Pterodactyl, or if you want to use a project's compose.yaml and its instructions as they are. It is the more widely supported of the two, and most documentation assumes it.
Pick rootless Podman for apps you run yourself on a server that faces the internet. Each app runs under its own user, so a container breakout gets that user and not root, published ports stay behind your firewall, and updates and restarts are handled by systemd. It takes more setup per app.
What we tested
Both ran on the same Melonslab VC-P Alloy (2 vCPU, 8 GB) with Debian 13, at the same time, set up exactly as in our guides: rootless Podman with Caddy as the caddy user, and Docker from Docker's own repository. The server was secured first as in the security guide, with ufw allowing only ports 22, 80 and 443.
The app was Miniflux 2.3.3, a small feed reader written in Go, with PostgreSQL 17 for its data:
- Docker: a
compose.yamlin/opt/minifluxwith two services, the passwords as Compose secrets from files, and the app published on127.0.0.1:8083. - Podman: a pod under a user of its own,
miniflux, made from three Quadlet files, the passwords aspodman secret, and the app published on127.0.0.1:8084.
The same Caddy, running rootless as caddy, served both over HTTPS with Let's Encrypt certificates. In both, we added a feed through Miniflux's API over HTTPS and read its entries back, then tested an update, a reboot, the firewall and a container breakout.
| Docker | Rootless Podman | |
|---|---|---|
| Version | Docker 29.9.0, Compose 5.6.0 | Podman 5.4.2, from Debian |
| Install, following our guide | 28 seconds, 35 packages from Docker's repository | 26 seconds, 46 packages from Debian |
| Containers for the app | 2 | 3 (with the pod's small infra container) |
| Memory, the app's containers | 71 MB | 72 MB |
| Memory, what runs them | 178 MB, once for the server | 33 MB, for each app's user |
| Disk, images and data | 712 MB | 541 MB |
| Port published on all addresses, no ufw rule | open to the internet | closed by ufw |
| Root in a container, after a breakout | root on the server | the app's own user |
| Automatic image updates | not built in | podman auto-update, daily timer |
| Back after a reboot | yes, after one failed start of the app | yes, in order |
Memory is the proportional set size (PSS) of the processes, at idle, after a reboot. Disk is /var/lib/docker and /var/lib/containerd for Docker, and the miniflux user's ~/.local/share/containers for Podman.
Setup
Both install in under half a minute. Podman is a single apt install from Debian. Docker takes a few more steps: Docker's signing key, its repository, then the packages. Debian's own docker.io package is older, so our guide uses Docker's.
After that, the two differ in how much each app needs:
- Docker: one directory and one
compose.yaml.docker compose up -dstarts it, as root. - Podman: a Linux user with linger switched on, then a
.podfile and one.containerfile for each container, in the user's~/.config/containers/systemd. You work as the user withmachinectl shell. It is more files, and the settings have different names than in Compose:PublishPort=instead ofports:,Secret=instead ofsecrets:.
Here Docker is simply easier. A project that ships a compose.yaml works as it is, while for Podman you translate it into Quadlet files yourself.
Compose
Compose files also run on rootless Podman. As the miniflux user, we ran the exact compose.yaml from the Docker test, with secrets, a health check and depends_on, in two ways:
podman compose up -d, with Docker's Compose plugin installed, aftersystemctl --user enable --now podman.socket. Podman hands the file to Docker Compose 5.6.0, which talks to Podman's socket instead of Docker.podman-compose up -d, from Debian'spodman-composepackage, version 1.3.0, without anything from Docker.
Both started the app, and it answered. But those containers do not get Quadlet's benefits: no systemd service for each, and no automatic updates. Our guides use Quadlet files for that reason. Larger Compose files, with build steps, profiles or features podman-compose does not cover, may need changes; we only tested this one.
Ports and the firewall
This is the difference most likely to surprise you. With ufw allowing only 22, 80 and 443, we published a test container on port 8080 in Docker and on 8090 in rootless Podman, on all addresses:
docker run -d --name test -p 8080:80 traefik/whoami
podman run -d --name test -p 8090:80 docker.io/traefik/whoami:v1.11.0
From a computer elsewhere on the internet, port 8080 answered, and port 8090 did not. ufw status showed no rule for either.
Docker forwards a published port to the container in its own nat rules, before ufw's rules are checked. Docker's documentation says the same: traffic to the container is diverted before it goes through ufw. Rootless Podman cannot change the server's firewall. Its published port is an ordinary listening socket, owned by pasta running as the user, so ufw treats it like any other program.
With Docker, you close such ports yourself: publish on 127.0.0.1, or add rules to the DOCKER-USER chain. Our Docker guide has a systemd service for that; with it, port 8080 stopped answering.
A container breakout
A breakout is a bug, in the kernel or the container runtime, that lets a process inside a container act on the server itself. What it gets depends on who the process is outside the container.
We did not use a real exploit. Instead, we gave a container what a breakout would give it, full privileges and the server's process namespace, and asked for a shell in the server's own namespaces:
docker run --rm --privileged --pid=host alpine nsenter -t 1 -m -u -i -n -p -- sh -c 'id; head -c 10 /etc/shadow'
In Docker, the shell was root on the server: uid=0(root), with the server's hostname and the first line of /etc/shadow. As the miniflux user, the same command in Podman stopped at nsenter: can't open '/proc/1/ns/ipc': Permission denied.
We also mounted the server's whole file system into a container, with -v /:/host. Docker's container read /etc/shadow and created a file in /root, owned by root. Podman's, even with --privileged, got Permission denied for /etc/shadow, /root and the caddy user's home.
The reason shows on the server. In Docker, user IDs inside a container are the same outside: Miniflux ran as nobody (65534) and PostgreSQL as 999, the same IDs as on the server, and root in a container is root. In rootless Podman, root in the container is the miniflux user, and every other ID comes from that user's own range: PostgreSQL ran as 166534 and Miniflux as 231069, IDs that own nothing else on the server.
Docker has its own protection against breakouts, AppArmor and seccomp profiles that were on by default, and a rootless mode of its own that we did not test. Anyone who can run docker controls the server, so do not add users to the docker group unless you would give them root.
Resources
The app's own containers used the same memory in both, 71 and 72 MB. What runs them differs:
- Docker:
dockerdused 96 MB,containerd53 MB, one shim per container 22 MB in all, anddocker-proxy7 MB, 178 MB in all. That is paid once, however many apps you run, and stays in memory with no containers at all. - Podman: there is no daemon. Each container has a
conmonof about half a megabyte. The user's network helper,pasta, used 25 MB, and the user's own systemd and D-Bus 7 MB, 33 MB in all. That is paid again for every app, since each has its own user.
With many small apps, the two come closer; with a handful, Podman uses less. On an Alloy, neither makes a difference you will notice.
Docker's images and data took 712 MB, against Podman's 541 MB for the same two images. Docker 29 keeps images in containerd's store, which holds both the downloaded layers and the unpacked ones.
Updates and backups
The app. With AutoUpdate=registry in the Quadlet files and the user's podman-auto-update.timer, Podman checks once a day for a newer image of the same tag and restarts the app when one is found. To test it, we gave both setups PostgreSQL 17.10 under the 17 tag. podman auto-update updated it to 17.11 within 5 seconds and restarted the pod. It rolls back by itself if the new container fails to start.
Docker has nothing like it. docker compose pull and docker compose up -d updated the same container in 15 seconds, but only because we ran them. Watchtower, the tool long used for this, has been archived on GitHub since 17 December 2025 and is no longer maintained.
The software itself. Podman comes from Debian, so the automatic security updates from the security guide cover it. Docker comes from its own repository, and is only updated automatically if you add it to them, as in the Docker guide. We checked that the Docker origin was then included.
Backups work the same way: pg_dump through docker compose exec or podman exec gave a dump of the database in both. Docker's volumes are under /var/lib/docker/volumes. Podman's are in the user's home, but owned by IDs from the user's range, so the user reads them through podman unshare or podman volume export, not directly.
Logs and reboots
Logs. Podman's containers log to the journal: journalctl --user -u miniflux-app as the user. The journal limits its own size. Docker keeps its own log files, read with docker compose logs, and grows them without limit until you set rotation in /etc/docker/daemon.json, as the Docker guide does.
Reboots. Both came back by themselves, and both sites answered within a minute of the reboot. Docker started both containers at once: Miniflux tried the database before PostgreSQL was ready, failed with connection refused, and Docker's restart policy started it again. depends_on with service_healthy only applies to docker compose up, not to Docker restarting containers at boot. Podman started the database first and the app when PostgreSQL reported healthy, as the Quadlet files say with Requires=, After= and Notify=healthy. For Miniflux, the extra restart did no harm.
Licence and cost
Both are open source under the Apache License 2.0, as the projects' repositories said when we checked on 14 October 2026. Docker Engine is built from the Moby project by Docker, Inc. Podman's website is run by Podman Container Tools, a series of LF Projects, under the Linux Foundation. Neither costs anything on a server. Docker, Inc. sells subscriptions for Docker Desktop and Docker Hub; the Engine used here needs neither.
Both used the same images from Docker Hub here, with its limits for pulls without an account. Docker fills in docker.io for a short name such as postgres:17; our Podman guides write every image's full name, such as docker.io/library/postgres:17.
Which one to pick
- Docker, if the software asks for it, if you follow a project's own Compose instructions, or if you use Docker's tools and ecosystem. Close the published ports as in Install Docker on Debian, and treat the
dockergroup as root. A VC-P Alloy ran Docker and this app with plenty to spare. - Rootless Podman, for apps you host yourself, one user each, especially where a breakout should not cost you the server. Start with Run containers rootless with Podman; our app guides, such as BookStack, all build on it. A VC-P Alloy suits it too.
You can run both on the same server, as we did. Keep their ports apart, and keep one web server, such as Caddy, on ports 80 and 443 for both.