Podman Quadlet: Containers as Real systemd Services

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.