Podman & Quadlets
Notes from setting up rootless Podman containers managed via Quadlets on my home server.
What Are Quadlets?
Quadlets let you define containers as systemd units using simple INI-style files instead of writing raw service files. Podman’s generator converts them automatically.
- Rootless:
~/.config/containers/systemd/ - System-level:
/etc/containers/systemd/ - Requires Podman 4.4+
Setting Up a Rootless Podman User
The idea is to run a dedicated user whose only job is running containers. No sudo, no root access - if something in a container goes sideways it stays contained.
Create the User
useradd -m -s /bin/bash podman
passwd podman
Set Up Subuid/Subgid Mappings
Rootless containers need a range of subordinate UIDs and GIDs to map container users to host users without root privileges.
# Check if they were created automatically
cat /etc/subuid
cat /etc/subgid
# If not, add them manually
usermod --add-subuids 100000-165535 --add-subgids 100000-165535 podman
Each user needs a unique, non-overlapping range. 65536 IDs is the standard allocation.
Enable Lingering
Without lingering, systemd kills the user’s services when they log out. This keeps them running.
loginctl enable-linger podman
Always Log In Properly
This trips people up. If you su podman from root, XDG_RUNTIME_DIR doesn’t get set and things break in subtle ways - sockets don’t appear where expected, %t expands to nothing in Quadlet files, systemd user services don’t behave.
Always use:
machinectl shell podman@
# or
su -l podman
Verify Rootless Works
podman run --rm hello-world
If that works, you’re good to go.
Basic Structure
[Unit]
Description=My Container
After=network-online.target
Wants=network-online.target
[Container]
Image=docker.io/myimage:latest
ContainerName=my-container
Volume=%h/mydata:/data:Z
[Service]
Restart=always
RestartSec=10s
[Install]
WantedBy=default.target
Useful systemd specifiers:
%h- expands to$HOME%t- expands to$XDG_RUNTIME_DIR(e.g./run/user/1000)
After adding or changing a .container file:
systemctl --user daemon-reload
Networking
Quadlet-managed networks get prefixed with systemd- internally. A traefik.network file becomes systemd-traefik in podman.
Check what’s actually there:
podman network ls
Reference a Quadlet-managed network:
Network=traefik.network
Reference a manually-created network:
Network=traefik
Automatic Image Updates
Add AutoUpdate=registry to your [Container] section - no label needed:
[Container]
Image=docker.io/myimage:latest
AutoUpdate=registry
Enable the timer:
# Rootless
systemctl --user enable --now podman-auto-update.timer
# System-level
systemctl enable --now podman-auto-update.timer
Preview what would update without doing anything:
podman auto-update --dry-run
Auto-updating
:latestis risky in production - a bad upstream change deploys automatically with no review.
Verifying Quadlet Units
# Check for parse errors
/usr/lib/systemd/system-generators/podman-system-generator --user --dryrun 2>&1
# List generated units
systemctl --user list-unit-files | grep <name>
# Validate a specific file
systemd-analyze --user verify ~/.config/containers/systemd/mycontainer.container
Troubleshooting
| Symptom | Cause | Fix |
|---|---|---|
Unit not found after daemon-reload |
File wrong location or parse error | Run podman-system-generator --user --dryrun |
statfs /podman/podman.sock: no such file |
%t not expanding - empty XDG_RUNTIME_DIR |
Hardcode socket path with your UID |
Socket missing after start podman.socket |
Socket path mismatch | Check systemctl --user cat podman.socket, add override |
Restart= ignored / unit fails |
Restart= in [Container] instead of [Service] |
Move it to [Service] |