A reference CI/CD pattern for Govula. The repository does not ship a .github/workflows/ file out of the box; this document is the canonical pattern operators should adapt.
Status: Partial. Pattern is documented; no shipped workflow file. The deterministic build script (scripts/build.sh) and the build-manifest pattern referenced below DO exist (see ../ENTERPRISE-DEPLOYMENT.md §"Deterministic Builds").
Pipeline shape
push to PR ──► lint + typecheck + unit tests (gate: must pass)
push to main ──► build + integration tests (gate: must pass)
──► deterministic build (build-manifest)
──► deploy to Railway (backend) + Vercel (frontend)
──► post-deploy smoke (health / readiness / CORS rejection)
──► tag git release with build hash
Reference GitHub Actions workflow
.github/workflows/deploy.yml (adapt to your repo):
name: deploy
on:
push:
branches: [main]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with: { node-version: '20', cache: 'npm' }
- run: npm ci
- run: npm run typecheck
- run: npm test
- name: Frontend
working-directory: frontend
run: |
npm ci
npm run typecheck
npm run build
deploy-backend:
needs: test
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Deploy Railway
env:
RAILWAY_TOKEN: ${{ secrets.RAILWAY_TOKEN }}
run: |
npm i -g @railway/cli
railway up --service govula-api
deploy-frontend:
needs: test
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Deploy Vercel
env:
VERCEL_TOKEN: ${{ secrets.VERCEL_TOKEN }}
working-directory: frontend
run: |
npm i -g vercel
vercel deploy --prod --token=$VERCEL_TOKEN
smoke:
needs: [deploy-backend, deploy-frontend]
runs-on: ubuntu-latest
steps:
- name: Liveness
run: curl -fsS ${{ secrets.BACKEND_URL }}/health
- name: Readiness
run: |
status=$(curl -s -o /tmp/r.json -w '%{http_code}' \
${{ secrets.BACKEND_URL }}/api/v1/health)
test "$status" = "200"
jq -e '.data.db == "ok" and .data.rls == "ok"' /tmp/r.json
- name: CORS rejection
run: |
status=$(curl -s -o /dev/null -w '%{http_code}' \
-H 'Origin: https://evil.example' \
${{ secrets.BACKEND_URL }}/api/v1/health)
test "$status" = "403"
Deploy gates
The smoke job above is the canonical gate. Add to it:
- Tracking-ingest happy-path (
POST /api/v1/investor/track→ 201) — see../production-checklist.mdfor the exact assertion. - Body-parser regression (bad JSON → 400, oversized body → 413).
- Frontend page coverage (
/,/investor,/enterprise/pilot, every page in the post-deploy checklist).
Secret handling
| Secret | Where it lives | Notes |
|---|---|---|
RAILWAY_TOKEN | GitHub Actions secret | Scope: deploy permission only |
VERCEL_TOKEN | GitHub Actions secret | Scope: deploy permission only |
BACKEND_URL / FRONTEND_URL | GitHub Actions secret | Public URLs; not sensitive but parameterised |
Application secrets (JWT_SECRET, DATABASE_URL, etc.) | Railway / Vercel env stores | Never in CI |
Federation keypair (FEDERATION_PRIVATE_KEY) | Railway env, encrypted at rest | Rotate quarterly; see secrets-management.md |
Rollback automation
Two patterns, pick one:
Pattern A — Manual one-click rollback (recommended for small teams)
Document the procedure in ../rollback-plan.md. On-call clicks Redeploy on the previous green deployment in Railway / Promote to Production on the previous Vercel deployment.
Pattern B — Auto-rollback on smoke failure
Extend smoke to fail loud, then add a follow-up job:
rollback:
needs: smoke
if: failure()
runs-on: ubuntu-latest
steps:
- name: Roll back Railway
env: { RAILWAY_TOKEN: ${{ secrets.RAILWAY_TOKEN }} }
run: railway redeploy --service govula-api --previous
Deterministic builds
scripts/build.sh (already in repo) generates a build-manifest.json containing version, git hash, build timestamp, Node version, and platform. CI should:
- Run the build inside a pinned Node 20 image.
- Use
npm ci(NOTnpm install). - Upload
build-manifest.jsonas a workflow artefact and surface its hash in the deploy log.
This makes "what code is running in production?" a single SHA lookup.
Where to read more
secrets-management.md— secret rotation../rollback-plan.md— manual rollback procedure../production-checklist.md— post-deploy assertions- In-app:
/docs/deployment/ci-cd