Run Podman Containers as systemd Services with Quadlet

Tested on Red Hat Enterprise Linux 10.2 (Coughlan)
Package podman-5.8.2-5.el10_2.x86_64
Applies to Any Linux host with Podman and systemd
Privilege sudo for rootful examples on the lab host
Scope Quadlet concept, generator model, search paths, first .container service, service vs container naming, [Install] boot wiring, daemon-reload, start/status/logs, service types overview, and restart behavior. Does not cover full [Container] directive reference, .pod tutorials, rootless linger depth, auto-update, or full troubleshooting workflows.
Related guides Podman pods
Podman port mapping

Quadlet turns Podman container definitions into normal systemd services. You maintain a small declarative file such as quadlet-demo.container, reload systemd, and manage quadlet-demo.service with systemctl while Podman creates the container at start time.

This guide walks through the generator model, your first rootful .container unit, and the naming and enablement rules that trip up experienced systemd administrators.

IMPORTANT
Edit the Quadlet source file (for example quadlet-demo.container), not the generated unit under the systemd generator output. After every change, run daemon-reload and restart the generated service.

What is Podman Quadlet?

Quadlet lets you describe Podman resources with systemd-style declarative files:

text
web.container
data.volume
app.network
application.pod
image.build
base.image
config.artifact
workload.kube

At boot or after systemctl daemon-reload, the Podman Quadlet generator reads these files and writes ordinary systemd service units.

text
quadlet-demo.container
Podman Quadlet generator
quadlet-demo.service
podman run ...
Podman container (quadlet-demo)

Keep three names separate:

text
Quadlet source file:   quadlet-demo.container
Generated systemd unit: quadlet-demo.service
Podman container:      quadlet-demo   (when ContainerName= is set)

The generated unit is quadlet-demo.service. The Podman container name comes from ContainerName= or defaults to systemd-<unit-basename> — it is not the service name itself.


Why use Quadlet instead of a handwritten podman run unit?

A hand-written systemd unit pushes lifecycle details onto you:

ini
[Service]
ExecStart=/usr/bin/podman run ...
ExecStop=/usr/bin/podman stop ...
ExecStopPost=/usr/bin/podman rm ...

You must handle container naming, conmon and PID behavior, dependencies, network and volume ordering, sdnotify integration, and Podman-specific stop and cleanup paths yourself.

Quadlet translates declarative container configuration into the correct generated service:

ini
[Container]
Image=registry.access.redhat.com/ubi9/ubi-minimal
ContainerName=quadlet-demo
Exec=sleep 3600

Hand-written ExecStart=podman run units are still possible. Quadlet is the supported Podman-native declarative approach for new systemd deployments.


Quadlet vs podman generate systemd

podman generate systemd followed an old workflow:

text
podman run/create
existing container
podman generate systemd
generated .service

Quadlet inverts that model:

text
desired .container configuration
systemd generator
.service
container created from declaration

podman generate systemd is deprecated for new work. The desired configuration file is the source of truth, not a container you created earlier on the CLI. Converting existing generated units is covered in Migrate from podman generate systemd to Quadlet.


Check Quadlet requirements

Confirm Podman and cgroup layout before you create the first unit:

bash
podman --version

Sample output:

output
podman version 5.8.2

Quadlet requires cgroup v2:

bash
podman info --format '{{.Host.CgroupsVersion}}'

Sample output:

output
v2

Check systemd and locate the generator on your distribution:

bash
systemctl --version

Sample output:

output
systemd 257 (257-23.el10_2.2-g009f2a8)

Generator paths vary by package layout. On RHEL 10 with Podman 5.8.2:

bash
rpm -ql podman | grep -E 'quadlet|system-generator'

Sample output (trimmed):

output
/usr/lib/systemd/system-generators/podman-system-generator
/usr/libexec/podman/quadlet

Use the podman-system-generator binary your package provides. Do not assume one universal path across distributions.


Quadlet unit types

Extension Purpose
.container Run a container
.pod Create and manage a Podman pod
.volume Ensure a Podman volume exists
.network Ensure a Podman network exists
.build Build an image
.image Pull or cache an image
.artifact Pull an OCI artifact
.kube Run Kubernetes YAML with podman kube play

