Skip to content
BlogOctober 29, 2026

An Image Cooldown Is Not a Migration Plan

Rohi Rikman
An Image Cooldown Is Not a Migration Plan
At 04:00 every day, Watchtower checks every container in my homelab's homelab-compose scope for a newer image. Databases are in that scope, and the PostgreSQL ones run postgres:latest. The comment above --cooldown-delay 72h in the Compose file calls the delay "the one control that makes unattended database updates less reckless." A database-breaking image does not become compatible because it has spent three days in a registry. The cooldown is a concrete update policy. It is not evidence that the next PostgreSQL image can use the existing data directory, or that independently published application images belong together. Unattended updates make it easy to confuse a quieter deployment process with a safer release. Everything below is the policy as checked in, not a reading of the running container. The Watchtower definition uses ghcr.io/nicholas-fedor/watchtower:latest with --label-enable and the scope homelab-compose. A container has to declare both its enabled state and the matching scope to be selected under that contract. That makes selection deliberate. A conventional Watchtower label brought in by an unrelated stack is not enough by itself. The repository's image-policy gate adds a negative check. It reports an image-bearing Compose service with no update-enable label, an enabled service with the wrong scope, or a scope attached without an enabled selection. The point is to make a missing decision visible. Watchtower silently ignoring an unlabelled container would otherwise look much like a container that simply has not needed an update. The gate source also states its limits. It does not contact registries, read running containers, prove signatures or establish runtime compatibility. Its test file defines negative controls for the label rules. An explicit update choice is evidence about selection, and only about selection. A cooldown provides a waiting period before an image becomes eligible. It creates an opportunity for somebody else to discover a problem and for an upstream publisher to respond before this updater acts. An opportunity is not an assurance. My own Watchtower notes go further. The Compose comment says a release broken badly enough to matter is usually pulled or patched within a day, and the README adds that a freshly pushed malicious image never gets applied at all. Neither belongs in an operating guarantee. Nothing in the Compose file demonstrates that a bad release will be withdrawn before the delay expires. An image can remain incompatible after the waiting period. A harmful image can remain available too. Credit the setting exactly as far as it goes. It decides when an image becomes eligible, and if the creation timestamp cannot be read, the update is deferred rather than applied. It does not test the image against this host's data or authenticate the intent of whoever published it. That narrower description is less comforting. It is also the one I can use when deciding what still needs checking. The Infisical Compose source declares postgres:latest with a persistent host data mount. The Watchtower risk record, RISK-008, explicitly includes databases in automatic updates and names the failure it accepts: a new PostgreSQL major refuses to start on a data directory written by the previous one. The updater can successfully replace the container while the database cannot start. That is a software-and-data compatibility problem. Waiting longer does not migrate the directory. Nor does a successful registry pull establish that a migration is unnecessary. I accepted RISK-008 on 2026-09-18 knowing no backups existed yet; I had deferred them the day before. That is why the record names the worst case as unrecoverable data loss for n8n workflows and the Infisical store rather than an inconvenient restart. Backups are still the open item: the procedure is written down in RB-11, but the backup job itself does not exist yet. Until it does, the cooldown is buying time, not safety. Do not copy that policy because it appears in my repository. A documented risk acceptance makes the tradeoff inspectable; it does not turn the tradeoff into a default for somebody else's database. If your requirement is that an unattended replacement cannot cross a database major without review, a delay is the wrong control to ask for that guarantee. The n8n source exposes another boundary. It declares n8nio/n8n:latest and n8nio/runners:latest, and the comment above the runner says n8n requires the two versions to match. The next line gives the mechanism: "Both stay in the same Watchtower scope so updates happen in one window." The runner also depends on n8n being healthy. Those declarations address different questions. The scope puts the containers in the same eligible set. The dependency expresses a startup relationship. Neither identifies a tested pair of immutable image contents. If related tags are published at different times, one update window does not by itself show that they resolve to a compatible pair. Nothing here shows the running pair is mismatched. Nothing shows a shared scope keeps them matched either. A coupled release needs evidence that names the actual application and runner versions being used together. A deployment window is a time boundary. A compatible release is a relationship between artifacts. They are not interchangeable. The Watchtower notes record that --rolling-restart was configured earlier and would have stopped the fork from starting at all. It exits at startup if any watched container has a linked dependency, Compose depends_on counts, and most of this estate has depends_on. The checked-in command no longer includes that option. The notes describe the default batch behavior as stopping dependents before dependencies and starting dependencies first. The Compose configuration also gives Watchtower a 120s stop timeout, raised from the default 10 seconds so a database is not killed mid-shutdown. These are operational controls worth understanding. They address ordering and the time allowed for shutdown. They do not establish that a new database binary can read old state, or that the application has finished a compatible schema migration. Keep each control attached to the failure it can actually affect. Otherwise the existence of several sensible settings becomes an unsupported claim that the release is safe. There is an authority boundary underneath the labels as well. Watchtower mounts the Docker socket read-write. The source notes explicitly distinguish selection from authority: the labels select what the updater is meant to replace; they do not reduce the socket's broader control over containers. That cost belongs in the update decision, but it is not an argument that every update must become a manual ceremony. It is an argument for deciding what the automation is allowed to assume. For a stateless component, accepting the next image under a chosen policy may be reasonable. For a stateful component or a coupled application release, ask for a compatibility boundary that the deployment can name and check. The repository's static image gate cannot supply that evidence, and neither can elapsed time. Before extending this policy to another service, I want the replacement unit defined: an image, a compatible set of images, or a version transition that also requires changing persistent state. Set the cooldown after deciding what may be replaced. Do not ask the clock to make that decision.
Share this post: