Podman
A daemonless, rootless container engine compatible with OCI standards and Docker CLI syntax.
Podman
A daemonless, rootless container engine compatible with OCI standards and Docker CLI syntax.
Overview
Podman is a container engine that provides a Docker-compatible command-line interface without requiring a daemon process. It excels at running containers as non-root users, integrating with systemd for service management, and managing pods (groups of containers sharing resources). Podman is particularly suited for security-conscious environments and systems where systemd integration is desirable.
graph TB
subgraph "Podman Architecture"
A[Podman CLI] --> B[Container Runtime]
B --> C[conmon]
C --> D[runc/crun]
A --> E[Image Store]
A --> F[Container Store]
subgraph "Pod Structure"
G[Pod] --> H[Infra Container]
G --> I[Container 1]
G --> J[Container 2]
H -.-> K[Shared Namespaces]
I -.-> K
J -.-> K
end
end
subgraph "Storage"
E --> L["/var/lib/containers"]
F --> L
end
Version note
Podman 6.0 (June 2026) removed several things that older material still assumes. Everything here works on 5.4+ unless flagged; check these before upgrading a 5.x host:
| Removed in 6.0 | Replacement |
|---|---|
| CNI networking | netavark (default since 4.0) |
| slirp4netns | pasta (default since 5.0) |
| cgroups v1 | cgroups v2 |
| iptables | nftables |
| BoltDB state | SQLite (auto-migrated on first 6.x start) |
| Intel Mac and Windows 10 hosts | — |
podman version
podman info --format '{{.Host.CgroupsVersion}} {{.Host.NetworkBackend}} {{.Host.DatabaseBackend}}'
# v2 netavark sqlite
Rootless Containers
Key Concepts
Rootless containers run entirely in user space without requiring root privileges, providing enhanced security through user namespace isolation.
Advantages:
- Security: Compromised containers cannot escalate to root on the host
- Multi-tenancy: Multiple users can run containers independently
- No daemon: Each user manages their own container storage
- Audit trail: Container actions tied to specific user accounts
Limitations:
- Cannot bind to privileged ports (< 1024) without configuration
- Some volume mounts may have permission constraints
- Network performance slightly reduced by user-space networking (pasta; slirp4netns was the old default and is gone in 6.0)
Common Commands/Patterns
# Check rootless prerequisites
podman info --format '{{.Host.Security.Rootless}}'
# View user namespace configuration
cat /etc/subuid
cat /etc/subgid
# Add subuid/subgid ranges for a user (as root)
usermod --add-subuids 100000-165535 --add-subgids 100000-165535 username
# Reset rootless storage (if corrupted)
podman system reset
# Run container as specific user inside container
podman run --user 1000:1000 nginx
# Check current user's container storage location
podman info --format '{{.Store.GraphRoot}}'
Examples
# Set up rootless environment for new user
sudo usermod --add-subuids 200000-265535 --add-subgids 200000-265535 newuser
# Allow binding to port 80 without root (requires sysctl)
sudo sysctl net.ipv4.ip_unprivileged_port_start=80
# Run rootless container with port mapping
podman run -d -p 8080:80 --name web nginx:alpine
# Verify rootless operation
podman info | grep -i rootless
Docker Compatibility
Key Concepts
Podman provides a Docker-compatible CLI, allowing most Docker commands to work unchanged. The podman-docker package installs a docker alias pointing to Podman.
flowchart LR
A[Docker Commands] --> B{podman-docker}
B --> C[Podman Engine]
C --> D[Containers]
E[Docker Compose] --> F[podman-compose]
F --> C
G[Docker API] --> H[podman system service]
H --> C
Common Commands/Patterns
# Install docker compatibility (Fedora/RHEL)
sudo dnf install podman-docker
# Enable Docker-compatible API socket
podman system service --time=0 unix:///tmp/podman.sock &
# Set DOCKER_HOST for compatibility
export DOCKER_HOST=unix:///tmp/podman.sock
# Use docker-compose with Podman socket
DOCKER_HOST=unix:///tmp/podman.sock docker-compose up
# Equivalent commands (all work identically)
docker run nginx # With podman-docker installed
podman run nginx # Native Podman
# Build images (identical syntax)
podman build -t myapp:latest .
docker build -t myapp:latest .
# Pull from Docker Hub
podman pull docker.io/library/nginx:latest
Examples
# Run Podman API as systemd user service
systemctl --user enable --now podman.socket
# Verify socket location
ls -la /run/user/$(id -u)/podman/podman.sock
# Use with Docker clients
export DOCKER_HOST=unix:///run/user/$(id -u)/podman/podman.sock
docker ps
docker images
Pod Management
Key Concepts
Pods are groups of containers sharing kernel namespaces — ipc, net and uts by default, matching Kubernetes; pid and cgroup are opt-in. This mirrors Kubernetes pod semantics, making local development and testing easier.
Three rules follow from the shared network namespace, and between them account for most first-day confusion:
- Ports are published on the pod, not on its containers.
--publishon apodman run --podis an error; put it onpodman pod create. - Containers reach each other over
localhost, so two containers in one pod cannot both bind the same port. - An infra container holds the namespaces open so the pod survives individual containers restarting. It is created by default (
--infra=falseto skip it, in which case nothing is shared unless--sharesays otherwise).
graph TB
subgraph "Pod: webapp"
A[Infra Container<br/>pause] --> B[Network NS]
A --> C[IPC NS]
D[nginx<br/>:80] --> B
D --> C
E[php-fpm<br/>:9000] --> B
E --> C
F[redis<br/>:6379] --> B
F --> C
end
G[Host Port 8080] --> B
Common Commands/Patterns
# Create a new pod
podman pod create --name mypod
# Create pod with port mapping
podman pod create --name webapp -p 8080:80 -p 3306:3306
# Create pod with resource limits shared across all its containers
podman pod create --name limited \
--cpus 2 \
--memory 1g
# Choose which namespaces are shared (default: ipc,net,uts)
podman pod create --name shared --share ipc,net,uts,pid
podman pod create --name shared --share +pid # append to the defaults
podman pod create --name isolated --share "" --infra=true
# Stop the pod when its last container exits (default is 'continue')
podman pod create --name job --exit-policy stop
# Name the infra container explicitly
podman pod create --name webapp --infra-name webapp-infra
# List all pods
podman pod ls
# Inspect pod details
podman pod inspect mypod
# View pod processes and live resource usage
podman pod top mypod
podman pod stats mypod
# Start/stop/restart pods
podman pod start mypod
podman pod stop mypod
podman pod restart mypod
# Remove pod (and all containers)
podman pod rm mypod
# Force remove running pod
podman pod rm -f mypod
# Remove all pods
podman pod rm --all
# View pod logs (all containers, interleaved)
podman pod logs mypod
podman pod logs -f --names mypod
# Which container holds the namespaces
podman pod inspect mypod --format '{{.InfraContainerID}}'
Examples
# Create a complete web application pod
podman pod create --name webapp -p 8080:80
# Add nginx container to pod
podman run -d --pod webapp --name nginx \
-v ./html:/usr/share/nginx/html:ro \
nginx:alpine
# Add PHP-FPM container to same pod
podman run -d --pod webapp --name php \
-v ./php:/var/www/html:ro \
php:8-fpm-alpine
# Add Redis container
podman run -d --pod webapp --name redis \
redis:alpine
# Containers can communicate via localhost
podman exec php ping -c 1 localhost # Reaches nginx and redis
# Ports belong to the pod: this fails
podman run -d --pod webapp -p 9000:9000 nginx:alpine
# Error: invalid config provided: published or exposed ports must be defined
# when the pod is created
# Generate Kubernetes YAML from a pod
podman kube generate webapp > webapp.yaml
# Create pod from Kubernetes YAML
podman kube play webapp.yaml
# Tear down a Kubernetes-defined pod
podman kube down webapp.yaml
podman kube play --down webapp.yaml # equivalent
The older
podman generate kube/podman play kubespellings still work as hidden aliases, butpodman kube generate/podman kube playare the current forms and the ones the documentation uses.
For running pods as systemd services — which is how you would actually deploy one — see the Podman Quadlets and systemd cheatsheet.
Systemd Integration
Key Concepts
Podman has no daemon, so systemd is its supervisor: restart policies, dependency ordering, boot-time start, and logging all come from systemd rather than from Podman itself. The supported way to wire that up is Quadlet — a systemd generator that turns short declarative files (.container, .pod, .network, .volume, .kube, .build, .image) into real service units at boot and on every daemon-reload.
podman generate systemd is deprecated (since 4.7; bug fixes only, no new features). Use it to read what an existing deployment does, not to build a new one.
flowchart LR
A["webapp.container"] --> B["Quadlet generator"]
B -->|"daemon-reload"| C["webapp.service"]
C --> D["systemd"]
D -->|"ExecStart"| E["podman run"]
E --> F["Container"]
F -.->|"readiness + health"| D
D -.->|"journald"| G["journalctl -u webapp"]
Common Commands/Patterns
# Quadlet files live here (rootless)
mkdir -p ~/.config/containers/systemd
# ...and here (rootful)
sudo mkdir -p /etc/containers/systemd
# The generator runs on reload; there is no install step
systemctl --user daemon-reload
systemctl --user start webapp
# Generated units are transient: `systemctl enable` will NOT work.
# Boot-time start comes from the [Install] section in the Quadlet file.
# Inspect what was generated
systemctl --user cat webapp.service
/usr/lib/systemd/system-generators/podman-system-generator --user --dryrun
# Logs
journalctl --user -u webapp.service -f
sudo journalctl -u webapp.service -f # rootful
# Survive logout and start at boot, rootless
loginctl enable-linger $USER
# Podman's own shipped units
systemctl --user enable --now podman-auto-update.timer
systemctl --user enable --now podman.socket # Docker-compatible API
Examples
# A container as a user service
cat > ~/.config/containers/systemd/webapp.container << 'EOF'
[Unit]
Description=Web application
[Container]
Image=docker.io/library/nginx:1.29-alpine
PublishPort=8080:80
Volume=webapp-data.volume:/usr/share/nginx/html:ro,z
HealthCmd=curl -fsS http://localhost/
HealthInterval=30s
HealthOnFailure=kill
AutoUpdate=registry
[Service]
Restart=always
[Install]
WantedBy=default.target
EOF
# The volume it depends on
cat > ~/.config/containers/systemd/webapp-data.volume << 'EOF'
[Volume]
VolumeName=webapp-data
EOF
systemctl --user daemon-reload
systemctl --user start webapp
systemctl --user status webapp
Note what you did not have to write: the podman run line, the ExecStop, the volume ordering, or the Type=notify plumbing. Quadlet generates all of it, and the container is named systemd-webapp unless you set ContainerName=.
The full treatment — multi-container pods, .network and .build units, sdnotify readiness, healthcheck-gated start-up, timers for scheduled jobs, auto-updates with rollback, and migrating off podman generate systemd — is in the Podman Quadlets and systemd cheatsheet.
Health Checks
Key Concepts
A healthcheck is a command Podman runs inside the container on a schedule. With no daemon to poll from, Podman schedules each check as a transient systemd timer created with systemd-run — so on a host without systemd (a build container, a minimal chroot) checks are defined but never fire on their own.
A container has three health states. It begins starting, and the first successful check promotes it to healthy — immediately, even mid start-period. What the start period buys is a grace window in which failures are ignored: they leave the status at starting and do not count towards --health-retries. Only once the window has passed do --health-retries consecutive failures mark it unhealthy.
stateDiagram-v2
[*] --> starting : container started
starting --> healthy : check passes
starting --> starting : failure inside start period
healthy --> unhealthy : retries exhausted
unhealthy --> healthy : check passes again
unhealthy --> [*] : on-failure action
Common Commands/Patterns
# Define a check at run time
podman run -d --name api \
--health-cmd 'curl -fsS http://localhost:9000/healthz' \
--health-interval 30s \
--health-timeout 5s \
--health-retries 3 \
--health-start-period 20s \
--health-on-failure kill \
localhost/api:latest
# Run the check now: exit 0 healthy, 1 unhealthy, 125 error
podman healthcheck run api
# Read the state
podman inspect api --format '{{.State.Health.Status}} {{.State.Health.FailingStreak}}'
podman inspect api --format '{{json .State.Health.Log}}' | jq '.[-1]'
# The ps STATUS column carries it too
podman ps --format '{{.Names}}\t{{.Status}}' # api Up 3 minutes (healthy)
podman ps --filter health=unhealthy
# Watch transitions
podman events --filter event=health_status
# A startup check gates the regular one — better than a huge start period,
# because it can restart a container that is genuinely wedged
podman run -d --name db \
--health-cmd 'pg_isready -U postgres' \
--health-interval 30s \
--health-startup-cmd 'pg_isready -U postgres' \
--health-startup-interval 2s \
--health-startup-retries 60 \
docker.io/library/postgres:17-alpine
| Flag | Default | Meaning |
|---|---|---|
--health-cmd |
from image | Runs inside the container; a bare string becomes CMD-SHELL |
--health-interval |
30s |
disable skips timer creation entirely |
--health-timeout |
30s |
Per-check deadline |
--health-retries |
3 |
Consecutive failures before unhealthy |
--health-start-period |
0s |
Grace window; failures inside it do not count |
--health-on-failure |
none |
none, kill, restart, stop |
Examples
# Under systemd, prefer 'kill' and let the unit's Restart= policy recover.
# 'restart' has Podman restart the container behind systemd's back, which
# gives you two restart loops fighting each other with no shared backoff.
podman run -d --name api --health-on-failure kill ... localhost/api:latest
# Gate systemd readiness on the healthcheck rather than on process start,
# so a dependent unit's After= actually means "after it works"
podman run -d --name db \
--sdnotify=healthy \
--health-cmd 'pg_isready -U postgres' \
--health-interval 10s \
docker.io/library/postgres:17-alpine
# Healthcheck timers are per-container transient units
systemctl --user list-timers --all | grep "$(podman inspect -f '{{.Id}}' api | cut -c1-12)"
In a Quadlet file every flag has a key — HealthCmd=, HealthInterval=, HealthOnFailure=, and so on — plus Notify=healthy for the readiness gating above. See the Podman Quadlets and systemd cheatsheet.
Image and Container Management
Key Concepts
While Podman commands mirror Docker, there are key differences in storage locations, image handling, and default behaviours.
| Aspect | Docker | Podman |
|---|---|---|
| Storage location | /var/lib/docker |
/var/lib/containers (root) or ~/.local/share/containers (rootless) |
| Default registry | docker.io |
Configurable via registries.conf |
| Image format | Docker format | OCI format (compatible) |
| Build tool | BuildKit | Buildah (integrated) |
Common Commands/Patterns
# Pull image (specify full registry path recommended)
podman pull docker.io/library/nginx:latest
podman pull quay.io/podman/hello
# List images
podman images
podman image ls
# Inspect image
podman image inspect nginx:latest
# Build image
podman build -t myapp:latest .
# Build with specific Containerfile
podman build -f Containerfile.prod -t myapp:prod .
# Tag image
podman tag myapp:latest registry.example.com/myapp:v1.0
# Push to registry
podman push registry.example.com/myapp:v1.0
# Save/load images
podman save -o nginx.tar nginx:latest
podman load -i nginx.tar
# Remove images
podman rmi nginx:latest
podman image prune # Remove dangling images
podman image prune -a # Remove all unused images
# Container lifecycle
podman run -d --name web nginx:alpine
podman ps # List running
podman ps -a # List all
podman start web
podman stop web
podman restart web
podman rm web
podman rm -f web # Force remove running
# Execute in running container
podman exec -it web /bin/sh
# View logs
podman logs web
podman logs -f web # Follow
podman logs --tail 100 web # Last 100 lines
# Inspect container
podman inspect web
# View resource usage
podman stats
podman top web
# Prune all unused data
podman system prune -a
Examples
# Configure default registries
# Edit /etc/containers/registries.conf or ~/.config/containers/registries.conf
cat > ~/.config/containers/registries.conf << 'EOF'
unqualified-search-registries = ["docker.io", "quay.io"]
[[registry]]
prefix = "docker.io"
location = "docker.io"
EOF
# Build multi-architecture image
podman build --platform linux/amd64,linux/arm64 \
--manifest myapp:latest .
# Push manifest list
podman manifest push myapp:latest docker://registry.example.com/myapp:latest
# Copy image between storage
podman image scp user1@host1::myimage user2@host2::
# Export/import container filesystem
podman export web > web-filesystem.tar
podman import web-filesystem.tar myimage:imported
# Commit container changes to new image
podman commit web myimage:modified
Volume and Network Management
Key Concepts
Podman supports named volumes, bind mounts, and tmpfs mounts. Rootless networking uses pasta for user-space networking (the default since 5.0, and the only option from 6.0); rootful networking uses netavark (the default since 4.0, and the only option from 6.0).
graph TB
subgraph "Volume Types"
A[Named Volume] --> D["/var/lib/containers/storage/volumes"]
B[Bind Mount] --> E[Host Directory]
C[tmpfs] --> F[Memory]
end
subgraph "Network Modes"
G[bridge] --> H[Default isolated network]
I[host] --> J[Share host network]
K[none] --> L[No networking]
M[container:id] --> N[Share with another container]
O[pasta] --> P[Rootless user-space networking]
end
Common Commands/Patterns
# Volume management
podman volume create mydata
podman volume ls
podman volume inspect mydata
podman volume rm mydata
podman volume prune
# Run with named volume
podman run -d --name db \
-v pgdata:/var/lib/postgresql/data \
postgres:15
# Run with bind mount
podman run -d --name web \
-v /host/path:/container/path:ro \
nginx:alpine
# SELinux volume labels
podman run -v ./data:/data:z nginx # Shared label
podman run -v ./data:/data:Z nginx # Private label
# tmpfs mount
podman run --tmpfs /tmp:size=100m nginx
# Network management
podman network create mynet
podman network ls
podman network inspect mynet
podman network rm mynet
podman network prune
# Connect container to network
podman run -d --name web --network mynet nginx
podman network connect mynet existing-container
podman network disconnect mynet existing-container
# Create network with specific subnet
podman network create --subnet 10.89.0.0/24 mynet
# DNS resolution between containers
# Custom networks have container-name DNS enabled by default (use --disable-dns to turn it off)
podman network create mynet
podman run -d --name db --network mynet postgres
podman run -d --name app --network mynet myapp
# App can reach database at hostname 'db'
# Run with host networking
podman run --network host nginx
# Run without networking
podman run --network none alpine
Examples
# Create application with persistent storage and custom network
podman network create app-network --subnet 172.20.0.0/16
podman volume create app-data
podman volume create db-data
# Database container
podman run -d --name postgres \
--network app-network \
-v db-data:/var/lib/postgresql/data \
-e POSTGRES_PASSWORD=secret \
postgres:15
# Application container
podman run -d --name myapp \
--network app-network \
-v app-data:/app/data \
-p 8080:8080 \
-e DATABASE_URL=postgresql://postgres:secret@postgres:5432/app \
myapp:latest
# Inspect volume usage
podman system df -v
# Backup volume
podman run --rm \
-v db-data:/data:ro \
-v ./backup:/backup \
alpine tar czf /backup/db-backup.tar.gz -C /data .
# Restore volume
podman run --rm \
-v db-data:/data \
-v ./backup:/backup:ro \
alpine tar xzf /backup/db-backup.tar.gz -C /data
Podman Compose
Key Concepts
Podman runs Compose files three ways, in descending order of preference:
podman compose(4.7+): a thin wrapper that finds an external provider (docker-composefirst, thenpodman-compose), starts the Podman socket, and points the provider at it. This is the current recommendation — nothing to configure.docker-composeagainst the Podman socket: the same thing done by hand, withDOCKER_HOSTset yourself.podman-compose: an independent Python reimplementation. Useful wheredocker-composecannot be installed, but its coverage of the Compose specification lags.
For anything long-lived, Quadlets are the better fit on a Podman host. Compose has its own restart policies, healthchecks, and depends_on conditions (including service_healthy, shown below), so the difference is not features — it is who supervises. Compose needs its own process or an outer unit to survive a reboot, and its ordering stops at the project boundary; Quadlet units are ordinary systemd services, so they start at boot, restart under systemd's backoff, log to journald, and can be ordered against anything else on the host. Compose is for development loops; Quadlets are for deployment.
flowchart TB
A[docker-compose.yml] --> B{Compose Tool}
B --> C[podman-compose]
B --> D[docker-compose + Podman socket]
C --> E[Podman]
D --> F[Podman API Socket]
F --> E
E --> G[Containers]
E --> H[Networks]
E --> I[Volumes]
Common Commands/Patterns
# Preferred: let Podman find and wire up a provider
podman compose up -d
podman compose ps
podman compose logs -f api
podman compose down
# Which provider did it pick?
podman compose --help | head -3
# Pin a provider explicitly
export PODMAN_COMPOSE_PROVIDER=/usr/bin/podman-compose
# or set compose_providers in the [engine] table of containers.conf
# Install podman-compose if you want the native implementation
sudo dnf install podman-compose # Fedora/RHEL
uv tool install podman-compose # anywhere with uv
# Basic operations (identical under any of the three routes)
podman compose up -d
podman compose down
podman compose down -v # ...and remove volumes
podman compose ps
podman compose logs -f api
podman compose exec db /bin/sh
podman compose up -d --build
podman compose up -d --scale web=3
podman compose pull
podman compose config
# Doing it by hand with docker-compose against the Podman socket
systemctl --user start podman.socket
export DOCKER_HOST=unix:///run/user/$(id -u)/podman/podman.sock
docker-compose up -d
# Silence the "executing external compose provider" warning
export PODMAN_COMPOSE_WARNING_LOGS=false
Examples
# Example docker-compose.yml
cat > compose.yaml << 'EOF'
services:
web:
image: nginx:alpine
ports:
- "8080:80"
volumes:
- ./html:/usr/share/nginx/html:ro
depends_on:
- api
api:
build: ./api
environment:
- DATABASE_URL=postgresql://postgres:secret@db:5432/app
depends_on:
db:
condition: service_healthy
db:
image: postgres:17-alpine
environment:
- POSTGRES_PASSWORD=secret
- POSTGRES_DB=app
volumes:
- pgdata:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 10s
retries: 5
volumes:
pgdata:
EOF
# Start all services
podman compose up -d
# View status
podman compose ps
# Follow logs for a specific service
podman compose logs -f api
# Execute a command in a service
podman compose exec db psql -U postgres -d app
# Rebuild and restart a single service
podman compose up -d --build api
# Stop and remove everything including volumes
podman compose down -v
# podman-compose-specific: put every service in one pod so they share
# a network namespace and reach each other on localhost
podman-compose --in-pod=true up -d
The version: key at the top of the file is obsolete in the Compose specification and warns on modern providers — drop it. Note also that Compose's depends_on only waits for the container to start, not to be ready; the service_healthy condition (which does wait) needs a healthcheck defined on the dependency.
Quick Reference
| Operation | Command |
|---|---|
| Run container | podman run -d --name web -p 8080:80 nginx |
| List containers | podman ps -a |
| Stop container | podman stop web |
| Remove container | podman rm web |
| View logs | podman logs -f web |
| Execute command | podman exec -it web /bin/sh |
| Build image | podman build -t myapp . |
| List images | podman images |
| Remove image | podman rmi myapp |
| Create pod | podman pod create --name mypod -p 8080:80 |
| Run in pod | podman run -d --pod mypod nginx |
| List pods | podman pod ls |
| Create volume | podman volume create mydata |
| Create network | podman network create mynet |
| Health status | podman inspect web --format '{{.State.Health.Status}}' |
| Run healthcheck now | podman healthcheck run web |
| Run under systemd | Write ~/.config/containers/systemd/web.container, then systemctl --user daemon-reload |
| Preview Quadlet output | /usr/lib/systemd/system-generators/podman-system-generator --user --dryrun |
| Pod to Kubernetes YAML | podman kube generate mypod > pod.yaml |
| Kubernetes YAML to pod | podman kube play pod.yaml |
| System cleanup | podman system prune -a |
| Check disk usage | podman system df |
| Start compose | podman compose up -d |
| Stop compose | podman compose down |
Common Issues and Solutions
| Issue | Cause | Solution |
|---|---|---|
ERRO[0000] cannot find UID/GID |
Missing subuid/subgid entries | Run sudo usermod --add-subuids 100000-165535 --add-subgids 100000-165535 $USER |
| Permission denied on volume | SELinux blocking access | Add :z or :Z suffix to volume mount |
| Cannot bind to port < 1024 | Rootless limitation | Use higher port or set sysctl net.ipv4.ip_unprivileged_port_start=80 |
| Image pull fails | Registry not configured | Add registry to registries.conf or use full path docker.io/library/nginx |
Error: statfs: no such file or directory |
Corrupted storage | Run podman system reset (WARNING: removes all data) |
| Slow rootless networking | Legacy slirp4netns in use | pasta is the default from 5.0 and the only option from 6.0; drop any --network slirp4netns pin |
| Container DNS not working | Using the default network or DNS disabled | Create a custom network (podman network create mynet) — container-name DNS is on by default there; the default podman network has no name resolution |
Quadlet unit "not found" after daemon-reload |
Generation failed on an unsupported or misspelled key | /usr/lib/systemd/system-generators/podman-system-generator --user --dryrun prints the error |
systemctl enable fails on a Quadlet unit |
Generated units are transient | Put [Install] WantedBy=default.target in the Quadlet file instead |
| User services stop at logout | Lingering not enabled | loginctl enable-linger $USER |
| Healthcheck never runs by itself | Checks are driven by transient systemd timers | Needs a running systemd; verify with systemctl --user list-timers --all |
Container in a pod rejects -p |
Ports belong to the pod | Publish on podman pod create instead |
Error: OCI runtime error |
cgroup v2 issues | Ensure cgroup v2 is enabled (mandatory from 6.0); on 5.x, --cgroup-manager=cgroupfs is a stopgap |
| Compose networking issues | Default network mode | Try podman-compose --in-pod=true up for shared networking |
| Volume mount shows empty | Wrong path or SELinux | Verify path exists and check ls -laZ for SELinux context |
Debugging Commands
# Check Podman system info
podman info
# Verbose container logs
podman logs --timestamps web
# Inspect container details
podman inspect web --format '{{.State.Status}}'
# View container events
podman events
# Check storage usage
podman system df -v
# Test network connectivity
podman run --rm alpine ping -c 3 8.8.8.8
# Verify rootless setup
podman unshare cat /proc/self/uid_map
Related Topics
The following topics complement this Podman cheatsheet and would be valuable additions:
- Podman Quadlets and systemd - The production half of this sheet: Quadlet unit types, multi-container pods, networks, timers, sdnotify readiness, and auto-updates
- Docker - Understand the Docker ecosystem that Podman is compatible with, including Dockerfile syntax and Docker Compose patterns
- Kubernetes - Podman's pod concept directly mirrors Kubernetes pods; essential for using
podman kube generateandpodman kube play - systemd - Unit files, targets, timers, drop-ins, and journald in depth
- Container Security - Image scanning, rootless security benefits, SELinux/AppArmor integration, and security best practices
- Buildah - Podman's underlying build tool for advanced image building without a Containerfile
- Skopeo - Companion tool for inspecting and copying container images between registries without pulling them locally