Podman Quadlet: Containers as Real systemd Services
Running a container as a service usually means setting a restart policy and hoping. Quadlet does it properly: you write a short declarative file and systemd generates a real unit from it.
The file
# ~/.config/containers/systemd/jellyfin.container
[Unit]
Description=Jellyfin media server
After=network-online.target
[Container]
Image=docker.io/jellyfin/jellyfin:latest
PublishPort=8096:8096
Volume=%h/jellyfin/config:/config:Z
Volume=%h/media:/media:ro,Z
Environment=TZ=Europe/London
AutoUpdate=registry
[Service]
Restart=always
TimeoutStartSec=300
[Install]
WantedBy=default.target
systemctl --user daemon-reload
systemctl --user start jellyfin
systemctl --user status jellyfin
journalctl --user -u jellyfin -f
myapp.container generates myapp.service. Nothing else to do.
Why this beats a restart policy
A container restart policy handles exactly one thing: the container exiting. systemd handles considerably more.
Ordering and dependencies. A container that needs a database can declare it:
[Unit]
Requires=postgres.service
After=postgres.service
Journal integration. Logs go to the journal like any service, so journalctl and the query builder work normally.
Resource limits. cgroup controls in the [Service] section:
[Service]
MemoryMax=2G
CPUQuota=50%
Boot without a daemon. Nothing needs to be running to watch and restart things. systemd already does that.
Volumes, networks, and pods
Each gets its own file type:
# data.volume
[Volume]
VolumeName=appdata
# backend.network
[Network]
NetworkName=backend
Subnet=10.89.1.0/24
Reference them from a container with the file name:
[Container]
Volume=data.volume:/data
Network=backend.network
systemd works out the dependency ordering itself, so the volume and network exist before the container starts.
Automatic updates
[Container]
AutoUpdate=registry
systemctl --user enable --now podman-auto-update.timer
Podman checks for a newer image, pulls it, restarts the container, and rolls back if the new one fails its health check. That last part is what separates it from a bare docker pull in a cron job.
Rootless persistence
For a rootless user service to survive logout:
loginctl enable-linger "$USER"
Without this the containers stop when your session ends, which is the most common Quadlet surprise on a server.
Against Compose
Compose describes a whole stack in one file and is what nearly all self-hosted software documents. Podman can run Compose files, so this is not an either-or.
Quadlet describes each piece separately, with relationships expressed as systemd dependencies, and gets systemd’s supervision for free.
For a stack you copied from a project’s README, use Compose and our generator. For services you intend to run for years on a machine you administer, Quadlet integrates better with everything else on the system.
Frequently Asked Questions
What is Quadlet?
A systemd generator that reads .container, .volume, .network, and .pod files and produces real systemd units from them. You write a short declarative file and systemd manages the container as an ordinary service.
Why not just use a restart policy?
Because a restart policy only handles the container crashing. systemd handles ordering against other units, dependencies, resource limits, journal integration, and starting at boot without a daemon watching. Quadlet gets all of that instead of reimplementing a subset.
Does Quadlet replace podman generate systemd?
Yes. podman generate systemd is deprecated. It produced a static unit file that had to be regenerated whenever the container changed. Quadlet keeps the description declarative and generates the unit at daemon-reload time.
Where do Quadlet files go?
For a user, ~/.config/containers/systemd/. For the system, /etc/containers/systemd/. The generator runs on daemon-reload and creates units named after the file, so myapp.container becomes myapp.service.
Can Quadlet replace Docker Compose?
For most self-hosted services, yes, and it is a different model. Compose describes a stack in one file; Quadlet describes each piece separately and expresses relationships through systemd dependencies. Podman can also run Compose files if you prefer that shape.
Why is my Quadlet unit not appearing?
Usually a syntax error or a missing daemon-reload. Run systemctl --user daemon-reload, then check with systemctl --user list-unit-files. The generator logs failures to the journal, which is where the actual error will be.