Skip to content
Search Rocky Linux

    Browse this documentation

    Rocky 9: running containers under systemd with Quadlet

    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.

    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.

    [Unit]
    Description=web
    After=network-online.target
    [Container]
    Image=docker.io/library/nginx:alpine
    ContainerName=web
    PublishPort=8080:80
    Volume=webdata:/usr/share/nginx/html:Z
    Environment=NGINX_ENTRYPOINT_QUIET_LOGS=1
    [Service]
    Restart=always
    [Install]
    WantedBy=multi-user.target

    Place it in /etc/containers/systemd/ (root) or ~/.config/containers/systemd/ (rootless).

    Terminal window
    sudo /usr/libexec/podman/quadlet -dryrun -v
    [Unit]
    Wants=network-online.target
    After=network-online.target
    Description=web
    SourcePath=/etc/containers/systemd/web.container
    RequiresMountsFor=%t/containers
    [Service]
    Restart=always
    ExecStop=/usr/bin/podman rm -v -f -i web
    Type=notify
    ExecStart=/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:alpine

    ExecStart 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.

    Terminal window
    sudo systemctl daemon-reload
    sudo systemctl start web.service
    systemctl status web.service

    The 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.

    • Editing the file without daemon-reload: systemd still holds the old unit;
    • Missing [Install]: systemctl enable cannot work (Quadlet uses WantedBy for 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”
    Terminal window
    sudo mkdir -p /etc/containers/systemd
    sudo tee /etc/containers/systemd/demo.container >/dev/null <<'EOF'
    [Unit]
    Description=demo
    [Container]
    Image=docker.io/library/nginx:alpine
    PublishPort=8081:80
    [Install]
    WantedBy=multi-user.target
    EOF
    sudo /usr/libexec/podman/quadlet -dryrun -v | grep ExecStart
    sudo systemctl daemon-reload
    sudo systemctl start demo.service
    systemctl is-active demo.service

    Checks:

    1. grep ExecStart shows --publish 8081:80
    2. systemctl is-active returns active
    3. podman ps lists 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/.