How to install and use Podman in Rocky/Alma Linux 9

Install Podman on Rocky Linux 9 or AlmaLinux 9, run OCI containers rootless or with sudo, persist PostgreSQL data with SELinux-friendly volumes, and run containers under systemd using podman generate systemd.

Podman is an OCI-compatible container engine: it runs the same images and follows much of the same CLI shape as Docker, but it does not rely on a single long-running daemon. Each podman command talks to your user (or root) process space, which makes Podman a natural fit for systemd units and for rootless workflows on Red Hat–family systems.

This guide covers Rocky Linux 9 and AlmaLinux 9 (and largely applies to RHEL 9 and similar derivatives). You will install Podman from AppStream, run a quick Rocky Linux image, deploy PostgreSQL with a persistent host directory, then register the container as a systemd service the supported way using podman generate systemd.

Related posts

Prerequisites

  • Rocky Linux 9 or AlmaLinux 9 (fully updated)
  • sudo access (this walkthrough uses rootful Podman for the PostgreSQL + systemd examples; rootless notes are below)
  • Network access to pull images (e.g. docker.io)

What you will do

  1. Update the host and install Podman (and optional Docker-compatible shim).
  2. Verify the installation and pull a small Rocky Linux container.
  3. Run PostgreSQL with a bind-mounted data directory and the :Z SELinux label.
  4. Generate a correct systemd unit with podman generate systemd --new.

Why Podman instead of Docker (short)

TopicPodman (typical)Docker Engine (typical)
DaemonNo central daemon requireddockerd runs continuously
CLIpodman … (often mirroring docker …)docker …
RootlessFirst-class: unprivileged users can run containersPossible but not the default mental model
PodsNative podman podSwarm / orchestrators

Podman works alongside Buildah (image builds) and Skopeo (copy/inspect/sign images). For scripts and tutorials that still say docker, you can install podman-docker so /usr/bin/docker invokes Podman (see below).

1. Update the system

1
sudo dnf update -y

Optional editor or tools:

1
sudo dnf install -y vim

2. Install Podman

Podman ships in the default AppStream repository on Rocky Linux 9 and AlmaLinux 9:

1
sudo dnf install -y podman

Optional — Docker-compatible command name

If you want existing scripts or muscle memory that call docker to use Podman underneath:

1
sudo dnf install -y podman-docker

Dependencies you may see include crun (or runc), containers-common, Skopeo-related packages, and SELinux policy modules for containers—notably different from older articles that mentioned atomic-registries, which is not the focus on EL9.

Verify the installation

1
2
podman version
podman info

Rootless check: as a normal user (without sudo), podman info should show rootless: true. The PostgreSQL and systemd examples later use sudo podman so the unit runs as system services with predictable paths; for development, rootless Podman is often enough.

3. Basic CLI: pull, run, list, remove

Most docker subcommands map directly: run, ps, pull, images, rm, exec, etc.

Run an interactive Rocky Linux shell

If you do not have Docker installed, docker run … will fail; the Podman equivalent is:

1
podman run --rm -it quay.io/rockylinux/rockylinux:9 bash

If your mirror or docs still use docker.io/rockylinux/rockylinux:9 or a minor tag like :9.4, either form is fine as long as the tag exists in the registry.

Inside the container you should see a shell as root (unless you add --user). Exit with exit or Ctrl+D.

List containers and images

1
2
podman ps -a
podman images

Remove a container and an image

1
2
podman rm CONTAINER_ID_OR_NAME
podman rmi IMAGE_REFERENCE

Tip: --rm on podman run deletes the container automatically when it exits—handy for one-off tests.

4. PostgreSQL with a persistent host directory

Container filesystems are ephemeral unless you attach storage. Here the data directory is a directory on the host, bind-mounted into the container.

Create a data directory

Pick a path (examples use a dedicated tree under /srv for system-wide services; you can use $HOME for experiments):

1
sudo mkdir -p /srv/postgres-podman/data

SELinux and the :Z suffix

On Rocky/Alma with SELinux enforcing, bind mounts often need a relabel so the container process can write the directory. Appending :Z to the volume tells Podman to relabel the content for private container use (do not share the same host path with two unrelated containers using :Z on both).

Run PostgreSQL (do not reuse example passwords in production)

