Running Production Services Rootless with Podman and Quadlet
Stop managing rootless containers with manual scripts. Learn how to use Podman Quadlet to turn declarative container definitions into robust, systemd-managed services.
05 Jul 2026, 23:31 UTC

Running containers as root is a security liability. If an attacker escapes a container running under a root-owned daemon, they potentially gain full control of the host. While rootless containers mitigate this by using user namespaces to map the container's root user to an unprivileged user on the host, managing these containers as persistent services often feels clunky. Manually running podman run commands or writing complex systemd unit files by hand is error-prone and tedious.
The solution is Quadlet. Introduced in the Podman 4.x series, Quadlet is a generator that allows you to define containers, volumes, and networks in a simple declarative format. Systemd then converts these definitions into native service units. This gives you the security of rootless operation with the reliability of systemd's process management.
The Quadlet Workflow
Unlike traditional systemd units where you must write the exact ExecStart command with all Podman flags, Quadlet uses .container files. These files use a simplified INI-style syntax that describes the desired state of the container. When you run systemctl --user daemon-reload, the podman-systemd-generator reads these files and creates the actual systemd service units in the background.
Key Configuration Locations
- Rootless:
~/.config/containers/systemd/ - Rootful:
/etc/containers/systemd/
Worked Example: Deploying a Rootless Web Service
Suppose you need to run a lightweight web application on port 8080 that must restart automatically if it crashes and start on boot without requiring a user to be logged in.
Step 1: Enable Lingering
By default, rootless user services stop when the user logs out. To keep the container running after you disconnect, run this command as the unprivileged user:
loginctl enable-linger $USER
Step 2: Create the Quadlet FileCreate a file at
~/.config/containers/systemd/web-app.container with the following content:
[Container]
Image=docker.io/library/nginx:alpine
PublishPort=8080:80
Volume=/home/user/html:/usr/share/nginx/html:Z
[Service]
Restart=always
[Install]
WantedBy=default.target
Note: The :Z suffix on the volume is critical for SELinux-enabled hosts (like Fedora or RHEL). It tells Podman to relabel the host directory so the container process has permission to read/write it.
Run these commands as the unprivileged user to generate the unit and start the service:
systemctl --user daemon-reload
systemctl --user enable --now web-app.service
Step 4: Verify the ResultCheck the status to ensure systemd is managing the Podman process:
systemctl --user status web-app.service
Trade-offs and Limitations
While Quadlet and rootless Podman significantly harden the host, there are engineering constraints to consider:
| Constraint | Impact | Workaround/Detail |
|---|---|---|
| Privileged Ports | Cannot bind to ports < 1024. | Use ports > 1024 or modify net.ipv4.ip_unprivileged_port_start via sysctl. |
| Networking | Rootless networking uses pasta or slirp4netns. |
Performance is lower than rootful bridge networking; pasta (default in newer versions) improves this. |
| Resource Control | Limited cgroup v1 support. | Ensure the host is using cgroup v2 for full rootless resource limiting. |
Practical Verification
To ensure your deployment is truly production-ready, perform these three checks:
- Persistence Check: Reboot the host. Without logging in via SSH, check from another machine if the service is reachable on port 8080. This verifies
enable-lingeris working. - Recovery Check: Manually kill the container process using
podman kill. Runsystemctl --user status web-appto verify that systemd automatically restarted the container. - Version Check: Run
podman --version. Quadlet syntax evolved between 4.x and 5.x; if certain keys are ignored, consultman podman-systemd.uniton your specific version.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.