AlmaLinux 9: Quadlet file syntax and debugging
Test scope
Section titled “Test scope”In an AlmaLinux 9.8 privileged container (host kernel 5.14.0-687.54.1.el9_8, podman 5.8.2): wrote a .container file and ran /usr/libexec/podman/quadlet -dryrun -v. Log: plans/content-expansion/evidence/logs/alma-quadlet-services.log.
The container has no systemd, so service start and boot autostart were not verified; this page focuses on how to write the file and whether the generated unit is correct.
Where the file goes
Section titled “Where the file goes”| Mode | Directory |
|---|---|
| root | /etc/containers/systemd/ |
| rootless | ~/.config/containers/systemd/ |
The file name decides the unit name: web.container → web.service. A wrong directory produces no error — the unit simply does not exist, which is the most common “I wrote it but nothing happened”.
A complete .container
Section titled “A complete .container”[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.targetFour sections, four jobs: [Unit] dependencies and description, [Container] container arguments, [Service] restart policy, [Install] boot startup.
Verify the translation with dry-run
Section titled “Verify the translation with dry-run”sudo /usr/libexec/podman/quadlet -dryrun -v[Unit]Wants=network-online.targetAfter=network-online.targetDescription=webSourcePath=/etc/containers/systemd/web.container
[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:alpineThree places confirm the configuration: --publish, -v and --env inside ExecStart, plus whether After=network-online.target was preserved.
Making changes take effect
Section titled “Making changes take effect”sudo systemctl daemon-reloadsudo systemctl restart web.servicesystemctl status web.service --no-pagerdaemon-reload triggers Quadlet to regenerate the unit; a restart alone keeps using the old unit.
Common traps
Section titled “Common traps”- Missing
[Install]:systemctl enablereports the unit as not found; - Missing dependencies: the container starts before the network and fails to bind or reach the peer;
- Wrong directory for rootless: files in
/etcdo not apply to a normal user.
Practice: write, inspect, change a parameter
Section titled “Practice: write, inspect, change a parameter”sudo mkdir -p /etc/containers/systemdsudo tee /etc/containers/systemd/lab.container >/dev/null <<'EOF'[Unit]Description=lab[Container]Image=docker.io/library/nginx:alpinePublishPort=8082:80[Install]WantedBy=multi-user.targetEOFsudo /usr/libexec/podman/quadlet -dryrun -v | grep -E "ExecStart|PublishPort"Checks:
ExecStartcontains--publish 8082:80- Change the port to 8083 and rerun dry-run;
ExecStartfollows - After
daemon-reload,systemctl list-unit-files | grep labshows the generated unit
常见问题
The Quadlet file exists but systemctl cannot find the unit?
Check the directory first (root uses /etc/containers/systemd/), then run systemctl daemon-reload; Quadlet units are generated dynamically and do not exist before the reload.
Which field maps to ports, volumes and environment?
PublishPort maps to --publish, Volume to -v, Environment to --env; run quadlet -dryrun -v and check ExecStart field by field.
The container starts before the network and fails?
Add After=network-online.target and Wants=network-online.target in [Unit] so systemd waits for the network before starting the container.
Next steps
Section titled “Next steps”- Containers and images: Containers and images
- Firewall rules: firewall-cmd rules
- Post-install checks: 7 checks after install