Replace YOUR_SECURE_PASSWORD with a strong secret (or load variables from a root-only file):

1
2
3
4
5
6
7
8
sudo podman run -d \
  --name pg-demo \
  -p 5432:5432 \
  -v /srv/postgres-podman/data:/var/lib/postgresql/data:Z \
  -e POSTGRES_PASSWORD=YOUR_SECURE_PASSWORD \
  -e POSTGRES_USER=appuser \
  -e POSTGRES_DB=appdb \
  docker.io/library/postgres:14-alpine

Check status and logs:

1
2
sudo podman ps
sudo podman logs pg-demo

Connect with podman exec

1
sudo podman exec -it pg-demo psql -U appuser -d appdb -c "SELECT version();"

Stop and remove

Graceful stop:

1
sudo podman stop pg-demo

Remove the container (data remains on the host under /srv/postgres-podman/data):

1
sudo podman rm pg-demo

Force-remove a stuck container:

1
sudo podman rm -f pg-demo

5. systemd: use podman generate systemd

The old pattern of hand-writing ExecStart=/usr/bin/podman run … is easy to get wrong: the original article omitted --name, left [Install] incomplete, and embedded passwords in the unit. On current Podman, the supported approach is to generate units.

Clean up any test container

1
sudo podman rm -f pg-demo 2>/dev/null || true

Create a named, long-lived container (no --rm)

Adjust paths and credentials to match your environment:

1
2
3
4
5
6
7
8
sudo podman create \
  --name postgres-podman \
  -p 5432:5432 \
  -v /srv/postgres-podman/data:/var/lib/postgresql/data:Z \
  -e POSTGRES_PASSWORD=YOUR_SECURE_PASSWORD \
  -e POSTGRES_USER=appuser \
  -e POSTGRES_DB=appdb \
  docker.io/library/postgres:14-alpine

Generate a system service (--files writes container-postgres-podman.service in the current directory):

1
2
3
cd /tmp
sudo podman generate systemd --new --files --name postgres-podman
sudo mv container-postgres-podman.service /etc/systemd/system/

What --new does: systemd will podman run a fresh container instance according to the unit instead of only attaching to an already-running ID—this plays nicely with reboots and systemctl restart.

Reload and enable:

1
2
3
sudo systemctl daemon-reload
sudo systemctl enable --now container-postgres-podman.service
sudo systemctl status container-postgres-podman.service

View logs:

1
journalctl -u container-postgres-podman.service -f

Secrets and production hygiene

  • Prefer EnvironmentFile=-/etc/default/postgres-podman (mode 600) instead of inline -e POSTGRES_PASSWORD=… in a world-readable unit fragment, or use your secrets manager.
  • Bind to 127.0.0.1:5432:5432 if only local apps should reach Postgres.
  • Keep postgres:14-alpine pinned by digest in real deployments so upgrades are deliberate.

Rootless Podman (quick notes)

  • Rootless containers use subuids/subgids; storage lives under your user (~/.local/share/containers).
  • Publishing ports below 1024 may require root or sysctl / firewalld tuning.
  • For systemd –user services, you can generate units into ~/.config/systemd/user/ and use loginctl enable-linger if you need services to survive logout—see podman generate systemd --help for --user.

Troubleshooting

IssueWhat to check
Permission denied on volumeSELinux: add :Z or :z appropriately; confirm host directory ownership.
Cannot pull imageProxy/firewall; registry mirrors; podman login for private registries.
Port already in useAnother Postgres or container on 5432; change -p mapping.
systemd fails immediatelyjournalctl -xeu container-postgres-podman.service; ensure --name matches the generated unit; run podman ps -a.
docker: command not foundInstall podman-docker or call podman explicitly.

Conclusion

You can install Podman from AppStream on Rocky Linux 9 and AlmaLinux 9, use the familiar Docker-like workflow for development, persist databases with bind mounts and SELinux labels, and promote a container to a production-style service with podman generate systemd --new instead of fragile hand-written units.

If you are standardizing automation for many hosts, compare this flow with Ansible-driven Docker installs and your organization’s preference for Podman versus Docker CE on RHEL-family servers.

comments powered by Disqus
Citizix Ltd
Built with Hugo
Theme Stack designed by Jimmy