---
id: "8.12"
title: "Hosting map"
dept: ops-and-infrastructure
status: draft
owner: Param
branded: false
last_verified: 2026-10-26
sources:
  - "brew/.wt-prod/docs/production.md:1-21"
  - "brew origin/main a559de6: deploy.sh:39-41, tools/promote.sh:65-105, tools/prod-env.sh:4-9, tools/deploy-demo.sh:14-16, tools/partner-copy.sh:13"
  - "brand-directions/_tools/deploy-handbook.py:1-115, brand-directions/_tools/deploy-docs.py:1-28"
  - "handbook/comments/server.mjs:1-15, handbook/comments/Dockerfile, handbook/tools/next-build.json"
  - "memory brew-prod-backend, brew-storage, brew-demo-mode, brew-prototype-repo, brand-directions-2026-10-25, brew-handbook"
---

# Hosting map

## Answer

Everything runs in one Google Cloud project, squareup-492617 in asia-south1, with Cloudflare in front for DNS; this page maps each site and service to where it runs, what it stores in and how it is released, including the docs and this handbook.

## Today

### Sites

| Site | Address | Runs on | Stores in | Released by | Source |
|---|---|---|---|---|---|
| Production | feed.joinsquareup.com | Cloud Run `brew-web`, 2 to 10 instances, and `brew-worker`, exactly 1, behind load balancer `squareup-urlmap`, Cloud Armor `brew-prod-armor` | Cloud SQL `brew-prod`, Postgres 17, regional HA; `gs://brew-prod` | `tools/promote.sh` | [src: brew/.wt-prod/docs/production.md:9-15] |
| Staging | brew.joinsquareup.com | Cloud Run `shopos-feed`, one instance, both roles | Cloud SQL `brew-staging`; `gs://brew-prototype`, mirrored | `./deploy.sh` | [src: brew/.wt-prod/docs/production.md:9-14] [src: brew/deploy.sh:39-41] |
| Demo | demo.joinsquareup.com | Cloud Run `brew-demo`, one instance, `DEMO_MODE=1` | `gs://brew-prototype-demo` only | `tools/deploy-demo.sh` | [src: brew/tools/deploy-demo.sh:14-16] |
| Shared docs | docs.joinsquareup.com | Firebase Hosting site `squareup-docs` | static files | `brand-directions/_tools/deploy-docs.py` | [src: brand-directions/_tools/deploy-docs.py:1-9] [src: memory brand-directions-2026-10-25] |
| Handbook | handbook.joinsquareup.com | Firebase Hosting site `squareup-handbook`, public with no sign in since 26 Oct at Param's call; pages send `X-Robots-Tag: noindex` so search engines skip them; DNS is a DNS-only Cloudflare CNAME to squareup-handbook.web.app | static build of `handbook/out` | `brand-directions/_tools/deploy-handbook.py --public`, domain by `brand-directions/_tools/handbook-domain.py` | [src: brand-directions/_tools/deploy-handbook.py:57-63] [src: Param, 26 Oct, "host it without any auth"] |
| Handbook comments | none while the handbook is public | Cloud Run `handbook-comments` needs the Cloudflare Access identity, so it stays undeployed; comments on pages do nothing for now | Firestore collection `handbook_comments` | not deployed | [src: brand-directions/_tools/deploy-handbook.py:10, 22] [src: handbook/comments/server.mjs:1-4] |

The handbook deploy refuses to upload unless `check.mjs` has 0 errors, a signed-out request to handbook.joinsquareup.com is sent to Cloudflare Access or refused, and the build exists. The pages are never uploaded to Hosting as files, because Hosting also serves every site at squareup-handbook.web.app, which Access does not cover. After release the script checks that the web.app, firebaseapp.com and run.app addresses all refuse a signed-out request, and stops with LEAK if one does not. `--skip-gate` is only for a first upload Param has approved. [src: brand-directions/_tools/deploy-handbook.py:4-9] [src: brand-directions/_tools/deploy-handbook.py:51-59]

Next's first build step is switching on that gate and sending Claude the Access team domain and audience tag. [src: handbook/tools/next-build.json]

### Cloud Run jobs

