Blog post

Declarative Containers with systemd and Podman Quadlet

Quadlet turns simple .container files into real systemd units. Write intent, not generated boilerplate, and let Podman’s generator keep services current after every daemon-reload.

An editorial illustration of declarative container management showing systemd unit configuration files and Quadlet service definitions alongside server monitoring indicators, rendered in deep blues and warm terminal highlights.

Quadlet lets you declare Podman containers as native systemd units. Instead of running podman generate systemd against a live container or maintaining a hand-written wrapper, you describe the desired container in a .container file. Podman’s Quadlet generator reads that file during boot and after daemon-reload, then exposes a normal service to systemd. The source remains small enough to review and keep in Git.

For a rootful service, create /etc/containers/systemd/app.container. A rootless service belongs at ~/.config/containers/systemd/app.container for the account that owns it:

[Unit]
Description=Example web service
After=network-online.target
Wants=network-online.target

[Container]
Image=ghcr.io/example/app:1.4.2
ContainerName=example-app
PublishPort=127.0.0.1:8080:8080
Volume=appdata.volume:/var/lib/app:Z
Network=appnet.network

[Service]
Restart=on-failure
TimeoutStartSec=900

[Install]
WantedBy=multi-user.target default.target

Declare the supporting units beside it as appdata.volume and appnet.network. Quadlet creates the dependency graph, so systemd starts the volume and network before the container. Pin an image tag or digest instead of using latest; a declarative file should not silently change behavior at the next pull. Put secrets in Podman secrets or a systemd credential mechanism rather than embedding them in the unit.

Reload and start the service with systemctl daemon-reload followed by systemctl start app.service. Rootless users need a user manager that survives logout, commonly enabled with loginctl enable-linger USER. Check the generated unit with systemctl cat app.service, inspect failures using journalctl -u app.service, and validate generator output with systemd-analyze --user --generators=true verify app.service in rootless mode. Rootful checks omit --user.

Quadlet depends on cgroup v2 and a sufficiently recent Podman release. Generated units are not ordinary hand-authored files, so do not edit their copies under /run or expect a useful systemctl enable workflow; express boot intent through [Install]. Rootless containers also cannot rely on a system-level User= directive, and SELinux labeling can make a bind mount fail until :Z or :z matches the sharing requirement. Quadlet is a strong fit for a few server services. Kubernetes or Compose remains easier when the application needs multi-host scheduling, extensive templating, or a large team already operates those systems.

Related areas

These What I Do pages are matched from the subject matter of this article, creating a cleaner path from educational content to implementation work.

Continue reading

Based on shared categories first, then the strongest overlap in tags.