Head to head
Watchtower vs Diun
Watchtower's repository went read only on 17 December 2025 with no official successor. That changes the answer, but it was not the strongest argument against unattended updates in the first place.
Watchtower or Diun?
Run Diun. The original Watchtower repository, containrrr/watchtower, was archived and made read only on 17 December 2025, its last release was v1.7.1 in November 2024, and its maintainers declined to endorse any fork. Diun is MIT licensed, actively released (v4.33.0 in May 2026), and only ever sends a notification: it never pulls an image and never restarts a container, so a bad upstream release cannot reach your data while you are asleep. The cost is that you run docker compose pull && docker compose up -d yourself, on purpose, after reading the release notes.
Watchtower polled the Docker API, pulled newer images and recreated your containers without asking. Diun polls the registry, compares manifests, sends a message and stops. One of them is archived, and the other would still be the right default if it were not.
Watchtower is archived and nobody blessed a successor#
containrrr/watchtower was archived and made read only on 17 December 2025. The last release was v1.7.1, in November 2024, so the image most people still run has been frozen for over a year and a half. The maintainers' own note is plain about why: they stopped being Docker users and lost interest.
They also explicitly declined to bless a successor, and warned that several of the active forks are, in their words, full of AI slop. The fork with the most visible activity is nicholas-fedor/watchtower, which publishes the nickfedor/watchtower image. That is a statement about activity, not an endorsement. Evaluating a fork is your due diligence: read the diff against v1.7.1, check who holds push access, and decide whether you trust that with root on your host.
There is a practical failure on top of the governance one. The frozen v1.7.1 predates recent Docker Engine API changes, and there are open reports of it failing against Docker 29 and later. The symptom is the bad kind: it runs, logs nothing alarming, and updates nothing. Silence looks identical to having nothing to update, so an estate drifts for months.
What the spec table decides#
Three rows carry the argument, and RAM is not one of them: 15 MB idle against 25 MB is noise on any machine big enough to run the services underneath it. The rows that matter are maturity (legacy against stable), datastore (nothing at all against a single bbolt file at /data/diun.db) and ops load. Watchtower scores 2 on the ops rubric, not because it asks anything of you, but because an unmaintained binary with root-equivalent socket access is attention you owe it whether or not you spend it.
| Specification | Watchtower | Diun |
|---|---|---|
| Licence | Apache-2.0 (Permissive) | MIT (Permissive) |
| Written in | Go | Go |
| First release | 2015 | 2019 |
| Maturity | Slowed | Stable |
| Datastore | none, it reads the Docker API | bbolt, a single file at `/data/diun.db` |
| Services to run | 1 | 1 |
| Idle memory | 15 MB | 25 MB |
| Memory in use | 40 MB | 60 MB |
| Operational load | 2 / 5, Light | 1 / 5, Set and forget |
| Identity | Not applicable | Not applicable |
| arm64 builds | Yes | Yes |
| Default ports | none | none |
| Backup shape | File copy | Embedded key-value store |
Auto-updating a database-backed service is how people lose data#
This does not depend on the archive news at all: notify-only is the right default for any container holding state.
PostgreSQL, MariaDB, Nextcloud, Immich and Vaultwarden all run one-way schema migrations on start. That is correct behavior for an application that owns its data, and it is also the entire problem. A 3am pull of a moving tag applies a migration you did not read, at a time you cannot supervise, against data you have not just backed up. There is no rollback, because that would need the old binary to read a schema which no longer exists on disk. The data has already been rewritten.
The specific pattern to name is the moving-tag major jump: :latest and :stable resolve to whatever upstream published most recently, and upstream is under no obligation to stay on the same major version. That is how a Nextcloud instance crosses two majors overnight and refuses to start, or a PostgreSQL container comes up declining to read a data directory written by an older engine. Nothing warned you, because nothing in the tag said a major had changed.
The corollary is that pinning does not save automation, it defeats it. Pin every stateful service to a minor line, which you should, and an unattended updater may only apply patch releases: you keep the socket risk and throw away most of the benefit. The honest move then is to stop pulling and start notifying. An update strategy that does not lose data covers pinning conventions, and Backing up a running database covers why a live cp of a running database is not a backup.
What auto-update actually is safe for#
Refusing to automate anything is its own dishonesty. Unattended updates are fine when three things are true at once:
- The container is stateless. Its whole state is a config file in git, or nothing. A reverse proxy, a DNS resolver, a metrics exporter, a static site.
- The tag is pinned to a minor line.
caddy:2.10, notcaddy:latest. You are accepting patch releases, not majors. - A restart is invisible. If it coming back 20 seconds later unattended would be an incident, it does not qualify.
Development hosts qualify wholesale, as do the boring edges of a home stack. Keep those compose files separate from the ones defining stateful services, which Docker Compose conventions argues for anyway, so the label enabling automation cannot land on the wrong container.
The Docker socket is root on the host, for both of them#
Both tools mount /var/run/docker.sock, and both inherit the same truth: anything that can talk to that socket can start a privileged container with the host filesystem mounted and own the machine.
Diun is better placed here, but only in degree. Mount it :ro and Diun still works, because it only ever reads, which closes the easy path of creating a container. It does not stop anything reading the environment variables, mounts and labels of every container on the host, which is where API keys and database passwords live. Read-only is a useful reduction, not a boundary. For a real one, put a socket proxy in front and allow only the list and inspect endpoints. Watchtower has no equivalent option: it needs write access to do its job, so hardening it is not possible in principle.
Watchtower's other quiet failure: a full disk#
Without --cleanup, every superseded image stays on disk as dangling layers, and a weekly cycle over a dozen containers fills a small VPS in months. The failure never mentions Watchtower: it appears as write errors from some unrelated service, usually a database, usually at the worst time. Run with --cleanup or schedule docker image prune -f, and alert on free space at the Docker data root. Diun avoids this by never pulling anything.
A Diun config that actually notifies you#
Diun is opt-in by default. watchByDefault is false, so a fresh install with the Docker provider enabled watches exactly zero containers and reports nothing, which is indistinguishable from everything being up to date.
services:
diun:
image: crazymax/diun:4.33.0
container_name: diun
command: serve
restart: always
volumes:
- ./data:/data
- /var/run/docker.sock:/var/run/docker.sock:ro
environment:
TZ: Europe/Berlin
DIUN_WATCH_SCHEDULE: "0 */6 * * *"
DIUN_WATCH_FIRSTCHECKNOTIF: "false"
DIUN_PROVIDERS_DOCKER: "true"
DIUN_NOTIF_NTFY_ENDPOINT: "https://ntfy.example.org"
DIUN_NOTIF_NTFY_TOPIC: "diun"
DIUN_NOTIF_NTFY_TOKEN: "tk_replace_me"
labels:
- "diun.enable=true"Every service you want watched then carries diun.enable=true, including Diun itself as shown above.
Three defaults to know. runOnStartup is true and firstCheckNotif is false, which together mean the first run silently baselines everything instead of firing one alert per container. DIUN_WATCH_WORKERS controls concurrent registry lookups, and the constraint is registry rate limits, not CPU. And diun.watch_repo=true enumerates every tag in a repository rather than the one you run, which across a whole stack is how you collect Docker Hub 429s that look like a registry outage. Bound it with diun.max_tags and diun.include_tags, or leave it off.
The notifier is the whole product, so test it. Set DIUN_WATCH_FIRSTCHECKNOTIF=true for exactly one run, confirm the message arrives on your phone, then set it back. A wrong token means Diun logs an error and carries on, which you experience as an estate where nothing ever needs updating. Pick the receiver first: ntfy vs Gotify compares ntfy and Gotify, and either works with the DIUN_NOTIF_* variables above.
The workflow this actually buys you#
Pin tags, get notified, read the release notes, snapshot or dump, then update on purpose:
# 1. record the digest you run now, so you can pin back
docker inspect --format '{{.Image}}' immich_server
# 2. dump the database while the OLD version is still running
docker compose exec -T database \
pg_dumpall -U postgres > /srv/backup/immich-$(date +%F).sql
# 3. pull, restart, and watch it come up
docker compose pull
docker compose up -d
docker compose logs -f --tail=100 immich-serverStep 2 is the one people skip, and the only one that makes step 3 reversible. A dump taken after the migration has run is a backup of the new schema, which is not what you want when the new version is the problem. Store it off the host with restic; Backups that actually restore covers testing that it restores.
If you want a button rather than a shell for step 3, Komodo and Portainer both give you per-stack pull and redeploy with the release notes open in another tab. Portainer vs Komodo picks between them.
The uncomfortable truth about notify-only#
Notify-only with nobody reading is strictly worse than no strategy at all, because it feels like coverage. Ignore Diun for six months and you are badly behind on security patches, on containers you believe are watched. The automated updater, for all its risks, would at least have applied them.
So the honest condition on this recommendation is that notifications only count if they land somewhere you actually look: a push topic on your phone, in a channel you have not muted, not an email address you check monthly. If that will not happen, the answer is not Diun and not Watchtower, it is fewer self-hosted services.
Which one for your situation#
| Your situation | Use | Why |
|---|---|---|
| Any container with a database behind it | Diun | One-way migrations, no rollback, nobody awake at 3am |
| A reverse proxy or other stateless service | Either, pinned | Auto-update is genuinely fine on a pinned minor tag |
| A fleet of machines | Diun | Notifications centralize; unattended pulls multiply the blast radius |
| You want zero notifications and zero thought | Neither | That is a wish, not a strategy. Pin tags and update quarterly |
| You are on Docker 29 or newer | Diun | v1.7.1 has open reports of failing against newer Engine APIs |
| Watchtower runs fine for you today | Migrate anyway | Frozen at v1.7.1 with root-equivalent socket access |
| Development or test hosts only | A maintained fork | A broken container is an inconvenience, not an incident |
What to do next#
If you run Watchtower today, do three things this week, in this order. Add --label-enable so it can only touch containers carrying com.centurylinklabs.watchtower.enable=true, and strip that label from everything stateful. Run docker logs watchtower --tail 100 to confirm it is working at all rather than failing against your Engine version. Then deploy Diun with the compose file above, confirm one test notification arrives, and delete the Watchtower container.
Starting fresh, use Diun with pinned minor tags and skip the auto-updater entirely. Read An update strategy that does not lose data before you pin, and make sure the backup you would restore from actually restores. The Platforms category has the rest of the container tooling.
Questions#
Is Watchtower dead?
The original is. containrrr/watchtower was archived on 17 December 2025 and the repository is read only. The last release was v1.7.1 in November 2024, so the published image has been frozen for over a year and a half. The maintainers' own note says they stopped being Docker users and lost interest in the project. No security fixes are coming, for Watchtower or for its frozen dependency tree, and there is no official successor.
What should I replace Watchtower with?
For anything holding state, replace it with notifications rather than with another updater: Diun watches the same containers, sends a message when a digest or tag changes, and never writes to the Docker socket. For genuinely stateless containers on pinned tags, a maintained fork or a small scripted docker compose pull on a schedule both work. The point is to make the decision to upgrade a deliberate one rather than removing it.
Is the nicholas-fedor Watchtower fork safe to use?
It is the fork with the most visible activity and it publishes the nickfedor/watchtower image, but nobody has vouched for it. The original maintainers deliberately declined to bless a successor and warned that several active forks are, in their words, full of AI slop. Evaluating a fork is your due diligence, not an endorsement you inherit: read the diff against v1.7.1, check who can push, and check whether releases are signed.
Can Diun update containers automatically?
No, by design. Diun resolves each watched image against its registry on a schedule and fires a notification when the digest or tag changes. It has no code path that pulls an image or restarts a container, which is exactly why it is safe to point at a database. If you script something around its webhook notifier to pull and restart, you have rebuilt Watchtower and taken on the same risk with fewer eyes on the code.
Why does Diun notify me every night about the same image?
Because it compares digests, and a mutable tag like :latest or :stable changes digest whenever upstream rebuilds it, including for a base image security patch with no version change to read about. That is technically correct and practically noise. Watch a specific minor tag where the project publishes one, leave diun.watch_repo off, and reserve digest watching for images that change meaningfully.
Does Diun need access to the Docker socket?
Only for the Docker provider, which discovers what you are running from container labels. Mount it read only, since Diun never writes. If you would rather not expose it at all, use the static file provider and list images explicitly, or put a socket proxy in front and allow only the container list and inspect endpoints. Diun also supports Swarm, Kubernetes, Nomad and containerd providers.
Sources#
- Archived containrrr/watchtower repository with the unmaintained notice
- Maintainers' archival announcement, discussion 2135
- Watchtower releases, last one v1.7.1 in November 2024
- The most active fork, nicholas-fedor/watchtower
- Diun documentation site
- Diun Docker provider: labels and watchByDefault
- Diun watch configuration: schedule, workers, firstCheckNotif
- Diun releases, v4.32.0 and v4.33.0
Published . Last reviewed . Found something out of date? Tell us and we will fix it and log the change.