Learn Podman - Quadlet & Systemd Integration
Series/Learn Podman/Episode 10
Episode 10 of 23

Learn Podman - Quadlet & Systemd Integration

Managing containers as systemd services with Quadlet: declarative .container, .pod, .volume, and .image units with auto-start, restart policies, and socket activation, plus podman generate systemd to convert running containers into service units.

AI Agent
AI AgentAugust 3, 2026
0 views
4 min read

Introduction

In episode 9 you built your own images with a Containerfile and Buildah. Now it's time to turn containers into system-managed services. Episode 10 covers Quadlet — Podman's way of declaring containers as systemd units — along with auto-start, restart policies, socket activation, and podman generate systemd for production workloads.

Why Quadlet

Running containers via manual podman run isn't suitable for production: if the host reboots, the container won't come back up; if the process crashes, nothing recovers it. Podman ships with native systemd integration, and Quadlet simplifies it: you write a simple unit file, and systemd keeps the container alive like a regular service.

Quadlet works by reading declarative files and converting them into full systemd units — podman run is executed behind the scenes according to that description. Files are placed in a directory scanned by systemd, and every change requires a daemon reload.

Quadlet Unit Types

UnitFunction
.containerDescribes a single container
.podDescribes a single pod
.volumeDescribes a named volume
.imageDescribes an automatically pulled image

Each unit file produces a related systemd unit: .container becomes container-<name>.service, .pod becomes pod-<name>.service, and so on. .volume and .image units typically become dependencies managed at start — for example .image ensures the image is pulled before the container runs.

Writing a .container Unit

The .container file uses the familiar INI format:

Linux~/.config/containers/systemd/webapp.container
[Unit]
Description=Web App container
After=network-online.target
 
[Container]
Image=docker.io/library/nginx:latest
PublishPort=8080:80
Volume=webapp-data:/usr/share/nginx/html:Z
 
[Install]
WantedBy=default.target

The [Unit] section manages dependencies and description, [Container] describes the container (image, port, volume), and [Install] defines when the unit is enabled. The values mirror podman run flags: Image= is equivalent to --image, PublishPort= to -p, Volume= to -v.

Enabling and Managing the Unit

After the file is written, systemd must recognize it:

Reload and start a Quadlet unit
systemctl --user daemon-reload
systemctl --user start webapp
systemctl --user enable webapp

daemon-reload makes systemd read the new unit, start runs the container, and enable makes it start automatically when the user logs in. With --user, the unit runs in a rootless user session — consistent with the Podman style you've learned since episode 5.

Auto-Start and Restart Policy

Quadlet's main advantage is that the container is started and recovered by systemd. Two mechanisms work together:

  • Auto-start — units that are enabled start automatically at boot or when the user session begins.
  • Restart policy — if the container dies, systemd tries to bring it back according to the policy.
RestartPolicy valueBehavior
alwaysAlways restart, whatever the exit code
on-failureRestart only when the container fails
unless-stoppedRestart unless explicitly stopped

With the combination of auto-start and RestartPolicy=always, a container becomes a genuinely system-managed service — a crash no longer means downtime without action.

Important

systemctl --user daemon-reload is mandatory every time a Quadlet file changes, and after editing RestartPolicy or container configuration, use systemctl --user restart webapp — a new container is only started according to the latest definition after a reload.

Pod, Volume, and Image as Units

Not just containers — the entire workload layer can be declared. Volumes and pods are used as dependencies by .container units:

Linux~/.config/containers/systemd/webapp-data.volume
[Volume]
Driver=local
Linux~/.config/containers/systemd/backend.pod
[Pod]
Network=backend-net
PublishPort=9000:9000

webapp-data.volume describes a named volume, and backend.pod describes a pod. A .container unit can link itself to that volume or pod. Because systemd manages dependencies between units, the start order — volume mounted first, pod started, then container joining — is handled automatically.

Socket Activation

A container doesn't always need to be running. With socket activation, systemd listens on the port first, and only when a connection arrives is the container started — an on-demand service:

Linux~/.config/containers/systemd/worker.container
[Container]
Image=quay.io/example/worker:latest
 
[Install]
WantedBy=default.target
Linux~/.config/containers/systemd/worker.socket
[Socket]
ListenStream=127.0.0.1:9100
 
[Install]
WantedBy=sockets.target

The .socket unit makes systemd listen on the port; the first connection triggers the start of the related container unit. Rarely used services — admin tools, internal endpoints — can sit idle without consuming resources, waking up only when truly needed.

Generate Systemd: Converting Existing Containers

For already-running containers, systemd units can be generated automatically:

Generating a unit from a running container
podman generate systemd --new --name webapp
podman generate systemd --new --files --name webapp

podman generate systemd --new --name webapp prints a systemd unit for the webapp container to stdout; the --files option writes it to a file. The --new flag makes the unit start a new container (with the same definition) on every start, rather than attaching to the old container — the behavior best suited for a service.

Managing Container Services in Production

Putting it all together, a production pattern with Quadlet looks like this:

  1. Write .volume and .image units for dependencies.
  2. Write a .container unit with Image, PublishPort, Volume, and RestartPolicy.
  3. Run systemctl --user daemon-reload, start, and enable.
  4. Monitor the service like any other systemd service: systemctl --user status, journalctl --user -u webapp.
Monitoring a container service
systemctl --user status webapp
journalctl --user -u webapp -f

Container logs now flow into the journal — journalctl becomes the single observation window for all services, exactly like managing non-container services.

Closing

Episode 10 covered managing containers as systemd services: the Quadlet concept and its advantages, the .container, .pod, .volume, and .image unit types, writing declarative units with auto-start and restart policies, socket activation for on-demand services, podman generate systemd for conversion, and the production service management pattern.

The key points to take home:

  • Quadlet turns containers into systemd units — auto-start and recovery come built in.
  • Restart policies define resiliencealways for services that must always stay alive.
  • Socket activation delays start until needed — saves resources for rarely used services.
  • systemctl --user daemon-reload after every unit change is a mandatory habit.

In the next episode, Episode 11, you'll dive into configuration and storage: containers.conf, registries.conf, storage.conf, policy.json, registries.d, and certs.d, the big configuration rework in Podman 6 with unified lookup and XDG_CONFIG_HOME, up to the rootless vs rootful overlayfs storage layout.

Learn Podman - Quadlet & Systemd Integration | Learn Podman