From 81bd01c5d6834d4abe66f6ac169a37dbc9b18c66 Mon Sep 17 00:00:00 2001 From: Samuel Enocsson Date: Tue, 4 Aug 2026 19:30:58 +0200 Subject: [PATCH] docs: describe one-instance-per-server topology and why MODE is exclusive --- CLAUDE.md | 11 ++++++++--- README.md | 31 +++++++++++++++++++++++++++---- 2 files changed, 35 insertions(+), 7 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 9b0153b..586fba1 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -47,9 +47,14 @@ curl -sH "Authorization: Bearer $UPDATER_API_KEY" \ Tests rely on this — don't drop the nil-check. - **Stateless**: no DB, no config file, no on-disk audit log. Docker daemon is the source of truth. -- **Compose mode and Swarm mode are selected once per deployment via `MODE`**, never - mixed at request time. Swarm mode's security gate is opt-in label only — there is - no STACKS_ROOT-equivalent path check, since Swarm services have no local compose +- **`MODE` is exclusive because each instance owns exactly one Docker daemon.** + A composite "handle both at once" mode has been considered and rejected: it would + still only reach one daemon, so it buys nothing. Fleets run one instance per + server and CI fans out. Reopen this only if a Swarm manager node starts running + standalone Compose stacks locally. Rationale and rejected alternatives: + `docs/superpowers/specs/2026-08-04-multi-server-fanout-design.md`. +- **Swarm mode's security gate is opt-in label only** — there is no + STACKS_ROOT-equivalent path check, since Swarm services have no local compose file. Don't add one; don't weaken Compose mode's three-factor gate to match. ## Gotchas diff --git a/README.md b/README.md index 8a41057..60f0ffe 100644 --- a/README.md +++ b/README.md @@ -46,6 +46,28 @@ Two things to get right when running in Swarm mode: mounted. This is a deployment concern the service cannot detect or work around. +## Deploying across multiple servers + +An instance can only reach the Docker daemon it is configured against. It cannot +update stacks on other machines. The deployment model follows from that: + +**One instance per server.** Each server runs its own package-updater against its +own local Docker socket, with `MODE` set to whatever that machine runs: + +| Server | `MODE` | Updates | +|---|---|---| +| Swarm manager node | `swarm` | All Swarm services in the cluster | +| Standalone Compose host | `compose` | Compose stacks on that host | + +**CI fans out.** The [Gitea composite action](gitea-action/README.md) takes a list of +endpoints and posts `/update` to every instance, so one workflow run reaches the +whole fleet. Each instance answers for its own machine; there is no aggregated +cross-server response and no instance coordinates any other. + +This is why `MODE` is exclusive rather than a mode that handles both at once: an +instance that could do both would still only reach one daemon, so the extra +generality buys nothing. + ## Quick start 1. Build and push the image (e.g. via your own CI). @@ -68,7 +90,7 @@ All via environment variables. | `LOG_LEVEL` | no | `info` | `debug` / `info` / `warn` / `error`. | | `UPDATE_TIMEOUT` | no | `5m` | Per-job timeout (Go duration). | | `OPT_IN_LABEL` | no | `se.shcizo.auto-update` | Label name to check; value must equal `"true"`. | -| `MODE` | no | `compose` | `compose` or `swarm`. Selects the update mechanism for the whole deployment; not mixed per-request. | +| `MODE` | no | `compose` | `compose` or `swarm`. Which paradigm *this instance's* Docker daemon runs. See [Deploying across multiple servers](#deploying-across-multiple-servers). | ## Endpoints @@ -100,7 +122,8 @@ These are tracked in the spec's section 2 and section 15 as deliberate out-of-sc - **Self-update wiring**: `internal/selfupdate.Wrapped` exists and is unit-tested but is not wired into the live queue. The HTTP response flush ordering for self-replacement is a future enhancement; for now, expect to manually rerun `docker compose up -d` on the host if pushing a new image of `package-updater` itself causes a mid-response interruption. - **No rollback**: Compose's "keep old container if new fails to start" is the only safety net. -- **Single host only in Compose mode**. Swarm mode (`MODE=swarm`) is the - multi-node path, but only from a manager node's point of view — the updater - itself still needs manager API access (see "Swarm mode" above). +- **One instance reaches one daemon.** An instance never updates another server; + fleets run one instance per server with CI fanning out to all of them (see + "Deploying across multiple servers"). Swarm mode is the exception in that a + single manager-node instance covers the whole cluster. - **No per-repo API keys**: a single shared bearer token is used.