Podman: The Docker Alternative Guide
PROGRAMMING LANGUAGES Dec. 31, 2025, 5:30 p.m.

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 the podman systemd 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 a Dockerfile using Buildah under the covers.
  • podman images – lists locally stored images, similar to docker 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 -a for 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: Use podman pod generate to export a pod’s definition as a YAML file, then feed it to podman play kube for 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, install slirp4netns and set export PODMAN_USERNS=keep-id in 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: Combine podman generate kube with kubectl 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 docker or podman group, or use rootless mode with slirp4netns.
  • 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-compose package or enable the podman-plugins repository.

For deeper diagnostics, the podman --log-level=debug flag provides verbose output that mirrors Docker’s --debug mode.

Best Practices for Production Deployments

  1. Prefer rootless containers wherever possible; reserve privileged containers for hardware‑access workloads.
  2. Leverage podman generate systemd to manage long‑running services with native init systems.
  3. Store images in a private OCI registry and use signed images (via cosign) to guarantee integrity.
  4. Regularly prune unused images and containers with podman system prune -a to keep disk usage low.
  5. 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.

Share this article