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)
| Product | Domain | Hosting site | Backend / repo | |
|---|---|---|---|---|
| Website — customers | delivere.app | client-web-delivere | Deliver-E-Website | live |
| Website — merchants | merchants.delivere.app | merchants-web-delivere | Deliver-E-Website | live |
| Website — driver | driver.delivere.app | driver-web-delivere | Deliver-E-Website | live |
| Admin console | admin.delivere.app | admin-web-delivere | Admin-app | live |
| Admin API | admin.api.delivere.app | admin-api-delivere ⇒ Cloud Run admin-api | Admin-api | live |
| Client API | api.delivere.app | client-api-delivere ⇒ Cloud Run client-api | Deliver-E-Services- | live |
| Docs (this site) | docs.delivere.app | docs-web-delivere | Docs | live |
| E4 website | e4.delivere.app | — (GitHub Pages) | E4-website | live |
| Client app (Flutter) | — (Play Console) | — | Deliver-E-Development | live |
Staging (deliver-e-staging)
| Product | Domain | Hosting site | Backend / repo | Notes |
|---|---|---|---|---|
| Website — customers | stg.delivere.app | client-web-stg-delivere | Deliver-E-Website | live |
| Website — merchants | stg.merchants.delivere.app not attached yet | merchants-web-stg-delivere | Deliver-E-Website | |
| Website — driver | stg.driver.delivere.app not attached yet | driver-web-stg-delivere | Deliver-E-Website | |
| Admin console | stg.admin.delivere.app | admin-web-stg-delivere | Admin-app | live |
| Admin API | stg.admin.api.delivere.app | admin-api-stg ⇒ Cloud Run admin-api-staging | Admin-api | live |
| Client API | stg.api.delivere.app | client-api-staging ⇒ Cloud Run client-api-staging | Deliver-E-Services- | live |
| Client app (Flutter) | — (.stg flavor AAB) | — | Deliver-E-Development | built on push to staging |
| Docs / E4 | no 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.appnamespace (admin.api.delivere.app, futurepayments.api.delivere.app…). The environment prefix goes leftmost:stg.<prod-domain>. No other environment tokens in domains. - Firebase Hosting sites:
<thing>-web[-stg]-deliverefor web apps,<thing>-api[-stg|-delivere]for API rewrite sites. Site IDs are globally unique across all of Firebase and immutable — the-deliveresuffix 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.
| Branch | Purpose | On push |
|---|---|---|
feature/<name> | work branches, cut from dev/develop, deleted after merge | — |
dev / develop | integration | dev-project Firestore indexes (where applicable) |
staging | the real preview environment | full staging deploy (Hosting / Cloud Run / Firestore) |
main | production | full prod deploy |
- Nothing reaches
mainexcept viastaging. 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 ifmaincontains changes (compared by patch-id, so identical cherry-picked hotfixes pass) not present instaging. - 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_dispatchso 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
| Environment | Firebase / GCP project | Cloud Run services (europe-west1) |
|---|---|---|
| Production | deliver-e-prod | admin-api, client-api |
| Staging | deliver-e-staging | admin-api-staging, client-api-staging |
| Dev / local | deliver-e-android-v1 (+ emulators) | — (run locally) |
- Prod deploy auth: Workload Identity Federation (no stored keys) — pool
github-pool, deployer SAgh-deployer@deliver-e-prod; each repo needs a one-time pool binding + theGCP_WIF_PROVIDER/GCP_DEPLOY_SArepo variables. - Staging deploy auth:
GCP_SA_KEY_STAGINGrepo secret (staging SA key). - New public Cloud Run services: the org may enforce Domain Restricted Sharing, which blocks new
allUsersinvoker 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.app→199.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; thenamefield takes the subdomain only, anddryRunvalidates without writing). No manual console edits — the console's host field auto-appends the domain and has produced doubled records likex.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 atsend.<sender-domain>, sosend.send.delivere.appis 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.
- Create the new site (Hosting REST API,
POST /projects/{p}/sites?siteId=…). - Clone the live content:
versions:clonefrom 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. - DNS first (Porkbun API): ensure the explicit
Arecord exists and set/editTXT hosting-site=<new-site>. - Move the domain:
DELETE …/sites/{old}/customDomains/{domain}thenPOST …/sites/{new}/customDomains?customDomainId={domain}. Moves between sites keep serving throughout; only brand-new attaches wait a few minutes for a certificate. - Update the repo config (
firebase.json/.firebasercsite IDs) through the normal branch flow so CI targets the new site. Do not delete the old site until this reachesmain— prod deploys would otherwise publish into a void. - 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. - 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.