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.

JobWhat it does
lint-jspnpm biome check --error-on-warnings .
lint-pyruff check backend/ + ruff format --check backend/
test-web-appvitest run --coverage in ui/web-app
test-marketingvitest run --coverage in ui/marketing
test-iosvitest run --coverage in ui/ios
test-apipytest --cov --cov-fail-under=80 in backend/api
test-workerpytest --cov --cov-fail-under=80 in backend/worker
type-checkturbo run type-check
openapi-driftRegenerate 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)
buildturbo 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 branchhotfix/* 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.

WorkflowDeploysPath filter
deploy-api.ymlbackend/api — FastAPI HTTP servicebackend/api/**
deploy-worker.ymlbackend/worker — Restate durable-execution handlersbackend/worker/**
deploy-web-app.ymlui/web-app — Vite + TanStack web appui/web-app/**
deploy-marketing.ymlui/marketing — Next.js marketing siteui/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

StepCommand
Authgoogle-github-actions/authWorkload Identity Federation (keyless)
Setuppip install dbt-bigquery
Extract-loadTrigger the neon-to-bq Cloud Run Job (neon_to_bq.py) — incremental Neon → BigQuery raw_*
Rundbt run --target prod (transforms in BigQuery)
Testdbt 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)