Skip to content

Self-update: recreate via the Docker API — zero-config, with rollback - #27

Merged
swimmesberger merged 1 commit into
mainfrom
wt/self-update-latest-experience-266172
Aug 10, 2026
Merged

Self-update: recreate via the Docker API — zero-config, with rollback#27
swimmesberger merged 1 commit into
mainfrom
wt/self-update-latest-experience-266172

Conversation

@swimmesberger

Copy link
Copy Markdown
Owner

Summary

Self-update no longer depends on the compose file at all. The coordinator now recreates the Watchtower container purely via the Docker API instead of running docker compose up -d, which removes the whole category of compose-configuration failures — most prominently:

Compose file not found at '/opt/watchtower/compose.yml' inside the Watchtower container. Mount the directory into the container ...

How it works

  1. The main process pulls the new image, then spawns the coordinator from the just-pulled image with only the Docker socket bound.
  2. The coordinator clones the running container's configuration onto the new image (ContainerCloneSpec — carries Config/HostConfig/networks, drops the id-derived default hostname so HOSTNAME-based self-detection keeps working, strips runtime-assigned network fields and the old container's stale alias, splits extra networks into post-create connects).
  3. It stops and renames the old container aside, creates + starts the replacement under the original name, and rolls back (remove new, rename back, restart old) if that fails — a failed update can never leave the host without Watchtower. The old container is removed only after the new one is up.

Consequences

  • Zero configuration: no compose path/project detection, no overrides section in Settings, no mount requirements. Self-update config is down to the registry credential. Works for any deployment shape, including docker run.
  • Trade-off (documented in docs/architecture.md): a compose file edited since the last deploy is not re-asserted by a self-update — run docker compose up -d on the host to apply compose-file changes.
  • The main process now watches the coordinator after spawning it: a failed (rolled-back) apply surfaces in the UI immediately instead of sticking at "restarting" forever, and concurrent applies are blocked for the whole restart window. On success, the next process instance reconciles the coordinator exit code at startup, as before.
  • Saving the config clears a lingering apply error (it is the remediation path), but never touches an in-flight stage.
  • DockerEngineClient gains raw-JSON inspect/create, rename and network-connect endpoints for the full-fidelity clone; Docker API error bodies are surfaced in exceptions instead of bare status codes.
  • ComposeCliService.ConfigAsync removed (no callers left); rpc-schema.json and the generated frontend client regenerated.

Testing

  • 481 tests green (341 application incl. 6 new ContainerCloneSpecTests pinning the cloning rules, 140 API); frontend typecheck clean.
  • Verified the simplified Settings card and the credential-only system.updateConfig round trip against a live dev stack in the browser.
  • Docker itself is not exercised in CI — run one manual self-update cycle on a test host before tagging a release.

…llback

Self-update no longer depends on the compose file at all. Previously, applying
an update ran docker compose up -d through the coordinator, which required the
compose file path (from container labels or manual overrides) to resolve on the
host — the top failure mode being "Compose file not found at ... inside the
Watchtower container".

The coordinator now recreates the Watchtower container purely via the Docker
API: it clones the running container's configuration onto the freshly pulled
image (ContainerCloneSpec — carries Config/HostConfig/networks, drops the
id-derived default hostname and runtime-assigned network fields, filters the
old container's stale alias), stops and renames the old container aside,
creates and starts the replacement under the original name, and rolls back to
the old container if that fails. The old container is only removed once the
new one is up, so a failed update can never leave the host without Watchtower.

Consequences:
- Zero configuration: no compose path/project detection, no overrides section
  in Settings, no mount requirements. Config is down to the registry
  credential. Works for any deployment shape, including docker run.
- Trade-off (documented in docs/architecture.md): a compose file edited since
  the last deploy is not re-asserted by a self-update — run
  docker compose up -d on the host to apply compose-file changes.
- The main process now watches the coordinator after spawning it, so a failed
  (rolled-back) apply surfaces in the UI immediately instead of sticking at
  "restarting" until the next restart; concurrent applies are blocked for the
  whole restart window. On success the next process instance reconciles the
  coordinator exit code at startup, as before.
- Saving the config clears a lingering apply error (remediation path), never
  an in-flight stage.
- DockerEngineClient gains raw-JSON inspect/create, rename and network-connect
  endpoints for the full-fidelity clone; Docker API error bodies are surfaced
  in exceptions instead of bare status codes.
- ComposeCliService.ConfigAsync removed (no callers left); rpc-schema and the
  generated frontend client regenerated.

The cloning rules are pinned by unit tests (ContainerCloneSpecTests); Docker
itself is not exercised in CI, so run one manual update cycle on a test host
before tagging a release.
@swimmesberger
swimmesberger merged commit 83756a1 into main Aug 10, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant