Rootless podman.
run, mount, map ports, keep SELinux on.
The dev team’s stack is moving to containers. Stand up rootless nginx and redis under an unprivileged user, with a bind-mount, a persistent volume, and SELinux staying enforcing.
Phase 3 starts here: the dev team’s stack moves into containers, and cuser1 wants the first two stood up. An nginx serving the same /srv/webcontent that has followed us since the shared-folder ticket on day three, with host port 8080 mapped to the container’s 80, and a redis named dev-cache with its data on a real volume at /srv/cuser1/redis-data. Both run rootless as the unprivileged cuser1, no root containers anywhere, and SELinux stays enforcing.
Two things bite everyone on rootless podman, and this ticket walks straight into both. A bind-mount without the SELinux relabel flag hands the container a directory it is not allowed to read, so nginx 403s. And a rootless user cannot bind a port under 1024, so anyone who reflexively maps port 80 gets permission denied. Map above 1024, remember the :Z, and the rest is easy.
The ticket
id cuser1 sudo dnf install -y podman container-tools podman --version
Concept review. Why podman, and the run flags.
man podman-run
On RHEL, podman is rootless by default, so each user has their own container store, and there is no daemon, each container is a child of podman itself. The CLI matches docker closely. The run flags you use constantly are below.
-d detached, run in the background --name NAME name the container -p host:cont port map, e.g. -p 8080:80 -v host:cont:Z volume mount; :Z relabels for SELinux -e KEY=VAL environment variable -it interactive with a TTY --rm auto-remove on exit
Switch to the container user and pull
enable-linger lets the user’s services keep running when they are logged out, which you will need for boot persistence later. Then work as the user and pull the images by their fully qualified names, since podman does not assume Docker Hub.
sudo loginctl enable-linger cuser1 sudo -iu cuser1 podman info | head -20 # confirms rootless podman pull docker.io/library/nginx:latest podman pull docker.io/library/redis:latest podman images
Run nginx with a bind-mount and port map
The :Z on the volume is the part that matters. It tells podman to relabel the host directory with a private SELinux container label so the container can read it. Leave it off and SELinux denies access, giving you a 403.
podman run -d \
--name dev-web \
-p 8080:80 \
-v /srv/webcontent:/usr/share/nginx/html:ro,Z \
nginx:latest
podman ps
curl http://localhost:8080/Run redis with a persistent volume
The volume keeps the data across container restarts, and :Z again handles SELinux.
mkdir -p /srv/cuser1/redis-data
podman run -d \
--name dev-cache \
-v /srv/cuser1/redis-data:/data:Z \
-p 6379:6379 \
redis:latest redis-server --appendonly yes
podman ps
podman logs dev-cache | tailInspect, exec, logs, lifecycle
podman inspect dev-web | head -50
podman inspect dev-web --format '{{.NetworkSettings.IPAddress}}'
podman stats --no-stream
podman exec -it dev-cache redis-cli ping # PONG
podman exec -it dev-web bash # a shell inside, then exit
podman logs --tail 20 dev-web
podman stop dev-web dev-cache
podman start dev-web dev-cache
podman rm -f dev-web dev-cacheThe two pitfalls: :Z and privileged ports
These are the failures worth reproducing once so you recognize them instantly.
# forgot :Z, SELinux denies the mount, nginx 403s podman run -d --name broken -v /srv/webcontent:/usr/share/nginx/html:ro nginx:latest podman logs broken | tail # permission errors podman rm -f broken # fix by re-running with :Z # a privileged port under 1024, rootless, is denied podman run -d --name p80 -p 80:80 nginx:latest # permission denied # use a port >= 1024, or raise net.ipv4.ip_unprivileged_port_start
Exam questions
Write the command first.
Q1. Pull the latest httpd image from Docker Hub and list local images.
Q2. Run an httpd container named webserver detached, mapping host 8080 to container 80, with a read-only bind-mount from /srv/webcontent.
Q3. Run a rootless redis named cache that survives reboot when the user is not logged in, volume at ~/redis-data, port 6379.
Answers.
# A1
podman pull docker.io/library/httpd:latest; podman images
# A2 (httpd content path differs from nginx)
podman run -d --name webserver -p 8080:80 \
-v /srv/webcontent:/usr/local/apache2/htdocs:ro,Z httpd:latest
# A3: linger + run + generate a systemd user unit
sudo loginctl enable-linger cuser1
podman run -d --name cache -p 6379:6379 -v ~/redis-data:/data:Z redis:latest
podman generate systemd --new --name cache --files
systemctl --user enable --now container-cache.serviceFinal checklist: confirm everything works
If every check passes, the ticket is done.
# 1. cuser1 has linger enabled loginctl show-user cuser1 | grep -i linger # 2. dev-web on 8080, dev-cache on 6379, both rootless podman ps # 3. volume mounts used :Z, SELinux still enforcing getenforce # 4. all three exam commands written from scratch # 5. tracker entry checked off
Reply to cuser1: both containers run rootless under cuser1. nginx on 8080 with the /srv/webcontent bind-mount (read-only, :Z), redis with a persistent volume at /srv/cuser1/redis-data. Next: wire them into systemd for boot persistence.