KNZLABS :: RHCSA Foundations

Module 9: Manage Containers

RHCSA domain covered: Manage Containers (10).

Lab in this kit: Lab 09 (rootless podman + Quadlets).

Containers are the newest addition to the RHCSA, and the exam's angle is specific: run containers *rootless* (as an unprivileged user), and make them start automatically and survive reboot via systemd. The connecting theme with the rest of the exam holds — it's not enough to podman run something; it has to come back after a reboot, which on RHEL 9 means wiring it into systemd with a Quadlet. Two recurring traps live here: SELinux labels on bind mounts (:Z) and the lingering setting that keeps a user's services alive after logout.

Why podman, not docker

What it is: RHEL 9 ships podman as its container engine. Unlike Docker, it has no central daemon, runs containers as your own user (rootless), and integrates natively with systemd. skopeo (inspect/copy images) and buildah (build images) complete the toolset.

Why it matters: Docker isn't in the base distribution, so every container task uses podman — and podman's daemonless, rootless design is exactly what the exam tests.

Image lifecycle

What it is: the commands to find, pull, and inspect container images before you run them.

Why it matters: you need an image locally before you can run a container, and the exam's registry is often a specific Red Hat one.


podman search registry.access.redhat.com/httpd          # find images
podman pull registry.access.redhat.com/ubi9/httpd-24     # download (as your user)
podman images                                            # list local images
podman inspect ubi9/httpd-24                             # detailed metadata

Rootless podman pull stores images under your home directory (~/.local/share/containers), not system-wide — so each user has their own image store. The ubi9 images (Universal Base Image) are Red Hat's freely redistributable base images and the usual source for exam tasks. podman commands deliberately mirror Docker's syntax, so prior Docker experience transfers directly.

Running a container

What it is: podman run creates and starts a container from an image, with flags controlling networking, storage, and lifecycle.

Why it matters: the flags here — port mapping, bind mounts, and especially the SELinux relabel — are precisely what tasks specify and where points are won or lost.


# Detached, named, with a port map and an SELinux-relabeled bind mount
podman run -d --name lab-web \
    -p 18080:8080 \
    -v /home/alice/web-content:/var/www/html:Z \
    registry.access.redhat.com/ubi9/httpd-24

podman ps                                 # running containers
podman logs lab-web                       # its output
podman exec -it lab-web bash              # get a shell inside

-d runs detached (in the background), --name gives it a stable name to reference, -p 18080:8080 maps host port 18080 to the container's 8080. The critical piece is the :Z suffix on the bind mount. When you mount a host directory into a container, SELinux won't let the container process touch it unless the files carry a container-accessible label. :Z (uppercase) tells podman to relabel that directory with a *private* label for this container; :z (lowercase) applies a *shared* label so multiple containers can use the same data. Omit it and, under enforcing SELinux, the container silently can't read or write the mount — a classic head-scratcher.

Rootless requirements: lingering

What it is: by default, a user's systemd session (and any services it runs) stops when that user logs out. loginctl enable-linger keeps it running.

Why it matters: a rootless container managed by user systemd will die at logout *unless* lingering is enabled — and the exam grades after a reboot, when nobody is logged in.


loginctl enable-linger alice                    # as root, once per user
loginctl show-user alice | grep Linger          # verify (Linger=yes)

This is subtle and easy to miss. Rootless containers run under the user's own systemd instance, which normally only exists while the user is logged in. enable-linger makes that user-systemd start at boot and persist regardless of login state — which is the only way a rootless container service comes back after a reboot. Forget it and your container works perfectly until you log out or reboot, then vanishes.

Quadlets — the modern systemd integration

What it is: a Quadlet is a declarative .container file that a systemd generator automatically turns into a full .service unit. You describe the container; systemd manages it.

Why it matters: on RHEL 9 this is the supported way to run a container as a managed service, and it's the assumed format on v10. You write a short .container file instead of a hand-crafted service unit.

Place the file under ~/.config/containers/systemd/ (rootless) or /etc/containers/systemd/ (system). Example ~/.config/containers/systemd/lab-web.container:


[Unit]
Description=Lab Web Container
After=network-online.target

[Container]
Image=registry.access.redhat.com/ubi9/httpd-24
ContainerName=lab-web
PublishPort=18080:8080
Volume=/home/alice/web-content:/var/www/html:Z

[Service]
Restart=always

[Install]
WantedBy=default.target

Then:


systemctl --user daemon-reload                  # generate the .service from the .container
systemctl --user enable --now lab-web.service   # enable + start it
systemctl --user status lab-web.service

The elegance is that you never write the .service file — the generator reads your .container and produces lab-web.service automatically at daemon-reload. The [Container] section maps directly to podman run flags (PublishPort = -p, Volume = -v, note the :Z again). Two things trip people up: you must daemon-reload for systemd to pick up the generated unit, and rootless Quadlets use systemctl --user (not plain systemctl) — mixing the two is a common confusion. Pair this with enable-linger and you have a rootless container that genuinely survives reboot.

Common pitfalls

  • Forgetting loginctl enable-linger → the rootless container service dies at logout and never comes back after reboot.
  • Forgetting :Z on a bind mount → SELinux denies the container access to the directory (silent under permissive, blocked under enforcing).
  • Editing or adding a Quadlet without systemctl --user daemon-reload → systemd never generates the .service and there's nothing to start.
  • Using plain systemctl instead of systemctl --user for a rootless container → you're talking to the wrong systemd instance.
  • Trying to bind a rootless container to a port below 1024 → not allowed by default. Use a high port (like 18080) or adjust net.ipv4.ip_unprivileged_port_start.

Exam tips

  • The rootless-persistence recipe is a three-part combo: write the Quadlet, enable-linger for the user, and systemctl --user enable --now. Miss any one and it won't survive reboot.
  • After setting it up, actually reboot and confirm the container came back with systemctl --user status (or podman ps) — the exam checks exactly this.
  • When a container can't read a mounted directory, suspect the missing :Z before anything else.
Next: Appendix A — RHCSA v10 deltas.