Chapter Objective: Pull and manage container images, run and manage containers with Podman, persist data with volumes, and run containers as managed systemd services using Quadlet.

Key commands: podman pull, podman run, podman ps, podman volume

What Is Podman?

Podman is RHEL's container engine — it builds, pulls, runs, and manages OCI-compliant containers, using the same image format and much of the same command syntax as Docker. Unlike Docker's client-server model with a constantly running background daemon, Podman is daemonless: each command runs as its own process, and containers are managed directly by the kernel.

🔵 Why It Matters No central daemon means no single point of failure controlling every container, and it's a major part of what makes running containers as an unprivileged user practical and secure by default on RHEL.

Working with Images

# Search a configured registry for an image
podman search nginx

# Pull an image without running it yet
podman pull registry.access.redhat.com/ubi9/ubi

# List locally stored images
podman images

# Remove an image
podman rmi ubi9/ubi

# Inspect image metadata
podman inspect ubi9/ubi
✅ Tip — Prefer Red Hat's UBI Images Red Hat's Universal Base Images (UBI), from registry.access.redhat.com, are freely redistributable and built on the same foundation as RHEL itself — a solid default choice for images you build on top of in a Red Hat environment.

Running and Managing Containers

# Run a container interactively, removing it automatically when it exits
podman run -it --rm ubi9/ubi /bin/bash

# Run a container in the background (detached), mapping a port
podman run -d --name webserver -p 8080:80 nginx

# List running containers
podman ps

# List ALL containers, including stopped ones
podman ps -a

# View a container's logs
podman logs webserver

# Stop, start, or remove a container
podman stop webserver
podman start webserver
podman rm webserver

# Get an interactive shell inside a RUNNING container
podman exec -it webserver /bin/bash
⚠️ Warning — Stopping ≠ Removing A stopped container still exists and still occupies its allocated resources on disk until explicitly removed with podman rm (or run with --rm to begin with). podman ps -a often reveals a pile of stopped containers a system administrator forgot were still there.

Rootless Containers

A regular, unprivileged user can run Podman containers without any special setup — this is Podman's default and recommended mode on RHEL, called rootless. Inside the container, a process may appear to run as root, but it's actually mapped to an unprivileged UID on the host through user namespaces.

# A regular user (no sudo needed) can run containers directly
podman run -d --name myapp -p 8080:80 nginx
🔵 Why It Matters If a rootless container is somehow compromised, the attacker is still constrained to the privileges of the unprivileged host user that launched it — a meaningfully smaller blast radius than a compromised container running as real root.
✅ Tip — Rootless by Default, Root Only When Truly Needed Reach for sudo podman (rootful) only when a specific container genuinely requires host-level privileges — binding to a port below 1024 without special configuration, for example. Otherwise, rootless is the safer and generally recommended default.

Persisting Data with Volumes

A container's own filesystem is ephemeral — removing the container discards any changes made inside it. Volumes provide storage that persists independently of any one container's lifecycle.

# Create a named volume
podman volume create webdata

# Mount it into a container
podman run -d --name webserver -p 8080:80 -v webdata:/usr/share/nginx/html nginx

# Mount a host directory directly instead of a named volume
podman run -d --name webserver -p 8080:80 -v /srv/web:/usr/share/nginx/html:Z nginx

# List volumes
podman volume ls

# Remove a volume no longer needed
podman volume rm webdata
🔵 Note — The :Z Suffix Matters on RHEL When bind-mounting a host directory, appending :Z tells Podman to apply an appropriate private SELinux label to that content for the container — without it, SELinux (Chapter 6) may deny the container access to the mounted directory entirely.

Container Networking

# Map a host port to a container port (host:container)
podman run -d -p 8080:80 nginx

# List configured networks
podman network ls

# Create a custom network so containers can reach each other by name
podman network create appnet
podman run -d --name db --network appnet postgres
podman run -d --name api --network appnet myapi
✅ Tip — Shared Networks Enable Name-Based Discovery Containers attached to the same custom network can reach each other by container name rather than needing to track IP addresses — useful for a small multi-container application without reaching for a full orchestration platform.

Running Containers as systemd Services with Quadlet

For a container that should behave like any other managed service — starting at boot, restarting on failure, logging through the journal — RHEL's current recommended approach is Quadlet: a declarative .container file that systemd's generator turns into a full service unit automatically.

# Rootless quadlet files live here (create the directory if needed)
mkdir -p ~/.config/containers/systemd/

cat > ~/.config/containers/systemd/webserver.container << 'EOF'
[Unit]
Description=Nginx Web Server

[Container]
Image=docker.io/library/nginx:latest
PublishPort=8080:80
Volume=webdata.volume:/usr/share/nginx/html

[Service]
Restart=always

[Install]
WantedBy=default.target
EOF

# Reload systemd so it picks up the new quadlet definition
systemctl --user daemon-reload

# Manage it exactly like any other systemd service (note the .service suffix)
systemctl --user enable --now webserver.service
systemctl --user status webserver.service
journalctl --user -u webserver.service
🔵 Note — Quadlet Replaced podman generate systemd An older approach, podman generate systemd, produced a static unit file from an already-running container — convenient once, but prone to drifting out of sync with reality. Quadlet regenerates the service definition fresh from the source file every time, which is why it's now the recommended path.
✅ Tip — Enable Lingering for Rootless Boot-Time Services A rootless Quadlet service normally only starts when that user logs in. Run loginctl enable-linger username so it can start at boot and keep running without an active login session.

Key Terms for Chapter 17

Podman
RHEL's daemonless container engine, compatible with the OCI image format
rootless container
A container run by an unprivileged user, with in-container "root" mapped to a non-privileged host UID
UBI (Universal Base Image)
Red Hat's freely redistributable base container images
volume
Persistent storage that survives independently of any single container's lifecycle
:Z suffix
Bind-mount option applying a private SELinux label so a container can access host directory content
Quadlet
A declarative .container/.volume/.network file format that systemd's generator converts into managed service units

Review Questions

  1. What is the key architectural difference between Podman and a daemon-based container engine?
  2. Why does running containers rootless improve security compared to running them as root?
  3. What's the difference between stopping a container with podman stop and removing it with podman rm?
  4. What does appending :Z to a bind-mounted volume accomplish on a RHEL system?
  5. Why might two containers attached to the same custom Podman network be able to reach each other by name?
  6. What advantage does Quadlet have over the older podman generate systemd approach?
  7. Why is loginctl enable-linger relevant to a rootless Quadlet service that should start at boot?