CI/CD
CI/CD — GitHub Actions
All CI/CD pipelines run on GitHub Actions. Each workflow lives in its own .yml file under .github/workflows/.
ci.yml — Pull Request Quality Gate
Trigger: pull_request targeting dev (feature work), release/* (release candidates), or hotfix/* (production hotfixes) — see the branch strategy
All jobs run in parallel where possible. No PR may be merged unless all jobs pass.
| Job | What it does |
|---|---|
lint-js | pnpm biome check --error-on-warnings . |
lint-py | ruff check backend/ + ruff format --check backend/ |
test-web-app | vitest run --coverage in ui/web-app |
test-marketing | vitest run --coverage in ui/marketing |
test-ios | vitest run --coverage in ui/ios |
test-api | pytest --cov --cov-fail-under=80 in backend/api |
test-worker | pytest --cov --cov-fail-under=80 in backend/worker |
type-check | turbo run type-check |
openapi-drift | Regenerate the ui/web-app API client from backend/api's OpenAPI schema and fail on any diff vs the committed client (keeps the typed client in lockstep with the API) |
build | turbo run build (depends on all lint + test jobs) |
Configure GitHub branch protection to require all CI jobs to pass before merging to dev. release/* and hotfix/* PRs into main additionally run the integration / E2E suite against a Neon Production branch — hotfix/* mirrors release/* (branches off the Production project, targets main, runs the same release suite). See Secure PHI GitOps.
Deployment workflows — one per deployable surface
Each deployable surface has its own workflow, triggered by a push to main with a path filter scoped to that surface. All follow the same pattern: authenticate via Workload Identity Federation (keyless), build + push a Docker image to Artifact Registry, then google-github-actions/deploy-cloudrun.
| Workflow | Deploys | Path filter |
|---|---|---|
deploy-api.yml | backend/api — FastAPI HTTP service | backend/api/** |
deploy-worker.yml | backend/worker — Restate durable-execution handlers | backend/worker/** |
deploy-web-app.yml | ui/web-app — Vite + TanStack web app | ui/web-app/** |
deploy-marketing.yml | ui/marketing — Next.js marketing site | ui/marketing/** |
Workload Identity Federation is the required auth method — it eliminates storing GCP service-account keys as GitHub secrets.
PR Preview Environments — pr-env-up.yml / pr-env-down.yml
Every pull request gets an isolated full-stack environment — a Neon branch, a backend API on Cloud Run, and a frontend UI on Cloud Run — that is automatically provisioned and torn down.
See PR Preview Environments for the full specification, including workflow structure, secrets, and application code requirements.
dbt.yml — Nightly Data Pipeline
Trigger: Schedule 0 3 * * * (3:00 AM UTC) + workflow_dispatch
| Step | Command |
|---|---|
| Auth | google-github-actions/auth — Workload Identity Federation (keyless) |
| Setup | pip install dbt-bigquery |
| Extract-load | Trigger the neon-to-bq Cloud Run Job (neon_to_bq.py) — incremental Neon → BigQuery raw_* |
| Run | dbt run --target prod (transforms in BigQuery) |
| Test | dbt test (BigQuery) |
dbt uses the dbt-bigquery adapter and authenticates to BigQuery via Workload Identity Federation — it transforms entirely inside BigQuery and never connects to Neon/Postgres. The Neon → BigQuery move is the separate extract-load step, which runs as a scheduled Cloud Run Job before dbt run.
See Data Pipeline for details on the extract-load job and dbt models.
Workflow Organization
Each workflow is a separate file, following the code organization principle:
.github/workflows/
├── ci.yml # PR quality gate (lint · Vitest · pytest · type-check · openapi-drift · build)
├── deploy-api.yml # Cloud Run deploy — backend/api (FastAPI)
├── deploy-worker.yml # Cloud Run deploy — backend/worker (Restate)
├── deploy-web-app.yml # Cloud Run deploy — ui/web-app
├── deploy-marketing.yml # Cloud Run deploy — ui/marketing
├── pr-env-up.yml # Ephemeral PR environment spin-up
├── pr-env-down.yml # Ephemeral PR environment teardown
└── dbt.yml # Nightly data pipeline (extract-load Cloud Run Job + dbt run/test on BigQuery)