---
id: "8.1"
title: "Runbook index"
dept: ops-and-infrastructure
status: draft
owner: Param
branded: false
last_verified: "2026-10-26"
sources:
  - "brew origin/main a559de6: deploy.sh:1-53, tools/promote.sh:1-119, tools/prod-env.sh:1-13, tools/flush-live.sh:1-26, tools/reset-brand.sh:1-20, db/reset-brand.mjs:1-13, db/delete-brand.mjs:1-12, tools/reset-jams.sh:1-30, tools/partner-copy.sh:1-22, tools/deploy-demo.sh:1-26, tools/demo-sync.sh:1-30, db/reset-jams.mjs:1-30, db/status.mjs:1-27, db/connector-ping.mjs:1-24, db/parity.mjs:1-30, db/import-bucket.mjs:1-30, db/migrate.js:1-27"
  - "brew/.wt-prod/docs/production.md:23-81"
  - "brew origin/main d6154b3: docs/production.md:66-110, .github/workflows/test.yml:1-37"
  - "brand-directions/_tools/deploy-handbook.py:1-115, brand-directions/_tools/deploy-docs.py:1-60"
  - "memory brew-prod-backend, brew-wipe-data, brew-jam-reset, brew-dns-local, brew-parallel-sessions-deploy, brew-storage"
---

# Runbook index

## Answer

This is the list of every operational runbook we have, what each one is for and when to reach for it, so nobody runs a script from memory or picks the wrong one for the site in front of them.

## Today

### Releasing

| Runbook | Use it when | Site | Where it is written |
|---|---|---|---|
| `./deploy.sh` | A change is tested and committed; deploy right away | Staging | [Deploy and promote](/ops-and-infrastructure/deploy-and-promote) [src: brew/deploy.sh:1-7] |
| `tools/promote.sh` | Staging runs what production should run | Production | [Deploy and promote](/ops-and-infrastructure/deploy-and-promote) [src: brew/tools/promote.sh:1-9] |
| `tools/deploy-demo.sh` | The demo needs new code | Demo | [src: brew/tools/deploy-demo.sh:1-6] |
| `tools/demo-sync.sh <slug>` | A prepared brand's public pack goes to the demo | Demo | [src: brew/tools/demo-sync.sh:1-7] |
| `tools/partner-copy.sh <slug> apply` | A partner's data is copied once from staging to production | Staging to production | [src: brew/tools/partner-copy.sh:1-7] |
| `python3 brand-directions/_tools/deploy-handbook.py` | The handbook changed and `check.mjs` passes | Handbook | [Hosting map](/ops-and-infrastructure/hosting-map) [src: brand-directions/_tools/deploy-handbook.py:1-10] |
| `python3 brand-directions/_tools/deploy-docs.py` | The shared docs changed | Docs | [src: brand-directions/_tools/deploy-docs.py:1-4] |

### Restoring and checking

| Runbook | Use it when | Site | Where it is written |
|---|---|---|---|
| Clone to a moment | A bad migration or mass damage on production | Production | [Restore](/ops-and-infrastructure/restore) [src: brew/.wt-prod/docs/production.md:47-50] |
| One brand back | One brand deleted or damaged by mistake | Production | [Restore](/ops-and-infrastructure/restore) [src: brew/.wt-prod/docs/production.md:52-55] |
| Restore test | Monthly | Production into staging | [Restore](/ops-and-infrastructure/restore) [src: brew/.wt-prod/docs/production.md:59-64] |
| `db/status.mjs` through job `brew-ops` | Is Arata still connected, how big are the tables, are jobs stuck | Staging | [src: brew/db/status.mjs:1-2] [src: memory brew-prod-backend] |
| `db/connector-ping.mjs` through jobs `brew-prod-ping` and `brew-staging-ping` | Do a brand's stored sign-ins still read, after a restore or a copy | Either | [Protected connections](/ops-and-infrastructure/protected-connections) [src: brew/db/connector-ping.mjs:1-4] [src: memory brew-prod-backend] |
| `db/parity.mjs` | Does Postgres hold everything the bucket holds | Staging | [src: brew/db/parity.mjs:1-3] |
| `db/import-bucket.mjs` through job `brew-import` | After any staging deploy that ran on the bucket | Staging | [src: brew/db/import-bucket.mjs:1-6] [src: memory brew-storage] |
| Three DNS checks | Someone says the site is down | Any | [DNS and domains](/ops-and-infrastructure/dns-and-domains) [src: memory brew-dns-local] |

