Quadlet puts container parameters in resource description files, which a systemd generator turns into services. Distinguishing "source file → generated unit → running container" is the key to understanding when a change takes effect and why a service may still be missing even after its file has been written.
Quadlet describes resources; systemd manages their lifecycle#
This article is based on the stable documentation and v6.1.3 source checked in October 2026. It assumes Linux, local rootless Podman, a systemd user manager, and cgroup v2. Podman, the generator, and networking tools should already be configured; on NixOS, continue installing them declaratively through Nix. These examples have not been deployed or tested locally.
The [Container] section in a .container file is converted by Quadlet, while standard [Unit] and [Service] sections are handled by systemd. web-demo.container normally generates web-demo.service, and the default container name is systemd-web-demo; ContainerName can explicitly change it. Service names and container names are used at different interfaces: systemctl takes the former, while podman logs takes the latter.[1]
The generator reads source files and produces runtime units when the manager starts and during daemon-reload. daemon-reload does not thereby start new services or recreate running containers. Generated files are build products: edit the .container source or its drop-in rather than manually editing the generated .service.
Three files make a complete local service#
This example serves a static page using the public BusyBox image: web-demo.container references a named volume and a bridge network. Put the files in $XDG_CONFIG_HOME/containers/systemd/; when that variable is unset, use ~/.config/containers/systemd/. These names are for demonstration only; first check that no resources with the same names already exist.
# web-data.volume
[Volume]
VolumeName=web-demo-data
# web-net.network
[Network]
NetworkName=web-demo-net
Driver=bridge
# web-demo.container
[Unit]
Description=Local Quadlet Web Demo
[Container]
Image=docker.io/library/busybox:1.37
ContainerName=quadlet-web-demo
Network=web-net.network
Volume=web-data.volume:/www
PublishPort=127.0.0.1:8080:8080
Exec=sh -c 'test -e /www/index.html || printf "Quadlet demo\n" > /www/index.html; exec httpd -f -p 8080 -h /www'
LogDriver=journald
[Service]
Restart=on-failure
RestartSec=5
TimeoutStartSec=300
[Install]
WantedBy=default.targetThe code blocks represent three separate files; do not combine them into one .container. The volume stores /www, and the example page is written at initial startup only if it does not already exist. httpd runs in the foreground, with systemd managing its lifecycle; the host publishes only 127.0.0.1:8080. This pins a tag rather than an immutable digest, so record the actual image digest before long-term use.
Volume=web-data.volume:/www and Network=web-net.network reference Quadlet source filenames. The converter resolves the actual resource names and adds the corresponding service dependencies. The volume unit is normally named web-data-volume.service, and the network unit web-net-network.service. These differ from the Podman resource names controlled by VolumeName and NetworkName respectively.[2][4][5]
Generating, inspecting, and starting are three separate actions#
After saving the files, first reload and inspect the generated output. Reloading and reading that output do not start containers; start is the step that may pull images, create resources, and run them.
systemctl --user daemon-reload
systemctl --user cat web-demo.service
# 明确会启动服务
systemctl --user start web-demo.service
systemctl --user status web-demo.service
journalctl --user -u web-demo.service -n 80 --no-pager
# 服务运行后,从宿主验证 HTTP 行为
curl --fail --show-error http://127.0.0.1:8080/Use cat to inspect the converted parameters, status to observe the service state, and curl to verify HTTP behavior. TimeoutStartSec=300 allows more time for image pulls or initialization; it cannot fix image or network errors.
Do not run systemctl enable on the generated web-demo.service. Generated services cannot be enabled like ordinary persistent units. Quadlet processes [Install] in the source file during generation to establish associations such as WantedBy. default.target refers to the user manager's target. A target that is already running does not automatically start newly added services merely because daemon-reload ran, so the first activation still needs an explicit start.[1]
Data needs its own storage across container restarts#
The v6.1.3 converter generates podman run and exit-cleanup logic, removing the corresponding container when the service stops. Do not rely on the container's writable layer to retain data across service restarts. Named volumes and bind mounts have independent lifecycles; the example page is stored in web-demo-data.[2]
# 读取已有资源
podman ps -a --filter name=quadlet-web-demo
podman volume inspect web-demo-data
podman logs --tail 40 quadlet-web-demo
# 这两条会改变服务运行状态
systemctl --user stop web-demo.service
systemctl --user start web-demo.serviceAfter changing the source file, run daemon-reload and then restart to update the runtime parameters. Stopping the service does not delete the data volume; deleting the .volume file is not the same as deliberately destroying its data. To retire a service, first stop it, remove or adjust the source files and [Install], then run daemon-reload. Finally, inventory the retained volumes and networks separately. Clean up data only for confirmed resources; do not casually run a global prune.
If you switch to Volume=%h/path/to/site:/www:ro, read/write permissions still follow rootless UID mapping and host directory permissions. In that case, change Exec to start httpd directly. SELinux's Z is for dedicated directories only; never apply it to the entire home directory.
Dependency ordering is different from application readiness#
After=db.service only orders the relevant jobs; it does not bring up db by itself. Requires=db.service together with After combines the activation dependency with ordering. Even if the dependency reaches active, that does not mean the database has completed migrations, opened its listening port, or can accept requests. The application still needs connection retries or a readiness design that incorporates actual health.
By default, .container uses the notify type, with notifications coming from conmon by default in v6.1.3. Notify=true is appropriate when the application itself correctly sends sd_notify. Notify=healthy waits until the Podman health check passes before notifying systemd, and also requires a valid HealthCmd. Do not mechanically add Notify=true to every image, or it may wait indefinitely for a notification that never arrives.[3][2]
Health-check commands run inside the container, so the required tools must be present and the check must actually represent application availability. unhealthy does not automatically mean the process has exited; Restart=on-failure responds to service failure. Restarting after a sustained unhealthy state requires a separately designed HealthOnFailure policy.
Login, boot, and image updates each have their own requirements#
WantedBy=default.target establishes an association with the user manager's startup target; logging in normally starts the user manager. Starting without a login and continuing after logout require a user lingering policy. You can begin with a read-only query:
loginctl show-user "$USER" -p LingerWhether to enable linger is a system administration decision; on NixOS it should be expressed in the system configuration. Writing User=someone in a system service does not automatically turn it into a rootless Quadlet user unit. Put the files in the corresponding user's search path and run them through that user's manager.[1][7]
AutoUpdate=registry only sets the update label. A podman-auto-update service, timer, or manual invocation must still drive the checks, pulls, and service restarts. Plan the fully qualified image name, update window, and data backups first. This example does not enable automatic updates; alternatively, pin a digest and plan changes to Image.[6]
If the service is missing, check generation first#
Unit not found first means that the manager did not find the service. Possible causes include the search path, syntax, missing referenced files, or fields unsupported by the installed version. Container application execution has not yet begun. Run dryrun using the generator path actually supplied by your distribution:
# 路径示意;NixOS 应查实际安装的生成器,不照抄 /usr/lib 路径
/path/to/podman-system-generator --user --dryrun
# 只检查专用测试目录中的源文件
QUADLET_UNIT_DIRS=/path/to/quadlet-test \
/path/to/podman-system-generator --user --dryrun
# systemd 版本支持该选项时,验证生成服务
systemd-analyze --user --generators=true verify web-demo.servicedryrun outputs the conversion results and generation errors without starting services. If a key is unknown, first compare podman version with the manual for that version; do not assume an older installation supports something merely because the latest online documentation describes it. If the service generates but fails to start, examine the journal, image name, network components, and storage permissions. If it is active but HTTP fails, continue with the ports and application logs.[1]
The source files, generated units, and request results show intent, parameters, and actual behavior respectively.
References & further reading
- Quadlet generation, search paths, and enablement rules ↗
- Podman 6.1.3 Quadlet converter source ↗
- Quadlet container fields and notification mechanisms ↗
- Quadlet volume fields ↗
- Quadlet network fields ↗
- Podman automatic updates ↗
- Official Podman rootless service troubleshooting ↗
Adapted from technical notes. Examples use generic names.
Continue along a reading path
Container permissions and lifecycles
- 01Rootless containers: whose file permissions count?
- 02How does Quadlet turn container declarations into services?
Podman container workflows
- 01Podman fundamentals
- 02A basic Podman usage guide
- 03Rootless containers: whose file permissions count?
- 04Rootless configuration: mappings, ICMP, and registries
- 05Pods and Kubernetes manifests
- 06How does Quadlet turn container declarations into services?
- 07Pods and multi-service configuration with Quadlet
- 08Compose providers and interoperability with Docker tools
- 09Managing Podman on NixOS
- 10Advanced Podman operations and tuning
- 11Docker and Podman: architecture, compatibility, and coexistence