Skip to content
Infrastructure

Researching update automation for the homelab

By Victor Da Luz
homelab docker proxmox updates automation self-hosted

I counted the services running in my homelab and got to twenty-three. Then I tried to answer a simpler question: when was each of them last updated? I could not. Updates happened when I remembered, which in practice meant some containers were current, some were months behind, and a few were on whatever image existed the day I deployed them. This post is the research spike that came out of that: what tools exist for keeping self-hosted services updated, what tradeoffs they carry, and what I decided to do about it.

What I’m actually updating

The shape of the problem matters more than the tool list, so here is the shape. Most services run as Docker Compose stacks inside Proxmox LXC containers, one service per container. That means three distinct update surfaces:

  • Container images, the application itself. This is where most of the drift lives.
  • System packages inside each LXC, the Debian base that Docker runs on.
  • The Proxmox hosts, which I am deliberately leaving out of any automation.

The supporting pieces were already in place before this research: nightly container backups, health checks defined on many of the compose stacks, uptime monitoring, and a notification relay that funnels alerts to where I read them. That existing plumbing ends up shaping the decision more than any feature comparison.

One more confession from the audit: my image tags are inconsistent. Some services pin versions, like postgres:15-alpine. Others ride latest. A latest tag plus no update process is the worst of both worlds: you do not control when changes arrive, and you also do not get them until you happen to pull.

The contenders

Four tools cover the realistic options for Docker image updates, and they sit at distinct points on the automation spectrum.

Watchtower is the name everyone knows: it watches your containers and replaces them when a new image appears. Fully automatic. The problem is that the original project has been unmaintained since 2023, and it shows. Against current Docker engines it fails outright with client version 1.25 is too old. There is a maintained fork (nicholas-fedor/watchtower) that fixes the API compatibility, but building my update strategy on an abandoned project’s fork gave me pause. The deeper issue is the model itself: fully automated replacement with no review step means a breaking change lands at 3am and I find out when something stops working.

Diun takes the opposite stance: it never touches your containers. It watches registries on a schedule and tells you when an image you run has a new version. Notification channels are excellent, including a direct integration with Apprise, which I already run as my notification hub. The cost is obvious: someone still has to apply the update, and that someone is me.

Renovate treats updates as code review. It scans a Git repository, finds version references (Docker tags among many other ecosystems), and opens pull requests when updates exist. Since my compose files already live in Git, this is philosophically appealing: every update becomes a reviewable, revertable commit. But it only fits services managed through the Git workflow, and it is the most setup of the four.

What’s Up Docker (WUD) lands between Diun and Watchtower: a web dashboard showing every container and its update status, with one-click manual updates and multi-host support. Nice to look at, but it adds another web service to maintain, and the one-click update still needs me at a browser.

The pattern across all four: the more automated the tool, the less control at exactly the moment something breaks. That is not a flaw, it is the tradeoff, and the right answer depends on the service.

System packages are a different problem

Container images get the attention, but every LXC also has a Debian system underneath that needs security patches. For that, the boring native answer is the right one: unattended-upgrades, restricted to the security origin only:

Unattended-Upgrade::Origins-Pattern {
    "origin=Debian,codename=${distro_codename}-security,label=Debian-Security";
};

Security-only is the line that makes this safe to automate. Security patches are small, targeted, and tested; general package upgrades can restart daemons and change behavior. With the origin restricted, the risk profile is low enough that I am comfortable letting every container patch itself. A dry run shows what it would do before you commit:

unattended-upgrade --dry-run --debug

The Proxmox hosts stay manual. Hypervisor updates can touch the kernel, the cluster stack, and ZFS, any of which can require a reboot that takes every service on the node down with it. That is a read-the-release-notes-first operation, not a cron job.

Full auto, notify-only, or hybrid

With the tools mapped, the strategy question is which services get which treatment. Three options:

  1. Fully automated everywhere: lowest effort, always patched, and one bad release away from silent breakage.
  2. Notify-only everywhere: full control, but twenty-three services’ worth of notifications to act on, and updates start lagging the moment discipline slips.
  3. Hybrid: automate where breakage is cheap, review where it is not.

The hybrid split requires being honest about which services are actually critical. My list: the reverse proxy, DNS, home automation, and the monitoring stack itself. If any of those breaks, either the network degrades for everyone in the house or I lose the ability to see what else broke. Dashboards, media servers, and productivity apps are the opposite: if one is down for a morning, nothing else cares.

So: critical services get notifications and a human. Non-critical services can update themselves. System security patches run automatically everywhere. Proxmox stays manual.

Safety nets before any automation

None of the above is safe without a way back. Three mechanisms cover it.

Proxmox snapshots are the cheap insurance. One command before any risky update, instant rollback if it goes wrong:

pct snapshot 116 before-update-$(date +%Y%m%d%H%M%S)

Nightly backups already cover the slower path: if a bad update corrupts something a snapshot rollback cannot fix, the container restores from last night’s state.

Health checks close the loop. A compose healthcheck plus uptime monitoring means a broken update announces itself within a minute instead of waiting for me to notice a dead web UI.

One gotcha from this research worth repeating: Docker Compose’s rollback_config does nothing on standalone Compose. Those options only function under Docker Swarm. On a plain Compose host your rollback options are reverting the image tag, rolling back the snapshot, or restoring the backup, so the snapshot habit matters more than it first appears.

The decision

Hybrid, in phases, starting with the lowest-risk piece:

  1. Now: unattended-upgrades, security-only, in every LXC. Smallest risk, immediate value.
  2. Next: Diun watching all container images, notifying through Apprise. Then automation for the non-critical tier, either the Watchtower fork or an Ansible playbook driven by the same notifications.
  3. Later: written runbooks for the services that need ordered updates (anything with a database migration), and pre-update snapshots wired into the process instead of relying on me remembering.
  4. Maybe: Renovate, if I migrate the compose files to a full PR workflow.

I also came out of the research with a homework item that no tool solves: migrating the latest tags to pinned versions. Notification tools tell you a new version exists; that information is only actionable when you know what version you are on.

Lessons

  • There is no single update tool for a homelab, because there is no single update problem. Container images, system packages, and the hypervisor have different risk profiles and need different answers.
  • Notification-only tools like Diun are underrated. The hard part of updates is not running docker compose pull, it is knowing an update exists and deciding when to take it.
  • Automate by blast radius, not by enthusiasm. The honest critical-versus-non-critical list was the most useful artifact of the whole exercise.
  • Security-only unattended-upgrades is the rare case where full automation is the conservative choice.
  • Check your assumptions about rollback before you need it. I assumed Compose could roll back a bad update; it cannot outside Swarm.
  • An unmaintained tool is a liability even when it still works. Watchtower failing against modern Docker is what happens two years after the last maintainer leaves.

Related reading

Infrastructure

Self-hosting file sync with Syncthing, kept local-only

I wanted Dropbox-style file sync across my own devices without putting the files on anyone else's servers, and without announcing my devices to the internet. Here is how I deployed Syncthing on Docker-in-LXC and ran it entirely on my LAN.

Read
Infrastructure

Deploying RomM, a self-hosted ROM manager, in the homelab

I wanted a web-based, self-hosted way to organize a pile of game backups with real metadata. RomM was the only option that fit. Here is the Docker-in-LXC deployment, the two gotchas that cost me time, and the NAS mount mistake that bit me later.

Read

Ready to Transform Your Career?

Let's work together to unlock your potential and achieve your professional goals.