Skip to content
Learni
View all tutorials
DevOps

How to Master Podman as an Expert in 2026

Lire en français

Introduction

Podman, Red Hat's daemonless container tool, is revolutionizing deployments in 2026 with its native rootless mode, OCI compatibility, and Kubernetes integration. Unlike Docker, Podman skips the privileged daemon, eliminating root vulnerabilities and simplifying multi-user production environments.

This expert tutorial targets senior DevOps engineers: from multi-container pods to systemd quadlets for persistent services, including TLS secrets and podman play kube. Why it matters? In production, 70% of breaches stem from daemons; Podman drops that risk to zero while supporting Compose and Buildah.

Master cgroups v2 storage config, macvlan networks, and monitoring with podman stats. By the end, deploy a scalable rootless NGINX-Postgres stack. Estimated time: 2 hours for a pro setup worth bookmarking. (142 words)

Prerequisites

  • OS: Fedora 40+, RHEL 9+, Ubuntu 24.04 (cgroups v2 enabled: systemd.unified_cgroup_hierarchy=1 in GRUB)
  • User: Non-root with subUID/subGID (check id -u and /etc/subuid)
  • Knowledge: Docker CLI, Kubernetes YAML, systemd units
  • Tools: podman 5.0+ (dnf install podman or apt install podman), optional buildah, skopeo
  • Disk space: 10GB for images/layers

Install and Configure Rootless Podman

terminal-install.sh
#!/bin/bash
# Fedora/RHEL
dnf install -y podman podman-compose buildah skopeo fuse-overlayfs

# Ubuntu
# apt update && apt install -y podman podman-compose buildah skopeo fuse-overlayfs

# Enable linger for persistent services
loginctl enable-linger $USER

# Config rootless storage (fuse-overlayfs for high perf)
mkdir -p ~/.config/containers
cat > ~/.config/containers/storage.conf << EOF
[storage]
driver = "overlay"
runroot = "/run/user/$(id -u)/containers"
graphroot = "/home/$(id -u)/.local/share/containers/storage"
[storage.options.overlay]
ignore_sizes = true
mount_program =
/usr/bin/fuse-overlayfs
mountopt = "nodev=ro,nosuid,size=100000k"
EOF

# Verify
podman info | grep -E 'cgroup|rootless'

This script installs Podman and dependencies, sets up fuse-overlayfs storage for high-performance rootless mode (avoids perf loss vs native overlayfs). enable-linger enables persistent pods after logout. Check podman info: must show 'rootless' and cgroup v2. Pitfall: without subUID, podman run fails with 'permission denied'.

Verification and First Container

Run the script above. Think of Podman as your personal 'Docker' without root—each user gets an isolated namespace. Test with podman run --rm alpine echo 'Podman OK'. Next: pods for multi-service setups.

Create a Multi-Container Pod

terminal-pod.sh
#!/bin/bash
# Create pod with NGINX + logging sidecar
podman pod create --name monpod -p 8080:80 --hostname monpod

# Main NGINX
podman run -d --pod monpod --name nginx \
  docker.io/nginx:alpine \
  sh -c 'echo "<h1>Podman Expert 2026</h1>" > /usr/share/nginx/html/index.html && nginx -g "daemon off;"'

# Sidecar logs to shared volume
podman run -d --pod monpod --name logsidecar \
  docker.io/alpine \
  sh -c 'while true; do echo "$(date)" >> /shared/logs.txt; sleep 5; done'

# Volume for intra-pod sharing
podman volume create sharedlogs
podman run -d --pod monpod --name logsidecar \
  -v sharedlogs:/shared \
  docker.io/alpine \
  sh -c 'while true; do echo "$(date)" >> /shared/logs.txt; sleep 5; done'

# Inspect
podman pod ps
podman logs monpod -f

Creates a Kubernetes-like pod with NGINX exposed on 8080 and a persistent logging sidecar via volume. --pod ties containers to the same network/PID/cgroup. Access http://localhost:8080. Pitfall: without volume, logs are lost on restart; use podman volume ls to list.

Advanced Pod and Network Management

Pods share IPC/net/UTS like a mini-K8s. For production, add macvlan: podman network create -d macvlan -o parent=eth0 macnet. Analogy: pod = shared apartment, containers = communicating rooms via localhost.

Build Custom Multi-Stage Image

Containerfile
FROM golang:1.23-alpine AS builder
WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 GOOS=linux go build -o /app/bin/server .

FROM alpine:3.20
RUN apk --no-cache add ca-certificates
WORKDIR /app
COPY --from=builder /app/bin/server .
EXPOSE 3000
USER 1001
CMD ["./server"]

Containerfile (not Dockerfile) for rootless multi-stage Go app build. USER 1001 ensures non-root security. Build: podman build -t monapp:v1 .. Run: podman run -d -p 3000:3000 --userns=keep-id monapp:v1. Pitfall: without CGO_ENABLED=0, binary too large; inspect layers with podman image inspect.

Build and Push with Podman

