Skip to content

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.

  1. 01

    inspect

    Capture the running container’s full config.

  2. 02

    pull

    Fetch the new image.

  3. 03

    stop

    Gracefully stop the old container.

  4. 04

    rename

    Archive it as <name>-old-<ts>, the rollback source.

  5. 05

    create

    Recreate from the same config + new image.

  6. 06

    start

    Bring the new container up.

  7. 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 ↗.

freshdock update modes and what each one does
modewhen it actswhat it does
liveevery --interval (default 300s)Pull and recreate on every new digest.
nightlycron 0 4 * * * (04:00 daily)Recreate if a newer image exists.
weeklycron 0 4 * * 0 (04:00 Sunday)Recreate if a newer image exists.
monthlycron 0 4 1 * * (04:00 on the 1st)Recreate if a newer image exists.
watchevery --intervalReport only: notify, never pull or restart. The default.
offneverIgnored 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.

Plain Docker, incl. Engine 29+Primary target, API negotiated
Docker Desktop (Linux/macOS/Win)Supported
Portainer (CE & Business)Same Docker socket
Podman 4+Docker-compatible socket
Docker Compose projectsUpdated as one unit, in depends_on order
Dockge / Komodo & compose UIsSupported; compose files untouched
Kubernetes / SwarmOut of scope; use platform-native

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.

Install freshdock