Podman vs Docker for Self-Hosting: I Measured the Difference

For self-hosting on Linux in 2026, Podman is the better default. It has no daemon, runs rootless out of the box, and its Quadlet unit files make containers behave like any other systemd service on your box. I say that as someone whose own stack still runs on Docker . After years of reading that Podman is lighter, faster, and safer, I installed it next to Docker and measured the difference on my own hardware. Some claims held up: rootless Podman with pasta networking (Podman’s user-mode network layer) beat rootful Docker’s bridge on download throughput in every run. There is also no daemon holding memory between deployments. One claim did not survive: the often-repeated “Podman starts containers about 50 ms faster” was a statistical tie on my machine.
Key Takeaways
- Podman is the better self-hosting default; Docker still wins on Compose compatibility.
- Container startup speed is a tie: 352 vs 342 ms in my tests.
- Rootless Podman with pasta beat Docker’s bridge on every download throughput run.
- Docker’s idle daemons hold 144 MB of RAM; Podman uses none between runs.
- Quadlet runs containers as plain systemd services, with automatic image updates built in.
The Podman vs Docker for self-hosting question comes up every few weeks on r/selfhosted . The honest answer is that neither runtime is always better. They run the same OCI images on the same OCI runtimes, and the container itself runs the same way. The real differences are in how containers get launched, managed, secured, and tied into the rest of your system. This post breaks those down with real numbers, so you can pick based on your own setup, not tribal loyalty. Docker remains the safer pick if your workflow leans on Docker Compose v2 plugins, Docker Desktop’s GUI , or tools that still assume the Docker socket API.
What I Measured on My Own Hardware
All numbers below come from my own machine: a Ryzen 9 5900X with 64 GB of RAM on kernel 6.17, running Linux Mint 22.3 (Ubuntu 24.04 base). Docker Engine 29.5 runs as root, the standard install. Podman 4.9.3 is what apt ships on Ubuntu 24.04-based distros. I ran it rootless, because that is the setup a self-hoster gets by default. Nothing else was running during the tests, and the Docker daemon was freshly restarted. Startup times come from hyperfine
over 20 runs with images pre-pulled. Throughput comes from iperf3 against a published container port, the same path your reverse proxy or media server actually serves on.
| Measurement | Docker 29.5 (rootful) | Podman 4.9 (rootless) |
|---|---|---|
| Idle daemon memory | 144 MB (dockerd + containerd) | 0 MB, no daemon exists |
Start alpine true, warm image | 352 ms ± 12 | 342 ms ± 31 |
| Published-port download | 21-52 Gbit/s, unstable | 60-65 Gbit/s (pasta), ~59 (slirp4netns) |
| Published-port upload | 53.8 Gbit/s | 56.2 (pasta), 54.9 (slirp4netns) |
| Idle nginx:alpine memory | 20.1 MB + 4.0 MB shim | 15.3 MB + 2.5 MB conmon + network process |
Three results surprised me. First, container startup is a tie. Docker’s daemon round-trip costs nothing measurable on a warm image. If you have seen the “Podman starts 50 ms faster” claim in other comparisons, it did not reproduce here. Second, rootless networking is no longer the handicap it used to be. Podman with pasta held 60 to 65 Gbit/s on the download path in every run. Docker’s bridge swung between 21 and 52 Gbit/s on the same test, so pasta was both faster and more consistent. Third, each rootless Podman container with pasta carries its own 29 MB network process. The slirp4netns option needs only 3.6 MB, and Docker’s per-container shim sits at 4 MB. Containers in a pod share one network namespace, which spreads that pasta cost across the group.
One note on the daemon number: 144 MB is the floor, measured right after a restart with zero containers.
Daemon vs Daemonless: the Core Architecture Split
The biggest difference between Podman and Docker is the daemon. Docker runs a background process called dockerd that listens on a Unix socket, usually /var/run/docker.sock. Every docker CLI command talks to this daemon, and the daemon manages every container. It’s a client-server model, and it has worked well for over a decade.
Podman takes a different approach. There is no daemon. When you run podman run, Podman forks a new process straight from your shell and sets up the container with the OCI runtime. That runtime is crun by default on most distros, with runc as an option. The container process then becomes a direct child of your session. No middleman.
This design choice has a few practical effects. If Docker’s daemon crashes or you upgrade Docker Engine, every running container goes down with it. With Podman, each container is its own process tree, so one container crashing has no effect on the others. Docker’s daemon also sits in memory whether or not containers run: 144 MB of RSS for dockerd plus containerd on my box, with zero containers. Podman uses nothing when containers are idle, since there is no background process to keep around. On a 2 GB VPS running a handful of services, that difference is not academic.
Then there is the rootless question. Podman runs rootless containers out of the box. A rootless container can’t create real network devices, since that needs root, so a helper process has to carry its traffic. pasta
, the successor to the slower slirp4netns, is that helper. It runs as a plain process under your user. On the container side it picks up raw packets; on the host side it sends the same data onward through ordinary sockets instead of emulating a whole network stack, which is why it loses so little speed. Podman 5.x uses pasta by default. On the 4.9 release that Ubuntu-based distros ship, you select it per container with --network pasta. Given the throughput numbers above, it is worth typing. Docker does support rootless mode, but it’s opt-in, and many tutorials and quick-start guides skip it.
Quadlet: Running Self-Hosted Services as systemd Units
Systemd integration is where Podman pulls ahead for a home server. The reason is Quadlet
, built in since Podman 4.4. You write a declarative .container unit file, and systemd manages the service on its own. Here is a practical example for a Caddy reverse proxy:
# ~/.config/containers/systemd/caddy.container
[Container]
Image=docker.io/library/caddy:2-alpine
PublishPort=80:80
PublishPort=443:443
Volume=caddy-data.volume:/data
Volume=caddy-config.volume:/config
Volume=%h/Caddyfile:/etc/caddy/Caddyfile:ro,Z
[Service]
Restart=always
[Install]
WantedBy=default.targetDrop that file in place, run systemctl --user daemon-reload, and you have a container managed with the same tools you use for everything else. That means journalctl for logs, systemctl for lifecycle, and dependency ordering with After= and Requires=. No extra orchestration layer needed.
Two notes worth knowing before you copy commands from older guides. First, you will still find tutorials (including some that rank on page 1 of Google today) recommending podman generate systemd. That command is deprecated since Podman 4.7; Quadlet replaced it, so start there instead. Second, add AutoUpdate=registry to a Quadlet [Container] section and podman auto-update will pull new images and restart the service for you. Paired with a systemd timer, that covers the unattended updates most self-hosters bolt on with Watchtower under Docker.
Docker has no equal to Quadlet. You can write systemd units that call docker run, but then you’re bolting two management systems together instead of using one. If you don’t already manage periodic tasks with systemd, our guide on replacing cron with systemd timers
is a good primer. It covers the same dependency and logging model that makes Quadlet services easy to maintain.
Docker Compose and Tooling Compatibility
For years, Compose support was the biggest practical gap between Podman and Docker. That gap has narrowed a lot, but it has not fully closed, and for many self-hosters it is still the deciding factor.
Podman ships with podman compose, which hands off to external Compose tools. It can use the standalone docker compose v2 binary or podman-compose, a Python rewrite now at version 1.3+. For most standard docker-compose.yml files, this works without changes. That covers services, volumes, networks, port mappings, and environment variables. A multi-container app like a Plausible Analytics deployment
ships an Elixir web app plus Postgres and ClickHouse. It runs the same way under either runtime.
Docker Compose v2, bundled with current Docker Engine releases (29.x as of mid-2026), is still the reference build with the fullest feature coverage. Features like watch for live file syncing, advanced build options, and profiles for per-environment service groups work more reliably in the Docker-native version.
The compatibility layer that bridges the gap is podman system service, which exposes a Docker-compatible REST API:
# Start the Podman API socket for your user
systemctl --user enable --now podman.socket
# Point Docker-expecting tools at it
export DOCKER_HOST=unix:///run/user/$(id -u)/podman/podman.sockWith this socket running, most tools that expect /var/run/docker.sock will work with Podman. That includes VS Code Dev Containers
, Testcontainers
, and GitHub Actions services: blocks. Some need an extra environment variable or a config tweak. If you run a self-hosted Gitea instance
, the Podman socket drops in for the Docker socket in Gitea Actions runners.
One handy Podman feature for development work is the --userns=keep-id flag:
podman run --userns=keep-id -v ./project:/app:Z my-dev-imageThis maps your host UID into the container, so files created inside bind mounts belong to your user, not root. It fixes the common file permission mismatch that frustrates Docker rootless users. They otherwise have to mess with chown commands or custom entrypoint scripts.

