Build and deploy
Deployment is branch-driven and mostly automatic. The parts that are not automatic are the ones that cause incidents, so read the exceptions before the happy path.
The happy path
Test
- Push to any non-
mainbranch of an app or API repository. - CI builds an image and tags it
:test(a moving tag), stamping an OCI revision label. - CI resolves the VPS branch, runs
git reset --hardon the gateway repository, thengenerate.sh, thenreconcile.sh. - Compose brings the stack up and installs the crons.
- The deploy holds
flock, so two deploys cannot overlap.
vps-reverse-proxy/stacks/greyscreentv-test/docker-compose.yml is the source of truth for the
test stack. Its README is stale about image tags and the Firestore API.
Production
- Push or merge to
main. - CI builds a
:prodimage and pushes it. - A deploy job runs behind the GitHub
productionenvironment. Whether it needs approval depends on that environment's protection rules, not on the workflow file. - The same
flock-guarded deploy applies it.
Repositories with a production pipeline: gstv-website, greyscreen-webview,
gstv-admin-web, gstv-customer-api, gstv-admin-api.
Repositories without one:
| Repository | Reality |
|---|---|
gstv-vps | Its main workflow is manual and has no deploy job. The production image is a separate Artifact Registry tag that no repo builds. |
gstv-multimedia | Test auto-deploys with no gate. |
gstv-tv, greyscreen-expo-webview | EAS + store distribution, not servers. |
gstv-infra, gstv-secrets | Operator-run scripts. |
The rule that matters
When a change touches both the worker and the API — a new queue, new job fields, a lane move — deploy the worker image first, then the API.
A producer running ahead of its consumer is not a stuck job; it is an infinite work signal. The worker VM scheduler counts a non-empty queue as work and will create a machine every reconcile tick that nothing can drain. This is exactly what produced 212 VM creations in 24 hours on 2026-10-21.
Building locally
| Repository | Command |
|---|---|
| Website | npm run build:test / npm run build:prod (mode selects the env file) |
| Webview | npm run build |
| Admin web | npm run build:test / npm run build:prod |
| Customer API | npm run build |
| Admin API | npm run build |
| Worker | npm run build |
| Multimedia | container build; needs gcsfuse at runtime |
A frontend build without the matching env file fails on purpose. Do not create a .env to get
past it; set the mode.
Deploying the worker by hand
Only when you are deliberately moving a queue contract and have deployed the consumer:
- Build and push the image the instance template references, not the CI reference.
- Confirm the tag and digest.
- Drain or pause producers if the new image is incompatible with in-flight jobs.
- Let the scheduler replace the VM, or delete the current one so the next reconcile creates it from the new template.
There is no automatic path for this. Treat it as a production change with a rollback plan.
Gateway changes
domains.json is the source of truth. After adding or changing a route:
- Run
generate.shto regenerate the per-domain nginx configs. - Run
reconcile.shto apply them. - Confirm the path publicly — a route that only exists in the repo is not live.
Push to a non-main branch of the gateway repo to reach the test VPS.
Native releases
- Phone shell: EAS profiles in
eas.json.APP_VARIANT/EAS_BUILD_PROFILEselect the Firebase key folder and app IDs. Android production builds come from theandroidbranch, iOS fromios. - TV: EAS plus Play. The submit profile references
google-play-key.json, which does not exist in the repository.
Native releases do not follow a server rollback. A bad client can outlive a backend fix.
Pre-deploy checklist
- Which environments does this change affect? Production is a separate approval.
- Does it touch a queue contract or shared image? If yes, worker first.
- Does it add a public path?
domains.json+generate.sh. - Does it change artwork dimensions or WebP settings? Both the server and the client mirror.
- Does it change a Firestore document ID or field name? That is an API contract.
- Are the env files present for the mode you are building?
Next: Rollback — what is actually possible.