| Job | Does | Runs as | Source |
|---|---|---|---|
| `brew-prod-migrate` | `db/migrate.js` on production, during a promote | `brew-prod-runtime` | [src: brew/tools/promote.sh:65-67] |
| `brew-prod-clean` | Smoke test and clean gate, inside the VPC | `brew-prod-runtime` | [src: brew/tools/promote.sh:85-89] |
| `brew-partner-copy` | Partner copy from staging to production | `brew-partner-copy` | [src: brew/tools/partner-copy.sh:13] |
| `brew-ops` | Staging status, jam reset, parity | `brew-runtime` | [src: memory brew-prod-backend] |
| `brew-import` | Bucket to Postgres import on staging | | [src: memory brew-storage] |
| `brew-prod-ping`, `brew-staging-ping` | Read-only connector ping | | [src: memory brew-prod-backend] |

### Shared pieces

| Piece | Detail | Source |
|---|---|---|
| Network | Direct VPC egress on subnet `default`; databases have private IPs only, 172.30.0.4 and 172.30.0.3 | [src: brew/.wt-prod/docs/production.md:11] [src: brew/tools/prod-env.sh:13] |
| Ingress | `brew-web` takes only load balancer traffic; `brew-worker` is internal | [src: brew/tools/promote.sh:74] [src: brew/tools/promote.sh:94] |
| CDN | Off on production; session affinity made it useless | [src: memory brew-prod-backend] |
| Backups bucket | `gs://brew-prod-backups`: nightly export and the promote lock | [src: brew/.wt-prod/docs/production.md:40] |
| Service accounts | `brew-prod-runtime`, `brew-runtime`, `brew-partner-copy`, `brew-backup` | [src: brew/tools/prod-env.sh:5-6] [src: memory brew-prod-backend] |
| Source code | private GitHub repo wulfor24/brew, branch main | [src: memory brew-prototype-repo] |

### Retired

Cloud Run `arata-feed`, `arata-feed-v1` and `shopos-feed-demo` with their routes were removed on 15 Oct; the buckets `gs://squareup-feed` and `gs://squareup-feed-demo` were kept. [src: memory brew-prototype-repo]

## Tomorrow

| Item | Version | Tag |
|---|---|---|
| Production, staging and demo as above | v1 | Stays |
| Staging's bucket mirror | v2 | Goes |
| The demo on its own Postgres | v2 | New |
| Handbook comments service deployed | v1 | New, after the gate is on |
| Production on the product's own domain | v2 | Changes, waits on the name |

## How we do it

1. Release each site only with its own tool from the table.
2. Publish the handbook:

```bash
cd /Users/param/Documents/ChatGPT/handbook && node check.mjs && node build.mjs && node tools/snapshot.mjs build
python3 ../brand-directions/_tools/deploy-handbook.py
```

3. Publish the shared docs:

```bash
cd /Users/param/Documents/ChatGPT/brand-directions && node _tools/bundle.mjs && python3 _tools/deploy-docs.py
```

4. The deploy script builds and deploys the service itself; set `CF_TEAM_DOMAIN`, `CF_ACCESS_AUD` and `ALLOWED_EMAILS` before running it. [src: handbook/comments/server.mjs:9-13]
5. A new service gets a row in this map the day it is created.

## Numbers

| Metric | Definition | Where measured |
|---|---|---|
| Services running | Cloud Run services and jobs in squareup-492617 | `gcloud run services list`, Proposed monthly check against this map |
| Unmapped services | Services running that this page does not list | Proposed; target 0 |

## Open

1. **When does the handbook go live?** The deploy script already keeps the pages off public hosting and refuses a release if squareup-handbook.web.app, firebaseapp.com or run.app answer a signed-out request, but nothing is uploaded until Cloudflare Access is on. Proposal: Param switches on Access for handbook.joinsquareup.com this week and sends the team domain and audience tag, then the first release runs with the gate, never with `--skip-gate`.
2. **Who besides Param gets into the handbook?** Proposal: Access allows Param, Sai and Arvind by email; the comments service's `ALLOWED_EMAILS` holds the same three.
3. **Should we clean up the old buckets `gs://squareup-feed` and `gs://squareup-feed-demo`?** Proposal: export a copy to cold storage, then delete them after Param confirms nothing reads them.
