Self-updating Containers on Linux with Quadlet aka podman-system-generator
Daniel Holth
Posted on October 30, 2024
If you are using a modern Linux system like Fedora 40, you may want to configure systemd
which manages services, and podman
to run containers as an alternative to Docker. This setup makes it possible to run rootless containers as a normal user without worrying about giving the container global permissions.
systemd
uses .service
files to control what to run. For example, samba
, a file server, has /usr/lib/systemd/system/samba.service
[Unit]
Description=Samba AD Daemon
Documentation=man:samba(8) man:samba(7) man:smb.conf(5)
Wants=network-online.target
After=network.target network-online.target
[Service]
Type=notify
PIDFile=/run/samba.pid
LimitNOFILE=16384
EnvironmentFile=-/etc/sysconfig/samba
ExecStart=/usr/sbin/samba --foreground --no-process-group $SAMBAOPTIONS
ExecReload=/bin/kill -HUP $MAINPID
Environment=KRB5CCNAME=FILE:/run/samba/krb5cc_samba
[Install]
WantedBy=multi-user.target
We want our container to start automatically on boot and restart when needed, so we need a systemd
.service
file. In the old days we would create a container using podman
and then manually put a .service
file for that container somewhere.
This works for a while but our service references an exact container. When we want to update the container, we have to re-write the service file to reference the update. On my system the service file referenced the new container in one place and the old one in the other, I forgot to update both, and systemd kept restarting my container.
Six months later, I discovered Quadlet.
Quadlet is a generator
to alleviate the burden of manually writing .service
files, one of many found in /usr/lib/systemd/system-generators
:
# ls -1 /usr/lib/systemd/system-generators
kdump-dep-generator.sh
nfs-server-generator
podman-system-generator
rpc-pipefs-generator
selinux-autorelabel-generator.sh
systemd-bless-boot-generator
systemd-cryptsetup-generator
systemd-debug-generator
systemd-fstab-generator
systemd-getty-generator
systemd-gpt-auto-generator
systemd-hibernate-resume-generator
systemd-integritysetup-generator
systemd-rc-local-generator
systemd-run-generator
systemd-system-update-generator
systemd-sysv-generator
systemd-veritysetup-generator
zram-generator
Generators convert higer-level configuration to systemd early on during startup, when we call systemctl daemon-reload
, or when we call systemctl --user daemon-reload
for rootless services.
Examples
Mosquitto is a simple message broker I've chosen to run as root. After placing the .container
file in the right place and running systemctl daemon-reload
(or rebooting), we can run systemctl start mosquitto
. All the podman
commands to pull, run docker.io/library/eclipse-mosquitto:latest
are taken care of for us.
# cat /etc/containers/systemd/mosquitto.container
[Container]
ContainerName=mosquitto
HostName=hass
Image=docker.io/library/eclipse-mosquitto:latest
Volume=/etc/mosquitto:/mosquitto/config
Volume=/mosquitto/data
Volume=/mosquitto/log
# mqtt
PublishPort=1883:1883
The service winds up in /etc/containers/systemd/mosquitto.container
:
# Automatically generated by /usr/lib/systemd/system-generators/podman-system-generator
#
# unifi.container
[X-Container]
ContainerName=mosquitto
HostName=hass
Image=docker.io/library/eclipse-mosquitto:latest
Volume=/etc/mosquitto:/mosquitto/config
Volume=/mosquitto/data
Volume=/mosquitto/log
# mqtt
PublishPort=1883:1883
[Unit]
Wants=network-online.target
After=network-online.target
SourcePath=/etc/containers/systemd/mosquitto.container
RequiresMountsFor=%t/containers
RequiresMountsFor=/etc/mosquitto
[Service]
Environment=PODMAN_SYSTEMD_UNIT=%n
KillMode=mixed
ExecStop=/usr/bin/podman rm -v -f -i --cidfile=%t/%N.cid
ExecStopPost=-/usr/bin/podman rm -v -f -i --cidfile=%t/%N.cid
Delegate=yes
Type=notify
NotifyAccess=all
SyslogIdentifier=%N
ExecStart=/usr/bin/podman run --name=mosquitto --cidfile=%t/%N.cid --replace --rm --cgroups=split --sdnotify=conmon -d -v /etc/mosquitto:/mosquitto/config -v /mosquitto/data -v /mosquitto/log --publish 1883:1883 --hostname hass docker.io/library/eclipse-mosquitto:latest
Rootless Containers
The rootless .container
files go into ~/.config/containers/systemd
. I'm using one to run Home Assistant.
$ find .* -name \*.container
.config/containers/systemd/homeassistant.container
My container file looks like this. I also gave it permission to /dev/ttyACM0
for a ZigBee radio, and CAP_NET_RAW,CAP_NET_BIND_SERVICE
to attempt to allow it to bind to any port, and monitor the network for new devices.
$ cat homeassistant.container
[Unit]
Description=Homeassistant
[Container]
AddCapability=CAP_NET_RAW,CAP_NET_BIND_SERVICE
AddDevice=/dev/ttyACM0
ContainerName=homeassistant
Environment=TZ=America/New_York
Image=ghcr.io/home-assistant/home-assistant:latest
Network=host
Volume=/home/me/hass/config:/config
PodmanArgs=--privileged --group-add=keep-groups
# GroupAdd=keep-groups # future release? https://docs.podman.io/en/latest/markdown/podman-systemd.unit.5.html
[Install]
WantedBy=default.target
The generated service file winds up in /run/<uid>/systemd
. Now an up-to-date Home Assistant container runs as my user when I start my Fedora server.
/run/user/1000/systemd$ cat generator/*
# Automatically generated by /usr/lib/systemd/user-generators/podman-user-generator
#
[Unit]
Wants=network-online.target
After=network-online.target
Description=Homeassistant
SourcePath=/home/me/.config/containers/systemd/homeassistant.container
RequiresMountsFor=%t/containers
RequiresMountsFor=/home/me/hass/config
[X-Container]
AddCapability=CAP_NET_RAW,CAP_NET_BIND_SERVICE
AddDevice=/dev/ttyACM0
ContainerName=homeassistant
Environment=TZ=America/New_York
Image=ghcr.io/home-assistant/home-assistant:latest
Network=host
Volume=/home/me/hass/config:/config
PodmanArgs=--privileged --group-add=keep-groups
# GroupAdd=keep-groups # future release? https://docs.podman.io/en/latest/markdown/podman-systemd.unit.5.html
[Install]
WantedBy=default.target
[Service]
Environment=PODMAN_SYSTEMD_UNIT=%n
KillMode=mixed
ExecStop=/usr/bin/podman rm -v -f -i --cidfile=%t/%N.cid
ExecStopPost=-/usr/bin/podman rm -v -f -i --cidfile=%t/%N.cid
Delegate=yes
Type=notify
NotifyAccess=all
SyslogIdentifier=%N
ExecStart=/usr/bin/podman run --name=homeassistant --cidfile=%t/%N.cid --replace --rm --cgroups=split --network=host --sdnotify=conmon -d --device=/dev/ttyACM0 --cap-add=cap_net_raw,cap_net_bind_service -v /home/me/hass/config:/config --env TZ=America/New_York --privileged --group-add=keep-groups ghcr.io/home-assistant/home-assistant:latest
Conclusion
systemd
has many features including user-mode process management, powerful cron
-like service timers, and now container management features that long-term Linux users may not be familiar with. After struggling through the sometimes-spotty documentation of this newish feature, we've managed to create a maintainable set of containerized services on our home Linux server.
Posted on October 30, 2024
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.
Related
November 27, 2024