Skip to content

Operations ​

How Whereabot runs in production, and the traps we already hit. The step-by-step runbook is infra/README.md. The code and workflows are the source of truth for every value. This page names the files and explains why they are shaped the way they are.

Delivery is GitOps ​

Every change reaches dev and prod through a workflow on main. Nobody deploys from a laptop except as break-glass.

WhatWorkflowTrigger
Tests, lint, line capci.ymlevery PR and push
Database migrationsmigrations.ymlpush to main: dev, then production
Relay + Supabase configdeploy.ymlpush to main: test, image, dev, then production after approval
Landing page and docssite-cloudflare-deploy.ymlpush to main
iOS app to TestFlightios-testflight-release.ymltag app-vX.Y.Z

Migrations and deploys are separate workflows with no ordering between them. Keep that in mind for the next section.

Config that needs a migration ships in two pushes ​

The Supabase auth config (for example an auth hook) is applied by deploy.yml. The Postgres function it points at is created by migrations.yml. If both land in one push, the config can reach an environment before the function exists, and the apply fails.

  1. First push: the migration only.
  2. Check that migrations.yml applied it in dev and production.
  3. Second push: the config in supabase.tf that uses it.

New migration files must sort after every migration already applied. CI refuses a file with an older timestamp. See adding a migration.

Optional secrets stay behind a flag ​

Cloud Run refuses a revision that mounts a secret with no version. The APNs key secret is created empty, and a human adds the key later. So the relay mounts it only when apns_enabled is true. See push.tf and cloud_run.tf. Turn the flag on per environment only after the secret has a version.

Domains, DNS and certificates ​

The live hosts are listed once, in infra/README.md, Live URLs.

  • Relay host. A Cloud Run domain mapping (cloud_run_domain_mapping.tf). Google lets only a verified owner of the domain create a mapping, and the CI service account is not one. A human domain owner creates the mapping once, then imports it into the prod state. The resource ignores certificate_mode, because it reads back empty after import and would force a replacement.
  • DNS records. In infra/dns/, applied by a human. The relay CNAME must be DNS only (not proxied). Google issues the certificate and must see the CNAME.
  • Verify, do not trust the dashboard. A record saved in the Cloudflare dashboard did not persist once. After any DNS change, query a public resolver for the record, then wait until the relay answers over HTTPS. The certificate can take a while after DNS is correct.
  • Site host. The site workflow attaches the custom domain to the Worker and owns the apex record.

Release the iOS app ​

The normal path is a tag. The steps are in infra/README.md, Release the iOS app.

  • scripts/release-app-version.sh bumps every version file. Merge that, then push the tag it prints.
  • Fallback when CI cannot run: build and upload from a Mac with the same two steps as the workflow (a Tauri iOS build with the App Store Connect export method, then an upload with altool). Use the same API key and environment values the workflow reads.
  • TestFlight rejects a version it already has. Bump and tag again.

Steps that need a human ​

Some steps change shared accounts. An agent prepares them, but a human does them or explicitly approves them:

  • DNS changes in the Cloudflare account that owns the zone.
  • Writing secret values (GitHub environment secrets, Secret Manager versions).
  • IAM grants and domain ownership in Google Search Console.
  • Approving the production environment gate.

After a human step, verify the result (resolver query, /health, workflow run) before you continue.