ci: two-stage deploy — staging automatic, production behind a manual approval #33
@@ -112,11 +112,15 @@ Full details in `docs/DEPLOY.md`. The short version:
|
||||
- **Never push to `main` directly.** Work on a feature branch and open a PR;
|
||||
branch protection requires the PR checks (typecheck, tests, builds, AI review)
|
||||
to pass before merge.
|
||||
- Merging to `main` deploys automatically: images are built once, deployed to
|
||||
the **staging** Portainer stack, verified by the Playwright journeys in
|
||||
`e2e/`, and only then retagged `:prod` and rolled out to production.
|
||||
- Merging to `main` deploys automatically **to staging only**: images are
|
||||
built once, deployed to the staging Portainer stack, and verified by the
|
||||
Playwright journeys in `e2e/`. Production is a second, manual approval:
|
||||
the "Promote to Production (manual)" workflow in Gitea Actions, run after
|
||||
testing the feature on staging. It refuses commits whose staging E2E gate
|
||||
isn't green. Never trigger it yourself — promotion is the human's call.
|
||||
- If you change user-facing behaviour, update or extend the `e2e/` journey
|
||||
tests in the same PR — they are the promotion gate.
|
||||
tests in the same PR — they gate whether staging is fit for human testing
|
||||
and whether a commit is promotable.
|
||||
|
||||
## Recent Changes
|
||||
|
||||
|
||||
+36
-15
@@ -1,41 +1,61 @@
|
||||
# SDLC & Deployment Pipeline
|
||||
|
||||
SchoolCompare uses a fully automated staging → production pipeline on Gitea
|
||||
Actions. AI writes the code on feature branches; the pipeline verifies every
|
||||
change on a staging environment before promoting the exact same images to
|
||||
production. Human input is directional only: feature requests, PR review if
|
||||
desired, and intervention when a gate fails.
|
||||
SchoolCompare uses a two-stage deploy model on Gitea Actions with two human
|
||||
approvals. AI writes the code on feature branches; the first approval merges
|
||||
the PR, which deploys to staging and runs the E2E gate; the second approval —
|
||||
after manual testing on staging — promotes the exact same images to
|
||||
production via a manual workflow.
|
||||
|
||||
## The flow
|
||||
|
||||
```
|
||||
feature branch (AI-authored)
|
||||
│ PR to main
|
||||
│ PR to main ← approval #1
|
||||
▼
|
||||
PR checks (.gitea/workflows/pr-checks.yml)
|
||||
typecheck + unit tests + backend smoke + image builds (no push)
|
||||
+ Claude code review posted as a PR comment (severe findings fail the check)
|
||||
│ merge (branch protection requires green checks)
|
||||
▼
|
||||
Deploy pipeline (.gitea/workflows/deploy.yml)
|
||||
Stage pipeline (.gitea/workflows/deploy.yml) — automatic
|
||||
1. build & push images → tags sha-<sha>, staging
|
||||
2. staging Portainer webhook → wait for staging health
|
||||
3. Playwright E2E journeys against staging
|
||||
4. retag sha-<sha> → :prod (same bytes — build once, promote the image)
|
||||
3. Playwright E2E journeys against staging ← gate before human testing
|
||||
▼
|
||||
Manual testing on staging (stx.schoolcompare.co.uk)
|
||||
│ Actions → "Promote to Production (manual)" ← approval #2
|
||||
▼
|
||||
Promote pipeline (.gitea/workflows/promote.yml) — manual dispatch
|
||||
1. resolve target sha (input, or latest main if empty)
|
||||
2. REFUSE unless that commit's "E2E Journeys against Staging" status is green
|
||||
3. retag sha-<sha> → :prod (same bytes — build once, promote the image)
|
||||
previous :prod saved as :prod-previous
|
||||
5. prod Portainer webhook → wait for prod health
|
||||
4. prod Portainer webhook → wait for prod health
|
||||
```
|
||||
|
||||
Key principle: **build once, promote the exact image**. Production pins `:prod`,
|
||||
which only moves after the E2E gate passes on staging. Nothing tags `:latest`
|
||||
which only moves when a human runs the promote workflow — and the workflow
|
||||
only accepts commits that passed the staging E2E gate. Nothing tags `:latest`
|
||||
anymore.
|
||||
|
||||
## Branch & PR workflow
|
||||
|
||||
- `main` is protected: no direct pushes, PRs require green status checks.
|
||||
- All work (human or AI) happens on feature branches → PR to `main`.
|
||||
- Merging to `main` **is** the release action. If staging or the E2E gate
|
||||
fails, production is untouched.
|
||||
- Merging to `main` releases **to staging only**. Production moves only on
|
||||
the second approval. If staging or the E2E gate fails, fix forward —
|
||||
production is untouched either way.
|
||||
|
||||
## Promotion granularity
|
||||
|
||||
Staging always runs the latest `main`. Promoting approves a *state of main*,
|
||||
not a single PR — if two PRs merged since the last promotion, they ship
|
||||
together. Test staging accordingly. To promote an older state, pass its
|
||||
commit SHA to the promote workflow (its images must still exist in the
|
||||
registry).
|
||||
|
||||
Staging quirk for manual testing: external `/api` is broken at the staging
|
||||
proxy — exercise API endpoints from the host, not via the public staging URL.
|
||||
|
||||
## Environments
|
||||
|
||||
@@ -92,8 +112,9 @@ fail the E2E gate. That's the point: staging absorbs the risk.
|
||||
|
||||
## Rollback
|
||||
|
||||
Every promotion first re-points `:prod-previous` at the outgoing `:prod`.
|
||||
To roll back:
|
||||
Re-run "Promote to Production (manual)" with the SHA of the last good commit
|
||||
(fastest, fully gated), or manually re-point the tags — every promotion first
|
||||
saves the outgoing `:prod` as `:prod-previous`:
|
||||
|
||||
```bash
|
||||
for img in backend frontend pipeline; do
|
||||
|
||||
Reference in New Issue
Block a user