Podman¶
What You'll Learn¶
- How Podman's architecture differs from Docker Engine
- How rootless containers work, and the UID-mapping and port issues you'll meet
- Pods, and generating or playing Kubernetes YAML
- Running containers as systemd services with Quadlet
- When to choose Podman, Docker, or both
Architecture: No Central Daemon¶
flowchart LR
subgraph Docker
DC[docker CLI] --> DD["dockerd (root daemon)"] --> DCT[containerd] --> DR[runc]
end
subgraph Podman
PC[podman CLI] --> CM["conmon (per container)"] --> PR["crun / runc"]
end
podman runforks the container directly, with a small conmon process per container to hold its stdio and exit code — similar to containerd's shim.- There's no long-running root daemon to protect or crash. Each user's containers are that user's processes.
- Podman uses the same OCI images and registries, so images built by Docker run under Podman and vice versa.
Install and First Run¶
sudo apt-get install -y podman # Ubuntu; `dnf install podman` on Fedora/RHEL
podman version
podman info --format '{{.Host.Security.Rootless}}' # true for a normal user
podman run -d --name web -p 8080:80 docker.io/library/nginx:stable
podman ps
curl -I http://localhost:8080
podman logs web
podman rm -f web
The CLI is intentionally Docker-compatible. Many teams simply alias docker=podman.
Use fully qualified image names
Docker assumes docker.io for short names like nginx. Podman consults unqualified-search-registries in /etc/containers/registries.conf and may prompt you to choose. Writing docker.io/library/nginx:stable avoids ambiguity — and avoids pulling a lookalike image from an unexpected registry.
Rootless Containers¶
Rootless is Podman's default for normal users. "root" inside the container maps to your user outside, using a range of subordinate IDs:
grep "$USER" /etc/subuid /etc/subgid
# alice:100000:65536
podman run --rm docker.io/library/alpine id # uid=0(root) inside
podman run -d --name sleeper docker.io/library/alpine sleep 600
ps -o user,pid,cmd -C sleep # alice ... sleep 600 — your user on the host
podman unshare cat /proc/self/uid_map
# 0 1000 1
# 1 100000 65536
If a container escapes, it lands as an unprivileged user, not root.
Rootless trade-offs¶
| Issue | Why | Fix |
|---|---|---|
| Can't publish ports below 1024 | Unprivileged users can't bind low ports | Publish a high port behind a proxy, or lower net.ipv4.ip_unprivileged_port_start |
| Permission denied on bind mounts | Container UIDs map to subordinate host UIDs | Use :U to chown the mount to the container user, or podman unshare chown |
| Slower or different networking | User-mode networking (pasta) instead of kernel bridges |
Usually fine; use rootful Podman for heavy network workloads |
| Containers stop when you log out | User services end with the session | loginctl enable-linger <user> |
mkdir -p data
podman run --rm -v ./data:/data:U docker.io/library/alpine touch /data/ok
ls -ln data # owned by a mapped subordinate UID
Pods¶
A pod groups containers that share a network namespace (and optionally more), exactly like a Kubernetes Pod:
podman pod create --name shop -p 8080:80
podman run -d --pod shop --name app docker.io/library/nginx:stable
podman run -d --pod shop --name sidecar docker.io/library/alpine \
sh -c 'while true; do wget -qO- http://127.0.0.1:80 >/dev/null && echo ok; sleep 10; done'
podman pod ps
podman logs sidecar # the sidecar reaches nginx on localhost — same network namespace
Kubernetes YAML Both Ways¶
podman kube generate shop > shop-pod.yaml # export the running pod as Kubernetes YAML
podman pod rm -f shop
podman kube play shop-pod.yaml # recreate it from YAML
podman kube down shop-pod.yaml
Handy for prototyping manifests locally — review the generated YAML before applying it to a real cluster.
Running Containers as systemd Services: Quadlet¶
Quadlet turns a small unit file into a systemd service that runs a container, with restart handling and boot integration:
[Unit]
Description=Nginx web container
[Container]
Image=docker.io/library/nginx:stable
PublishPort=8080:80
Volume=%h/site:/usr/share/nginx/html:ro,Z
AutoUpdate=registry
[Service]
Restart=always
[Install]
WantedBy=default.target
systemctl --user daemon-reload
systemctl --user start web.service
systemctl --user status web.service
journalctl --user -u web.service -f
loginctl enable-linger "$USER" # keep it running without a login session
System-wide units go in /etc/containers/systemd/ and are managed with sudo systemctl. AutoUpdate=registry plus podman auto-update pulls newer images for the tag and restarts the service.
Compose With Podman¶
systemctl --user enable --now podman.socket
export DOCKER_HOST=unix://$XDG_RUNTIME_DIR/podman/podman.sock
docker compose up -d # Docker's Compose, talking to Podman's Docker-compatible API
# or: podman compose up -d # wraps an installed compose provider
Podman vs. Docker¶
| Docker Engine | Podman | |
|---|---|---|
| Architecture | Root daemon (dockerd) + containerd |
Daemonless; conmon per container |
| Rootless | Optional mode | Default for users |
| Pods | No | Yes |
| systemd integration | Restart policies inside Docker | Quadlet units managed by systemd |
| Kubernetes YAML | No | kube generate / kube play |
| Compose | Native docker compose |
Via the Docker-compatible socket or podman compose |
| Desktop app | Docker Desktop | Podman Desktop |
| Typical home | Developer laptops, CI, many servers | RHEL/Fedora servers, security-focused environments |
Choose based on platform support, existing tooling, security requirements, and what your team already operates — both run the same images.
Common Mistakes¶
- Short image names resolving to an unexpected registry.
- Expecting rootless containers to bind port 80.
- Chasing bind-mount permission errors without understanding UID mapping.
- Rootless services that die at logout because lingering isn't enabled.
- Assuming a Docker Compose file with Docker-specific features (like
host.docker.internalon Linux) behaves identically.
Check Your Understanding¶
- What replaces
dockerdin Podman's architecture? - When root inside a rootless container writes a file to a bind mount, who owns it on the host?
- What does a pod share between its containers?
- How does Quadlet make a container start at boot?
Next¶
Continue to Kubernetes Container Runtimes.