terminal-build.sh
#!/bin/bash
# With Containerfile above
podman build -t localhost/monapp:v1 .
podman run -d --name testapp -p 3000:3000 monapp:v1
curl localhost:3000

# Macvlan network for prod-like setup
podman network create -d macvlan -o parent=lo macnet
podman network exists macnet && podman run -d --network macnet --ip 192.168.100.10 monapp:v1

# Export OCI to registry (e.g., Quay.io)
# podman tag monapp:v1 quay.io/user/monapp:v1
# podman push quay.io/user/monapp:v1

podman ps -a

Builds, runs with userns keep-id (maps host UID), macvlan network for dedicated IP. Curl tests endpoint. Pitfall: without --userns=keep-id, host volumes fail in rootless; clean up with podman system prune.

Kubernetes Integration with Play Kube

Podman natively imports Kubernetes YAML. Create pod-kube.yaml for complex stacks.

Podman Play Kube from YAML

pod-kube.yaml
apiVersion: v1
kind: Pod
metadata:
  name: kubepod
  labels:
    app: demo
spec:
  containers:
  - name: nginx
    image: nginx:alpine
    ports:
    - containerPort: 80
    volumeMounts:
    - mountPath: /shared
      name: sharedvol
  - name: busybox
    image: busybox:1.36
    command: ['sh', '-c', 'while true; do echo "Log $(date)" >> /shared/app.log; sleep 10; done']
    volumeMounts:
    - mountPath: /shared
      name: sharedvol
  volumes:
  - name: sharedvol
    emptyDir: {}
---
apiVersion: v1
kind: Service
metadata:
  name: kubepod-svc
spec:
  selector:
    app: demo
  ports:
  - port: 80
    targetPort: 80

Valid Kubernetes Pod+Service YAML. podman play kube pod-kube.yaml deploys rootless. Expose with podman play kube --service kubepod-svc. Pitfall: emptyDir volumes non-persistent; map host volumes with hostPath.

Run Play Kube and Monitor

terminal-kube.sh
#!/bin/bash
podman play kube pod-kube.yaml
podman kube play --service kubepod-svc

# Monitor
podman pod ps
podman stats kubepod
podman logs kubepod/nginx

# Scale (Podman 5+)
podman play kube --replicas 3 pod-kube.yaml

# Cleanup
podman kube down pod-kube.yaml

Deploys YAML, exposes service, shows cgroup/CPU stats. Scales replicas for HA. Pitfall: single instance without --replicas; use podman events --filter pod=kubepod for live logs.

Quadlets for Systemd Integration

Quadlets are auto-generated systemd units from .container/.pod files. Place in ~/.config/containers/systemd/. Analogy: cron meets Docker Compose.

Quadlet Pod with Secrets

~/.config/containers/systemd/monpod.pod
[Pod]
PodName=monpod
PublishPort=8080:80
PublishPort=5432:5432
Network=macnet

[Container]nginx
Image=docker.io/nginx:alpine
Environment=NGINX_PORT=80

[Container]postgres
Image=postgres:16-alpine
Environment=POSTGRES_PASSWORD=secret123
Environment=POSTGRES_DB=appdb
Volume=pgdata:/var/lib/postgresql/data

[Service]
Restart=always

Quadlet .pod deploys NGINX+Postgres with persistent volume and custom network. Secrets via env (improve with podman secret). Run: systemctl --user daemon-reload && systemctl --user start monpod. Pitfall: forget daemon-reload, unit invisible.

TLS Secrets and Systemd Start

terminal-quadlet.sh
#!/bin/bash
# Create TLS secrets
podman secret create tls-cert cert.pem
podman secret create tls-key key.pem

# Add to quadlet (edit monpod.pod)
# [Container]nginx
# Secrets=tls-cert:/etc/ssl/cert.pem=tls.crt
# Secrets=tls-key:/etc/ssl/key.pem=tls.key

# Reload and start
systemctl --user daemon-reload
systemctl --user enable --now monpod
systemctl --user status monpod
journalctl --user -u monpod -f

# Auto-start on boot (linger already enabled)

Podman secrets stored encrypted in RAM, mounted read-only. Edit quadlet for TLS. journalctl provides unified logs. Pitfall: secrets non-persistent without podman secret create --driver file.

Best Practices

  • Rootless only: Always use --userns=keep-id for host volumes, audit with podman system migrate.
  • Quadlets > Compose: Prefer systemd for prod HA; migrate with podman generate systemd.
  • Isolated networks: Create per-app podman network create --driver bridge private.
  • Auto pruning: Add weekly cron podman system prune -af.
  • Monitoring: podman stats --no-stream + Podman Prometheus exporter.

Common Errors to Avoid

  • Cgroup v1: Pods fail; fix GRUB systemd.unified_cgroup_hierarchy=1 + reboot.
  • Missing subUID: podman run crashes; edit /etc/subuid with usermod --add-subuids 100000-165535 $USER.
  • Slow fuse-overlayfs: On SSD, force overlay if kernel 6+ supports rootless overlay.
  • Root port bind: Ports <1024 refused rootless; use sysctl net.ipv4.ip_unprivileged_port_start=80.

Next Steps