Podman runs OCI containers without a daemon and without root by default. It’s in the standard repositories of all major distributions. Here’s how to install it and get productive. For the background, see Podman vs Docker.
Install
Ubuntu 22.04+ / Debian 12+:
sudo apt update
sudo apt install -y podman
AlmaLinux / Rocky / RHEL / Fedora:
sudo dnf install -y podman
Check:
podman --version
podman info | head -30
Distribution packages can lag behind upstream, particularly on older LTS releases; that’s usually fine for servers.
Run a rootless container
As a normal user — no sudo:
podman run -d --name web -p 8080:80 docker.io/library/nginx:stable
podman ps
curl http://localhost:8080
Images and containers are stored in your home directory (~/.local/share/containers). Each user has their own separate set.
Rootless operation relies on subordinate UID/GID ranges. Check your user has them:
grep $USER /etc/subuid /etc/subgid
If missing (users created before the package was installed, or by some tools):
sudo usermod --add-subuids 100000-165535 --add-subgids 100000-165535 $USER
podman system migrate
Short image names and registries
Docker defaults to Docker Hub; Podman asks which registry to use for short names (or uses registries.conf). Use fully qualified names to avoid ambiguity:
podman pull docker.io/library/postgres:17
podman pull ghcr.io/org/app:1.0
podman pull quay.io/prometheus/prometheus
Configure search registries in /etc/containers/registries.conf (unqualified-search-registries = ["docker.io"]) if you prefer short names.
Everyday commands
The CLI mirrors Docker’s:
podman images
podman logs -f web
podman exec -it web sh
podman stop web && podman rm web
podman volume ls
podman system prune -a
Many people add alias docker=podman; the podman-docker package installs a docker wrapper system-wide.
Ports below 1024 (rootless)
Rootless containers can’t bind ports under 1024 by default. Options:
- Publish on a high port and put Nginx/Caddy on the host in front.
- Lower the limit:
echo 'net.ipv4.ip_unprivileged_port_start=80' | sudo tee /etc/sysctl.d/99-podman.conf && sudo sysctl --system. - Run that container as root with
sudo podman.
Compose
sudo apt install -y podman-compose # or: pipx install podman-compose
podman compose up -d # uses an available Compose provider
Alternatively enable the Docker-compatible socket and use Docker Compose:
systemctl --user enable --now podman.socket
export DOCKER_HOST=unix://$XDG_RUNTIME_DIR/podman/podman.sock
docker compose up -d
Start containers at boot with Quadlet
Podman 4.4+ supports Quadlet: declarative .container files that systemd turns into services.
mkdir -p ~/.config/containers/systemd
cat > ~/.config/containers/systemd/web.container <<'EOF'
[Container]
Image=docker.io/library/nginx:stable
PublishPort=8080:80
[Service]
Restart=always
[Install]
WantedBy=default.target
EOF
systemctl --user daemon-reload
systemctl --user start web.service
loginctl enable-linger $USER # run user services without an active login
Logs: journalctl --user -u web. For system-wide services, place files in /etc/containers/systemd/ and use sudo systemctl.
Older Podman versions used podman generate systemd, now deprecated in favour of Quadlet.
Automatic image updates
Label containers and enable the timer:
[Container]
Image=docker.io/library/nginx:stable
AutoUpdate=registry
systemctl --user enable --now podman-auto-update.timer
SELinux (RHEL family)
On SELinux systems, add :Z (private) or :z (shared) to bind mounts so Podman relabels them:
podman run -v /srv/data:/data:Z …
Troubleshooting
ERRO[0000] cannot find UID/GID for user— add subuid/subgid ranges (above).short-name resolution enforced but cannot prompt without a TTY— use fully qualified image names.- Containers stop when you log out — enable lingering.
permission deniedon volumes — rootless UID mapping; use--userns=keep-idto map your UID into the container, orpodman unshare chownto fix ownership.
Podman works on any KVM VPS — and on RHEL-family systems, it’s the default choice.