Auto-update & rollout¶
Scope: the course page's §11 covers the
AutoUpdate=label and the timer-enable commands. This page slows down on the mechanics of a single update cycle — what gets checked, what a "rollout" means for one Quadlet unit versus a fleet, and why rootless auto-update has a hidden dependency (linger) that's easy to miss until it silently stops firing.
TL;DR — in 30 seconds:
- A cycle is resolve → compare digest → pull → stop/start — and it's forward-only by default: without SDNOTIFY readiness reporting, the rollback path stays dormant and a bad update just crash-loops on the new image.
AutoUpdate=registrywatches the remote registry's digest;AutoUpdate=localwatches the local image (e.g. after a.buildrebuild) — pick based on where the new image actually appears.- Rootless auto-update needs
loginctl enable-linger— without it, the user systemd instance (and its timer) silently stops the moment the last login session ends.
1. One cycle, four steps — and a rollback most units never trigger¶
Each time podman-auto-update.timer fires, podman auto-update walks every container carrying the
io.containers.autoupdate label and, for each one labeled registry, does exactly this:
- Resolve the image reference against the registry to get the current digest.
- Compare that digest with the digest of the image currently backing the running container.
- If they differ, pull the new image.
- Stop the unit, then start it again — now
ExecStart=runs against the freshly pulled image.
That's the whole cycle. podman auto-update defaults to attempting a rollback when a restart fails
(--rollback is on by default) — but systemd only counts a restart as "failed" when the container reports
readiness via SDNOTIFY, which a plain .container unit doesn't send. Without that signal, a restart that
merely starts the process counts as success even if the app crashes moments later, so the rollback path
stays dormant and the unit crash-loops forward on the new image instead
(podman-auto-update(1)). If the
unit's [Container] section carries a HealthCmd=, systemd's own Restart= policy is what saves you
after the fact the same way — restarting on the new image, not reverting to the old one. Treat auto-update
as forward-only unless you've deliberately wired SDNOTIFY reporting yourself — not a canary or a
blue/green rollout.
2. registry vs local — two different triggers, not two speeds¶
AutoUpdate=registry and AutoUpdate=local don't mean "check often" vs "check rarely" — they watch two
different sources of truth for a new image:
AutoUpdate= value |
What triggers a replace | Typical use |
|---|---|---|
registry |
The remote registry's digest for the image tag has changed | Images you pull from Docker Hub / Quay / your registry — the common case |
local |
The local image with that tag has changed (e.g. after a podman build) |
Images built on the host via a .build Quadlet unit, never pushed anywhere |
local exists because a registry check is meaningless for an image that's never been pushed — there's
nothing remote to compare against. If you're iterating on a .build-managed image on the same host, local
is what notices your rebuild and restarts the container to pick it up; registry would just see a stable
remote digest and never fire.
3. "Rollout" means one unit at a time, not a fleet strategy¶
Quadlet and podman-auto-update operate at the level of a single systemd service — there is no
built-in concept of replicas, a rolling window, or ordering across containers. Two consequences worth
internalising before you rely on it in production:
- Each labeled unit is stopped and restarted independently, on its own schedule tick. If you run the
same image as two separate
.containerunits (say, on two hosts, or two pods), nothing coordinates them — both can restart at the same moment, both go through the same downtime window, and there's no drain-then-cutover step. This is the mechanism a single self-hosted service uses to stay patched, not the mechanism a multi-replica service uses to stay available during a deploy. - Pod membership doesn't change the unit boundary. A container inside a
.podis still checked and restarted as its own service; the pod's other members keep running unless they're independently labeled and also due for an update. Restarting one pod member briefly breaks whatever depended on it being up (shared network namespace, not shared lifecycle) — it does not tear down or recreate the pod itself.
For anything where a stop/start window is unacceptable, auto-update is the wrong tool — that's a job for an external deploy pipeline with health-gated rollout, not a nightly label-driven timer.
4. Rootless auto-update has a hidden dependency: linger¶
The system-wide podman-auto-update.timer runs under the root user's systemd instance and needs nothing
extra — it's alive as long as the host is up. The rootless form (systemctl --user enable --now
podman-auto-update.timer) runs inside your user's systemd instance, and a user systemd instance normally
stops the moment your last login session ends — SSH disconnect, logout, the lot.
loginctl enable-linger <user> is what keeps that user instance running after logout — the same setting
course.md §10–11 introduces for keeping any rootless Quadlet service alive unattended. Skip it, and the symptom is
easy to misread: the timer looks correctly enabled (systemctl --user list-timers shows it while you're
logged in), but it silently stops firing the moment your session ends — no error, no log, just a container
that quietly stops getting nightly updates until you happen to be logged in again. Check
loginctl show-user <user> --property=Linger if a rootless auto-update seems to have "stopped working" —
that's almost always the first thing to verify, before suspecting the registry or the label.
flowchart TB
Timer["podman-auto-update.timer<br/>(daily)"] --> Cmd["podman auto-update"]
Cmd --> Check{"AutoUpdate=<br/>registry or local?"}
Check -->|registry| Resolve["resolve tag → registry digest"]
Check -->|local| LocalCmp["compare vs local image digest"]
Resolve --> Cmp{"digest changed?"}
LocalCmp --> Cmp
Cmp -->|no| Skip["unit untouched"]
Cmp -->|yes| Pull["pull new image"] --> Restart["stop unit → start unit<br/>(no health gate, no rollback)"]
subgraph Rootless["rootless timer only"]
Linger["loginctl enable-linger"] -.->|"keeps user systemd<br/>alive after logout"| Timer
end
5. How this connects¶
02-pods-networks-volumes.md§3 already names this page's pod-membership point — a container inside a.podis checked and restarted as its own service, independent of the pod's other members. This page is where that claim is grounded: auto-update operates one systemd service at a time, with no concept of a pod as a restart unit.course.md§10–11 is whereAutoUpdate=registry/localand the rootlessloginctl enable-lingerrequirement are first introduced as setup steps; this page is the mechanics behind them — what a cycle actually checks, and why linger specifically (not some other setting) is the thing that keeps a rootless timer alive.- Podman's course page (module 03, Containers, §2) covers the plain-Podman alternative:
--restart=alwayspluspodman-restart.service, with the same rootlessloginctl enable-lingerdependency for boot survival — and names Quadlet as the better fix. This page is that better fix's own update-time counterpart: linger doesn't just keep a rootless container running across a reboot, it keeps the timer that patches it running across a logout.
Common gotchas
- Treating auto-update as a health-gated or canary rollout. Fix: it operates one systemd service at a time with no replica coordination, drain step, or (without SDNOTIFY) reliable rollback — treat it as forward-only patching for a single self-hosted service, not a deploy strategy.
- Using
AutoUpdate=registryfor a locally-built, never-pushed image. Fix:registrycompares against the remote digest, which never changes for an image that's never pushed — uselocalso apodman buildrebuild is what triggers the update. - Assuming a rootless
podman-auto-update.timerkeeps firing after logout. Fix: it needsloginctl enable-linger <user>— without it, the user systemd instance stops silently at the end of the session, with no error and no log. - Expecting a failed restart to roll back automatically. Fix: rollback only fires when systemd sees
the restart as "failed," which needs the container to report readiness via SDNOTIFY — a plain
.containerunit without that (or withoutHealthCmd=backingRestart=) just crash-loops forward on the new image instead.
Check yourself — a rootless podman-auto-update.timer was working fine, then silently stopped updating a container, with no errors anywhere. What's the first thing to check?
loginctl show-user <user> --property=Linger — a rootless user systemd instance stops the moment the
last login session ends unless linger is enabled; that's the most common reason a rootless timer "just
stops" with no error trail.
Check yourself — you have an image built locally with a .build Quadlet unit and never pushed anywhere. Which AutoUpdate= value actually picks up a rebuild, and why doesn't the other one work?
local — it compares against the local image's digest, which changes on rebuild. registry compares
against the remote registry's digest, which is meaningless (and never changes) for an image that's
never pushed.
You can defend this when you can walk through the four steps of one auto-update cycle and say where a
health check or rollback would (and wouldn't) fit; explain when local fires and registry doesn't (and
vice versa); say why restarting one Quadlet unit isn't the same as a rolling deploy across replicas or pod
members; and explain why a rootless podman-auto-update.timer needs loginctl enable-linger to keep firing
after you log out.