Features
Health-gated Docker updates, by default.
freshdock runs the full update cycle (check, pull, recreate, verify, clean up) and treats “did the new container actually come up healthy?” as the gate for every update. Here’s how each part works. For exact flags and tables, the reference docs ↗ are the source of truth.
The recreate lifecycle
A container is “updated” only once it proves healthy.
Whether triggered manually or by the scheduler, every update runs the same gated cycle, and reverts on failure.
- 01
inspect
Capture the running container’s full config.
- 02
pull
Fetch the new image.
- 03
stop
Gracefully stop the old container.
- 04
rename
Archive it as <name>-old-<ts>, the rollback source.
- 05
create
Recreate from the same config + new image.
- 06
start
Bring the new container up.
- 07
health-gate
Wait for healthy, or the grace period.
healthy
The archive is removed; the update stands. Opt in to also delete the superseded image with freshdock.cleanup=true.
timeout / crashed → roll back
The failed container is removed and the archive is renamed back and restarted. You’re left running exactly what you had, and a failed notification is sent.
timings (currently hardcoded): health timeout 120s · grace period 10s · poll 1s
Verdicts, grace periods, and image cleanup in full → health & rollback docs ↗.
Per-container modes
Mix update strategies on one daemon.
Each opted-in container picks a freshdock.mode. The scheduler polls live/watch on an interval and fires calendar modes on cron, overridable per container with freshdock.schedule. The default, watch, never restarts anything.
Cron fields, DST handling, and missed-window behaviour → scheduling docs ↗.
| mode | when it acts | what it does |
|---|---|---|
| live | every --interval (default 300s) | Pull and recreate on every new digest. |
| nightly | cron 0 4 * * * (04:00 daily) | Recreate if a newer image exists. |
| weekly | cron 0 4 * * 0 (04:00 Sunday) | Recreate if a newer image exists. |
| monthly | cron 0 4 1 * * (04:00 on the 1st) | Recreate if a newer image exists. |
| watch | every --interval | Report only: notify, never pull or restart. The default. |
| off | never | Ignored by the scheduler entirely. |
Five registries
Digest checks use rate-friendly HEAD requests (not full pulls), anonymous or authenticated.
- Docker Hubanonymous + authenticated
- GHCRghcr.io · PAT or public
- Quay.ioquay.io
- lscr.ioLinuxServer
- Any OCI registrybearer-token auth
PAT scopes & credentials → registry auth ↗.
Four notifiers
Each target subscribes to just the events it wants: available, succeeded, failed.
- Webhookgeneric JSON POST
- Discordcoloured embed
- TelegramBot API message
- SMTPemail, STARTTLS or implicit TLS
Payloads & the event matrix → notifications ↗.
Compose projects
Your migration runs before your new code does.
A stack is one update unit
A one-shot migrate service sits in exited (0) once it has run, so a per-container updater never sees it again. Your app image moves, the app is recreated, and the migration keeps the old code: new application code against an old schema. Nothing crashes, so no health check catches it either.
freshdock updates the whole project instead. It re-runs the one-shots your services wait on with service_completed_successfully, waits for them to exit cleanly, then updates the rest in depends_on order. If the migration fails, the rollout stops: nothing downstream is touched, and your stack keeps serving the version it was already on.
There is nothing to configure and no compose file to mount. Compose already writes the whole dependency graph into container labels, so freshdock reads it straight off the Docker socket. Your migration service does not even need a freshdock.enable label: the depends_on condition is what marks it as a step that has to finish first.
The full rules, and the opt-out switch → Compose projects ↗.
Lifecycle hooks
Some apps need a hand around an update. Declare commands as labels and freshdock runs them inside the container: freshdock.lifecycle.pre-update runs before the stop and can veto or defer the update (exit 75 means "not now, retry later"), post-update runs once the new container is healthy, for maintenance like cache clears. Hooks never weaken the health gate: a failing post-update hook is logged, never rolled back.
Timeouts, exit codes, and examples → lifecycle hooks docs ↗.
Watchtower labels, read directly
A migrated fleet keeps its com.centurylinklabs.watchtower.* labels: enable, monitor-only, and the lifecycle hook labels (timeouts in Watchtower's minutes) all work as-is. A freshdock.* label always wins, so you translate at your own pace. Unsupported labels (no-pull, depends-on) are warned about, not silently dropped. Ordering is covered a different way: inside a Compose project freshdock follows Compose's own depends_on, so there is no label to translate.
If you also relied on Watchtower watching everything by default, FRESHDOCK_WATCH_ALL=true turns that model back on for the whole fleet. A container steps out with freshdock.mode=off, and an enable=false watchtower label still counts as an exclusion. It stays off unless you ask for it.
The full mapping → migration guide ↗.
Optional, best-effort cleanup
A healthy update always removes the replaced container archive. Opt in to also delete the superseded image (freshdock.cleanup or [settings] cleanup), and (separately) enable a daemon-wide dangling-image prune ([settings] prune_dangling). A shared base image still in use is kept. Cleanup never fails an update, and freshdock never removes volumes.
One static binary
Built in Rust as a single static-musl binary, ≤ 10 MB, no runtime dependencies. The multi-arch image (amd64, arm64, armv7) is a thin wrapper, not a 100 MB runtime sitting beside the containers it manages.
Compatibility
Where it runs, and where it doesn’t.
Three commands
check · recreate · run
$freshdock check
Read-only status table. Lists opted-in containers, resolves the latest digest per unique image, reports what has updates. Never mutates.
$freshdock recreate <name>
Manually update one container: inspect → pull → stop → rename → create → start, health-gated with rollback. Refuses containers you’ve opted out.
$freshdock run
The scheduler daemon. Each tick it acts on due containers per their mode; watch is report-only. Graceful SIGINT/SIGTERM drain.
Every flag and example → CLI reference ↗.
Try it read-only first.
freshdock check changes nothing. It just shows you what would update. Install it and run that before you trust it with a thing.