Skip to content

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=registry watches the remote registry's digest; AutoUpdate=local watches the local image (e.g. after a .build rebuild) — 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:

  1. Resolve the image reference against the registry to get the current digest.
  2. Compare that digest with the digest of the image currently backing the running container.
  3. If they differ, pull the new image.
  4. 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 .container units (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 .pod is 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 .pod is 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 where AutoUpdate=registry/local and the rootless loginctl enable-linger requirement 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=always plus podman-restart.service, with the same rootless loginctl enable-linger dependency 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=registry for a locally-built, never-pushed image. Fix: registry compares against the remote digest, which never changes for an image that's never pushed — use local so a podman build rebuild is what triggers the update.
  • Assuming a rootless podman-auto-update.timer keeps firing after logout. Fix: it needs loginctl 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 .container unit without that (or without HealthCmd= backing Restart=) 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.