Podman containers.
Podman is RHEL's container engine β drop-in compatible with the docker CLI, but daemonless and rootless by default. Watch a container go from `podman run` to a persistent systemd unit via Quadlet, the modern way to make rootless services survive reboots.
Podman containers animated tutorial. Daemonless and rootless containers, podman pull/run/exec/logs/ps/inspect, volumes and port publishing, the difference from Docker, and persisting containers as systemd units with Quadlet. RHCSA EX200 ready.Daemonless, rootless, and Docker-CLI compatible. Watch a container go from pull β run β exec β persistent systemd unit (Quadlet).
- Β·Vague familiarity with containers / Docker
- Β·The systemd-targets / units lesson
- Β·Comfortable running commands as a normal user
Pull, run, exec, log into containers; mount volumes correctly on SELinux; understand rootless mode; persist with Quadlet.
pace: 10 minutes
Daemonless, rootless β what makes podman different
Docker runs a single privileged daemon (dockerd) that owns every container. Podman doesn't: each `podman run` is a normal process owned by your user, supervised by conmon. Containers run rootless by default β your UID inside the container maps to your UID outside, no root needed. Same CLI verbs as docker (`alias docker=podman` usually just works), but no daemon to crash, no root to compromise.
$podman version$alias docker=podman$podman infoThree things separate Podman from Docker on RHEL: no daemon, rootless by default, and first-class systemd integration via Quadlet. The first two improve security; the third is what makes containers feel native β they show up in `systemctl status`, log to the journal, and survive reboots like any other service.
$podman pull IMG$podman run -d --name N -p H:C IMG$podman run --rm -it IMG sh$podman ps / ps -a$podman images$podman exec -it N bash$podman logs -f N$podman inspect N | jq .$podman stop / start / restart / rm N$podman rmi IMG$-v /host:/container:Z$podman volume create / ls / rm$~/.config/containers/systemd/X.container$systemctl --user daemon-reload$systemctl --user enable --now X.service$loginctl enable-linger USERBiggest architectural difference between Podman and Docker?
You bind-mount /srv/html into a container on RHEL with SELinux enforcing. The container can't read the files. What's the fix?
You want a rootless container to survive reboots. Best modern approach on RHEL 9?
A rootless `podman run -p 80:80 nginx` fails with a permission error. Why, and the cleanest workaround?
These aren't graded β they're just for active recall, which is what actually makes the lesson stick.
systemd targets & units
Quadlet turns containers into systemd .service units. Solidify how units + dependencies + targets work and Quadlet stops feeling magical.
Read this lesson as text6 chapters Β· quiz answers
01Daemonless, rootless β what makes podman different
Docker runs a single privileged daemon (dockerd) that owns every container. Podman doesn't: each
podman runis a normal process owned by your user, supervised by conmon. Containers run rootless by default β your UID inside the container maps to your UID outside, no root needed. Same CLI verbs as docker (alias docker=podmanusually just works), but no daemon to crash, no root to compromise.$ podman versionβ client + engine versions$ alias docker=podmanβ shim for muscle memory$ podman infoβ rootless?, storage driver, ...
02pull and run
podman pull docker.io/library/nginx:1.25fetches the image (registries.conf decides search order β quay.io is often default on RHEL).podman run --name web -d -p 8080:80 nginx:1.25starts a detached container, publishes host port 8080 β container 80.--rmremoves the container when it stops.-itfor interactive shells. The image is layered, immutable; the running container is a writable layer on top.$ podman pull docker.io/library/nginx:1.25β fetch image$ podman run --name web -d -p 8080:80 nginx:1.25β detached + port$ podman run --rm -it alpine shβ interactive, auto-rm$ podman images / podman psβ what's pulled / running
03exec, logs, inspect
Don't bake debug tools into the image β
podman exec -it web bashdrops a shell inside the running container.podman logs -f webtails its stdout/stderr (whatever the app writes there).podman inspect webdumps the full config + state as JSON β pipe throughjqfor one field.podman stop/start/restart/rmcomplete the lifecycle.$ podman exec -it web bashβ shell in a running container$ podman logs -f webβ tail logs$ podman inspect web | jq '.[0].NetworkSettings'β one field of inspect$ podman stop web && podman rm webβ shut down + remove
04Volumes β keeping data when the container goes
Containers are ephemeral; their writable layer dies with them. Persistent state lives in VOLUMES (managed by podman) or BIND MOUNTS (a host path mounted in).
-v /host/path:/container/path:Zbind-mounts; the:Ztells SELinux to relabel for the container (critical on RHEL).-v myvol:/var/lib/datauses a named volume managed by podman. List volumes withpodman volume ls.$ podman run -d -p 8080:80 -v /srv/html:/usr/share/nginx/html:Z nginx:1.25β bind mount with SELinux relabel$ podman volume create webdataβ named volume$ podman run -v webdata:/var/lib/data alpineβ use it$ podman volume ls / inspect / rmβ manage
05Rootless β and where files actually live
Running as a normal user, your container images and volumes live under ~/.local/share/containers/storage, not /var/lib/containers. UID 0 inside maps to YOUR uid outside (subuid/subgid ranges handle additional UIDs). One gotcha: rootless containers can't bind to host ports < 1024 by default β use -p 8080:80 (high host port) and reverse-proxy, or grant the cap via /etc/sysctl.d for that range.
$ podman info | grep -i rootlessβ confirm rootless mode$ cat /etc/subuid /etc/subgidβ your UID range$ loginctl enable-linger $USERβ keep services running after logout$ sysctl net.ipv4.ip_unprivileged_port_start=80β allow rootless :80 (cautiously)
06Quadlet β containers as systemd units
To make a rootless container survive reboots, run it as a systemd USER service. The modern way is QUADLET: drop a .container file in ~/.config/containers/systemd/ describing the image + options, and systemd auto-generates a service unit for you. Then
systemctl --user enable --now web.service. Reload withsystemctl --user daemon-reloadafter edits. Older docs usepodman generate systemdβ Quadlet is the newer, cleaner replacement.$ ~/.config/containers/systemd/web.containerβ Quadlet file (Image=, PublishPort=, etc.)$ systemctl --user daemon-reloadβ regenerate units from Quadlets$ systemctl --user enable --now web.serviceβ start + autostart$ systemctl --user status web.serviceβ verify$ loginctl enable-linger $USERβ user services run without login
Quiz β answers
- Biggest architectural difference between Podman and Docker?
- βDocker uses a privileged daemon (dockerd) that owns all containers. Podman is daemonless β each container is a process owned by your user, supervised by conmon β and runs rootless by default.No daemon means no single point of failure, no privileged service to compromise, and easier rootless operation. Image format and CLI verbs match Docker, so most workflows port over directly.
- You bind-mount /srv/html into a container on RHEL with SELinux enforcing. The container can't read the files. What's the fix?
- βAdd :Z to the volume option β
-v /srv/html:/usr/share/nginx/html:Zβ so podman relabels the directory's SELinux context for container access.Without :Z (or :z for shared), the host directory keeps a context the container's process type can't read. :Z relabels for THIS container; :z is shared (multiple containers). Disabling SELinux is the wrong fix. - You want a rootless container to survive reboots. Best modern approach on RHEL 9?
- βWrite a Quadlet (.container file) under ~/.config/containers/systemd/, run
systemctl --user daemon-reload, thensystemctl --user enable --now NAME.service. Also runloginctl enable-linger $USERso user services run without an active login.Quadlet is the modern Podman + systemd integration; it supersededpodman generate systemd. The linger step is critical β without it, user services stop when you log out. - A rootless
podman run -p 80:80 nginxfails with a permission error. Why, and the cleanest workaround? - βRootless containers can't bind to host ports below 1024 by default; map to a high host port like
-p 8080:80and reverse-proxy, or lowernet.ipv4.ip_unprivileged_port_startvia sysctl.Privileged ports (<1024) are a kernel restriction, not a podman one. Either pick a high host port (cleanest) or globally lower the threshold withsysctl net.ipv4.ip_unprivileged_port_start=80(persist in /etc/sysctl.d) if you really need :80.
keep going β these pair well with what you just learned.
SELinux contexts
Why Apache can't read your file. Watch the policy engine decide β and learn the workflow for fixing denials. RHCSA-ready.
Linux boot process
From power button to login prompt. Every stage in the chain β UEFI, GRUB, kernel, initramfs, systemd β with the rescue moves that save you. RHCSA-ready.
LVM stacking
Layer-by-layer build of an LVM stack: disks β PVs β VG β LVs β filesystem. Then extend live and snapshot. RHCSA-ready.