Running Rootless Podman Services with Quadlet: A Practical Guide
Learn how to define rootless Podman containers as user‑level systemd services with Quadlet, enable lingering for boot persistence, handle low‑port limits, and set up auto‑update with rollback.
23 Jun 2026, 00:06 UTC

Problem: running a container as a non‑root user that survives logout
You want a web application to start automatically when the system boots, run without root privileges, and keep running after you log out of your session. Writing and maintaining hand‑crafted systemd unit files for each container is tedious and error‑prone.
Thesis: Quadlet turns declarative container files into user‑level systemd services
Quadlet is a systemd generator bundled with Podman (available since Podman 4.4). By placing a simple .container file in ~/.config/containers/systemd and reloading the user systemd daemon, Quadlet creates a regular .service unit that provides journal logging, automatic restarts, and dependency ordering without you ever touching generated unit files.
Creating a Quadlet container file
For a minimal example, let’s run an NGINX container that listens on port 8080 inside the container and publishes it to the host on the same port.
- Create the directory if it does not exist:
mkdir -p ~/.config/containers/systemd
- Save the following as
webapp.containerin that directory:
[Container]
Image=docker.io/library/nginx:alpine
PublishPort=8080:80
Volume=nginx-cache:/var/cache/nginx:Z
[Service]
Restart=on-failure
[Install]
WantedBy=default.target
The [Container] section mirrors the options you would pass to podman run. The [Service] section adds a restart policy, and [Install] makes the unit start with the user’s default target.
Enabling lingering and starting the service
- Tell systemd to reload its user configuration:
systemctl --user daemon-reload
- Start the newly generated service:
systemctl --user start webapp.service
- Verify that the container is running:
podman ps --filter name=webapp
You should see a line with the NGINX image and the published ports.
- Check the logs via journalctl:
journalctl --user -u webapp.service -f
To make the service survive logout and start at boot, enable lingering for your user:
loginctl enable-liner $USER
You can confirm it with:
loginctl show-user $USER -p Linger
The output should show Linger=yes.
Dealing with low‑port binding and networking trade‑offs
Rootless Podman cannot bind ports below 1024 by default because the unprivileged user namespace lacks the necessary capability. If you try PublishPort=80:80 you will see an error similar to:
Error: unable to configure port forwarding: permission deniedThree practical approaches are:
- Publish a high port on the host, e.g.
PublishPort=8080:80(as in the example). - Raise the kernel’s
net.ipv4.ip_unprivileged_port_startsysctl to a lower value (requires host admin rights and weakens the traditional low‑port guard). - Place a reverse proxy (such as Caddy or nginx) that runs on the host and binds to 80/443, then forward to the container’s high port.
Rootless networking uses the pasta stack by default in Podman 5.0 (earlier versions used slirp4netns). pasta provides fast port forwarding but still cannot bind low ports; the slower slirp4netns backend can, which is why some tutorials mention switching networks for low‑port access.
Auto‑update and rollback (actionable closing)
Quadlet supports an AutoUpdate= key. Adding AutoUpdate=registry to the [Container] section tells Podman to check for newer image versions. Pair this with a systemd timer that runs podman auto-update (typically installed as /usr/lib/systemd/system/podman-auto-update.timer) to create a self‑updating service. If an update causes issues, you can roll back with:
podman auto-update rollback webapp
This gives you a rootless container that behaves like a traditional systemd service, complete with automatic upgrades and a safe rollback path.
Limitations and how to verify them
Because rootless Podman stores images and containers in a per‑user location (usually ~/.local/share/containers/storage), images pulled with sudo podman pull are invisible to your rootless user. To check, run:
podman info --format '{{.Store.StoreRoot}}'
Confirm the path points under your home directory. If you need an image pulled as root, either pull it again as your user or adjust the storage configuration (beyond the scope of this guide).
Finally, always validate Quadlet syntax against the man page for your Podman version:
man podman-systemd.unit
This ensures the keys you use (e.g., PublishPort, AutoUpdate) are supported and avoids surprises after a daemon reload.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.