Expert · Boot, recovery and containers · topic 43 of 57 · 35 min
Persistent storage and containers as systemd services
Explanation
Keeping data outside the container
A container's writable layer is thrown away when the container is removed. A database or upload folder must live on the host. A bind mount maps a host directory into the container: -v /srv/web:/var/www/html. Podman also has named volumes (podman volume create), stored under the user's container storage; bind mounts are easier to back up and inspect.
SELinux and the :Z option
Container processes run with the SELinux type container_t, which may only use files labelled for containers (container_file_t). A normal host directory has another label, so the container gets "Permission denied". Adding :Z to the volume tells Podman to relabel the directory privately for this container. Lower-case :z labels it so several containers can share it.
$ mkdir -p ~/web-content
$ echo "Namaste from the host" > ~/web-content/index.html
$ podman run -d --name web -p 8080:8080 -v ~/web-content:/var/www/html:Z registry.access.redhat.com/ubi9/httpd-24
$ ls -Zd ~/web-content
system_u:object_r:container_file_t:s0:c215,c840 /home/asha/web-content
$ curl localhost:8080
Namaste from the hostRootless containers map user IDs: root inside the container is your own UID outside, and other IDs inside map to a private range from /etc/subuid. If an image runs as a non-root user, files it writes on the host may show a high UID number. podman unshare chown UID:GID dir changes ownership as the container sees it.
Starting a container at boot: Quadlet
A container started by hand does not come back after a reboot. The modern RHEL 9 way (Podman 4.4 and later) is Quadlet: you write a small .container file, and systemd generates a service from it. For a rootless container the file goes in ~/.config/containers/systemd/; for a root container, in /etc/containers/systemd/.
# ~/.config/containers/systemd/web.container
[Unit]
Description=Company website container
[Container]
Image=registry.access.redhat.com/ubi9/httpd-24
ContainerName=web
PublishPort=8080:8080
Volume=%h/web-content:/var/www/html:Z
[Install]
WantedBy=default.target$ systemctl --user daemon-reload # Quadlet generates web.service
$ systemctl --user start web.service
$ systemctl --user status web.service --no-pager | head -3
● web.service - Company website container
Loaded: loaded (/home/asha/.config/containers/systemd/web.container; generated)
Active: active (running) since Mon 2026-10-19 18:02:11 IST; 4s ago
$ loginctl enable-linger asha # start asha's services at boot, without a login
$ loginctl show-user asha | grep Linger
Linger=yesThe older way: podman generate systemd
Earlier RHEL 9 releases, and many exam guides, use podman generate systemd --name web --files --new to write a unit file from an existing container, then copy it to ~/.config/systemd/user/ and run systemctl --user enable --now container-web.service. This still works but is deprecated in favour of Quadlet. Check the current exam objectives and use whatever method they expect; both give a systemd user service plus enable-linger.
Common mistakes
- Using su - asha and then systemctl --user: it fails with "Failed to connect to bus". Log in as the user over SSH (or a real login) so the user's systemd and XDG_RUNTIME_DIR exist.
- Forgetting loginctl enable-linger: the container starts when the user logs in and stops when they log out, not at boot.
- Leaving out :Z and getting Permission denied, or using it on a shared system directory.
- Editing the .container file and forgetting systemctl --user daemon-reload.
Need help?
Stuck on a word or an idea? Search this course, or every course, and jump straight to the explanation or a worked question.
Try:
Watch the lesson
Persistent storage and containers as systemd services · 1/7
Data inside a container is lost when it is removed. Keep it on the host with a volume.
Prefer a video by a teacher? Find free videos on “podman quadlet rootless systemd user service enable-linger rhel 9” on YouTube ↗
In real life: A survey app for an NGO in Odisha
An NGO in Odisha collects field survey data through a small web app. A volunteer admin runs it as a rootless container under a dedicated user, with its SQLite data in /home/survey/data mounted with :Z. A Quadlet file and enable-linger make it start after every power cut without anyone logging in, and the nightly backup simply copies the data directory.
$ cat ~/.config/containers/systemd/survey.container
[Container]
Image=registry.example.org/ngo/survey:1.4
PublishPort=8080:8000
Volume=%h/data:/app/data:Z
[Install]
WantedBy=default.target
$ loginctl show-user survey -p Linger
Linger=yesPractice (0/4 done)
A container gets Permission denied on a mounted host folder, and ausearch shows container_t. Fix?
What does loginctl enable-linger asha do?
Write parse_volume(spec) for a podman -v value "HOST:CONTAINER[:OPTIONS]". Return a dict with host, container and options (a list, empty if none). Also add "selinux": "private" if options include "Z", "shared" if they include "z", else None. Example: "/srv/web:/var/www/html:Z,ro" → {"host": "/srv/web", "container": "/var/www/html", "options": ["Z", "ro"], "selinux": "private"}.
As a user named "webops" (log in over SSH, not with su), run the UBI httpd container with content from ~/web-content mounted with :Z on port 8080, make it a systemd user service with Quadlet, enable lingering, reboot, and confirm it is running before anyone logs in.
Quick quiz
Read more (free)
Related help
Before this, learn
This topic builds on these. If something here feels new, open them first.
Related topics
- Recovering a lost root passwordBoot, recovery and containers · same module
- GRUB2, kernel arguments and the default kernelBoot, recovery and containers · same module
- Running containers: ports, volumes, networks and limitsDevOps: Git, Docker, Kubernetes and CI/CD · Docker
- SELinux: what it is and its modesSELinux and SSH
- Logs: journalctl, rsyslog and a persistent journalProcesses, services and logs
- Processes, signals and systemd servicesDevOps: Git, Docker, Kubernetes and CI/CD · Linux for DevOps
Questions people ask about this
Open a question to see the answer and why. The first ones are this topic's own quiz, so try the quiz above before you peek.
What is the difference between :Z and :z on a volume?
Answer: :Z labels it for this container only; :z lets several containers share it
Private versus shared SELinux labels.
Where does a rootless Quadlet .container file go?
Answer: ~/.config/containers/systemd/
Root-level files go in /etc/containers/systemd/.
Why does systemctl --user fail after su - webops?
Answer: su does not start a full login session with the user's systemd and runtime directory
Log in directly (SSH) as the user.
What happens to files written inside a container (not in a volume) after podman rm?
Answer: They are deleted with the container
The container layer is removed.
Why can't a rootless container publish host port 80 by default?Related
Answer: Ports below 1024 need root privileges
Unprivileged users cannot bind low ports by default.
A service shows "active" and "disabled". What happens after a reboot?Related
Answer: It does not start
Disabled means it is not started at boot.
What happens to files a container wrote into its own file system when you run docker rm on it?Related
Answer: They are deleted with the container
The writable layer belongs to the container. Use a volume for data that must survive.
From Running containers: ports, volumes, networks and limits
What does Persistent=true do in a timer?Related
Answer: Runs a missed job soon after boot if the machine was off at the scheduled time
It catches up on missed runs.
Pass the quiz (60% or more) or mark this topic as done when you are ready.
Your progress is saved on this device only.
Next steps
- This is the last topic of “Boot, recovery and containers”. The module exam comes next.
- Module 8 exam: Boot, recovery and containers · 13 questions · 25 min, without help. 5 topics of this module still to finish first.
- Then the next module, Ansible fundamentals. 57 topics left in the course before the final exam.
← Previous: Containers with Podman: images, run, ports and logs