This article focuses on .container. Per-type depth lives in sibling guides such as Podman Quadlet .container file explained and Run Podman pods with Quadlet. Kubernetes YAML through .kube is also covered in podman kube play context.

Generated service naming follows the basename:

  • web.containerweb.service
  • app.networkapp-network.service
  • data.volumedata-volume.service
  • backend.podbackend-pod.service

Where Quadlet files are stored

Rootful search paths

Rootful Quadlet files are read from paths such as:

text
/run/containers/systemd/
/etc/containers/systemd/
/usr/share/containers/systemd/

Later paths in the search order can be overridden by earlier ones depending on distribution policy. For administrator-created persistent rootful units, use:

text
/etc/containers/systemd/

Rootless search paths

Rootless units are read from:

text
$XDG_RUNTIME_DIR/containers/systemd/
$XDG_CONFIG_HOME/containers/systemd/
~/.config/containers/systemd/
/etc/containers/systemd/users/${UID}/
/etc/containers/systemd/users/
/usr/share/containers/systemd/users/${UID}/
/usr/share/containers/systemd/users/

Rootless lifecycle, lingering, and systemctl --user workflows belong in Rootless Podman Quadlet. Per-UID paths under /etc/containers/systemd/users/${UID}/ also underpin podmansh login shells that confine accounts inside a container at SSH login. This guide uses rootful examples under /etc/containers/systemd/.


Create your first .container Quadlet

Create quadlet-demo.container with four standard sections:

ini
[Unit]
Description=Podman web container

[Container]
Image=registry.access.redhat.com/ubi9/ubi-minimal
ContainerName=quadlet-demo
Exec=sleep 3600

[Service]
Restart=on-failure

[Install]
WantedBy=multi-user.target

Save it on the lab host:

bash
sudo tee /etc/containers/systemd/quadlet-demo.container > /dev/null <<'EOF'
[Unit]
Description=Podman web container

[Container]
Image=registry.access.redhat.com/ubi9/ubi-minimal
ContainerName=quadlet-demo
Exec=sleep 3600

[Service]
Restart=on-failure

[Install]
WantedBy=multi-user.target
EOF

Section roles:

text
[Unit]       → standard systemd relationships and metadata
[Container]  → Podman container configuration
[Service]    → generated service behavior (restart, timeouts)
[Install]    → boot dependencies interpreted by the generator

quay.io/podman/hello is a useful smoke-test image, but it prints once and exits. The lab uses ubi-minimal with Exec=sleep 3600 so the service stays running while you inspect status and logs.


Reload systemd after creating or editing Quadlet

Quadlet is a systemd generator. Editing quadlet-demo.container does not directly change an already-loaded quadlet-demo.service until the generator runs again.

Reload the systemd manager:

bash
sudo systemctl daemon-reload

daemon-reload exits silently on success. Confirm the generated unit exists:

bash
sudo systemctl status quadlet-demo.service

Sample output:

output
○ quadlet-demo.service - Podman web container
     Loaded: loaded (/etc/containers/systemd/quadlet-demo.container; generated)
     Active: inactive (dead)

The Loaded: ... (generated) line confirms systemd created the unit from your Quadlet file. inactive (dead) is normal before the first start.


Start the Quadlet service

Start the generated unit:

bash
sudo systemctl start quadlet-demo.service

Check service state:

bash
sudo systemctl status quadlet-demo.service

Sample output (trimmed):

output
● quadlet-demo.service - Podman web container
     Loaded: loaded (/etc/containers/systemd/quadlet-demo.container; generated)
     Active: active (running) since Sun 2026-08-23 02:13:30 IST; 1s ago
   Main PID: 227868 (conmon)
     CGroup: /system.slice/quadlet-demo.service
             ├─libpod-payload-...
             │ └─227870 sleep 3600
             └─runtime

The service is quadlet-demo.service. The workload runs inside the Podman container quadlet-demo.

Confirm the container Podman created:

bash
sudo podman ps --filter name=quadlet-demo --format 'table {{.Names}}\t{{.Status}}\t{{.Image}}'

