Deliver-E Infrastructure

standards & topology

Products, domains & sites

The single source of truth for what serves where. Every custom domain fronts a Firebase Hosting site; API domains are Hosting rewrite sites that proxy everything to a Cloud Run service.

Production (deliver-e-prod)

ProductDomainHosting siteBackend / repo
Website — customersdelivere.appclient-web-delivereDeliver-E-Websitelive
Website — merchantsmerchants.delivere.appmerchants-web-delivereDeliver-E-Websitelive
Website — driverdriver.delivere.appdriver-web-delivereDeliver-E-Websitelive
Admin consoleadmin.delivere.appadmin-web-delivereAdmin-applive
Admin APIadmin.api.delivere.appadmin-api-delivere ⇒ Cloud Run admin-apiAdmin-apilive
Client APIapi.delivere.appclient-api-delivere ⇒ Cloud Run client-apiDeliver-E-Services-live
Docs (this site)docs.delivere.appdocs-web-delivereDocslive
E4 websitee4.delivere.app— (GitHub Pages)E4-websitelive
Client app (Flutter)— (Play Console)Deliver-E-Developmentlive

Staging (deliver-e-staging)

ProductDomainHosting siteBackend / repoNotes
Website — customersstg.delivere.appclient-web-stg-delivereDeliver-E-Websitelive
Website — merchantsstg.merchants.delivere.app not attached yetmerchants-web-stg-delivereDeliver-E-Website
Website — driverstg.driver.delivere.app not attached yetdriver-web-stg-delivereDeliver-E-Website
Admin consolestg.admin.delivere.appadmin-web-stg-delivereAdmin-applive
Admin APIstg.admin.api.delivere.appadmin-api-stg ⇒ Cloud Run admin-api-stagingAdmin-apilive
Client APIstg.api.delivere.appclient-api-staging ⇒ Cloud Run client-api-stagingDeliver-E-Services-live
Client app (Flutter)— (.stg flavor AAB)Deliver-E-Developmentbuilt on push to staging
Docs / E4no staging by design — static, low blast-radius; PR previews cover pre-merge checks

Naming conventions

  • Domains: resources nest under the apex — all APIs live in the api.delivere.app namespace (admin.api.delivere.app, future payments.api.delivere.app…). The environment prefix goes leftmost: stg.<prod-domain>. No other environment tokens in domains.
  • Firebase Hosting sites: <thing>-web[-stg]-delivere for web apps, <thing>-api[-stg|-delivere] for API rewrite sites. Site IDs are globally unique across all of Firebase and immutable — the -delivere suffix guarantees availability; renaming a site means creating a new one and migrating (see runbook).
  • Cloud Run services: <thing>-api (prod) / <thing>-api-staging (staging), one per Firebase project. Region is europe-west1 — Firebase Hosting rewrites do not support africa-south1.
  • Repos: new repos follow the template repo (workflows, branches, README, AGENTS.md). One product per repo.

Branch & pipeline model

The gold standard, implemented identically in Admin-api, Admin-app, Deliver-E-Services- and Deliver-E-Website.

BranchPurposeOn push
feature/<name>work branches, cut from dev/develop, deleted after merge
dev / developintegrationdev-project Firestore indexes (where applicable)
stagingthe real preview environmentfull staging deploy (Hosting / Cloud Run / Firestore)
mainproductionfull prod deploy
  • Nothing reaches main except via staging. Branch protection is unavailable on the org's current GitHub plan (private repos, free tier), so this is enforced by a guard step at the top of every prod deploy workflow: the deploy fails if main contains changes (compared by patch-id, so identical cherry-picked hotfixes pass) not present in staging.
  • PR preview channels only on PRs targeting main — internal feature→dev and dev→staging PRs must not create prod preview channels (staging is the preview).
  • Pin CI tool versions (e.g. firebase-tools@15.22.3, never @latest) — an upstream release once broke all deploys for a day.
  • All deploy workflows carry workflow_dispatch so a failed run can be re-run manually after fixing secrets/infra.
  • Repos without staging (Docs, E4-website) state so in their README and simply have no staging branch or workflow — absence + documentation, not disabled leftovers.

Environments & services

EnvironmentFirebase / GCP projectCloud Run services (europe-west1)
Productiondeliver-e-prodadmin-api, client-api
Stagingdeliver-e-stagingadmin-api-staging, client-api-staging
Dev / localdeliver-e-android-v1 (+ emulators)— (run locally)
  • Prod deploy auth: Workload Identity Federation (no stored keys) — pool github-pool, deployer SA gh-deployer@deliver-e-prod; each repo needs a one-time pool binding + the GCP_WIF_PROVIDER/GCP_DEPLOY_SA repo variables.
  • Staging deploy auth: GCP_SA_KEY_STAGING repo secret (staging SA key).
  • New public Cloud Run services: the org may enforce Domain Restricted Sharing, which blocks new allUsers invoker grants (existing ones are grandfathered). If a fresh service 403s after deploy, that's why.

DNS doctrine (Porkbun)

  • Every Firebase-attached hostname gets an explicit pair: A 199.36.158.100 + TXT hosting-site=<site-id>. The A record routes; the TXT proves which site owns the name (Firebase requires per-name proof — a shared IP alone is ambiguous and would otherwise allow hostname takeover).
  • Wildcards (*.delivere.app, *.api.delivere.app, *.admin.api.delivere.app199.36.158.100) are a safety net, not a substitute. A DNS wildcard only answers for names with no records at all — the moment a TXT exists at a name, the wildcard stops covering its A lookup. (Learned the hard way: adding ownership TXTs without explicit A records briefly took hostnames offline.)
  • Order of operations: create DNS records before telling Firebase about a domain — querying a name before its record exists poisons resolver negative-caches for up to the SOA TTL.
  • All DNS changes go through the Porkbun API (api.porkbun.com/api/json/v3; the name field takes the subdomain only, and dryRun validates without writing). No manual console edits — the console's host field auto-appends the domain and has produced doubled records like x.delivere.app.delivere.app.
  • Email records are load-bearing: Google Workspace (apex MX/DKIM/DMARC), Firebase Auth mail (mail./staging./dev. subdomains) and Resend (send.*) — note Resend puts records at send.<sender-domain>, so send.send.delivere.app is legitimate, not a typo.

Site-migration runbook

Validated on every production domain (2026-07). Moves a domain between Hosting sites with zero downtime — no rebuild, no console.

  1. Create the new site (Hosting REST API, POST /projects/{p}/sites?siteId=…).
  2. Clone the live content: versions:clone from the old site's live version, finalize: true, then create a release on the new site. Byte-identical — no rebuild, no env drift. Verify on <site>.web.app.
  3. DNS first (Porkbun API): ensure the explicit A record exists and set/edit TXT hosting-site=<new-site>.
  4. Move the domain: DELETE …/sites/{old}/customDomains/{domain} then POST …/sites/{new}/customDomains?customDomainId={domain}. Moves between sites keep serving throughout; only brand-new attaches wait a few minutes for a certificate.
  5. Update the repo config (firebase.json / .firebaserc site IDs) through the normal branch flow so CI targets the new site. Do not delete the old site until this reaches main — prod deploys would otherwise publish into a void.
  6. Verify ownership flips to OWNERSHIP_ACTIVE (~10-min recheck cycle) and the domain serves. If an edge cache serves a stale “Site Not Found”, release a fresh version (clone-to-self) to purge it.
  7. Delete the old site. (The project's default site can never be deleted — it just goes dormant.)
API access notes: Hosting REST calls need an OAuth token plus the x-goog-user-project header; the Hosting API is authoritative for domain state — the console lags it.