Podman was built as a drop-in replacement for Docker with two fundamental architectural changes: it has no daemon, and it runs rootless by default. These are not minor implementation details — they fundamentally change the security model. Docker requires a persistent root daemon that all containers depend on (a single point of failure and a high-value attack target). Podman runs containers as direct child processes of the user, with no daemon to compromise.

For many workloads, the transition from Docker to Podman is trivially easy. For others, subtle differences in networking, storage, and ecosystem support make the choice more nuanced. This guide covers the technical details you need to make an informed decision.

Architecture: Daemon vs Daemonless

Docker Architecture

# Docker's process model:
#
# User CLI (docker) ──── Docker Daemon (dockerd) ──── containerd ──── runc
#                            ↑
#                      Root process
#                      Always running
#                      Single point of failure

# If dockerd crashes, ALL containers become unmanageable
# The daemon holds the state of every container

systemctl status docker
# docker.service - Docker Application Container Engine
#   Active: active (running)
#   Main PID: 1234 (dockerd)

Podman Architecture

# Podman's process model:
#
# User CLI (podman) ──── conmon ──── OCI runtime (crun/runc)
#                          ↑
#                    Per-container monitor
#                    No central daemon
#                    User-scoped process

# Each container is a direct child process
# No daemon means no single point of failure
# Containers survive even if Podman is updated/restarted

# Check - no podman daemon running
pgrep -f podman  # Nothing (no daemon)

# Containers are visible as regular processes
ps aux | grep conmon
# user  5678  conmon --api-version 1 -c abc123...
Aspect Docker Podman
Daemon Required (dockerd) None (fork-exec model)
Default runtime runc crun (faster, C-based)
Rootless Optional (requires setup) Default
Container monitor containerd-shim conmon (per-container)
Socket/API /var/run/docker.sock User-scoped socket (optional)
Service restart impact All containers affected Containers unaffected

Rootless by Default

Podman's rootless mode uses user namespaces to run containers without any root privileges. The container's root user maps to the invoking user's UID on the host.

# Run a container as a regular user (no sudo needed)
podman run -d --name web nginx:alpine

# The container process runs as your user on the host
ps aux | grep nginx
# myuser  12345  ... nginx: master process

# The container sees root inside
podman exec web id
# uid=0(root) gid=0(root)

# But on the host, it's mapped to your UID
ls -la ~/.local/share/containers/

# User namespace mapping
podman unshare cat /proc/self/uid_map
# 0  1000  1
# 1  100000  65536
Tip: Rootless Podman requires slirp4netns or pasta for networking (since unprivileged users cannot create bridge interfaces) and fuse-overlayfs for the overlay filesystem. Modern distributions install these automatically with Podman.

Rootless Limitations

# Port binding: rootless containers cannot bind to ports below 1024
podman run -d -p 80:80 nginx:alpine
# Error: rootlessport cannot expose privileged port 80

# Workaround 1: Use a high port
podman run -d -p 8080:80 nginx:alpine

# Workaround 2: Allow unprivileged port binding
sudo sysctl net.ipv4.ip_unprivileged_port_start=80

# Workaround 3: Use rootful Podman for port 80/443 only
sudo podman run -d -p 80:80 nginx:alpine

CLI Compatibility

Podman was designed as a CLI-compatible replacement for Docker. Most Docker commands work identically with Podman:

# These commands are identical in both tools:
podman pull nginx:alpine
podman run -d --name web -p 8080:80 nginx:alpine
podman ps
podman logs web
podman exec -it web /bin/sh
podman stop web
podman rm web
podman images
podman build -t my-app .
podman push my-app:latest registry.example.com/my-app:latest

# You can alias docker to podman
alias docker=podman
# Most scripts and tools work without modification

Compose Compatibility

Podman supports Docker Compose files through two mechanisms:

# Option 1: podman-compose (Python-based, community tool)
pip install podman-compose
podman-compose up -d
podman-compose down

# Option 2: docker-compose with Podman's Docker-compatible socket
# Enable the Podman socket (emulates Docker socket)
systemctl --user enable --now podman.socket

# Set DOCKER_HOST to point to Podman's socket
export DOCKER_HOST=unix:///run/user/$(id -u)/podman/podman.sock

# Now docker-compose works with Podman backend
docker-compose up -d
docker-compose ps
Warning: Podman Compose compatibility is good but not perfect. Complex Compose files that rely on Docker-specific features (build secrets, custom network drivers, or service dependency health checks) may require adjustments. Always test your Compose workflow with Podman before committing to a migration.

Systemd Integration

One of Podman's standout features is native systemd integration. You can generate systemd unit files for containers and manage them with standard systemd tools:

# Generate a systemd unit file for a container
podman generate systemd --new --name web > ~/.config/systemd/user/container-web.service

# The generated unit file:
# [Unit]
# Description=Podman container-web.service
# Wants=network-online.target
# After=network-online.target
#
# [Service]
# Environment=PODMAN_SYSTEMD_UNIT=%n
# Restart=on-failure
# ExecStartPre=/bin/rm -f %t/%n.ctr-id
# ExecStart=/usr/bin/podman run \
#   --cidfile=%t/%n.ctr-id \
#   --cgroups=no-conmon \
#   --rm --sdnotify=conmon \
#   -d --replace --name web \
#   -p 8080:80 nginx:alpine
# ExecStop=/usr/bin/podman stop --ignore --cidfile=%t/%n.ctr-id
# ExecStopPost=/usr/bin/podman rm -f --ignore --cidfile=%t/%n.ctr-id
#
# [Install]
# WantedBy=default.target

# Enable and start
systemctl --user daemon-reload
systemctl --user enable --now container-web.service

# Manage with standard systemd commands
systemctl --user status container-web
systemctl --user restart container-web
journalctl --user -u container-web

Quadlet: Declarative Container Units

Podman 4.4+ introduced Quadlet, a more elegant way to define container services as systemd units:

# ~/.config/containers/systemd/web.container
[Container]
Image=nginx:alpine
PublishPort=8080:80
Volume=web-data:/usr/share/nginx/html:ro

[Service]
Restart=always

[Install]
WantedBy=default.target

# Podman automatically converts this to a systemd unit
systemctl --user daemon-reload
systemctl --user start web.service

Pods: The Kubernetes Connection

Podman has a concept of "pods" that mirrors Kubernetes pods. A pod is a group of containers that share the same network namespace, and optionally other namespaces:

# Create a pod
podman pod create --name my-app -p 8080:80

# Add containers to the pod (they share the network)
podman run -d --pod my-app --name web nginx:alpine
podman run -d --pod my-app --name api my-api:latest
podman run -d --pod my-app --name cache redis:alpine

# All containers share localhost
# The API container can reach Redis at localhost:6379
# Nginx can reach the API at localhost:3000

# List pods
podman pod list
podman pod inspect my-app

Kubernetes YAML Support

Podman can generate and consume Kubernetes YAML, providing a bridge between local development and Kubernetes deployment:

# Generate Kubernetes YAML from a running pod
podman generate kube my-app > my-app.yaml

# The generated YAML:
# apiVersion: v1
# kind: Pod
# metadata:
#   name: my-app
# spec:
#   containers:
#   - name: web
#     image: nginx:alpine
#     ports:
#     - containerPort: 80
#       hostPort: 8080
#   - name: api
#     image: my-api:latest
#   - name: cache
#     image: redis:alpine

# Play (deploy) a Kubernetes YAML locally
podman kube play my-app.yaml

# Tear down
podman kube down my-app.yaml

# This works for development and testing:
# Develop with podman kube play locally,
# then deploy the same YAML to Kubernetes in production

Image Compatibility

Podman uses the same OCI image format as Docker. Images built with Docker work in Podman and vice versa:

# Pull from Docker Hub (default)
podman pull docker.io/library/nginx:alpine

# Pull from other registries
podman pull quay.io/podman/hello

# Build images (Dockerfile or Containerfile)
podman build -t my-app -f Dockerfile .
podman build -t my-app -f Containerfile .

# Registry configuration
# /etc/containers/registries.conf (or ~/.config/containers/registries.conf)
[registries.search]
registries = ['docker.io', 'quay.io', 'ghcr.io']

# Push to any OCI-compatible registry
podman push my-app:latest ghcr.io/myorg/my-app:latest

Migration Path: Docker to Podman

  1. Install Podman alongside Docker (they coexist without conflict)
  2. Test your containers with podman run to verify compatibility
  3. Test your Compose files with podman-compose or via the Docker-compatible socket
  4. Migrate systemd services using podman generate systemd or Quadlet
  5. Update CI/CD pipelines to use Podman (most require only s/docker/podman/)
  6. Remove Docker once all workloads are validated
# Step 1: Install Podman
sudo apt install podman  # Debian/Ubuntu
sudo dnf install podman  # Fedora/RHEL

# Step 2: Test individual containers
podman run -d --name test-web -p 8081:80 nginx:alpine
curl http://localhost:8081
podman rm -f test-web

# Step 3: Test Compose files
export DOCKER_HOST=unix:///run/user/$(id -u)/podman/podman.sock
docker-compose -f my-stack.yml up -d
docker-compose -f my-stack.yml ps
docker-compose -f my-stack.yml down

# Step 4: Convert to systemd
podman generate systemd --new --files --name my-stack

When to Choose Which

Choose Docker When:

  • You need the broadest ecosystem compatibility (all tutorials, tools, and CI/CD systems assume Docker)
  • You use Docker Swarm for orchestration
  • You depend on BuildKit advanced features
  • Your team is already invested in Docker tooling
  • You use Docker Desktop on macOS/Windows for development

Choose Podman When:

  • Security is a top priority (rootless by default, no daemon)
  • You are on RHEL/Fedora/CentOS (Podman is the default container tool)
  • You want systemd integration for container management
  • You are developing for Kubernetes (pod concept, YAML generation)
  • You need to run containers without root privileges
  • You want containers that survive daemon restarts

The practical reality: For most workloads, Podman and Docker are interchangeable. The CLI compatibility is excellent, OCI images work in both, and Compose support continues to improve. The choice often comes down to organizational preference and ecosystem requirements rather than technical capability.

Performance Comparison

Metric Docker (rootful) Podman (rootless) Podman (rootful)
Container startup ~300ms ~350ms ~250ms (crun)
Image pull Similar Similar Similar
Network throughput (rootful) Near-native ~90% (slirp4netns) Near-native
Network throughput (pasta) Near-native ~97% (pasta) Near-native
Disk I/O overlay2 fuse-overlayfs (~95%) overlay2
Memory overhead dockerd ~50MB No daemon No daemon
Tip: Rootless Podman with the pasta networking backend (available in Podman 4.0+) significantly closes the performance gap with rootful containers. If you are seeing network performance issues with rootless Podman, ensure you are using pasta instead of the older slirp4netns.

Both Docker and Podman are excellent container runtimes. Docker's strength is its ecosystem and ubiquity. Podman's strength is its security model and systemd integration. The good news is that the OCI standard ensures you are never locked into either — your images, your Compose files, and your knowledge transfer between both tools with minimal friction.