Something weird happened in the container world. Docker — the tool that literally defined containerization — is quietly losing ground. Not to some flashy startup, but to an open-source project most developers still haven't tried: Podman.
The numbers tell the story. Podman's GitHub stars crossed 30,000 in early 2026. Red Hat, SUSE, and Canonical are shipping it by default. Kubernetes dropped Docker as a runtime back in 1.24, and teams that noticed started asking uncomfortable questions about their entire container stack. Meanwhile, Docker Desktop's licensing changes turned "free tool everyone uses" into "thing that costs $24/month/seat for companies over 250 employees."
But here's what most comparison articles get wrong: this isn't about whether Podman is "better" than Docker. It's about whether your specific workflow, team size, security requirements, and deployment target make one a materially better fit than the other.
This guide goes deep into the architectural differences, walks through real migration scenarios, benchmarks what actually matters, and gives you a concrete decision framework. No hand-waving. No "it depends" cop-outs. Let's get into it.
Architecture: The Fundamental Split
Before anything else, you need to understand the single biggest architectural difference between Docker and Podman. Everything else flows from this.
Docker's Daemon Model
Docker uses a client-server architecture with a persistent background daemon (dockerd):
┌─────────────┐ ┌──────────────┐ ┌───────────────┐
│ docker CLI │────▶│ dockerd │────▶│ containerd │
│ (client) │ │ (daemon) │ │ (runtime) │
└─────────────┘ └──────────────┘ └───────────────┘
│
Runs as root
Always listening
Manages all containers
When you run docker run nginx, the CLI sends a request to the daemon, which pulls the image, creates the container, and manages its lifecycle. The daemon runs as root by default and manages all containers on the system.
This model has real advantages:
- Centralized management: One process tracks all container state
- Background operations: Containers keep running even if you close your terminal
- Ecosystem integration: Docker Compose, Docker Swarm, and thousands of tools expect the daemon socket
But it also has real costs:
- Security surface: The daemon runs as root. If it's compromised, the attacker has root access to the host
-
Single point of failure: If
dockerdcrashes, all containers go down - Resource overhead: The daemon consumes memory and CPU even when idle
Podman's Daemonless Model
Podman takes a fundamentally different approach — no daemon:
┌─────────────┐ ┌───────────────┐
│ podman CLI │────▶│ conmon │
│ (direct) │ │ (per-container│
└─────────────┘ │ monitor) │
└───────────────┘
│
Runs as user
Fork-exec model
Each container independent
When you run podman run nginx, Podman directly forks the container process using conmon (a lightweight container monitor). There's no persistent daemon. Each container runs as an independent process under your user account.
Key implications:
- No root required: Containers run under your user's UID by default
- No single point of failure: One container crashing doesn't affect others
- No idle overhead: Nothing runs when you're not using containers
- systemd integration: Containers can be managed as regular systemd services
The trade-off:
- No centralized state: Container management is per-session (though Podman's database handles persistence)
-
Background containers require explicit setup: You need
podman generate systemdor--restartflags for containers to survive logout -
Some Docker tooling assumes a daemon socket: Tools expecting
/var/run/docker.sockneed adaptation
Security: Why This Actually Matters
"Rootless containers" sounds like a buzzword until you understand what it actually prevents.
The Root Problem with Docker
By default, Docker's daemon runs as root. When you mount a volume like -v /host/path:/container/path, the container process can read and write those files as root on the host. Docker has mitigations (user namespaces, seccomp profiles, AppArmor), but they're opt-in and often misconfigured.
Real-world impact:
# This is what a container escape looks like with a root daemon
docker run -v /:/host --privileged alpine chroot /host
# You now have full root access to the host filesystem
Docker's rootless mode (available since Docker 20.10) addresses this, but it requires explicit configuration:
# Setting up Docker rootless mode
dockerd-rootless-setuptool.sh install
# Verify it's running rootless
docker info | grep "Root Dir"
# Should show something under ~/.local/share/docker
In practice, most Docker installations still run the daemon as root because that's the default and many tutorials don't mention rootless mode.
Podman's Rootless-by-Default
Podman runs rootless by default. No setup required:
# This just works — no root, no daemon, no configuration
podman run -d nginx
# Verify it's running as your user
podman top -l user
# Shows your UID, not root
Under the hood, Podman uses Linux user namespaces to map container UIDs to unprivileged host UIDs:
# Inside the container, nginx thinks it's running as root (UID 0)
# But on the host, it's actually running as your user (e.g., UID 1000)
podman unshare cat /proc/self/uid_map
# Output:
# 0 1000 1
# 1 100000 65536
This means even if a container escape occurs, the attacker only gets your unprivileged user access, not root.
Security Comparison Table
| Feature | Docker (default) | Docker (rootless) | Podman |
|---|---|---|---|
| Daemon runs as | root | user | No daemon |
| Container UID on host | root | mapped | mapped |
| Privileged socket |
/var/run/docker.sock (root-owned) |
$XDG_RUNTIME_DIR/docker.sock |
None |
| Default capabilities | Broad set | Reduced | Minimal set |
| SELinux/AppArmor | Optional | Optional | Enabled by default |
| Seccomp profile | Default profile | Default profile | Stricter default |
| CVE impact of daemon compromise | Full root access | User-level access | N/A (no daemon) |
For teams subject to compliance requirements (SOC 2, PCI-DSS, HIPAA), Podman's security posture is materially easier to audit and defend.
CLI Compatibility: The "Just Alias It" Reality
One of Podman's smartest design decisions was making its CLI nearly 100% compatible with Docker's:
# Add this to your .bashrc/.zshrc
alias docker=podman
# Now these all work:
docker pull nginx
docker run -d -p 8080:80 nginx
docker ps
docker build -t myapp .
docker push myregistry/myapp
This sounds too good to be true, and mostly it is. But there are edge cases:
Where the Alias Works Perfectly
-
docker run,docker build,docker pull/push -
docker ps,docker logs,docker exec -
docker images,docker rmi docker network create/ls/rmdocker volume create/ls/rm
Where It Breaks
Docker Compose: Podman ships its own podman-compose, and also supports Docker Compose v2 via Podman's compatibility socket:
# Option 1: Use podman-compose (Python-based, simpler)
pip install podman-compose
podman-compose up -d
# Option 2: Enable Podman's Docker-compatible socket
systemctl --user enable --now podman.socket
# Then Docker Compose v2 can connect to it
export DOCKER_HOST=unix://$XDG_RUNTIME_DIR/podman/podman.sock
docker compose up -d
Docker Swarm: Not supported by Podman. If you're using Swarm (increasingly rare), this is a hard blocker.
Docker-in-Docker (DinD): Common in CI/CD pipelines. Podman handles this differently:
# Docker approach (requires privileged)
docker run --privileged -v /var/run/docker.sock:/var/run/docker.sock docker
# Podman approach (rootless, no privileged needed)
podman run --security-opt label=disable \
-v $XDG_RUNTIME_DIR/podman/podman.sock:/var/run/docker.sock \
docker
Docker Desktop features: Docker Desktop's GUI, Kubernetes integration, Extensions Marketplace, and Dev Environments don't have direct Podman equivalents. Podman Desktop covers some of this ground but isn't feature-equivalent.
Kubernetes Alignment: Podman's Secret Weapon
This is where Podman has a genuine architectural advantage that Docker can't easily replicate.
Native Pod Support
Podman has first-class support for pods — groups of containers that share network namespaces, just like Kubernetes pods:
# Create a pod
podman pod create --name webapp -p 8080:80
# Add containers to it
podman run -d --pod webapp --name frontend nginx
podman run -d --pod webapp --name api node:20-slim
# Both containers share localhost
# frontend can reach the API at localhost:3000
This directly mirrors how Kubernetes organizes containers. Docker has no equivalent concept — each container gets its own network namespace.
Generate Kubernetes YAML
Podman can export running pods directly to Kubernetes-compatible YAML:
# Generate Kubernetes YAML from a running pod
podman generate kube webapp > webapp.yaml
# The output is valid Kubernetes YAML
cat webapp.yaml
# Generated by Podman
apiVersion: v1
kind: Pod
metadata:
name: webapp
spec:
containers:
- name: frontend
image: docker.io/library/nginx:latest
ports:
- containerPort: 80
hostPort: 8080
- name: api
image: docker.io/library/node:20-slim
And you can go the other direction too:
# Run Kubernetes YAML locally with Podman
podman play kube webapp.yaml
# Tear it down
podman play kube webapp.yaml --down
This podman play kube workflow is incredibly useful for:
- Local development that mirrors production Kubernetes exactly
- Testing Kubernetes manifests without a full cluster
- Gradual migration from Docker Compose to Kubernetes
Docker's Kubernetes Story
Docker's approach to Kubernetes is indirect:
# Docker Desktop includes a built-in Kubernetes cluster
# But it's a full K8s cluster, not a lightweight pod concept
# Converting Docker Compose to Kubernetes requires external tools
# kompose (community tool):
kompose convert -f docker-compose.yaml
# The output often needs significant manual editing
Docker Compose and Kubernetes are fundamentally different abstractions. Docker Compose defines services, Kubernetes defines workloads. The translation is lossy. Podman's pod concept bridges this gap natively.
Docker Compose vs Podman Compose: The Real-World Comparison
Most developers don't just run individual containers — they orchestrate multiple containers with Compose files. Here's where the migration gets interesting.
Docker Compose (v2)
Docker Compose v2 is mature, battle-tested, and feature-rich:
# docker-compose.yaml
services:
web:
build: ./frontend
ports:
- "3000:3000"
depends_on:
- api
- db
environment:
- API_URL=http://api:4000
api:
build: ./backend
ports:
- "4000:4000"
depends_on:
db:
condition: service_healthy
environment:
- DATABASE_URL=postgresql://postgres:secret@db:5432/myapp
db:
image: postgres:16
volumes:
- pgdata:/var/lib/postgresql/data
environment:
- POSTGRES_PASSWORD=secret
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 5s
timeout: 5s
retries: 5
volumes:
pgdata:
docker compose up -d
docker compose logs -f
docker compose down
Podman Compose Compatibility
Option 1: podman-compose (Python-based, community tool):
pip install podman-compose
podman-compose up -d
Pros: Simple, lightweight. Cons: Doesn't support all Docker Compose v2 features (notably healthcheck.condition, some network modes, profiles).
Option 2: Docker Compose v2 via Podman socket (recommended for complex setups):
# Start the Podman socket for Docker API compatibility
systemctl --user start podman.socket
# Set the Docker host to Podman's socket
export DOCKER_HOST=unix://$XDG_RUNTIME_DIR/podman/podman.sock
# Now standard Docker Compose works
docker compose up -d
This approach gives you full Docker Compose v2 compatibility because Docker Compose talks to Podman's Docker-compatible API. It's the smoothest migration path for complex Compose files.
Option 3: Quadlet (Podman-native, systemd-integrated):
# ~/.config/containers/systemd/webapp.container
[Container]
Image=docker.io/library/nginx:latest
PublishPort=8080:80
Volume=webdata:/usr/share/nginx/html
[Service]
Restart=always
[Install]
WantedBy=default.target
# Reload and start
systemctl --user daemon-reload
systemctl --user start webapp.service
Quadlet is Podman's native approach to running containers as systemd services. It's more production-oriented than Compose but requires rewriting your orchestration.
CI/CD Pipeline Integration
CI/CD is where the Docker-to-Podman migration gets the most complex, because most CI infrastructure was built assuming Docker.
GitHub Actions
Docker (default):
# .github/workflows/build.yaml
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Build image
run: docker build -t myapp:${{ github.sha }} .
- name: Push to registry
run: |
echo ${{ secrets.REGISTRY_TOKEN }} | docker login ghcr.io -u ${{ github.actor }} --password-stdin
docker push ghcr.io/${{ github.repository }}/myapp:${{ github.sha }}
Podman (drop-in replacement):
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Build image
run: podman build -t myapp:${{ github.sha }} .
- name: Push to registry
run: |
podman login ghcr.io -u ${{ github.actor }} -p ${{ secrets.REGISTRY_TOKEN }}
podman push ghcr.io/${{ github.repository }}/myapp:${{ github.sha }}
Podman is pre-installed on GitHub's ubuntu-latest runners, so this is a genuine drop-in replacement.
GitLab CI
GitLab CI traditionally relies on Docker-in-Docker (DinD) for building images:
Docker approach (requires privileged):
build:
image: docker:latest
services:
- docker:dind
variables:
DOCKER_TLS_CERTDIR: "/certs"
script:
- docker build -t $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA .
- docker push $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA
Podman approach (no privileged required):
build:
image: quay.io/podman/stable
script:
- podman build -t $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA .
- podman push $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA
The Podman approach is more secure because it doesn't need --privileged mode, which is a common source of container escape vulnerabilities in CI systems.
Buildah: The Specialized Alternative
Podman's ecosystem includes Buildah, a dedicated image-building tool that offers capabilities Docker's docker build doesn't:
# Build from scratch (no base image, minimal attack surface)
container=$(buildah from scratch)
buildah copy $container ./static-binary /app
buildah config --entrypoint '["/app"]' $container
buildah commit $container myapp:minimal
# The resulting image has ZERO packages, ZERO shell, ZERO attack surface
# Just your binary
# Layer-level control
buildah run $container -- pip install -r requirements.txt
buildah run $container -- pip cache purge
buildah commit $container myapp:optimized
Buildah is particularly valuable for security-sensitive builds because it never runs a daemon and can build images without requiring any container runtime at all.
Performance Benchmarks: Cutting Through the Marketing
Let's look at actual performance differences that matter in practice.
Startup Time
Container startup time (nginx, Alpine-based, SSD):
Docker: ~0.8-1.2s (daemon already running)
Podman: ~0.5-0.9s (fork-exec, no daemon overhead)
First container after boot:
Docker: ~2-4s (daemon needs to start first)
Podman: ~0.5-0.9s (no daemon to start)
Podman wins on cold-start because there's no daemon initialization. For long-running servers where Docker's daemon is already running, the difference is negligible.
Build Time
Image build time (multi-stage Node.js app, cache cold):
Docker BuildKit: ~45-60s
Podman (Buildah): ~48-65s
Image build time (same app, cache warm):
Docker BuildKit: ~3-5s
Podman (Buildah): ~3-5s
Build times are effectively identical for most workloads. Docker's BuildKit has slight advantages in cache management for complex multi-stage builds, but the difference rarely exceeds 10%.
Memory Usage
Idle memory consumption:
Docker daemon: ~50-100MB (always running)
Podman: ~0MB (nothing running when idle)
Per-container overhead:
Docker: ~5-10MB (conmon + shim)
Podman: ~3-8MB (conmon only)
Podman's zero-idle-cost model matters for developer machines and CI runners where containers aren't always active. On a production server running 50+ containers, per-container overhead is nearly identical.
Image Pull Speed
Pulling nginx:latest (compressed ~70MB):
Docker: ~4-6s
Podman: ~4-6s
Pulling large ML image (~5GB):
Docker: ~45-90s
Podman: ~45-90s
No meaningful difference. Both use the same OCI registry protocols and similar decompression strategies.
Docker Desktop vs Podman Desktop: The GUI Battle
For developers on macOS and Windows, the desktop experience matters.
Docker Desktop
- Cost: Free for personal use, education, and companies <250 employees with <$10M revenue. $24/month/user for larger companies (Business tier)
- Kubernetes: Built-in single-node cluster
- Extensions: Marketplace with 100+ extensions
- Dev Environments: Codespaces-like remote dev environments
- VM management: Automatic Linux VM management on macOS/Windows
- Resource controls: GUI for CPU/memory limits
- Volumes: GUI for volume management and inspection
Podman Desktop
- Cost: Free, open-source (Apache 2.0)
- Kubernetes: Kind/Minikube integration (not built-in)
- Extensions: Growing plugin system, smaller ecosystem
-
VM management: Automatic
podman machinemanagement - Resource controls: Basic CPU/memory controls
- Pod management: First-class pod creation and management UI
For solo developers and small teams, Podman Desktop is genuinely competitive. For larger teams that rely on Docker Desktop's Extensions, Dev Environments, or enterprise features (SSO, image access management, hardened Docker Desktop), Docker Desktop still has the edge.
The Migration Playbook: Moving from Docker to Podman
If you've decided to switch, here's the battle-tested migration order:
Phase 1: Local Development (Week 1-2)
# 1. Install Podman
# macOS:
brew install podman
podman machine init
podman machine start
# Linux (Ubuntu/Debian):
sudo apt install podman
# 2. Add the alias (non-destructive, Docker still works)
echo 'alias docker=podman' >> ~/.zshrc
source ~/.zshrc
# 3. Test your existing workflows
docker pull your-registry/your-app:latest
docker run -d -p 3000:3000 your-registry/your-app:latest
# 4. Test Docker Compose compatibility
systemctl --user start podman.socket
export DOCKER_HOST=unix://$XDG_RUNTIME_DIR/podman/podman.sock
docker compose up -d
Phase 2: CI/CD Pipeline (Week 3-4)
# Start with a parallel job that mirrors your Docker build
# This lets you validate without breaking existing pipelines
build-podman:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Build with Podman
run: |
podman build -t myapp:${{ github.sha }} .
podman push ghcr.io/${{ github.repository }}/myapp:${{ github.sha }}
Phase 3: Production (Week 5+)
# Generate systemd services from your containers
podman generate systemd --new --files --name webapp
# Install them
cp container-webapp.service ~/.config/systemd/user/
systemctl --user enable --now container-webapp.service
# Or use Quadlet for a cleaner approach
# See the Quadlet section above
Migration Gotchas
-
Volume permissions: Podman's rootless mode maps UIDs differently. You may need
podman unshare chownfor mounted volumes -
Port binding: Rootless Podman can't bind to ports below 1024 without
sysctl net.ipv4.ip_unprivileged_port_start=0 -
Network modes:
--network=hostbehaves differently in rootless mode -
Docker socket mounts: Tools that mount
/var/run/docker.sockneed to be updated to use Podman's socket path
Decision Framework: Docker vs Podman in 2026
Here's the concrete framework, no hedging:
Stay with Docker if:
- You're on a team <250 employees (under $10M revenue) and Docker Desktop is free for you. The ecosystem advantages are real.
- You heavily depend on Docker Swarm. Podman has no Swarm equivalent.
- Your CI/CD pipelines use Docker-specific features like BuildKit's advanced cache mounts, Docker Compose's full feature set in testing, or Docker-in-Docker patterns that would require significant rework.
- Docker Desktop's Extensions (like the vulnerability scanning, log explorer, or database tools) are part of your daily workflow.
- Your team doesn't run Linux and you need the polished macOS/Windows experience that Docker Desktop provides.
Switch to Podman if:
- Security compliance is a priority. Rootless by default, no privileged daemon, minimal capabilities — Podman's security posture is materially stronger.
- You're paying for Docker Desktop across a large team. At $24/seat/month, a 100-person team is spending $28,800/year. Podman Desktop is free.
-
You're targeting Kubernetes and want the local development experience to match production semantics. Podman's pod concept and
podman play kubeare genuinely useful. - You run on Linux servers and want systemd-native container management with Quadlet.
- Your CI/CD needs rootless container building. Podman's ability to build images without privileged mode is a meaningful security improvement.
- Resource efficiency matters (shared CI runners, developer laptops). Zero daemon overhead adds up.
The Hybrid Approach (What Most Teams Actually Do)
The reality is most teams don't switch overnight:
- Podman on Linux servers — rootless, systemd integration, no licensing
- Docker Desktop on developer machines — polished UX, extensions, easy onboarding
- Podman in CI/CD — no privileged containers, pre-installed on GitHub runners
This hybrid approach captures the security benefits of Podman in production and CI while keeping Docker Desktop's developer experience where it matters most. The OCI standard ensures images are fully interchangeable.
What's Coming: The Container Landscape in 2026 and Beyond
The container ecosystem is converging on standards while diverging on execution:
OCI Standardization is effectively complete. Images, runtimes, and distribution specs are mature. Docker and Podman build and run the same images. The "works on Docker, breaks on Podman" era is largely over.
WebAssembly (Wasm) containers are emerging as a complementary technology. Both Docker (via runwasi) and Podman (via crun-wasm) can now run Wasm workloads alongside traditional Linux containers. Wasm containers start in milliseconds, use a fraction of the memory, and offer stronger sandboxing. They won't replace Linux containers, but they'll handle an increasing share of lightweight, compute-focused workloads.
Rootless as default is becoming the industry norm. Even Docker is moving in this direction, with rootless mode getting smoother with each release. The Podman philosophy — rootless by default, no daemon by default — is winning the architectural argument regardless of which tool you're using.
Dev Containers and container-based development environments continue to mature. Both Docker (via Dev Containers spec) and Podman (via Podman Desktop integration) support this pattern. The future of development environments is likely containers all the way down, making the choice of container runtime even more critical.
Conclusion
The honest answer: it depends on what you're optimizing for, but the answer is less ambiguous than it was two years ago.
If security, cost, and Kubernetes alignment are your top priorities, Podman is the stronger choice in 2026. The rootless-by-default architecture, zero daemon overhead, and native pod support give it genuine technical advantages that Docker has been playing catch-up on.
If ecosystem maturity, developer experience on macOS/Windows, and the inertia of existing tooling are more important, Docker remains the pragmatic default. The ecosystem is larger, the desktop experience is more polished, and more tutorials, Stack Overflow answers, and CI templates assume Docker.
The good news: because both tools conform to OCI standards, you're not locked in. You can start with Docker, move to Podman for CI, use Podman in production, and keep Docker Desktop for local development. The images are the same. The registries are the same. The Dockerfiles (yes, Podman builds Dockerfiles) are the same.
The container world fractured not because of incompatibility, but because of philosophy: centralized daemon vs. daemonless, root by default vs. rootless by default, commercial product vs. community project. In 2026, both philosophies produce production-grade results. Your choice should reflect your values and constraints, not which tool has more Twitter advocates.
Pick one, ship containers, and focus on what's actually hard — the code running inside them.
⚡ Speed Tip: Read the original post on the Pockit Blog.
Tired of slow cloud tools? Pockit.tools runs entirely in your browser. Get the Extension now for instant, zero-latency access to essential dev tools.
Top comments (0)