Skip to content

Podman Reference: Rootless Containers, Pods, Systemd & Docker Migration

Podman is a daemonless container engine compatible with Docker CLI syntax. It’s the default on RHEL/Fedora and is increasingly common in rootless and CI environments where Docker’s daemon model is a security concern.

1. Podman vs Docker

Key differences and compatibility
Feature Podman Docker
Architecture Daemonless — each container is a direct child process Docker daemon (dockerd) as PID 1
Root requirement Rootless by default (user namespaces) Docker daemon runs as root (rootless mode exists but newer)
CLI compatibility Drop-in Docker CLI replacement for most commands Original
Pod support Native (runs K8s-style pod manifests) docker-compose only
Systemd integration First-class (podman generate systemd) Wrapper scripts
Default distros RHEL 8+, Fedora, AlmaLinux, Rocky Debian/Ubuntu (installed manually elsewhere)
Build podman build (uses Buildah) docker build (BuildKit)
# Drop-in alias (makes scripts portable):
alias docker=podman     # in ~/.bashrc or ~/.zshrc

# Check compatibility:
podman --version
podman info             # runtime, storage, network info

2. Core Commands

run, build, images, ps, exec — the daily drivers
# Run containers (same syntax as Docker):
podman run -d --name my-nginx -p 8080:80 nginx:alpine
podman run -it --rm ubuntu:22.04 bash
podman run -v /host/path:/container/path:Z nginx    # :Z = SELinux relabeling (RHEL/Fedora required)
podman run -e ENV_VAR=value --env-file .env myimage

# List containers:
podman ps                   # running
podman ps -a                # all including stopped

# Container management:
podman start my-nginx
podman stop my-nginx
podman restart my-nginx
podman rm my-nginx
podman rm -f my-nginx       # force stop + remove

# Images:
podman images               # list local images
podman pull nginx:1.25
podman rmi nginx:alpine
podman image prune          # remove dangling images
podman system prune -a      # remove all unused containers + images

# Execute commands:
podman exec -it my-nginx bash
podman exec my-nginx nginx -t    # test nginx config

# Logs:
podman logs my-nginx
podman logs -f --tail=50 my-nginx

# Build:
podman build -t my-app:latest .
podman build -t my-app:latest -f Dockerfile.prod .
podman build --no-cache -t my-app:latest .

3. Rootless Containers

Running containers without root — the security advantage
# Rootless by default — containers run as your user:
podman run -d --name web nginx:alpine
# nginx is running as YOUR user, not root
# Container process can't escape to host root even if compromised

# Check user namespace mapping:
podman unshare cat /proc/self/uid_map
# Typically: 0 (container root) → 100000 (host subuid start)

# SELinux labels (RHEL/Fedora):
# :Z — private, relabeled for this container only
# :z — shared, relabeled for multiple containers
podman run -v /mydata:/data:Z myapp   # use :Z for single container

# Port binding < 1024 (rootless limitation):
# Rootless containers cannot bind ports < 1024 by default
# Solutions:
# 1. Map to high port: -p 8080:80 and use a host firewall redirect
# 2. Set sysctl net.ipv4.ip_unprivileged_port_start=80 (systemwide change)
# 3. Use CAP_NET_BIND_SERVICE in slirp4netns (complex)

# Rootless storage location:
$HOME/.local/share/containers/storage   # images and layers (not /var/lib/docker)
Rootless containers are the primary security benefit of Podman. Even if an attacker escapes the container, they land as your unprivileged user, not root. In CI environments this is a significant risk reduction vs Docker's daemon model.

4. Pods (Kubernetes-style)

Run multi-container pods and generate K8s YAML
# Create a pod (shared network namespace across containers):
podman pod create --name my-pod -p 8080:80

# Add containers to the pod:
podman run -d --pod my-pod --name nginx nginx:alpine
podman run -d --pod my-pod --name app my-app:latest
# nginx and app share localhost — app can reach nginx on localhost:80

# Pod management:
podman pod ls
podman pod start my-pod
podman pod stop my-pod
podman pod rm my-pod

# Generate Kubernetes YAML from a running pod (excellent for migration):
podman generate kube my-pod > my-pod.yaml
# Then run on K8s: kubectl apply -f my-pod.yaml

# Play K8s YAML with Podman (run K8s manifests locally — no cluster needed):
podman play kube my-pod.yaml            # runs the K8s pod spec locally
podman play kube my-pod.yaml --down     # stop and remove

# Useful for: local dev, testing K8s manifests, CI without a cluster

5. Systemd Integration

Auto-start containers on boot with systemd units
# Generate systemd unit from a running container:
podman generate systemd --name my-nginx --files --new
# Creates: container-my-nginx.service
# --new: unit will create + remove container on start/stop (vs just start/stop existing)

# Install user-level service (rootless — no root needed):
mkdir -p ~/.config/systemd/user/
cp container-my-nginx.service ~/.config/systemd/user/
systemctl --user daemon-reload
systemctl --user enable container-my-nginx.service
systemctl --user start container-my-nginx.service

# For user services to run even when logged out:
loginctl enable-linger $USER    # keep user session alive (required for rootless)

# Check status:
systemctl --user status container-my-nginx.service
journalctl --user -u container-my-nginx.service -f

# Modern approach (Podman 4.4+) — Quadlets:
# Create /etc/containers/systemd/my-nginx.container (or ~/.config/containers/systemd/ for rootless)
[Unit]
Description=My Nginx Container

[Container]
Image=nginx:alpine
PublishPort=8080:80
Volume=/etc/nginx/nginx.conf:/etc/nginx/nginx.conf:ro,Z
Environment=ENV_VAR=value

[Install]
WantedBy=multi-user.target
# systemctl daemon-reload automatically generates the service unit

6. podman-compose & Registries

Compose compatibility, registry auth, and image management
# podman-compose (install separately — Docker Compose v2 syntax support):
pip3 install podman-compose
podman-compose up -d
podman-compose down

# Or use docker-compose with Podman socket:
export DOCKER_HOST=unix:///run/user/$(id -u)/podman/podman.sock
docker-compose up -d   # uses Podman as backend

# Registry auth:
podman login registry.example.com
podman login docker.io -u myuser -p mypassword
podman login --get-login docker.io   # check stored creds

# Registry configuration (/etc/containers/registries.conf or ~/.config/containers/registries.conf):
# Add shortname prefixes (instead of always typing full registry path):
unqualified-search-registries = ["docker.io", "quay.io", "registry.fedoraproject.org"]
# Now: podman pull nginx → searches docker.io/library/nginx first

# Tag and push:
podman tag my-app:latest registry.example.com/myorg/my-app:1.0.0
podman push registry.example.com/myorg/my-app:1.0.0

# Save/load images (transport):
podman save my-app:latest -o my-app.tar
podman load -i my-app.tar

# Copy images between transports (containers-storage, docker-archive, oci, docker:// registry):
skopeo copy docker://nginx:alpine containers-storage:nginx:alpine  # pull without running
skopeo inspect docker://nginx:latest                                # inspect without pulling

Track Podman, Docker, and container runtime releases.
ReleaseRun monitors Kubernetes, Docker, and 13+ DevOps technologies.

Related: Docker Reference | Dockerfile Best Practices | Docker Compose Reference | Docker EOL Tracker

🔍 Free tool: Docker Compose Security Checker — Podman is Compose-compatible — check your compose.yml for 9 security misconfigurations with an A–F grade.

Founded

2023 in London, UK

Contact

hello@releaserun.com