Podman Desktop (v1.x) now mirrors most of what Docker Desktop offers. You get a GUI for containers and images, Kubernetes integration, and an extension marketplace. Docker Desktop still has a larger extension library and smoother Kubernetes support, but the gap closes with each release.
Security and Isolation Compared
Security is the most frequently cited reason to prefer Podman for self-hosted services, and the reasoning is solid.
Docker’s default setup runs the daemon as root on the host. Containers also run as root (UID 0) inside the container by default. If an attacker breaks out of a container, through a kernel bug, a bad volume mount, or a rogue process, they land on the host as root. Docker’s rootless mode and userns-remap reduce this risk, but you have to turn them on yourself.
Podman flips this model. When you run a container as a regular user, Podman maps container root (UID 0) to your unprivileged UID on the host with user namespaces. A container escape gives only your unprivileged user’s access, not root. This is the default, not something you opt into. For a box exposed to the internet through a reverse proxy, that default is worth a lot.
The user namespace mapping needs /etc/subuid and /etc/subgid set up for your user. Most distributions do this when you create an account. But if you hit permission errors with rootless Podman, check these files first:
# Verify your user has subordinate UID/GID ranges
grep $USER /etc/subuid
grep $USER /etc/subgid
# Expected output like:
# youruser:100000:65536SELinux and AppArmor policies apply on their own to Podman containers, on Fedora/RHEL and Ubuntu respectively. Docker supports these too. But a common fix in Docker forums is “just run with --privileged or --security-opt label=disable”, which strips away those protections. Podman’s tighter defaults make this less tempting, because things tend to work without turning security off.
Podman also handles secrets. The --secret flag covers build-time secrets, and systemd-creds can inject encrypted secrets into Quadlet services:
# Create a secret
echo "my-db-password" | podman secret create db_pass -
# Use it in a container
podman run --secret db_pass my-appBoth runtimes support seccomp profiles, capability dropping, and read-only root filesystems. Podman’s default seccomp profile is slightly more restrictive out of the box.
For anyone eyeing Kubernetes later, Podman has a native pod concept that Docker lacks. You can create pods that group containers and share network namespaces, just like Kubernetes pods:
podman pod create --name my-app -p 8080:80
podman run --pod my-app -d nginx
podman run --pod my-app -d my-sidecarYou can also import and export Kubernetes YAML directly:
# Generate Kubernetes YAML from a running pod
podman kube generate my-app > my-app.yaml
# Deploy from Kubernetes YAML
podman kube play my-app.yamlThis makes Podman a natural stepping stone for workloads that will later run on a Kubernetes cluster.
Performance and Storage Drivers
In practice, runtime speed between Podman and Docker is nearly the same for most workloads. Both use the same OCI runtimes and the same overlay2 storage driver on modern kernels (6.x+). The containers themselves run at the same speed. My benchmarks back that up.
Container startup is the claim I most wanted to test, because “no daemon round-trip” sounds like it should win. It does not. Over 20 hyperfine runs with a warm alpine image, Docker averaged 352 ms and rootless Podman 342 ms, a difference inside the noise. If startup speed is your reason to switch, save yourself the migration.
Rootless networking is a different story. Podman with pasta held 60 to 65 Gbit/s on the inbound published-port path in every run on my machine. Rootful Docker’s bridge was erratic on the same test, swinging between 21 and 52 Gbit/s across five samples. slirp4netns behind the rootlesskit port forwarder landed around 59. The old advice that rootless networking costs you real throughput is out of date: on this path, rootless Podman was both the fastest and the steadiest thing I measured. The trade-off is memory, since each pasta process holds about 29 MB per container against 3.6 MB for slirp4netns. Group related containers into a pod and they share one network process.
Image pull speed is the same, since both use the containers/image library under the hood. Podman also supports zstd:chunked layer pulls, which allow partial downloads of large images. That helps when you only need a few changed layers from a multi-gigabyte base image.
On the build side, Docker uses BuildKit
by default. BuildKit is fast, caches well, and supports features like --mount=type=cache for persistent build caches. Podman uses Buildah
for builds, which reads the same Dockerfile syntax. BuildKit-specific features like cache mounts need Podman 5.x+. For standard multi-stage builds, both perform about the same.
Where images live is a practical difference worth knowing. Docker stores all images centrally in /var/lib/docker/, shared across all users. Podman stores images per-user in ~/.local/share/containers/ by default. If several users on the same machine pull the same images, Podman keeps duplicate copies and uses extra disk space. For single-user systems or servers with dedicated service accounts, this is a non-issue.
| Feature | Docker Engine 29.x | Podman 4.9/5.x |
|---|---|---|
| Storage driver | overlay2 | overlay2, btrfs, zfs |
| Build engine | BuildKit | Buildah |
| Rootless networking | slirp4netns | pasta (default in 5.x) |
| Image storage | Central (/var/lib/docker) | Per-user (~/.local/share/containers) |
| Cache mount support | Yes | Yes (5.x+) |
| zstd:chunked pulls | No | Yes |
Can Podman Replace Docker for Your Self-Hosting Setup?
For most home servers, yes, and after running these benchmarks I am planning the switch for my own stack the next time I rebuild it. Here is how I would decide.
Choose Podman if:
- You run a home server or VPS where services should survive reboots and update themselves. Quadlet unit files plus
podman auto-updategive you the most Linux-native way to run self-hosted services. You getjournalctlandsystemctlinstead of a second management layer. - You want rootless containers without extra setup. That counts most on a box exposed to the internet. Podman does this out of the box, and per my measurements you give up no throughput for it.
- You run Fedora, RHEL, or CentOS Stream. Podman is the default runtime on these distros, and Docker is not even in the official repos. Fighting the distro’s defaults is rarely worth it.
- You deploy to Kubernetes eventually and want pod-native workflows with
podman kube playandpodman kube generate.
Choose Docker if:
- Your self-hosted stack is a pile of
docker-compose.ymlfiles that works. You want every project’s README, every Portainer guide, and every forum answer to apply unmodified. Compose v2 under Docker is still the reference implementation. - Containers are also your day job, and your CI/CD pipelines, Docker Desktop extensions, or Docker Swarm setup assume the Docker socket. Keeping one runtime everywhere is a fair reason to stay.
The hybrid approach is also fine, and it is exactly how I ran these benchmarks. Both runtimes can live on the same system, since they use separate storage directories and separate socket paths. You can set alias docker=podman in your shell for muscle memory while you slowly move scripts over. It’s a practical way to test Podman without a full switch.
Distribution defaults in 2026: Fedora 43 and RHEL 10 ship Podman 5.x. Ubuntu 24.04-based distros (including Mint) ship Podman 4.9 from apt. That release already has Quadlet but defaults rootless networking to slirp4netns; add --network pasta yourself. Ubuntu 26.04 LTS ships Docker from upstream repos with Podman in universe. Arch ships both in the official repos. If your distro ships one by default, start there unless you have a clear reason not to.
The trend is clear. Podman’s compatibility with Docker tooling improves with every release, and major projects test against both runtimes more and more. If you are starting a self-hosting setup fresh, Podman is the pragmatic default. If you have a working Docker setup that is not causing problems, there is no urgent reason to migrate. Both run the same containers at the same speed, and I have the hyperfine output to prove it. The difference is in everything around the container.
Botmonster Tech