UAT and production are neighbours, not copies.
Same git history. Different projects, secrets, and hosts. The mistake that hurts is treating a deploy as a chance to clone one environment onto the other.
One trunk
main is the line. UAT and production are not long lived branches. Web clients promote by shipping the same commit to a second Firebase project. Mobile production is an EAS profile that pins the live API and groove.com.na.
The two worlds
| UAT | Production | |
|---|---|---|
| Firebase | groove-5afa9 | groove-prd |
| API | groove-api | groove-api-prd |
| Fan web | hosted.app URL | https://groove.com.na |
| Organisers | hosted.app URL | https://organiser.groove.com.na |
| API URL | Cloud Run host | https://api.groove.com.na |
| Mongo | UAT URI secret | groove-api-mongodb-uri · database groove |
| Maris | uat-api / uat-km | api-gw.mtc.com.na / km.mtc.com.na |
| SMTP and Maris secrets | shared UAT names | groove-api-prd-* only |
Shipping the API
- Build and push the container image you intend to ship.
- Run the production deploy script with IMAGE only.
- Do not export UAT YAML and apply it to groove-api-prd.
Before and after
- Before the Cloud Run update, confirm MONGODB_URI is groove-api-mongodb-uri.
- Confirm no *-uat or shared SMTP secret is mounted on prod.
- After the deploy, read the bindings again. Then hit /ready.
When it goes wrong
Prefer traffic back to a known good revision:
gcloud run services update-traffic … --to-revisions=<known-good>=100
That puts the previous image in front of fans without rebuilding the environment from UAT. A config clone is how the 23 July 2026 incident remounted the wrong Mongo, SMTP, and Maris. We prefer not to collect sequels.
Longer books
- docs/deployment/README.md
- docs/deployment/branching-and-environments.md
- docs/deployment/fan-web-production-runbook.md
- docs/deployment/organisers-production-runbook.md
- infra/gcp/deploy-api-production.sh
Running Groove · October 2026
