CI/CD Pipeline
Purpose
How code moves from a developer's machine to Production safely.
Pipeline
Push to feature branch
↓
Open PR into develop
↓
Automated checks run (lint, type-check, tests)
↓
Merge to develop → auto-deploys to Staging
↓
Manual verification on Staging
↓
Merge develop → main → auto-deploys to Production
Tooling
GitHub Actions (per the original infrastructure conversation) — handles the automated checks and triggers deployment to the VPS.
Deploy Mechanics (per original conversation, adapted for the current stack)
git pull
↓
npm install
↓
Run database migrations (03. Database → Migration Strategy — as a distinct step, before app restart)
↓
npm run build
↓
Restart app (zero-downtime restart preferred — e.g. via a process manager like PM2)
Why Migrations Are Called Out As Their Own Step
Given 03. Database → Migration Strategy's requirement that migrations run against Staging first and never bundle into app startup, this pipeline explicitly sequences migration-then-build-then-restart, rather than letting the app attempt to run against a schema it doesn't expect.
Open Questions
- Whether Production deploys require manual approval (a human clicking "deploy") or are fully automatic on merge to
main— recommend manual approval for Production specifically, given real money is at stake, even if Staging deploys automatically
Diagram
flowchart TD
A[Push to feature branch] --> B[Open PR into develop]
B --> C[Automated checks: lint, type-check, tests]
C --> D[Merge to develop]
D --> E[Auto-deploy to Staging]
E --> F[Manual verification]
F --> G[Merge develop into main]
G --> H[Manual approval for Production]
H --> I[Deploy to Production]
Update (2026-08-03): Two independent deploy paths
Following the FastAPI backend split, the "git pull u2192 npm install u2192 migrate u2192 build u2192 restart" mechanics above apply to the frontend only. The backend now has its own parallel path:
flowchart TD
A[git pull - backend repo] --> B[pip install / uv sync]
B --> C[Run Alembic migrations]
C --> D[Restart FastAPI - uvicorn/gunicorn, zero-downtime]
Frontend and backend can deploy independently (different repos or a monorepo with separate CI jobs — not yet decided, see Open Questions) since they're now separate services. The Staging-first, manual-approval-for-Production principles apply equally to both paths.
Open Questions (new)
- Monorepo (one repo, two deploy jobs) vs. two separate repos for frontend/backend — either is workable; monorepo keeps them in sync more easily, separate repos give cleaner independent versioning. Worth deciding before the codebase grows large enough that switching is painful.
Update (2026-08-04): Monorepo — Resolved
The earlier open question above ("monorepo vs. two separate repos") is resolved: monorepo, one repository with apps/frontend and apps/backend. Full reasoning and structure in 06. Infrastructure → Git Repository Strategy.
Update (2026-08-04): Canary Stage Added Between Approval and Full Production
The manual approval step above now gates the start of a canary deployment, not an immediate full Production rollout — see 06. Canary Deployment & Automated Rollback for the full flow: approval → 5% traffic → automated error-rate-gated monitoring (15 min checkpoint, 30 min total) → automated promotion to 100% or automated rollback. The human decision point stays exactly where it was (approving the deploy); what happens after approval is now automated rather than an immediate all-at-once cutover.
Update (2026-08-23): Frontend Deploys Via Vercel; Migration Step Corrected
Per Hosting Strategy's 2026-08-07 decision, the frontend (Next.js) now deploys via Vercel's own git-integrated pipeline (deploy-on-push, preview deployments per PR), not the "git pull → npm install → build → restart" VPS mechanics described above under "Deploy Mechanics" and reiterated in the 2026-08-03 "frontend only" update — those mechanics are retired for the frontend.
Also correcting an error in that same 2026-08-03 update: the "Deploy Mechanics" migration step ("Run database migrations") was written before the frontend/backend split and got folded into what the 2026-08-03 update then labeled "frontend only." Migrations are a backend/Alembic concern — they belong solely to the backend path already described in that update ("git pull - backend repo → pip install / uv sync → Run Alembic migrations → Restart FastAPI"), not to any frontend deploy step. The original "Deploy Mechanics" section above should be read as historical/superseded now that frontend deploys through Vercel and backend migrations run exclusively via the Alembic step in the backend path.
Update (2026-08-24): Monorepo Decision Reversed — Two Separate Repos
The "Update (2026-08-04): Monorepo — Resolved" entry below is superseded. Per Infrastructure & DevOps/Git Repository Strategy's 2026-08-24 reversal, frontend and backend now live in two separate repositories (sellvia-frontend, sellvia-backend), not one repo with apps/frontend/apps/backend.
Practical effect on this pipeline: the "git integration" each deploy path already runs from (Vercel watching the frontend repo, whatever triggers the backend VPS deploy watching the backend repo) now points at two distinct repos instead of one repo with two watched paths. The path-filtered-CI-jobs mechanism this doc's "two independent deploy paths" language originally implied (one repo, jobs scoped by path) is no longer how CI scoping works — it's simpler now: each repo's CI runs unconditionally on push/PR to itself, since there's no other service's code present to filter out. See Git Repository Strategy's "CI Per Repo" section.