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)
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