Rocky 9: running containers under systemd with Quadlet
Test scope
Section titled “Test scope”Executed in a Rocky Linux 9.8 privileged container (host kernel 5.14.0-687.54.1.el9_8, podman 5.8.2): wrote /etc/containers/systemd/web.container, then ran /usr/libexec/podman/quadlet -dryrun -v to inspect the generated unit. Log: plans/content-expansion/evidence/logs/rocky-quadlet-services.log.
The container has no running systemd, so systemctl start <unit> was not verified; this page verifies Quadlet’s generation result, i.e. whether configuration translates into the right unit.
Why generate systemd is out
Section titled “Why generate systemd is out”podman generate systemd is deprecated upstream; it produced snapshot-style units that had to be regenerated whenever container arguments changed. Quadlet flips this: you write a declarative .container file and systemd generates the unit at start time, so changing a parameter needs only a service restart.
Write a .container file
Section titled “Write a .container file”[Unit]Description=webAfter=network-online.target
[Container]Image=docker.io/library/nginx:alpineContainerName=webPublishPort=8080:80Volume=webdata:/usr/share/nginx/html:ZEnvironment=NGINX_ENTRYPOINT_QUIET_LOGS=1
[Service]Restart=always
[Install]WantedBy=multi-user.targetPlace it in /etc/containers/systemd/ (root) or ~/.config/containers/systemd/ (rootless).
Inspect what it generates
Section titled “Inspect what it generates”sudo /usr/libexec/podman/quadlet -dryrun -v[Unit]Wants=network-online.targetAfter=network-online.targetDescription=webSourcePath=/etc/containers/systemd/web.containerRequiresMountsFor=%t/containers
[Service]Restart=alwaysExecStop=/usr/bin/podman rm -v -f -i webType=notifyExecStart=/usr/bin/podman run --name web --replace --rm --cgroups=split \ --sdnotify=conmon -d -v webdata:/usr/share/nginx/html:Z \ --publish 8080:80 --env NGINX_ENTRYPOINT_QUIET_LOGS=1 \ docker.io/library/nginx:alpineExecStart is the translation: PublishPort becomes --publish 8080:80, Volume becomes -v, Environment becomes --env. dry-run neither writes units nor starts containers, which makes it ideal before changing configuration.
Making it take effect
Section titled “Making it take effect”sudo systemctl daemon-reloadsudo systemctl start web.servicesystemctl status web.serviceThe generated unit is named <file>.service (web.container → web.service). After editing the .container file you must run daemon-reload and restart the service for the unit to be regenerated.
Common traps
Section titled “Common traps”- Editing the file without
daemon-reload: systemd still holds the old unit; - Missing
[Install]:systemctl enablecannot work (Quadlet usesWantedByfor boot startup); - Wrong directory for rootless: rootless units live in
~/.config/containers/systemd/, not/etc.
Practice: write a .container and inspect the unit
Section titled “Practice: write a .container and inspect the unit”sudo mkdir -p /etc/containers/systemdsudo tee /etc/containers/systemd/demo.container >/dev/null <<'EOF'[Unit]Description=demo[Container]Image=docker.io/library/nginx:alpinePublishPort=8081:80[Install]WantedBy=multi-user.targetEOFsudo /usr/libexec/podman/quadlet -dryrun -v | grep ExecStartsudo systemctl daemon-reloadsudo systemctl start demo.servicesystemctl is-active demo.serviceChecks:
grep ExecStartshows--publish 8081:80systemctl is-activereturnsactivepodman pslists the container
常见问题
How does Quadlet differ from generate systemd?
generate systemd produced static units that had to be regenerated when arguments changed; Quadlet generates the unit at start time from a .container file, so editing the file plus daemon-reload and a restart is enough.
Why does systemctl enable fail to find the unit?
Quadlet units are generated dynamically and enable relies on the [Install] WantedBy entry; without it (or without daemon-reload) enable fails.
How do rootless containers use Quadlet?
Put the .container file in ~/.config/containers/systemd/ and use systemctl --user daemon-reload plus systemctl --user start; root uses /etc/containers/systemd/.
Next steps
Section titled “Next steps”- Containers and images: Containers and images
- Log rotation: logrotate in practice
- Post-install checks: 7 checks after install