### Wiping and resetting

| Runbook | Use it when | Site | Where it is written |
|---|---|---|---|
| `db/reset-jams.mjs` through job `brew-ops` | Param asks to start the jams fresh | Staging, on Postgres | [Wipe data and reset jams](/ops-and-infrastructure/wipe-data-and-reset-jams) [src: brew/db/reset-jams.mjs:1-7] |
| "Wipe data" | Param says "wipe data" for a test brand | Staging | [Wipe data and reset jams](/ops-and-infrastructure/wipe-data-and-reset-jams) [src: memory brew-wipe-data] |
| `db/reset-brand.mjs` through job `brew-ops` | One brand back to a fresh first run, connections kept; also the way to flush every brand, one slug at a time | Staging | [Wipe data and reset jams](/ops-and-infrastructure/wipe-data-and-reset-jams) [src: brew/db/reset-brand.mjs:1-11] |
| `db/delete-brand.mjs` through job `brew-ops`, or `brew-prod-ops` once made | A brand asks us to delete its data | Staging; production after the next promote | [Data requests](/customer-success-and-support/data-requests) [src: brew/db/delete-brand.mjs:1-10] [src: brew/docs/production.md:95-106] |

### Scripts that no longer fit the site they name

- `tools/reset-jams.sh` edits `gs://brew-prototype` directly. Staging has kept its records in Postgres since 25 Oct, so it edits the mirror, not the truth. [src: brew/tools/reset-jams.sh:6] [src: memory brew-jam-reset]
- `tools/reset-brand.sh` and `tools/flush-live.sh` are bucket era too, but since 26 Oct they exit 3 unless `STORAGE=gcs` and print the `brew-ops` command to run instead. [src: brew/tools/reset-brand.sh:11-19] [src: brew/tools/flush-live.sh:17-26]

### Checking

| Runbook | Use it when | Site | Where it is written |
|---|---|---|---|
| GitHub Actions workflow `test` | Every push and pull request runs the deploy test gate on Node 22 with Postgres 17 | GitHub | [Testing and QA](/engineering/testing-and-qa) [src: brew/.github/workflows/test.yml:1-37] |

## Tomorrow

| Item | Version | Tag |
|---|---|---|
| One index of runbooks, each linked to its page | v1 | Stays |
| A Postgres brand reset and deletion, run as `brew-ops` jobs, on origin/main | v1 | Stays |
| A `brew-prod-ops` job on production for deletions | v1 | New |
| Retire the three bucket scripts; two already refuse on Postgres | v1 | Goes |
| Every runbook printed from code into this index on each build | v2 | New |

## How we do it

1. Find the job in the tables above, then open its page. Never run a script from memory.
2. Check which site the script names before running it. Staging scripts hard-code `shopos-feed` and `brew.joinsquareup.com`; production scripts source `tools/prod-env.sh`.
3. Run cloud scripts from a clean, detached worktree of origin/main, never the shared checkout:

```bash
git -C /Users/param/Documents/ChatGPT/brew fetch -q origin
git -C /Users/param/Documents/ChatGPT/brew worktree add --detach "$TMPDIR/brew-ops" origin/main
```

4. Job arguments with flags use gcloud's `"--args=^:^a:b:c"` form. [src: memory brew-prod-backend]
5. When a runbook changes, update its page and this index in the same session.

## Numbers

| Metric | Definition | Where measured |
|---|---|---|
| Runbooks without a page | Scripts in `tools/` or `db/` that this index does not list | This page against `git ls-tree origin/main tools/ db/`, Proposed |
| Stale runbooks | Scripts that target storage the site no longer uses and still run | This page, 1 today, `tools/reset-jams.sh` |

## Open

1. **Who may run production runbooks?** Today the scripts run with whoever's gcloud sign-in is on the Mac. Proposal: only Param runs `promote.sh` and the restore runbooks until a second person has done one restore test with him.
2. **Should the bucket scripts be deleted now?** `tools/reset-brand.sh` and `tools/flush-live.sh` already stop on `STORAGE=pg`. Proposal: give `tools/reset-jams.sh` the same first lines, and delete all three once production has run on Postgres for a month.
