Skip to content

feat(proxy): automatic drift-detected proxy reboot on deploy - #24

Merged
mhenrixon merged 5 commits into
dashfrom
feat/proxy-auto-reboot
Jul 26, 2026
Merged

mhenrixon merged 5 commits into
dashfrom
feat/proxy-auto-reboot

Conversation

@mhenrixon

Copy link
Copy Markdown
Collaborator

Implements Phase 1b of the zero-downtime proxy reboot plan (companion to the mhenrixon/kamal-proxy hardening series #32-#37).

What

  • Drift detection: proxy containers are stamped with an org.kamal.proxy-config-digest label (SHA256 of the materialized run invocation, schema-versioned). kamal deploy / setup / proxy boot compare it against the current config — on both the proxy/run and legacy boot_config paths. Changed proxy options are no longer silently ignored by start_or_run.
  • Automatic serial reboot on drift before app boot; the fleet never loses all proxies at once. Opt out with proxy: reboot_on_deploy: false (root level) — deploy then prints a drift warning instead.
  • Safe-reboot worker shared with the hardened kamal proxy reboot: pull before stop (registry outages can no longer strand a host) → drain-stop → replace with digest label → wait for control socket → re-register this app's roles + accessories → verify against kamal-proxy list, failing loudly with missing service names.
  • Re-registration heals the empty-state-restore 404 failure mode even without the Go-side fixes.
  • Unlabelled containers (older kamal) converge with exactly one automatic reboot on first deploy.

Release ordering

Proxy image first: merge the kamal-proxy series, tag + publish ghcr.io/mhenrixon/kamal-proxy, then bump MINIMUM_VERSION here — the digest includes the version, so the first deploy converges every host.

Refs: basecamp/kamal#1055 (auto-reboot wontfix due to downtime — this removes the downtime), basecamp/kamal#1298, basecamp/kamal-proxy#86, basecamp/kamal#1552.

Groundwork for detecting proxy drift during deploy:

- Proxy::Run.digest / #config_digest: SHA256 over the materialized run
  invocation (image, run command, docker options), schema-versioned so a
  future serialization change causes exactly one converging reboot.
- Commands::Proxy#run and #start_or_run accept digest: and stamp it as
  the org.kamal.proxy-config-digest container label, on both the
  proxy/run and legacy boot_config paths.
- New commands: pull (pre-pull the proxy image before stopping the old
  container), list (docker exec kamal-proxy list), config_digest
  (read the label back), container_id, and stop with a --time override.
kamal deploy (and setup / proxy boot) now compares the running proxy
container's config-digest label against the digest the current
configuration produces. On drift it reboots the proxy automatically —
one host at a time, so a fleet never loses all proxies at once — before
app boot registers the new version. Previously changed proxy run options
were silently ignored by start_or_run, and a version bump required a
manual, unverified kamal proxy reboot.

The safe-reboot worker (Kamal::Cli::Proxy::Reboot) is shared by the
auto path and the hardened kamal proxy reboot command:

  pull image (before stopping — registry outages can no longer strand a
  host) -> stop with drain headroom -> remove -> run with digest label ->
  wait for the control socket -> re-register this app's roles and
  accessories -> verify them against kamal-proxy list, failing loudly
  with the missing service names.

Re-registration heals the exact failure mode where the proxy restores
from a torn state file with zero services and 404s everything.

Opt out with proxy: reboot_on_deploy: false (root level) — deploy then
prints a drift warning instead of silently ignoring the change.
Containers started by older kamal versions carry no digest label and
converge with one automatic reboot on the first deploy.

Unlabelled-drift, opt-out warning, matching-digest no-op, and hardened
reboot sequences are covered in CLI tests; an integration test exercises
the full deploy -> drift -> auto-reboot -> converge cycle.
Bumps MINIMUM_VERSION to v0.9.2.2 — the fork release carrying the
zero-downtime reboot hardening (atomic state writes, restore recovery
ladder, list --json, drain-on-stop, restore re-verification, reuse-port,
drain, hold).

The proxy run command now always passes --recheck-targets-on-restore
(proxy/run path): after a reboot, restored targets are re-verified with
live health checks instead of trusted blindly, so a target that died
while the proxy was down demotes to an honest 503 and rejoins on
recovery, rather than serving 502s forever.

Both changes alter the config digest, so the first deploy after
upgrading converges every host with one automatic serial reboot.

Release ordering: ghcr.io/mhenrixon/kamal-proxy:v0.9.2.2 must be
published (script/release-dash) before this ships in a gem release.
The MINIMUM_VERSION bump missed setup.sh, whose hardcoded seed tag must
equal MINIMUM_VERSION (documented in .claude/rules/upstream-sync.md) —
app_with_roles pulls the proxy via the local registry:4443 mirror, so
every fixture that boots a proxy failed with manifest unknown.

Also drop the stray kamal envify from the drift integration test: the
app fixture has no .env.erb and the deploy does not need one.
Since kamal-proxy v0.9.2.2 the server flushes kamal-proxy.state on every
shutdown, so any boot after the very first restores state — the log
assertion in the proxy integration test asserted the old behavior.
@mhenrixon
mhenrixon merged commit a0b1e2f into dash Jul 26, 2026
9 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant