Migrating from Watchtower to freshdock in 5 minutes
A practical, copy-paste migration from archived Watchtower to freshdock: translate labels, swap the service in docker-compose, and verify read-only first.
Watchtower is archived and breaks on Docker Engine 29+. Moving to freshdock is mostly a service swap. Since v1.4 freshdock reads your existing watchtower.* labels directly, so relabelling is optional. Here's the whole thing, start to finish.
1. Install freshdock
Pick whichever fits. The result is the same single binary:
cargo install freshdock
# or pull the multi-arch image
docker pull ghcr.io/turbootzz/freshdock:latest
Full options on the install page.
2. Translate your labels (or don't)
Since v1.4 freshdock reads the com.centurylinklabs.watchtower.* labels directly: enable, monitor-only, and the lifecycle hook labels all keep working, and a freshdock.* label always wins when both are present. Two things to know if you keep them: an enable=true container lands on freshdock's safe watch mode (detect and notify) until you give it an updating mode, and Watchtower's hook timeouts stay in minutes while freshdock's own are in seconds.
The native spelling, if and when you translate. The big one to internalise: freshdock is opt-in by default, so you rarely need to disable anything. Unlabelled containers are simply ignored.
| Watchtower | freshdock |
|---|---|
com.centurylinklabs.watchtower.enable=true | freshdock.enable=true |
watchtower.monitor-only=true | freshdock.mode=watch |
watchtower.lifecycle.pre-update / post-update | freshdock.lifecycle.pre-update / post-update |
WATCHTOWER_SCHEDULE (one global cron) | per-container freshdock.mode + freshdock.schedule |
watchtower.enable=false (with global watch) | just omit the labels |
no-pull has no freshdock equivalent: freshdock always pulls before recreate. The depends-on label is not read either, but ordering is covered a different way, since inside a Compose project freshdock follows Compose's own depends_on and needs no extra labels for it. Unsupported labels are warned about rather than silently dropped. If you rely on either, check the comparison page before switching.
Keeping Watchtower's opt-out model
If your fleet was built the Watchtower way, where everything is updated unless a label says otherwise, you don't have to relabel dozens of services to move. Turn the model on for the daemon instead:
freshdock:
image: ghcr.io/turbootzz/freshdock:latest
command: ["run"]
environment:
FRESHDOCK_WATCH_ALL: "true" # every container counts as enabled
FRESHDOCK_DEFAULT_MODE: "nightly" # ...and updates at 04:00
volumes:
- /var/run/docker.sock:/var/run/docker.sock
restart: unless-stopped
Exclusions read the way you expect: freshdock.mode=off or freshdock.enable=false on a container, and a com.centurylinklabs.watchtower.enable=false label you already have keeps working as one. freshdock also skips its own container unless you label it explicitly. Leave FRESHDOCK_DEFAULT_MODE out and everything picked up this way sits on watch, which is a good first week: you get the full inventory reported without a single restart. This is opt-in itself, so the default is still the safe one.
3. Swap the service in docker-compose
Replace the Watchtower service; relabel your apps now or later. Before:
services:
app:
image: ghcr.io/example/app:latest
labels:
- "com.centurylinklabs.watchtower.enable=true"
watchtower:
image: containrrr/watchtower
volumes:
- /var/run/docker.sock:/var/run/docker.sock
environment:
- WATCHTOWER_SCHEDULE=0 0 4 * * *
After:
services:
app:
image: ghcr.io/example/app:latest
labels:
- "freshdock.enable=true"
- "freshdock.mode=nightly" # 04:00 daily
freshdock:
image: ghcr.io/turbootzz/freshdock:latest
command: ["run"]
volumes:
- /var/run/docker.sock:/var/run/docker.sock
restart: unless-stopped
A read-only socket (:ro) is enough while everything is on watch; give freshdock a writable socket once a container is on an updating mode like nightly.
4. Verify read-only, then commit
Before you let it change anything, run the read-only check. It lists your opted-in containers and what has updates, and never pulls, stops, or recreates:
freshdock check
Happy with the table? You're done. The daemon (freshdock run) will now health-gate every update and roll back any that fail to come up.
Notifications and registries
If you used Watchtower's shoutrrr notifications, freshdock has webhook, Discord, Telegram, and SMTP backends. Declare a target with one env var (FRESHDOCK_NOTIFY_<NAME>_URL, shoutrrr-style) or in a small freshdock.toml; no config file is required. Private registry credentials (Docker Hub, GHCR, Quay, lscr) come from FRESHDOCK_REGISTRY_* env vars, no file required.
The complete label, flag, env, notification, and registry translation table (more than fits here) is the migration guide on the docs site. That's the authoritative reference; this post is just the five-minute path.