Podman: The Docker Alternative Guide
Podman has quietly become the go‑to container engine for developers who crave Docker’s simplicity without its daemon‑centric design. It offers a drop‑in CLI that feels familiar, yet runs containers root‑lessly and integrates tightly with systemd. In this guide we’ll walk through installing Podman, mastering its core commands, and exploring advanced features that make it a compelling Docker alternative.
Getting Started: Installation and Setup
Podman is available in the default repositories of most major Linux distributions. On Fedora, a simple dnf install podman does the trick, while Ubuntu users can pull it from the universe repo with apt install podman. macOS and Windows users can leverage the Podman Desktop app, which bundles a lightweight VM to run Linux containers.
After installation, verify the version to ensure you’re on a recent release (≥ 4.0). Newer versions include the podman compose plugin and enhanced rootless support, both essential for production‑grade workloads.
# Verify installation
import subprocess, sys
def run(cmd):
result = subprocess.run(cmd, shell=True, capture_output=True, text=True)
print(result.stdout.strip() or result.stderr.strip())
run("podman --version")
Pro tip: Enable thepodmansystemd socket (systemctl enable --now podman.socket) to speed up container start‑up by reusing a long‑running socket connection.
Core Concepts: Images, Containers, and Pods
At its heart, Podman mirrors Docker’s object model: images are immutable layers, containers are runtime instances, and pods group related containers sharing a network namespace. The biggest conceptual shift is that Podman does not require a central daemon; each command spawns a short‑lived process that talks directly to the OCI runtime.
Because there’s no daemon, you can run Podman commands as an unprivileged user, dramatically reducing the attack surface. This “rootless” mode works out‑of‑the‑box on recent kernels and leverages user namespaces under the hood.
Images: Pull, Build, and Manage
podman pull– fetches images from Docker Hub, Quay, or any OCI registry.podman build– builds images from aDockerfileusing Buildah under the covers.podman images– lists locally stored images, similar todocker images.
Here’s a quick example that builds a minimal Flask app image using a classic Dockerfile:
# Dockerfile (saved as Dockerfile)
FROM python:3.12-slim
WORKDIR /app
COPY app.py .
RUN pip install flask
EXPOSE 5000
CMD ["python", "app.py"]
# Build the image with Podman
import subprocess, textwrap
dockerfile = textwrap.dedent("""\
FROM python:3.12-slim
WORKDIR /app
COPY app.py .
RUN pip install flask
EXPOSE 5000
CMD ["python", "app.py"]
""")
# Write Dockerfile to disk (omitted for brevity)
subprocess.run("podman build -t myflask:latest .", shell=True, check=True)
Containers: Run, Inspect, and Clean Up
podman run– creates and starts a container in one step.podman ps– lists running containers; add-afor all.podman exec– runs a command inside a running container.podman rm– removes stopped containers.
Running the Flask image we just built is straightforward:
# Start the Flask container in detached mode, map port 5000
import subprocess, time
subprocess.run("podman run -d -p 5000:5000 --name myflask myflask:latest", shell=True, check=True)
# Give it a moment to start, then test the endpoint
time.sleep(2)
subprocess.run("curl http://localhost:5000", shell=True)
Pods: The Native Way to Orchestrate Multi‑Container Apps
Pods are Podman’s answer to Docker Compose’s multi‑container orchestration, but they operate at the kernel level. All containers inside a pod share the same network namespace, meaning they can communicate over localhost without exposing extra ports.
Creating a pod that runs a backend API and a Redis cache is a common pattern. First, define the pod, then launch each container into it.
# Create a pod named dev-pod, expose port 8000
subprocess.run("podman pod create --name dev-pod -p 8000:8000", shell=True, check=True)
# Launch a FastAPI container inside the pod
subprocess.run(
"podman run -d --pod dev-pod --name api ghcr.io/tiangolo/uvicorn-gunicorn-fastapi:python3.11",
shell=True, check=True
)
# Launch a Redis container in the same pod
subprocess.run(
"podman run -d --pod dev-pod --name cache redis:7-alpine",
shell=True, check=True
)
Now the FastAPI service can reach Redis simply via redis://localhost:6379. No extra networking configuration is required.
Pro tip: Usepodman pod generateto export a pod’s definition as a YAML file, then feed it topodman play kubefor Kubernetes‑style deployment.
Docker‑Compatible CLI: Minimal Migration Effort
One of Podman’s biggest selling points is its Docker‑compatible command set. Most Docker commands map 1:1, so you can often replace docker with podman in scripts without changes. Even the output format (JSON, table) remains consistent.
For example, converting a CI pipeline that builds and pushes images is trivial:
# CI step using Podman instead of Docker
subprocess.run("podman build -t registry.example.com/app:${CI_COMMIT_SHA} .", shell=True, check=True)
subprocess.run("podman push registry.example.com/app:${CI_COMMIT_SHA}", shell=True, check=True)
If you rely on Docker Compose files, the podman compose plugin can interpret them directly. Simply run podman compose up -d and watch the magic happen.
Rootless Containers: Security Without Sacrifices
Running containers as a non‑root user eliminates a whole class of privilege escalation attacks. Podman achieves this by creating a user namespace where the container’s root UID maps to the invoking user’s UID on the host.
Rootless mode works seamlessly for most workloads, but there are a few caveats: you cannot bind‑mount privileged devices, and certain network configurations (e.g., host mode) are restricted. In practice, these limitations rarely affect typical web or CI jobs.
Pro tip: To enable rootless networking on older kernels, installslirp4netnsand setexport PODMAN_USERNS=keep-idin your shell profile.
Systemd Integration: Containers as Services
Podman can generate native systemd unit files that manage container lifecycles just like traditional services. This is especially handy for production servers where you want containers to start on boot, restart on failure, and log to journalctl.
Generate a unit file with podman generate systemd, then enable it with systemctl:
# Generate a systemd unit for the previously created pod
subprocess.run(
"podman generate systemd --name dev-pod --files --restart-policy=always",
shell=True, check=True
)
# Move the unit to systemd directory and enable
subprocess.run("mv container-dev-pod.service /etc/systemd/system/", shell=True, check=True)
subprocess.run("systemctl daemon-reload && systemctl enable --now container-dev-pod.service", shell=True, check=True)
Now the pod behaves like any other service: systemctl status container-dev-pod shows live logs, and systemctl restart cleanly restarts all containers inside the pod.
CI/CD Pipelines: Faster, Safer Builds
Because Podman does not require a privileged daemon, it integrates cleanly with shared CI runners. GitHub Actions, GitLab CI, and Jenkins can all run Podman commands inside their own sandboxed environments.
Here’s a minimal GitHub Actions workflow that builds, tests, and pushes an image using Podman:
name: CI with Podman
on:
push:
branches: [main]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Install Podman
run: sudo apt-get update && sudo apt-get install -y podman
- name: Build image
run: podman build -t ghcr.io/${{ github.repository }}:${{ github.sha }} .
- name: Run tests
run: podman run --rm ghcr.io/${{ github.repository }}:${{ github.sha }} pytest
- name: Push image
env:
REGISTRY_TOKEN: ${{ secrets.GHCR_TOKEN }}
run: |
echo $REGISTRY_TOKEN | podman login ghcr.io -u ${{ github.actor }} --password-stdin
podman push ghcr.io/${{ github.repository }}:${{ github.sha }}
The workflow runs entirely without root privileges, making it ideal for shared runners that enforce strict security policies.
Real‑World Use Cases
Edge Devices and IoT Gateways
Edge deployments often lack a full Docker daemon and require low‑overhead container runtimes. Podman’s rootless mode, tiny footprint, and ability to run without a background service make it perfect for Raspberry Pi or Jetson devices that need to host micro‑services locally.
Development Environments
Developers who use VS Code Remote Containers can switch the backend from Docker to Podman with a single setting. This eliminates the need for elevated privileges on workstations, aligning local development with production security standards.
Kubernetes‑Native Workloads
Podman can generate OCI‑compatible images and even produce kube YAML via podman generate kube. Teams that build containers on laptops and push them directly to a Kubernetes cluster benefit from a consistent build environment that mirrors the target runtime.
Pro tip: Combinepodman generate kubewithkubectl apply -f -to test a pod locally before committing to a cluster.
Performance and Resource Consumption
Benchmarking Podman against Docker on a typical web workload shows comparable start‑up latency (≈ 50 ms) and CPU usage. The real advantage lies in memory consumption: without a persistent daemon, idle systems consume ~30 MB less RAM, which adds up on densely packed VMs.
Podman also supports cgroup v2 out of the box, giving you finer‑grained control over CPU and memory quotas. Use --cgroup-conf or the systemd driver for seamless integration with modern Linux distributions.
Troubleshooting Common Issues
- Network “permission denied” errors: Ensure your user is in the
dockerorpodmangroup, or use rootless mode withslirp4netns. - Image build failures on older kernels: Upgrade to kernel 5.10+ or enable user namespaces via
sysctl -w kernel.unprivileged_userns_clone=1. - Podman compose not found: Install the
podman-composepackage or enable thepodman-pluginsrepository.
For deeper diagnostics, the podman --log-level=debug flag provides verbose output that mirrors Docker’s --debug mode.
Best Practices for Production Deployments
- Prefer rootless containers wherever possible; reserve privileged containers for hardware‑access workloads.
- Leverage
podman generate systemdto manage long‑running services with native init systems. - Store images in a private OCI registry and use signed images (via
cosign) to guarantee integrity. - Regularly prune unused images and containers with
podman system prune -ato keep disk usage low. - Adopt the “build‑once, run‑anywhere” philosophy: build with Podman, run with Podman, and optionally deploy to Kubernetes without modification.
Conclusion
Podman delivers Docker’s developer friendliness while shedding the daemon, elevating security, and embracing modern Linux features. Its seamless Docker compatibility, native pod model, and deep systemd integration make it a robust alternative for everything from local development to edge deployments and CI pipelines. By mastering the commands and patterns outlined above, you’ll be ready to replace Docker with Podman and reap the benefits of a daemon‑less, rootless container ecosystem.