docs(deploy): runbook + workflow-rubriker matchar live-pipelinen #450
@@ -6,14 +6,14 @@ name: Deploy to Coolify
|
||||
# Dockerfile (build_pack: dockerfile) -- this workflow only fires the
|
||||
# rebuild+redeploy, it doesn't build or push an image itself.
|
||||
#
|
||||
# STATUS (2026-08-09): no Coolify application has been provisioned for 0pt
|
||||
# yet -- siax.config.json's `deploy.target` was flipped from "vercel" to
|
||||
# "coolify" to state the intended platform, but there is no app UUID to
|
||||
# point at. COOLIFY_APP_ID below is a literal placeholder. The guard step
|
||||
# fails the job loudly (rather than silently POSTing to a bogus/empty URL)
|
||||
# until both the Coolify app exists and its real UUID replaces the
|
||||
# placeholder here. See docs/architecture/ARCHITECTURE.md §5/§8 for the
|
||||
# open "Vercel or fleet" question this resolves the config side of.
|
||||
# STATUS (verified 2026-11-02): LIVE. The Coolify application exists
|
||||
# (dockerfile build pack, git.siax.io/sax3l/0pt, branch main) and its UUID is
|
||||
# supplied by the repository variable COOLIFY_APP_ID; COOLIFY_TOKEN and the
|
||||
# registry/cosign secrets are set. A push to main, or a manual
|
||||
# workflow_dispatch of this file, has been proven to verify the signed
|
||||
# attestation for the exact commit and then return HTTP 200 from Coolify.
|
||||
# The placeholder guard below remains as a fail-closed guard, not as a TODO.
|
||||
# Operational procedure and verification live in docs/runbook/deploy.md.
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
|
||||
@@ -29,8 +29,8 @@ name: Release chain (G17, ADR-W16-003)
|
||||
# workflow file -- learned the hard way on sax3l/h0st's and sax3l/aud0's
|
||||
# own G17 migrations. 0pt does not wire a G20 post-deploy step in this
|
||||
# pass at all (see `.siax/hooks/README.md` "G20 -- deliberately not wired
|
||||
# yet": no Coolify application provisioned for 0pt, no `/version`
|
||||
# endpoint), so the single-job constraint that forces G20 into its own
|
||||
# yet": the app is now provisioned and `/api/version` exists, but G20 is still
|
||||
# not wired in this pass), so the single-job constraint that forces G20 into its own
|
||||
# workflow file elsewhere does not yet bite here -- documented anyway so a
|
||||
# future PR adding G20 does not repeat the mistake.
|
||||
|
||||
|
||||
+77
-56
@@ -1,63 +1,84 @@
|
||||
# Runbook: Deploy — 0pt
|
||||
|
||||
> Mål: sätta 0pt.app på SIAX-flottan (Coolify) och avveckla Vercel i ordning.
|
||||
> Status 2026-08-10: `siax.config.json` deklarerar `target: coolify`; ingen
|
||||
> Coolify-app provisionerad; live-instansen körs fortfarande på Vercel.
|
||||
> Status 2026-11-02: **live on the SIAX fleet (Coolify)** at `https://0pt.app`.
|
||||
> The automatic chain and the manual path are both verified: a signed image is
|
||||
> attested, Coolify is called, the container restarts on the new commit, and
|
||||
> `/api/version` reports it.
|
||||
|
||||
## 1. Förutsättningar (blockerare för cutover)
|
||||
## Pipeline (automatic)
|
||||
|
||||
- [ ] Coolify-app provisionerad i `cloud.siax.io` (build_pack: dockerfile,
|
||||
repo `git.siax.io/sax3l/0pt`, gren `main`).
|
||||
- [ ] `COOLIFY_APP_ID` (appens UUID) ifylld i `.gitea/workflows/cd.yml`.
|
||||
- [ ] `COOLIFY_TOKEN`-secret satt på repot (Gitea → Settings → Actions → Secrets).
|
||||
- [ ] Env-secrets inlagda i Coolify-appen från Infisical-projektet `0pt`
|
||||
(se backup/restore + incident runbooks).
|
||||
- [ ] DNS-cutover planerad: `0pt.app` → Coolify-appens ingress (eller Caddy).
|
||||
|
||||
## 1b. Signerad-artefakt-grind (tillagd 2026-09-14, samma mönster som cl0ud/st0re/h0st)
|
||||
|
||||
`.gitea/workflows/release.yml` (nytt) bygger, SBOM:ar (syft), signerar
|
||||
(cosign) och evidensbokför varje push till `main`, och pushar till
|
||||
`git.cloud.siax.io/sax3l/0pt:sha-<12>`. `cd.yml`s `deploy`-jobb har nu ett
|
||||
extra steg (efter guard-steget i punkt 1 ovan) som kräver att exakt denna
|
||||
commits avbildning finns i registret och att dess cosign-signatur
|
||||
verifierar (mot committad `cosign.pub`) innan Coolify överhuvudtaget
|
||||
anropas.
|
||||
|
||||
Detta är byggt FÖRE Coolify-appen finns (se blockerarna i punkt 1) — så att
|
||||
grinden redan är på plats den dag appen provisioneras, i stället för att bli
|
||||
ett separat uppföljningsarbete. Samma ärliga begränsning som cl0ud/st0re/
|
||||
h0st: när appen väl provisioneras som `dockerfile` build pack (så som
|
||||
`siax.config.json` föreskriver) kör Coolify sin EGEN oberoende
|
||||
`docker build` från git-källan, inte den signerade avbildningen —
|
||||
`/api/v1/deploy` saknar digest-parameter helt. Sann digest-pinning skulle
|
||||
kräva att appen provisioneras som Coolifys separata `dockerimage`-resurstyp
|
||||
i stället, ett beslut som inte är fattat än. Se `.gitea/workflows/cd.yml`s
|
||||
"SIGNED-ARTIFACT GATE"-kommentar för den fullständiga skrivningen.
|
||||
|
||||
**Inte satt på `sax3l/0pt` ännu:** repo-secrets `REGISTRY_USER`,
|
||||
`REGISTRY_TOKEN`, `COSIGN_PRIVATE_KEY`, `COSIGN_PASSWORD`, `SIAX_CI_TOKEN` —
|
||||
`release.yml` fail-closed:ar på sin första körning tills dessa finns.
|
||||
|
||||
## 2. Deploy (när appen finns)
|
||||
|
||||
1. Push till `main` → `.gitea/workflows/cd.yml` POSTar till
|
||||
`https://cloud.siax.io/api/v1/deploy?uuid=$COOLIFY_APP_ID&force=false`,
|
||||
efter att den signerade-artefakt-grinden i punkt 1b gått grön.
|
||||
2. Följ run i Gitea Actions; verifiera att den slutar grön.
|
||||
3. Verifiera hälsa: `curl https://0pt.app/api/health` → `ok:true` och
|
||||
`db: configured` (när Postgres kopplats in).
|
||||
|
||||
## 3. Manuell redeploy (nöd)
|
||||
|
||||
```bash
|
||||
curl -fsS -X POST "https://cloud.siax.io/api/v1/deploy?uuid=<UUID>&force=true" \
|
||||
-H "Authorization: Bearer $COOLIFY_TOKEN"
|
||||
```
|
||||
git push main
|
||||
├─ ci.yml quality (typecheck, contrast, tests, build)
|
||||
│ integration (live OpenSearch + ClickHouse)
|
||||
│ security (Trivy, blocking)
|
||||
├─ security.yml push/PR/weekly scan
|
||||
├─ release.yml build + SBOM + blocking vuln scan + cosign attest + push
|
||||
│ → git.cloud.siax.io/sax3l/0pt:sha-<12>
|
||||
└─ cd.yml waits for THIS commit's attestation, verifies it against
|
||||
cosign.pub, then POSTs to Coolify
|
||||
prod-smoke.yml every 30 min: ready + db/stripe + running commit == main
|
||||
```
|
||||
|
||||
## 4. Vercel-avveckling (EFFER att Coolify kör stabilt)
|
||||
- One run per ref (`concurrency`); `push` is main-only, PRs are covered by the
|
||||
`pull_request` event. Deploys have their own non-cancelling group so two
|
||||
runs never race the same app.
|
||||
- Dependency caches: pnpm store (Node jobs), npm (doc0/n0d), pip (0pt-motor).
|
||||
|
||||
1. Verifiera att ingen verklig trafik/data ligger bakom (2026-08-10:
|
||||
`/api/health` visar `db/queue: not_configured` — marknads-/dev-läge).
|
||||
2. Peka DNS om till Coolify. 3. Behåll Vercel-projektet tills certifierad
|
||||
paritet (katalog, orderflöde, betalningar). 4. Avveckla först därefter.
|
||||
## Manual deploy (proven)
|
||||
|
||||
Trigger the same, signed path — do not POST to Coolify directly unless the
|
||||
pipeline itself is broken:
|
||||
|
||||
```bash
|
||||
curl -X POST -H "Authorization: token $GITEA_TOKEN" -H "Content-Type: application/json" \
|
||||
-d '{"ref":"main"}' \
|
||||
https://git.siax.io/api/v1/repos/admin/0pt/actions/workflows/cd.yml/dispatches
|
||||
```
|
||||
|
||||
Emergency only (bypasses the attestation gate; still builds the branch head):
|
||||
|
||||
```bash
|
||||
curl -X POST -H "Authorization: Bearer $COOLIFY_API_TOKEN" -H "Content-Type: application/json" \
|
||||
-d '{"uuid":"<COOLIFY_APP_ID>"}' https://cloud.siax.io/api/v1/deploy
|
||||
```
|
||||
|
||||
## Verify a deploy
|
||||
|
||||
```bash
|
||||
curl -s https://0pt.app/api/ready # {"ready":true,"db":"ok","stripe":"ok",...}
|
||||
curl -s https://0pt.app/api/health # liveness envelope
|
||||
curl -s https://0pt.app/api/version # {"commit":"<sha>"}
|
||||
```
|
||||
|
||||
`commit` must equal the current `main` SHA. `prod-smoke.yml` enforces exactly
|
||||
that every 30 minutes; a green deploy is never confused with a current service.
|
||||
|
||||
## Rollback
|
||||
|
||||
Coolify builds from the branch head, so a rollback is a normal revert:
|
||||
|
||||
```bash
|
||||
git revert --no-edit <bad-sha> && git push origin main
|
||||
```
|
||||
|
||||
That runs the same chain and deploys the reverted tree. Coolify's own
|
||||
"redeploy previous deployment" is the break-glass alternative.
|
||||
|
||||
## Configuration (names only — values live in Coolify/Infisical)
|
||||
|
||||
- Coolify app: `dockerfile` build pack, repo `git.siax.io/sax3l/0pt`, branch
|
||||
`main`, health check `/api/health`, domains `0pt.app` + `api.0pt.app`.
|
||||
- Repository variable: `COOLIFY_APP_ID`.
|
||||
- Repository secrets: `COOLIFY_TOKEN`, `REGISTRY_USER`, `REGISTRY_TOKEN`,
|
||||
`COSIGN_PRIVATE_KEY`, `COSIGN_PASSWORD`, `SIAX_CI_TOKEN`.
|
||||
- App env: `DATABASE_URL`, `STRIPE_SECRET_KEY`, `AUTH_SECRET`… see `.env.example`
|
||||
for the full, documented list (DOC0, INF0/decision Ollama, OpenFGA/project
|
||||
grants, model policy, credit price book, OIDC).
|
||||
|
||||
## Known limitation
|
||||
|
||||
Coolify rebuilds from source; `/api/v1/deploy` has no digest parameter, so the
|
||||
verified digest is recorded as provenance for the deploy rather than pinned as
|
||||
the running bytes. True digest pinning would require a Coolify `dockerimage`
|
||||
resource — an infrastructure decision, not a CI-YAML change.
|
||||
|
||||
Reference in New Issue
Block a user