Problem: I attempted to create a systemd unit for a podman container to autostart at boot, but encountered the following info message and decided to investigate:

user@host $ podman generate systemd --new --name syncthing

DEPRECATED command:
It is recommended to use Quadlets for running containers and pods under systemd.

Solution: Podman Quadlets, a declarative way to create systemd units for containers.

Understanding Podman Quadlets

Podman Quadlets use six types of files: .container, .pod, .kube, .network, .image, and .volume to create the respective units.

Using volume, image, and network units allows containers to depend on them being automatically pre-created. This is particularly beneficial when using special options to control their creation, as Podman otherwise creates them with default options.

Podman recursively searches in ~/.config/containers/systemd/ for these files.

For more information, refer to the Podman Systemd Unit Documentation.

Example: Syncthing Container

Here’s an example of a .container file for a Syncthing container:

~/.config/containers/systemd/syncthing.container
---
[Unit]
Description=Syncthing
## Stops the unit if the service starts more than 3 times every 400 seconds.
StartLimitIntervalSec=400
StartLimitBurst=3

[Container]
ContainerName=syncthing
Image=docker.io/syncthing/syncthing:latest
# PodmanArgs=--memory 128m
## TCP file transfers
PublishPort=10.0.0.2:22000:22000/tcp
## QUIC file transfers
PublishPort=10.0.0.2:22000:22000/udp
## Web UI
PublishPort=10.0.0.2:8087:8384
## Receive local discovery broadcasts
PublishPort=10.0.0.2:21027:21027/udp 
Volume=/path/in/host/for/syncthing:/var/syncthing
Timezone=Europe/Madrid
UIDMap=0:1:65536

[Service]
Restart=on-failure
MemoryMax=128M
## Allow enough time for the image to be downloaded
TimeoutStartSec=900
## Wait 90 seconds to restart again
RestartSec=90

[Install]
WantedBy=multi-user.target default.target

To apply the changes, reload the systemd daemon and start the service:

systemctl --user daemon-reload
systemctl --user start syncthing.service

This configuration ensures the service starts at boot. If you don’t want a unit to start at boot, remove the [Install] section of the .container file.

Note: Pods will be implemented in Podman 5.0 and not in 4.9, so I will update my pods in the future.


Podlet

Podlet is a utility that allows the creation of the necessary quadlets files from a Podman command. It can be run in a container. I tried it and is not fully functional, but covers the basics.

Here’s an example of how to run Podlet in a container:

podman run -it quay.io/k9withabone/podlet --install --description Caddy \
  podman run \
  --restart always \
  -p 8000:80 \
  -p 8443:443 \
  -v ./Caddyfile:/etc/caddy/Caddyfile:Z \
  -v caddy_data:/data \
  -m 1G \
  docker.io/library/caddy:latest

For more information, visit the Podlet GitHub Repository.


Ansible

Now, let’s demonstrate how to migrate from Ansible’s built-in container plugin to using Quadlets.

Given that I have a variable to set up the path for Syncthing data, I need to create a template.

Here is my current deployment of the container in Ansible:

./tasks/containers/syncthing.yml
---
- name: Create syncthing container
  containers.podman.podman_container:
    name: syncthing
    image: docker.io/syncthing/syncthing:latest
    state: present
    recreate: yes
    restart_policy: on-failure:10
    volume:
      - "{{ host_podman_syncthing_path }}:/var/syncthing"
    memory: 128m
    publish: 
      - "10.0.0.2:22000:22000/tcp" # TCP file transfers
      - "10.0.0.2:22000:22000/udp" # QUIC file transfers
      - "10.0.0.2:8087:8384" # Web UI
      - "10.0.0.2:21027:21027/udp" # Receive local discovery broadcasts
    env:
      TZ=Europe/Madrid
    uidmap: "0:1:65536"

Here is the new Ansible template to create the .container Quadlet with the variable:

./templates/containers/syncthing/syncthing.container.j2
---
[Unit]
Description=Syncthing
## Stops the unit if the service starts more than 3 times in 400 seconds.
StartLimitIntervalSec=400
StartLimitBurst=3

[Container]
ContainerName=syncthing
Image=docker.io/syncthing/syncthing:latest
# PodmanArgs=--memory 128m
## TCP file transfers
PublishPort=10.0.0.2:22000:22000/tcp
## QUIC file transfers
PublishPort=10.0.0.2:22000:22000/udp
## Web UI
PublishPort=10.0.0.2:8087:8384
## Receive local discovery broadcasts
PublishPort=10.0.0.2:21027:21027/udp
Volume={{ host_podman_syncthing_path }}:/var/syncthing
Timezone=Europe/Madrid
UIDMap=0:1:65536
## Auto update to the latest image
AutoUpdate=registry

[Service]
Restart=on-failure
MemoryMax=128M
## Allow enough time for the image to be downloaded
TimeoutStartSec=900
## Wait 90 seconds to restart again
RestartSec=90

[Install]
WantedBy=multi-user.target default.target

And deploy the template and reload systemd:

./tasks/containers/syncthing.yml
---
- name: Create a systemd unit for syncthing container
  ansible.builtin.template:
    src: "./templates/containers/syncthing/syncthing.container.j2"
    dest: '{{ host_podman_systemd_files_path }}/syncthing/syncthing.container'
    owner: '1000'
    group: '1000'
    mode: '0600'
    force: yes ## Overwrites the existing file
  register: container_unit

- name: Reload systemd daemon as user
  when: container_unit.changed
  ansible.builtin.systemd:
    daemon_reload: yes
    scope: user

Resources