Run Podman Containers as Systemd Services on RHEL with Quadlet
RHEL 9 lets you declare Podman containers in systemd‑style .container files, called Quadlets. These files are turned into systemd services at daemon‑reload time, giving you a vendor‑supported, declarative alternative to docker‑compose or hand‑written unit files.
10 Jan 2026, 16:00 UTC

Problem: Managing Containers at Boot on RHEL
When a Red Hat Enterprise Linux (RHEL) system needs to run containers as part of its core services, the traditional approach is to hand‑write .service files or rely on docker‑compose‑style tools. Both paths have drawbacks: hand‑written units are brittle, hard to version, and hard to keep in sync with image updates; docker‑compose is not vendor‑supported on RHEL and introduces an extra binary layer.
The gap is filled by Quadlet, a feature shipped with Podman 4.4+ and available in RHEL 9.x. Quadlet allows you to declare a container in a simple .container file that systemd turns into a real service unit automatically. This gives you a declarative, vendor‑supported path that blends container semantics with systemd’s mature dependency and restart logic.
What Quadlet Brings to the Table
- Declarative syntax that mirrors systemd unit files.
- Automatic generation of
.serviceunits onsystemctl daemon‑reload. - Support for rootful (
/etc/containers/systemd/) and rootless (~/.config/containers/systemd/) workloads. - Built‑in support for common container options: image, ports, volumes, environment, restart policy.
- Integration with systemd’s ordering and dependency mechanisms via additional Quadlet types (
.network,.volume, etc.).
Where to Put Your Quadlet Files
For a system‑wide, rootful service you place the file in /etc/containers/systemd/. For a user‑specific, rootless service you put it in ~/.config/containers/systemd/. In both cases the file name ends with .container and is automatically detected by systemd when you reload the daemon.
Example: A Simple “MyApp” Service
# /etc/containers/systemd/myapp.container
[Container]
Image=ghcr.io/example/myapp:latest
PublishPort=8080:80
Volume=/var/lib/myapp/data:/data:Z
Environment=APP_ENV=production
[Service]
Restart=always
RestartSec=5
Explanation of the directives:
Image=pulls the container image from the registry.PublishPort=maps host port 8080 to container port 80.Volume=mounts a host directory into the container. The:Zsuffix tells SELinux to label the directory for shared use.Environment=sets an environment variable inside the container.Restart=alwaysandRestartSec=5are standard systemd restart options, ensuring the container is automatically restarted after a crash.
Deploying the Quadlet
Verify you’re on a supported RHEL release:
podman --version # should report 4.4 or newerPlace the file in the appropriate directory and make sure it’s owned by root (for rootful) or the user (for rootless).
Reload systemd to let it generate the real unit:
sudo systemctl daemon-reloadStart and enable the service:
sudo systemctl enable --now myapp.serviceCheck that the container is running:
systemctl status myapp.service podman ps -a | grep myappVerify restart behavior by killing the container process (e.g.,
sudo kill -9 $(pidof myapp)) and watching systemd bring it back up.
Rootless Quadlet with Lingering
Rootless containers run as a regular user. To have them start at boot, the user must enable lingering:
loginctl enable-linger $(whoami)
After that, the same steps above apply, but the service file lives in ~/.config/containers/systemd/. Systemd will create a user‑scoped unit that survives logout.
Trade‑offs and Limitations
- Not a full Compose replacement: Quadlet files are single‑container definitions. Multi‑service stacks that require build steps, shared networks, or complex dependency graphs still need
podman‑composeor Kubernetes/OpenShift. - Version‑sensitive directives: The set of supported keys in
[Container]and[Service]can change between Podman releases. Always checkman podman‑systemd.unitfor the exact syntax supported by your RHEL minor release. - SELinux labeling: When mounting host volumes, you must use the
:Zor:zsuffix (or set the correct context manually). Without proper labeling, containers may fail to read/write the volume and the error logs can be confusing. - Rootful vs Rootless path: Running containers as root gives you full privileges but also increases the blast radius. Use rootless Quadlets where possible for workloads that don’t require privileged access.
Practical Checklist Before You Roll Out
- Confirm Podman ≥ 4.4:
podman --version - Verify the Quadlet file syntax against the local
man podman‑systemd.unit - Test the service on a staging node:
systemctl daemon-reload,systemctl start,systemctl status - Check SELinux context of any mounted volumes. Use
chcon -t svirt_sandbox_file_t /var/lib/myapp/dataif needed. - If using rootless, run
loginctl enable-linger $(whoami)and ensure the user’s home directory is accessible to systemd. - Monitor logs with
journalctl -u myapp.serviceto confirm container output is captured.
Conclusion
Quadlet gives RHEL 9 users a clean, vendor‑supported path to run Podman containers as systemd services. By declaring containers in a declarative file, you avoid the maintenance burden of hand‑written units, gain the safety of systemd’s restart and ordering logic, and keep your deployment aligned with Red Hat’s support policies. While it doesn’t replace complex multi‑service orchestration, for single‑container services—especially rootless workloads—Quadlet is the pragmatic choice.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.