Architecture
The app bundle is pure code it reads all data and models from the GCS CDN (gs://off-the-pace-cdn) at runtime. So a code deploy runs entirely in CI. The data export needs the 6 GB warehouse, which is not in CI producing and publishing data is an operator step that runs before a release. CI then validates that data in staging, promotes it to prod, and deploys the code.
One-time cloud setup
CI authenticates to GCP with Workload Identity Federation no long-lived JSON key.Provision WIF
Run the provisioning script from a machine with owner-level ADC:It prints two values. Store them as repository Actions variables (Settings → Secrets and variables → Actions → Variables tab they are not sensitive):
Until
GCP_WORKLOAD_IDENTITY_PROVIDER is set, deploy.yml and preview.yml skip on their if: guards releases won’t show a red deploy before setup.Enable GCS object versioning
Object versioning makes data rollback restore real parquet bytes rather than just the version pointer:This is also codified in
infra/terraform/bucket.tf (make tf-apply).Cutting a release
Versioning is automated by release-please (release-please.yml), driven by Conventional Commits on main.
Land conventional commits on main
Use
feat:, fix:, perf:, etc. as commit prefixes. release-please keeps an open “chore: release X.Y.Z” PR with the computed version and a generated CHANGELOG.md. Review it.Publish data to staging
From a machine with the 6 GB warehouse:
Merge the release PR
That tags the release and publishes a GitHub Release, which triggers
deploy.yml:- Smoke staging
- Build the app bundle
- Promote staging → prod (atomic manifest flip)
firebase deploy(code)- Smoke prod
make app-deploy still works for emergencies but bypasses staging/smoke prefer the pipeline.Preview deploys
Every app PR (preview.yml) builds against the staging data prefix and deploys to a Firebase preview channel pr-<n>, posting the shareable URL as a PR comment (expires 7 days). Fork PRs are skipped no WIF access.
Rollback
Prod’s data version pointer is theversion field in _manifest.json. Every promotion archives the superseded prod manifest to gs://…/data/manifest-archive/.
Parquet bytes vs. version pointer.
make app-rollback restores what the app loads, not necessarily what the bucket stores (publish overwrites parquet in place). To recover the actual parquet bytes you need object versioning enabled (step 2 of setup):Workflows at a glance
Local equivalents
Data pipeline
The DAG that produces the data that deployment promotes.
Observability
Synthetic monitors that guard the live serving plane after every deploy.