Podman Quadlet: make your container be systemd unit
Quadlet na di systemd generator wey dey turn one web.container file to web.service, so your Podman container go come back afta reboot without daemon or compose file.
Wetin be Podman Quadlet
Podman Quadlet na di systemd generator wey Podman dey ship. You go write one small text file like web.container, put am inside ~/.config/containers/systemd/, run one reload, and systemd go get one ordinary service wey dem dey call web.service. From dat moment, your container na systemd unit like any oda unit for di VPS, so e fit come back afta reboot with no Docker daemon and no compose file for anywhere.
Na dis one dey make Podman complete. Podman no get background daemon wey dey watch your container, wey mean say notin dey there to bring am back when e die or when di box reboot. Systemd na di ting wey already dey run for your VPS, so Quadlet just dey hand di work give am. If you never read why Podman no need daemon di way Docker need one, check am first, because dat gap na exactly wetin Quadlet dey fill.
Every command for dis page na you go run am for your own VPS. Take dem one by one, and check each one before you move to di next.
Where di Quadlet file suppose dey
For rootless (you dey use your normal user account, no sudo), di main folder na $XDG_CONFIG_HOME/containers/systemd/, wey almost always be ~/.config/containers/systemd/. Quadlet also dey read $XDG_RUNTIME_DIR/containers/systemd/ for temporary unit, plus /etc/containers/systemd/users/ wey admin fit take drop unit for user.
For root, di folder na /etc/containers/systemd/. /run/containers/systemd/ na for temporary unit, and /usr/share/containers/systemd/ na for wetin di distro package ship.
Di rootless folder no dey exist from factory, so create am yourself.
mkdir -p ~/.config/containers/systemdPut di file for wrong folder and notin go happen at all. No error dey show, because di generator no even see di file. Dat na di first ting to check when your unit no wan show.
How you go check say Quadlet dey your box
No dey trust wetin any blog talk about which release carry which version. Ask your own box.
podman --version
ls -l /usr/lib/systemd/user-generators/
ls -l /usr/lib/systemd/system-generators/ | grep podmanDi generator na one symlink wey dem dey call podman-user-generator (for rootless) or podman-system-generator (for root), and di two dey point go di same binary for /usr/libexec/podman/quadlet. If di user one no dey inside /usr/lib/systemd/user-generators/, your .container file for ~/.config no go ever turn service, because di systemd user manager no get anybody to call. Some package build don ship without dat symlink before, so na real check, no be ceremony.
Di fastest way to know whether Quadlet read your file na di dry run. E no dey write anytin.
/usr/libexec/podman/quadlet --user --dryrunRead wetin e print. Dat na di service file wey systemd go actually use. If your unit no appear inside am, di problem dey for di file or for di folder, no be for systemd.
Di four unit kind: .container, .volume, .network, .pod
.container na di main one. E dey describe one container: which image, which port, which mount. Quadlet dey turn web.container to web.service, so di service name na di file name.
.volume dey create named Podman volume. Di volume wey webdata.volume create dey carry di name systemd-webdata, because Quadlet dey add systemd- for front unless you set di name yourself.
.network dey create Podman network di same way, so webnet.network dey give you network wey dem dey call systemd-webnet.
.pod dey create pod, and container fit join am with Pod=name.pod inside di .container file. Pod na group of container wey dey share di same network namespace, so dem fit reach each oda for localhost.
Oda kind dey too, like .kube, .image, .build and .artifact, but di four wey dey up na di ones wey you go touch every day.
Example wey full ground: container wey get volume and network
Three file, all of dem inside ~/.config/containers/systemd/.
First, webnet.network:
[Unit]
Description=Network for di web stack
[Network]Di [Network] section fit empty, and di default go do di work. If you want fixed range you fit add Subnet=10.89.10.0/24 inside am.
Next, webdata.volume:
[Unit]
Description=Data volume for di web stack
[Volume]Then di container itself, web.container:
[Unit]
Description=Caddy web server
After=network-online.target
[Container]
Image=docker.io/library/caddy:2
ContainerName=web
PublishPort=8080:80
Volume=webdata.volume:/data
Network=webnet.network
[Service]
Restart=always
[Install]
WantedBy=default.targetMake we go line by line. Image= na di only line wey one .container must get. Use di full name with registry, because short name fit resolve to different registry depending on wetin dey inside registries.conf.
ContainerName=web dey set di name wey podman ps go show. Leave am out and Quadlet go call di container systemd-web, wey dey confuse person wey dey find am by hand.
Volume=webdata.volume:/data dey point go di .volume unit, no be to folder for disk. Quadlet dey see dat .volume name and e dey add di correct ordering, so di volume dey ready before di container start. Network=webnet.network dey work di same way.
PublishPort=8080:80 dey open port 8080 for di host side. As rootless user you no fit bind port under 1024 by default, because di kernel dey reserve dem for root. Na why we use 8080, and you fit put reverse proxy for front if you need port 80.
Restart=always inside [Service] na plain systemd, no be Podman. Quadlet dey generate Type=notify service, wey mean say systemd dey wait make Podman talk say di container don ready before e mark di unit as started. If dat kind ting new for you, wetin Type=notify and Type=simple really mean go explain am, and which restart policy suppose enter your unit go help you choose between always and on-failure.
Reload, start am, and check di status
systemctl --user daemon-reload
systemctl --user start web.service
systemctl --user status web.servicedaemon-reload na di step wey dey run di generator again. Until you run am, systemd no sabi say your file dey, because generator dey run for boot and for reload only.
For root unit inside /etc/containers/systemd/, na sudo systemctl daemon-reload and sudo systemctl start web.service you go run.
Two more command dey show you wetin dey happen.
podman ps
journalctl --user -u web.service -n 50One ting to sabi well: no try edit web.service. Dat file na generated file wey dey live for temporary location, so any change you make go vanish for di next reload. Edit web.container, then reload.
Why systemctl enable no dey work for Quadlet
Dis one dey catch plenty people. systemctl --user enable web.service no dey work for Quadlet unit, because systemd no dey allow person enable unit wey generator create. Di unit no dey inside any permanent folder, so notin dey there to link.
Wetin dey bring di service up for boot na di [Install] section inside di Quadlet file itself.
[Install]
WantedBy=default.targetFor rootless user, default.target na wetin di user manager dey boot into, so dat line na di correct one. For root unit, use WantedBy=multi-user.target. You fit even put di two for di same line. Afta you add or change [Install], run daemon-reload again, because na di generator dey plant di wanted-by link.
If you forget dat section, di service go still start when you type systemctl --user start web.service by hand, but e no go come back afta reboot. Dat na quiet failure, and na di kind ting wey person go only notice di day di box reboot for midnight.
Why your container dey die when you comot for SSH
Rootless Podman dey run under your user systemd manager, wey dem dey call user@1000.service for di user wey get UID 1000. By default dat manager dey start when you login, and e dey stop when your last session comot. So your container fit die di moment you close SSH, even though everytin work fine while you still dey inside.
Di fix na lingering.
loginctl enable-linger $USER
loginctl show-user $USER --property=LingerWith linger on, systemd dey start your user manager for boot even if you never login at all, and e no dey kill am when your session end. Any rootless container wey suppose dey up 24 hours need dis one. Na di same wahala dey worry Ollama wey you dey run rootless with Podman, because di model server go just comot di moment your shell close.
For root unit you no need linger, because di system manager no dey tied to any login session.
podman generate systemd na di old road
If you copy setup from blog post wey dem write two or three years back, you go see podman generate systemd --new --name web > ~/.config/systemd/user/web.service and then systemctl --user enable web.service. Podman documentation don mark dat command as deprecated, and e dey tell person make e use Quadlet file instead.
Di difference matter. podman generate systemd dey write static .service file wey you own, and you go enable am by hand, so di old instruction about enable make sense for dat old road. Quadlet dey generate di service fresh from your .container file every reload, so di Quadlet file na di source of truth and [Install] na di way to enable. Mix di two and you go end up with container wey dey start twice, or one wey no dey start at all.
Wetin dey commonly spoil, and why
Wrong extension. web.containers or web.conf no go do anytin, because di generator only dey pick di exact extension wey e sabi.
No reload afta edit. Di generator dey run for boot and for daemon-reload only, so your change go just sit for disk dey do notin.
SELinux. For Rocky, Alma or Fedora, bind mount from host folder fit dey denied until you add :Z for di end of di Volume= line, like Volume=/srv/site:/usr/share/caddy:Z. Named volume no get dis wahala.
Image wey never dey local. Di first start fit take long because Podman dey pull di image, and if di start timeout too small e fit look like failure. TimeoutStartSec=900 inside [Service] dey give am room.
User bus no dey reachable. If you run systemctl --user through sudo or inside script wey no get XDG_RUNTIME_DIR set, di user manager no dey reachable at all. Login as dat user proper, or set XDG_RUNTIME_DIR=/run/user/$(id -u) before di command.
If wetin you dey do na to move compose stack come Quadlet, read how compose stack dey come back afta reboot first, because di two approach dey solve di same problem with different tool, and e dey easier to move when you sabi wetin you dey leave behind.
FAQ
Why my Quadlet service no dey start afta reboot?
Two ting dey cause am pass every oda one. First, di [Install] section with WantedBy=default.target (rootless) or WantedBy=multi-user.target (root) no dey inside your .container file, so notin dey pull di service for boot. systemctl enable no fit fix dis, because di unit na generated unit. Second, for rootless, linger no dey enabled for your user, so your user manager no dey start until somebody login. Run loginctl enable-linger $USER, then check am with loginctl show-user $USER --property=Linger.
I run systemctl --user enable and e fail. Wetin I go do?
Na normal. You no dey enable Quadlet service with systemctl. Put [Install] with di correct WantedBy= inside di .container file, run systemctl --user daemon-reload, and di generator go handle di rest. Use systemctl --user start web.service when you wan start am now, and systemctl --user status web.service to look wetin dey happen.
Make I still use podman generate systemd?
No. Podman documentation don mark am deprecated and e dey point person go Quadlet. If you get old .service file wey dat command write inside ~/.config/systemd/user/, disable and delete am before you write di Quadlet version, so di two no go fight over di same container name.
Di .service file wey Quadlet generate dey where?
E no dey for any folder wey you suppose edit. Di generator dey write am fresh for temporary location every time systemd reload, so any hand edit go disappear. To see exactly wetin e go generate, run /usr/libexec/podman/quadlet --user --dryrun for rootless and read wetin e print. Change web.container when you wan change anytin.
How Quadlet dey name di volume and network wey e create?
Quadlet dey add systemd- for front of di unit name. So webdata.volume dey give volume wey dem dey call systemd-webdata, and webnet.network dey give network wey dem dey call systemd-webnet. Inside di .container file you still dey write di unit name (Volume=webdata.volume:/data), no be di prefixed name, because Quadlet dey do di translation and di ordering for you.