Sample output:

output
NAMES         STATUS         IMAGE
quadlet-demo  Up 5 seconds   registry.access.redhat.com/ubi9/ubi-minimal:latest

Quadlet service name vs container name

For web.container, Quadlet always creates web.service.

The default Podman container name is systemd-web unless you set ContainerName=:

bash
sudo tee /etc/containers/systemd/web.container > /dev/null <<'EOF'
[Unit]
Description=Default naming demo

[Container]
Image=registry.access.redhat.com/ubi9/ubi-minimal
Exec=sleep 3600

[Service]
Restart=on-failure

[Install]
WantedBy=multi-user.target
EOF

Reload so systemd regenerates web.service from the new file:

bash
sudo systemctl daemon-reload

Start the unit and compare the default Podman container name:

bash
sudo systemctl start web.service

List running containers to see the default name:

bash
sudo podman ps --filter name=systemd-web --format 'table {{.Names}}\t{{.Status}}'

Sample output:

output
NAMES        STATUS
systemd-web  Up 2 seconds

With ContainerName=web in [Container], the Podman container would be web, but the generated systemd unit would still be web.service.


Why systemctl enable does not work normally

Many administrators instinctively run:

bash
sudo systemctl enable quadlet-demo.service

Sample output:

output
Failed to enable unit: Unit /run/systemd/generator/quadlet-demo.service is transient or generated

Generated Quadlet services are transient. systemctl enable cannot create the usual persistent symlinks for them.

Put boot wiring in the Quadlet source instead:

ini
[Install]
WantedBy=multi-user.target

After you add or change [Install], reload again:

bash
sudo systemctl daemon-reload

Check how systemd classifies the unit:

bash
systemctl is-enabled quadlet-demo.service

Sample output:

output
generated

The generated state is expected. Quadlet interprets supported [Install] keys such as WantedBy=, RequiredBy=, UpheldBy=, and Alias= when generating the unit.


Start a Quadlet automatically at boot

Rootful services commonly use:

ini
[Install]
WantedBy=multi-user.target

Rootless user services often target default.target instead. Match the target to the systemd scope that should pull the service in at boot.

Do not rely on systemctl enable. Configure [Install] in the .container file, run daemon-reload, and verify after reboot that the service starts without a manual systemctl start.


Restart containers with systemd

When systemd manages a Quadlet container, let systemd own restart behavior:

ini
[Service]
Restart=always
RestartSec=5

Do not add --restart=always through PodmanArgs= as the primary restart mechanism. Container restart policy and service restart policy should not fight each other.

Restart the running demo:

bash
sudo systemctl restart quadlet-demo.service

systemctl restart stops the old container and starts a fresh one according to the generated unit lifecycle.


View Quadlet logs

Service lifecycle messages go to the journal:

bash
sudo journalctl -u quadlet-demo.service

Follow live output:

bash
sudo journalctl -u quadlet-demo.service -f

Container stdout and stderr are also available through Podman when the container is running:

bash
sudo podman logs quadlet-demo
Tool Shows
journalctl -u name.service systemd unit lifecycle, generator events, conmon
podman logs container container stdout/stderr

Generator failures and missing units are covered in Podman Quadlet troubleshooting.


What happens when the service stops?

Stop the service:

bash
sudo systemctl stop quadlet-demo.service

Check whether the container still exists:

bash
sudo podman ps -a --filter name=quadlet-demo --format 'table {{.Names}}\t{{.Status}}'

Sample output:

output
NAMES       STATUS

An empty table means the generated service removed the container on stop. Quadlet-managed units create and tear down containers with the service lifecycle rather than treating a manually created CLI container as permanent state.

That model matters later for image updates and replacement workflows.


Quadlet service types

Generated units use different systemd service types depending on the Quadlet extension:

Quadlet type Typical systemd type
.container, .kube notify (default)
.pod forking
.volume, .network, .build, .image, .artifact oneshot

When systemctl status shows active (exited) on a volume or network unit, that is normal for a oneshot dependency service. Container units should show active (running) while the workload is up.

Preview what the generator will write:

bash
sudo /usr/lib/systemd/system-generators/podman-system-generator --dryrun

Sample output (trimmed):

output
---quadlet-demo.service---
[Unit]
Description=Podman web container
SourcePath=/etc/containers/systemd/quadlet-demo.container

[X-Container]
Image=registry.access.redhat.com/ubi9/ubi-minimal
ContainerName=quadlet-demo
Exec=sleep 3600

[Service]
Restart=on-failure
ExecStart=/usr/bin/podman run ...

Use the dry-run output on your host when you need the exact ExecStart, stop, and cleanup commands for your Podman version.


Long image pulls and TimeoutStartSec

A .container or .kube service can exceed systemd's normal startup timeout while Podman pulls or builds an image. Raise TimeoutStartSec= in [Service] or pre-pull the image. Resource Quadlets such as .volume, .network, .build, .image, and .artifact are oneshot units on Podman 5.8.2; their startup timeout is disabled by default and TimeoutStartSec does not apply in the same way.

ini
[Service]
TimeoutStartSec=900

Pre-pulling the image with podman pull avoids the timeout entirely. Do not add a large TimeoutStartSec to every unit without a reason.


Common Quadlet errors

Symptom Likely cause Fix
systemctl enable fails on generated unit Quadlet services are transient Put WantedBy= in [Install] and run daemon-reload
Container named systemd-web unexpectedly Default naming without ContainerName= Set ContainerName= or expect the systemd- prefix
Service not found after edit Generator has not rerun sudo systemctl daemon-reload
Changes do not appear Service not restarted after reload Restart name.service after daemon-reload
Unit missing entirely Invalid directive in Quadlet file Run podman-system-generator --dryrun and read generator errors

Deeper debugging belongs in Podman Quadlet troubleshooting.


References


Summary

Podman Quadlet is a systemd generator, not a separate daemon. You write declarative files such as quadlet-demo.container under /etc/containers/systemd/, run daemon-reload, and manage the generated quadlet-demo.service unit with systemctl. The Podman container name is separate from the service name — expect systemd-<basename> unless you set ContainerName=.

The trap that catches experienced systemd users is systemctl enable. Generated Quadlet units are transient, so boot wiring belongs in [Install] WantedBy= inside the Quadlet source file. After every edit, reload systemd and restart the service; use journalctl -u for unit lifecycle logs and podman logs for container output.

For rootless paths, lingering, and user-manager workflows, continue with Rootless Podman Quadlet. For every [Container] directive, see Podman Quadlet .container file explained. To move off legacy podman generate systemd units, use Migrate from podman generate systemd to Quadlet.


Frequently Asked Questions

1. What is Podman Quadlet?

Quadlet is a systemd generator built into Podman. You write declarative files such as web.container, run daemon-reload, and systemd creates ordinary service units that start and stop containers for you.

2. Where do I put a rootful Quadlet file?

Place administrator-created rootful units in /etc/containers/systemd/. After you edit the file, run sudo systemctl daemon-reload and manage the generated name.service unit with systemctl.

3. Why does systemctl enable fail on a Quadlet service?

Generated Quadlet services are transient units. Boot wiring belongs in the Quadlet source file under [Install], for example WantedBy=multi-user.target, not in a manual systemctl enable command.

4. What is the difference between web.service and the Podman container name?

web.container becomes web.service. The Podman container is systemd-web unless you set ContainerName= in the [Container] section. The service name and container name are separate identifiers.

5. Is podman generate systemd still supported?

podman generate systemd is deprecated for new work. Podman recommends Quadlet because the desired resource configuration is the source of truth. The old command remains for existing units but is not the path for new deployments.
Deepak Prasad

R&D Engineer

Founder of GoLinuxCloud with more than 15 years of expertise in Linux, Python, Go, Laravel, DevOps, Kubernetes, Git, Shell scripting, OpenShift, AWS, Networking, and Security. With extensive experience, he excels across development, DevOps, networking, and security, delivering robust and efficient solutions for diverse projects.

  • Go (programming language)
  • Python (programming language)
  • DevOps
  • Computer Security
  • Cloud Computing
  • Kubernetes
  • Linux
  • Ansible (software)