feat(deploy): add stage tier — park-buzi as the staging booth
Model the staging-vs-production split that fleet-deployment-komodo flagged as open. Three tiers: dev (working, no booth) -> stage (staging booth park-buzi, real-world test) -> main (production, manual + pinned). - build-images.yml: trigger on [dev, stage, main]. The tag computation is already branch-derived, so :stage / :stage-<sha> build with no other change. - komodo/resources.toml: park-buzi now branch=stage + TAG=stage-<sha> (pinned; no webhook even on staging). BACKUP_KEY already wired as a per-booth secret. - komodo/README.md: a Promotion (dev->stage->main) section; per-booth secret list now includes backup_key; hard-rule #1 generalised to pinned <branch>-<sha>. - wiki: fleet-deployment-komodo open-item resolved + a Promotion-tiers table; deploy-trigger choice generalised; container-deployment tag list gains :stage. Promotion is a merge: when dev is ready, merge dev->stage, CI builds the image, bump TAG=stage-<sha> in resources.toml, deploy from Core. stage is branched from dev HEAD so the first real-world test carries the full current app. Per-booth secrets must pre-exist in Core; migrations run at boot so a promotion auto-migrates the staging ledger (where a bad migration is caught before production). Claude-Session: https://claude.ai/code/session_01Xcm6ikLgGoCxxHrxtjkk5V
This commit is contained in:
@@ -35,8 +35,10 @@ The **desktop** app stays on its own tag-only `release.yml` (Tauri installers),
|
||||
## Branch-aware (the user's hard requirement)
|
||||
|
||||
- **Image tags = branch + short SHA.** A push to `dev` builds `…/parking-server:dev` +
|
||||
`…/parking-server:dev-<sha>`; `main` builds `:main` + `:main-<sha>`. The moving branch tag is the
|
||||
deploy pointer; the branch-SHA tag is the immutable record. Same for `parking-vision`.
|
||||
`…/parking-server:dev-<sha>`; `stage` builds `:stage` + `:stage-<sha>`; `main` builds `:main` +
|
||||
`:main-<sha>`. The moving branch tag is the deploy pointer; the branch-SHA tag is the immutable
|
||||
record. Same for `parking-vision`. (The `stage` tier — the staging booth — was added 2026-06-29;
|
||||
see [[fleet-deployment-komodo]] "Promotion tiers".)
|
||||
- **Per-env compose.** A base `docker-compose.yml` + overrides: `docker-compose.dev.yml` (build
|
||||
locally, expose ports, `stub` recognizer) and `docker-compose.prod.yml` (pull pinned images,
|
||||
`restart: always`, `fast_alpr`, vision kept internal). `REGISTRY`/`TAG` come from env, so a deploy
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
type: decision
|
||||
tags: [parking, deployment, fleet, komodo, netbird, offline-first, threat-model]
|
||||
sources: []
|
||||
updated: 2026-06-27
|
||||
updated: 2026-06-29
|
||||
status: settled
|
||||
---
|
||||
|
||||
@@ -49,13 +49,37 @@ Komodo Core (off-site) ──── NetBird mesh ─┼─▶ Periphery @ booth-
|
||||
1. **Fleet size: many/growing.** Komodo is treated as load-bearing infrastructure, not a
|
||||
convenience. This is what tips the decision away from "SSH-over-NetBird + a playbook".
|
||||
2. **Deploy trigger: always manual + pinned.** **No deploy webhook on a booth Stack.** A human
|
||||
deploys a specific immutable `TAG=dev-<sha>` from Core. This preserves the determinism we
|
||||
chose when pinning the booth tag (a moving `:dev` auto-redeploying a production booth is the
|
||||
surprise we explicitly rejected). A *staging* booth MAY track `:dev`; a production booth
|
||||
never does.
|
||||
3. **Secrets: Komodo-managed (per-booth, unique).** Core's secret store injects `JWT_SECRET`
|
||||
and `EVENT_SIGNING_KEY` into the Stack at deploy. This scales (no SSH-to-N-booths to rotate
|
||||
a key) — but see the threat-model tension below; the keys MUST be **distinct per booth**.
|
||||
deploys a specific immutable `TAG=<branch>-<sha>` from Core. This preserves the determinism we
|
||||
chose when pinning the booth tag (a moving tag auto-redeploying a booth is the surprise we
|
||||
explicitly rejected). This holds for **every** booth — including the **staging** booth: it tracks
|
||||
the `stage` *branch* but still runs a **pinned `stage-<sha>`** (an earlier sketch floated a moving
|
||||
`:dev` + webhook for staging; rejected in favour of pinned-everywhere). See "Promotion tiers".
|
||||
3. **Secrets: Komodo-managed (per-booth, unique).** Core's secret store injects `JWT_SECRET`,
|
||||
`EVENT_SIGNING_KEY` and `BACKUP_KEY` into the Stack at deploy. This scales (no SSH-to-N-booths to
|
||||
rotate a key) — but see the threat-model tension below; the keys MUST be **distinct per booth**.
|
||||
|
||||
## Promotion tiers — dev → stage → main (settled 2026-06-29)
|
||||
|
||||
Three git branches map to three image tags and three booth roles:
|
||||
|
||||
| Branch | Image tag | Role | Deploy |
|
||||
| --- | --- | --- | --- |
|
||||
| `dev` | `:dev` / `:dev-<sha>` | working branch | no booth runs it |
|
||||
| `stage` | `:stage` / `:stage-<sha>` | **staging booth (`park-buzi`)** — real-world test | **manual + pinned** `stage-<sha>` |
|
||||
| `main` | `:main` / `:main-<sha>` | **production** booths | manual + pinned `main-<sha>` |
|
||||
|
||||
- **Promotion is a merge, not a build trigger.** When `dev` is confident-ready, **merge `dev → stage`**;
|
||||
CI (`build-images.yml`, which now triggers on `dev`/`stage`/`main`) builds `:stage` + `:stage-<sha>`;
|
||||
then **bump `TAG=stage-<sha>`** in `komodo/resources.toml` and deploy from Core. `main` only ever
|
||||
receives what survived staging.
|
||||
- **`stage` was branched from `dev`** (2026-06-29) so the first real-world test carries the full current
|
||||
app, not a `main` that predates this work. `park-buzi`'s Stack `branch` + `TAG` both point at `stage`.
|
||||
- **DB migrations run at boot** (`migrate-runtime.mjs`), so a promotion auto-migrates the staging booth's
|
||||
SQLite ledger — staging is exactly where a bad migration is caught before production.
|
||||
- **Per-booth secrets must pre-exist** in Core for `park-buzi`: `[[park_buzi_jwt_secret]]`,
|
||||
`[[park_buzi_event_signing_key]]`, `[[park_buzi_backup_key]]` — distinct, never shared. Once the booth
|
||||
signs real entries under its `EVENT_SIGNING_KEY`, that key is load-bearing for its ledger forever
|
||||
(escrow it; see [[backup-recovery]]).
|
||||
|
||||
## Why this is safe (against the project's two forces)
|
||||
|
||||
@@ -136,8 +160,11 @@ here so it isn't re-litigated.
|
||||
then central secrets carry the blast-radius noted above.
|
||||
- **Periphery hardening checklist** folded into [[disk-os-hardening]] (interface binding, passkey,
|
||||
TLS, agent as TCB).
|
||||
- **Staging vs production booth split** (a staging booth on `:dev` with a webhook; production
|
||||
manual+pinned) — not yet modelled in `komodo/`.
|
||||
- **Staging vs production booth split** — ✅ MODELLED 2026-06-29 (see "Promotion tiers" below).
|
||||
`park-buzi` is the first **staging** booth, tracking the `stage` branch / `:stage` image, deployed
|
||||
**manual + pinned** (`TAG=stage-<sha>`, no webhook — we hold the no-moving-tag-on-a-booth line even
|
||||
on staging, *not* the webhook-on-staging option the earlier sketch floated). `komodo/resources.toml`
|
||||
+ `komodo/README.md` updated.
|
||||
- **Core backup / DR** — Core is now Tier-0; its loss = no fleet management (operation
|
||||
unaffected, per offline-first). Backup story TBD.
|
||||
|
||||
|
||||
Reference in New Issue
Block a user