Keep your docker containers up to date
See the codeA modern, health-gated Docker container auto-updater. A maintained successor to Watchtower, in a single Rust binary.
Website · Quickstart · Docs · Configuration · From Watchtower
Why freshdock instead of Watchtower? See the side-by-side comparison.
freshdock watches your running containers, notices when a newer image is published, and updates them safely: a broken new image rolls back automatically instead of leaving you with a dead service.
| Capability | What you get |
|---|---|
| Health-gated rollback | A container counts as updated only after its healthcheck passes, or after a grace period if it has none. If the new image fails to come up, the previous container is restored and you are notified. |
| Per-container modes | Drive each container with Docker labels: live, nightly, weekly, monthly, watch, off. Mix them freely on one daemon. |
| Five registries | Docker Hub, GHCR, Quay.io, lscr.io, and any OCI bearer-token registry, anonymous or authenticated. |
| Four notifiers | Webhook, Discord, Telegram, and SMTP, each subscribable to the events it cares about. |
| Lifecycle hooks | Run commands inside the container around an update. A pre-update hook can veto or defer (exit 75); a post-update hook handles maintenance like cache clears. |
| Compose-aware rollouts | A Compose project updates as one unit: the one-shot migrate service your app waits on is re-run before the new code starts, and a failed migration aborts the rollout. Read from Compose's own labels, no compose file needed. |
| VPN stacks survive updates | Containers routed through another container's network (network_mode: container:X, the gluetun pattern) are re-attached after that container updates instead of being left offline. |
| Watchtower drop-in | Reads com.centurylinklabs.watchtower.* labels (enable, monitor-only, lifecycle hooks) directly, so a fleet migrates without relabelling. FRESHDOCK_WATCH_ALL=true restores Watchtower's opt-out model, still on watch until FRESHDOCK_DEFAULT_MODE picks an updating mode. |
| Optional cleanup | Remove superseded images after a healthy update; optionally prune dangling images. |
| Single static binary | Under 10 MB, no runtime dependencies. No JVM, no Go runtime, no 100 MB image to manage your homelab. |
freshdock is opt-in: it ignores every container until you set freshdock.enable=true, and
an enabled container with no explicit mode defaults to watch (detect and notify, never
restart). One narrow exception: inside a Compose project, an unlabelled one-shot that
another service waits on is re-run as part of the rollout. An explicit opt-out still
wins. See Compose projects. To
include every container unless it opts out, see
watching every container.
Label a container to opt it in, then watch it with a read-only socket:
# docker-compose.yml
services:
web:
image: nginx:1.27
labels:
- "freshdock.enable=true" # opt in; mode defaults to watch
freshdock:
image: ghcr.io/turbootzz/freshdock:latest
command: ["run"]
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
restart: unless-stopped
freshdock check # read-only: which containers have updates available?
To let it update something, switch to freshdock.mode=nightly and give the daemon a
writable socket. Full walkthrough in the
Quickstart.
# crates.io
cargo install freshdock
# container image (multi-arch: amd64, arm64, armv7)
docker run -d --name freshdock --restart unless-stopped \
-v /var/run/docker.sock:/var/run/docker.sock \
ghcr.io/turbootzz/freshdock:latest run
Prebuilt static-musl binaries (amd64, arm64, armv7) are attached to each
release, with a SHA256SUMS file to
verify them (sha256sum -c SHA256SUMS). From source: clone the repo and run just build
(binary at target/release/freshdock).
| Mode | Behaviour |
|---|---|
live | Poll frequently; pull and recreate on every new digest. |
nightly / weekly / monthly | Check on a cron schedule (default 04:00); recreate if newer. |
watch | Detect updates and notify only, never pull or restart. |
off | Never updated. Still listed by freshdock check. |
Set the mode (and an optional cron override) with labels:
labels:
- "freshdock.enable=true"
- "freshdock.mode=weekly"
- "freshdock.schedule=0 2 * * 1" # 02:00 every Monday (overrides the default)
Full label vocabulary and cron syntax: Configuration and Scheduling.
| Quickstart | Up and running in a minute. |
| Configuration | Every label, env var, and freshdock.toml table (the single source of truth). |
| CLI reference | check, recreate, run, and all flags. |
| Scheduling & modes | Update modes and cron syntax. |
| Compose projects | A stack updated as one unit: one-shot migrations, depends_on ordering. |
| Notifications | Webhook, Discord, Telegram, SMTP. |
| Health & rollback | The recreate lifecycle and image cleanup. |
| Lifecycle hooks | Pre-update and post-update commands. |
| Registry auth | Private registries and credentials. |
| Deployment | Container, systemd, socket permissions, compatibility. |
| Troubleshooting | Symptom-first fixes for common first-run issues. |
| Migrating from Watchtower | Label/flag translation. |
| Architecture & roadmap | Design, phases, goals, risks. |
Runnable example stacks: examples/compose/. A commented config
template: freshdock.toml.example.
Watchtower was the de-facto Docker auto-updater for years. Its maintainers archived it on 17 December 2025, and it shipped an embedded Docker SDK (API 1.25) incompatible with Docker Engine 29+. The community has forks, but none combine what matters for a small homelab:
| Platform | Status |
|---|---|
| Plain Docker | Primary target. The API version is negotiated per daemon; CI exercises the Docker engine shipped on GitHub's ubuntu-latest runners. |
| Docker Desktop (Linux, macOS, Windows) | Supported. |
| Portainer (CE and Business) | Supported via the same Docker socket. |
| Podman 4+ | Supported via the Docker-compatible socket. |
| Docker Compose | A project is updated as one unit: one-shot service_completed_successfully services are re-run first, the rest follow in depends_on order. Compose files are never read or written. |
| Compose-based UIs (Dockge, Komodo, and similar) | Supported; the compose files themselves are untouched. |
| Kubernetes / Swarm | Out of scope. Use platform-native mechanisms. |
Socket discovery order, Podman paths, and the Docker API 1.44 floor for containers on more than one network are covered in Deployment.
Phases 0-7 are complete and freshdock has been stable since 1.0.0. The full plan and
architecture live in the roadmap;
release mechanics in RELEASE.md; per-version notes in
CHANGELOG.md.
Contributions are welcome. See CONTRIBUTING.md for local setup, the quality gates, and dependency hygiene. Security reports: SECURITY.md.
freshdock is free and open source. If it keeps your homelab up to date, you can support its development through GitHub Sponsors. Sponsoring is optional; stars, bug reports, and pull requests help just as much.
freshdock is licensed under the Apache License 2.0.
Rust
99.5%
Keep your docker containers up to date
See the codeA modern, health-gated Docker container auto-updater. A maintained successor to Watchtower, in a single Rust binary.
Website · Quickstart · Docs · Configuration · From Watchtower
Why freshdock instead of Watchtower? See the side-by-side comparison.
freshdock watches your running containers, notices when a newer image is published, and updates them safely: a broken new image rolls back automatically instead of leaving you with a dead service.
| Capability | What you get |
|---|---|
| Health-gated rollback | A container counts as updated only after its healthcheck passes, or after a grace period if it has none. If the new image fails to come up, the previous container is restored and you are notified. |
| Per-container modes | Drive each container with Docker labels: live, nightly, weekly, monthly, watch, off. Mix them freely on one daemon. |
| Five registries | Docker Hub, GHCR, Quay.io, lscr.io, and any OCI bearer-token registry, anonymous or authenticated. |
| Four notifiers | Webhook, Discord, Telegram, and SMTP, each subscribable to the events it cares about. |
| Lifecycle hooks | Run commands inside the container around an update. A pre-update hook can veto or defer (exit 75); a post-update hook handles maintenance like cache clears. |
| Compose-aware rollouts | A Compose project updates as one unit: the one-shot migrate service your app waits on is re-run before the new code starts, and a failed migration aborts the rollout. Read from Compose's own labels, no compose file needed. |
| VPN stacks survive updates | Containers routed through another container's network (network_mode: container:X, the gluetun pattern) are re-attached after that container updates instead of being left offline. |
| Watchtower drop-in | Reads com.centurylinklabs.watchtower.* labels (enable, monitor-only, lifecycle hooks) directly, so a fleet migrates without relabelling. FRESHDOCK_WATCH_ALL=true restores Watchtower's opt-out model, still on watch until FRESHDOCK_DEFAULT_MODE picks an updating mode. |
| Optional cleanup | Remove superseded images after a healthy update; optionally prune dangling images. |
| Single static binary | Under 10 MB, no runtime dependencies. No JVM, no Go runtime, no 100 MB image to manage your homelab. |
freshdock is opt-in: it ignores every container until you set freshdock.enable=true, and
an enabled container with no explicit mode defaults to watch (detect and notify, never
restart). One narrow exception: inside a Compose project, an unlabelled one-shot that
another service waits on is re-run as part of the rollout. An explicit opt-out still
wins. See Compose projects. To
include every container unless it opts out, see
watching every container.
Label a container to opt it in, then watch it with a read-only socket:
# docker-compose.yml
services:
web:
image: nginx:1.27
labels:
- "freshdock.enable=true" # opt in; mode defaults to watch
freshdock:
image: ghcr.io/turbootzz/freshdock:latest
command: ["run"]
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
restart: unless-stopped
freshdock check # read-only: which containers have updates available?
To let it update something, switch to freshdock.mode=nightly and give the daemon a
writable socket. Full walkthrough in the
Quickstart.
# crates.io
cargo install freshdock
# container image (multi-arch: amd64, arm64, armv7)
docker run -d --name freshdock --restart unless-stopped \
-v /var/run/docker.sock:/var/run/docker.sock \
ghcr.io/turbootzz/freshdock:latest run
Prebuilt static-musl binaries (amd64, arm64, armv7) are attached to each
release, with a SHA256SUMS file to
verify them (sha256sum -c SHA256SUMS). From source: clone the repo and run just build
(binary at target/release/freshdock).
| Mode | Behaviour |
|---|---|
live | Poll frequently; pull and recreate on every new digest. |
nightly / weekly / monthly | Check on a cron schedule (default 04:00); recreate if newer. |
watch | Detect updates and notify only, never pull or restart. |
off | Never updated. Still listed by freshdock check. |
Set the mode (and an optional cron override) with labels:
labels:
- "freshdock.enable=true"
- "freshdock.mode=weekly"
- "freshdock.schedule=0 2 * * 1" # 02:00 every Monday (overrides the default)
Full label vocabulary and cron syntax: Configuration and Scheduling.
| Quickstart | Up and running in a minute. |
| Configuration | Every label, env var, and freshdock.toml table (the single source of truth). |
| CLI reference | check, recreate, run, and all flags. |
| Scheduling & modes | Update modes and cron syntax. |
| Compose projects | A stack updated as one unit: one-shot migrations, depends_on ordering. |
| Notifications | Webhook, Discord, Telegram, SMTP. |
| Health & rollback | The recreate lifecycle and image cleanup. |
| Lifecycle hooks | Pre-update and post-update commands. |
| Registry auth | Private registries and credentials. |
| Deployment | Container, systemd, socket permissions, compatibility. |
| Troubleshooting | Symptom-first fixes for common first-run issues. |
| Migrating from Watchtower | Label/flag translation. |
| Architecture & roadmap | Design, phases, goals, risks. |
Runnable example stacks: examples/compose/. A commented config
template: freshdock.toml.example.
Watchtower was the de-facto Docker auto-updater for years. Its maintainers archived it on 17 December 2025, and it shipped an embedded Docker SDK (API 1.25) incompatible with Docker Engine 29+. The community has forks, but none combine what matters for a small homelab:
| Platform | Status |
|---|---|
| Plain Docker | Primary target. The API version is negotiated per daemon; CI exercises the Docker engine shipped on GitHub's ubuntu-latest runners. |
| Docker Desktop (Linux, macOS, Windows) | Supported. |
| Portainer (CE and Business) | Supported via the same Docker socket. |
| Podman 4+ | Supported via the Docker-compatible socket. |
| Docker Compose | A project is updated as one unit: one-shot service_completed_successfully services are re-run first, the rest follow in depends_on order. Compose files are never read or written. |
| Compose-based UIs (Dockge, Komodo, and similar) | Supported; the compose files themselves are untouched. |
| Kubernetes / Swarm | Out of scope. Use platform-native mechanisms. |
Socket discovery order, Podman paths, and the Docker API 1.44 floor for containers on more than one network are covered in Deployment.
Phases 0-7 are complete and freshdock has been stable since 1.0.0. The full plan and
architecture live in the roadmap;
release mechanics in RELEASE.md; per-version notes in
CHANGELOG.md.
Contributions are welcome. See CONTRIBUTING.md for local setup, the quality gates, and dependency hygiene. Security reports: SECURITY.md.
freshdock is free and open source. If it keeps your homelab up to date, you can support its development through GitHub Sponsors. Sponsoring is optional; stars, bug reports, and pull requests help just as much.
freshdock is licensed under the Apache License 2.0.
Rust
99.5%