Skip to content

Preview environments

Every pull request can get an ephemeral preview: a backend on Cloud Run plus the Members and Clinicians apps on Firebase Hosting preview channels. This lets a change be tested in isolation before it merges to develop.

The pipeline is the Preview - Backend and Apps workflow (preview-pr.yml); teardown is Preview - Cleanup Resources (preview-cleanup.yml) plus a scheduled Preview - Janitor (preview-janitor.yml). See GitHub workflows.

Triggers

  1. Pull request targeting develop, types ready_for_review and synchronize, path-filtered to backend, Members, Clinicians, and shared packages.
  2. Manual dispatch with force_deploy_all to bypass the optimization checks and force a redeploy.

How the pipeline works

To keep full rebuilds (15-30m+) rare, the workflow optimises in layers:

  • Change detection (detect_changes) — dorny/paths-filter decides which components (backend, members, clinicians) changed; unchanged components skip their whole chain.
  • Content hashing (check_*_changes) — for each changed component it hashes source + lock files (pnpm-lock.yaml / pubspec.lock), shared packages, and the Dockerfile, and compares to a value cached per PR. Hash match → skip; mismatch or force → deploy.

Jobs:

  • build_and_preview (backend) — builds the functions, Dockerises them (GHA layer cache; image tagged with the content hash), deploys to Cloud Run.
  • resolve_backend_url — frontend apps point at the new backend preview if one was deployed, otherwise fall back to the staging backend (so frontend-only PRs test against a stable API).
  • members_preview / clinicians_preview — install Flutter via FVM, inject the resolved BFF_URL into assets/environment_values/environment.json, flutter build web --release, deploy to a Firebase Hosting channel pr-{PR_NUMBER} (30-day expiry).
  • comment_deployment_status — posts/updates one sticky PR comment with per-component status (✅ deployed, ⏭️ skipped, ⚪ no change, ❌ failed), URLs, API endpoints, and the content hash + commit SHA.

Backend Cloud Run details

Production runs on Firebase Cloud Functions; previews reuse the same code on Cloud Run via an adapter:

  • functions/src/cloudrun.ts — wraps the Firebase Functions as a plain Express app.
  • Dockerfile / .dockerignore — multi-stage container build.

Each preview service:

  • Name backend-preview-pr-{PR_NUMBER}, region europe-west2, project perci-platform-staging.
  • Publicly accessible (test data only).
  • Limits: 2 GB memory, 1 CPU, 300s timeout, concurrency 80, min instances 0 (scale to zero), max 10.

Environment uses the staging config with preview overrides: ENV=preview, DD_ENV=preview, DD_SERVICE=perci-platform-backend-preview, DD_TRACE_ENABLED=false, SSO_CLEANUP_ENABLED=false, SLACK_PLATFORM_CHANNEL=platform-preview. Secrets are fetched from Google Secret Manager at deploy time.

Endpoints

  • API: {SERVICE_URL}/api/v1
  • Member BFF: {SERVICE_URL}/bff/v1
  • Clinical BFF: {SERVICE_URL}/bff_clinical/v1
  • Clinical Media: {SERVICE_URL}/bff_clinical_media/v1
  • Health: {SERVICE_URL}/_health · Info: {SERVICE_URL}/

The Redoc docs for each API are at …/openapi — see API reference.

Cleanup

  • On PR close/merge, preview-cleanup.yml deletes the Cloud Run service, its Artifact Registry images, and the PR's GitHub Actions caches, then comments confirmation.
  • preview-janitor.yml runs weekly (Sun 03:00 UTC) to sweep orphaned preview resources; a manual run supports a dry-run input.

Local testing

Run the Cloud Run adapter locally:

cd apps/perci-platform-backend/functions
pnpm install
pnpm run build            # clean && generate && tsc -b (generate needs secrets)
cp .env.sample .env       # then edit with local config
node lib/cloudrun.js      # serves at http://localhost:8080

Or build the container the same way CI does:

cd apps/perci-platform-backend
docker build -t backend-preview .
docker run -p 8080:8080 --env-file functions/.env backend-preview

Troubleshooting

Preview not deploying — check the workflow logs; common causes are TypeScript build errors, Docker build failures, missing GCP permissions, or inaccessible secrets.

503 errors — the container failed to start or crashed on init. Read the logs:

gcloud run services logs read backend-preview-pr-{PR_NUMBER} \
  --region europe-west2 --project perci-platform-staging

Environment variable issues — inspect the deployed service:

gcloud run services describe backend-preview-pr-{PR_NUMBER} \
  --region europe-west2 --project perci-platform-staging --format yaml

Security

Previews use staging credentials and are publicly reachable, so they must only handle test data. Secrets come from Secret Manager; preview URLs are posted as PR comments (visible to repo collaborators); services are deleted when the PR closes.