Pin every image to at least a minor version, let Renovate open a pull request when a new tag appears, read the release notes only for stateful services, and reserve fully automatic updates for containers whose failure costs you nothing. The setup takes an evening; the alternative — :latest everywhere plus a nightly Watchtower — works right up until an image ships a breaking change at 3am and you get to diagnose it from memory, because nothing recorded what version you were on before.

Sort your containers into three tiers

TierExamplesStrategy
Stateless, trivial blast radiusdashboards, ntfy, whoami, exportersAuto-update weekly
Stateful, well-behaved migrations*arr apps, Jellyfin, Miniflux, NavidromeRenovate PR, merge within days
Databases, auth, storagePostgres, Authentik, Nextcloud, VaultwardenPinned minor, manual, backup first

The tiering question is simply "what happens if this breaks while I'm asleep?" A broken dashboard is nothing; a botched database migration is a restore. Most homelab update pain comes from applying one strategy — either full automation or full paranoia — across all three tiers, which guarantees it's wrong for two of them.

Pinning: what to write in the compose file

image: ghcr.io/paperless-ngx/paperless-ngx:2.14 — a minor version, not :latest and not a bare major. Minor tags get you patch releases automatically on pull while making every meaningful change an explicit diff in git. The strict end of the spectrum is digest pinning (image@sha256:...), which nails the exact bytes and defeats tag retagging; it's the right call for tier 3 if you're thorough, and Renovate manages digest bumps just as happily as tag bumps. The unacceptable end is :latest on anything stateful — it makes "what changed?" unanswerable, and unanswerable is the worst property an incident can have.

This presumes your compose files live in git, which is the real foundation of the whole scheme — the compose patterns article makes that case. Once they do, your update history is git log and your rollback is git revert.

Renovate: the update queue that reads changelogs with you

Renovate scans your repo for image references and opens a PR per available update, with release notes linked in the PR body. Run it as the hosted GitHub app for free, or self-host it against Gitea or Forgejo on a schedule. A homelab-sized config:

{
  "$schema": "https://docs.renovatebot.com/renovate-schema.json",
  "extends": ["config:recommended", ":dependencyDashboard"],
  "packageRules": [
    {
      "matchUpdateTypes": ["patch"],
      "automerge": true
    },
    {
      "matchDatasources": ["docker"],
      "matchUpdateTypes": ["major"],
      "dependencyDashboardApproval": true
    }
  ]
}

Patches automerge; minors wait for a click; majors don't even open a PR until you approve them from the dashboard issue. The effect on behaviour is the point: updates become a queue you triage over coffee — read notes, merge, git pull && docker compose up -d — instead of an ambient anxiety. Since adopting this I update tier-2 apps within about a week of release, which is faster than my :latest era, when updates happened whenever I remembered to feel nervous.

Watchtower in 2026, and what it's still for

Watchtower's original repository went quiet years ago; the actively maintained continuation lives at nicholas-fedor/watchtower, and it remains the simplest way to auto-update the tier-1 dross. Scope it with labels so it never touches anything that matters:

services:
  watchtower:
    image: nickfedor/watchtower:latest
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
    command: --label-enable --cleanup --schedule "0 0 5 * * 1"

Only containers labelled com.centurylinklabs.watchtower.enable=true update, Monday at 05:00, old images cleaned up. The alternative pattern — Watchtower in monitor-only mode as a pure notifier — is legitimate but redundant once Renovate is doing that job with better context.

The rollback plan you write before you need it

Three layers, cheapest first. Git: the previous tag is one git revert away — this is the rollback you'll actually use, and it's why pinning matters. Images: don't docker image prune in the same breath as updating; yesterday's image on disk is an instant downgrade path. Data: before any tier-3 major, dump or snapshot — a pg_dump, or a restic snapshot of the volume — because databases migrate schemas forward on startup, and a new-schema volume will refuse the old image. That last mechanism is the one that turns "downgrade" into "restore", and knowing it in advance is the difference between a five-minute revert and a long night.

The corollary nobody enjoys: updating regularly in small steps is itself a rollback strategy. Twelve version-jumps of accumulated migrations is exactly the upgrade that fails, and it fails on the night you finally had time.

What I'd do

Compose in git, everything pinned to minors, the Renovate config above (hosted app if your repo's on GitHub, self-hosted otherwise), Watchtower label-scoped to the trivia tier on Monday mornings. Tier 3 by hand, on a weekend morning, dump first, one service at a time. It sounds like ceremony; measured honestly it's fifteen minutes a week, and the fire you don't have isn't hypothetical — check any self-hosting forum on the morning after a major Immich or Nextcloud release and count the :latest casualties.