CI / CD

Reference CI/CD pattern. The repository does not ship a workflow file — this page is the canonical pattern operators should adapt.

This section is intended for: Technical Team. Unauthorised access is restricted.

Audit status: PartialPattern documented. Deterministic build script (scripts/build.sh) and build-manifest pattern do exist.

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.md for 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

SecretWhere it livesNotes
RAILWAY_TOKENGitHub Actions secretScope: deploy permission only
VERCEL_TOKENGitHub Actions secretScope: deploy permission only
BACKEND_URL / FRONTEND_URLGitHub Actions secretPublic URLs; not sensitive but parameterised
Application secrets (JWT_SECRET, DATABASE_URL, etc.)Railway / Vercel env storesNever in CI
Federation keypair (FEDERATION_PRIVATE_KEY)Railway env, encrypted at restRotate 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:

  1. Run the build inside a pinned Node 20 image.
  2. Use npm ci (NOT npm install).
  3. Upload build-manifest.json as 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

Canonical source: docs/deployment/ci-cd.md

This page mirrors the markdown deployment hub on disk. The full markdown source includes additional code blocks, command examples, and embedded reference tables.

Hub index: /docs/deployment

You are here · Deploy · step 12
Secrets Managementnext step

Next in Deploy: Secrets Management.

What should I do next?

Activation Flowprimary

continues in "deploy"

Ranked using IA v1 graph + intent map + glossary density (deterministic; no AI inference).