Skip to content

Chuck Runbook

Chuck Runbook

Operational procedures for Chuck (Strapi 5 in apps/chuck/meat/).

Tier: secondary. Chuck is a content source for marketing pages on brisket. An outage degrades marketing copy and FAQ rendering. It does not affect signup, checkout, billing, or media generation. See chuck-oncall for SLO and escalation tier.

Deploy

Routine deploy

The Strapi-managed deploy command is the supported path (apps/chuck/meat/package.json"deploy": "strapi deploy").

Terminal window
cd apps/chuck/meat
pnpm install # if dependencies changed
pnpm build # admin panel bundle
pnpm strapi deploy

strapi deploy is the Strapi Cloud deploy command (the @strapi/plugin-cloud plugin is installed in apps/chuck/meat/package.json, but not wired in apps/chuck/meat/config/plugins.ts). TODO(@marty): document the actual production deployment target — Strapi Cloud, Railway, or a custom host. There is no apps/chuck/railway.json or apps/chuck/meat/railway.json checked in.

Pre-deploy checklist

  1. pnpm install succeeds and lockfile is committed.
  2. pnpm build succeeds locally with the production env.
  3. New / changed content types reviewed — see Schema Migrations.
  4. brisket is not blocking on a chuck change. If brisket needs a new field, deploy chuck first, then brisket.
  5. API token rotations coordinated with brisket (chuck-env).

Post-deploy verification

Terminal window
# Replace with the live host
curl -sf "https://<chuck-host>/_health" -o /dev/null && echo OK
curl -sf "https://<chuck-host>/api/checkout-faq-section?populate=*" \
-H "Authorization: Bearer $STRAPI_TOKEN" | jq '.data.id'

TODO(@marty): confirm the actual healthcheck path used by the runtime — Strapi 5 exposes /_health by default and the repo does not override it (no health route in apps/chuck/meat/src/index.ts or config/); admin login (/admin) remains the most reliable smoke test.

Schema Migrations

Strapi treats content-type schemas (apps/chuck/meat/src/api/*/content-types/*/schema.json) as code. Changing a schema adds, renames, or drops a column on next boot.

Discipline

  • Never edit schema.json on a running prod instance. The admin UI also writes schema.json — disable editing in prod via config/admin.ts if not already. TODO(@marty): confirm the current apps/chuck/meat/config/admin.ts setting in production — the checked-in file does not include a disable block for content-type builder.
  • Add fields, do not rename in place. Renames drop the old column. Add a new field, backfill via the admin or a script, then remove the old field in a follow-up deploy.
  • Test migrations locally first. Run with a clone of the prod DB (see Backup / Restore) and verify Strapi boots and data is intact.
  • Check brisket coupling. Field renames break apps/brisket/src/server/api/routers/*.ts consumers. Grep brisket for the field name before merging.
  • Components have shared schemas. A breaking change to a component (apps/chuck/meat/src/components/<cat>/<name>.json) propagates to every content type that uses it.

Drafts and publish

Every content type sets "options": { "draftAndPublish": true }. The default Strapi find query returns published only. Updates land as drafts until an editor publishes — runbook implication: a “missing content” report after a deploy may be unpublished drafts, not a bug.

Plugin Updates

@strapi/strapi, @strapi/plugin-cloud, and @strapi/plugin-users-permissions must move together at the same minor version (currently 5.5.1). Procedure:

  1. Branch.
  2. Update apps/chuck/meat/package.json versions in lockstep.
  3. pnpm install.
  4. pnpm build — admin panel must build clean.
  5. Boot with a copy of prod data; click through admin panel; smoke-test /api/checkout-faq-section.
  6. Read the Strapi 5 changelog for deprecations.

Third-party providers (strapi-provider-cloudflare-r2, strapi-plugin-tablify, @strapi/provider-email-nodemailer) move independently — verify each against the matching Strapi major.

Backup / Restore

Postgres dump

Terminal window
pg_dump "$DATABASE_URL" --format=custom --file=chuck-$(date -u +%Y%m%dT%H%M%SZ).dump

Restore:

Terminal window
pg_restore --clean --if-exists --no-owner -d "$DATABASE_URL_TARGET" chuck-...dump

Strapi transfer (DB + uploads, app-level)

Terminal window
cd apps/chuck/meat
pnpm strapi transfer --to file://./chuck-$(date -u +%Y%m%dT%H%M%SZ).tar.gz

The token is salted by TRANSFER_TOKEN_SALT (chuck-env). Transfers include uploads metadata; the R2 bucket itself must be backed up out-of-band (Cloudflare R2 versioning or scripted copy).

Cadence

No automated backup is checked into the repo (find apps/chuck -name 'backup*' -o -name 'cron*' returns no matches; no GitHub workflow under .github/workflows/ targets chuck). TODO(@marty): set up daily pg_dump to long-term storage and a quarterly transfer test.

Rollback

Chuck has two state surfaces: code (schema.json, components, plugins) and data (Postgres + R2).

Code-only rollback

git revert the deploy commit and redeploy via the steps above. Safe when the bad change was a schema addition or a code-only bug.

Schema-destructive rollback

If the bad deploy dropped a column or renamed a content type, restoring the previous deploy will not bring data back. Restore from the most recent pg_dump taken before the deploy:

  1. Put chuck into maintenance — TODO(@marty): document the chosen maintenance gate (Strapi 5 has no first-party maintenance toggle, so the choice between proxy-level gating and stopping the process needs to be recorded; the repo does not pin one).
  2. pg_restore to a fresh DB.
  3. Redeploy the previous code revision pointed at the restored DB.
  4. Notify brisket if the public surface changed.

Token / secret rollback

Rotating API_TOKEN_SALT invalidates every issued API token, including brisket’s STRAPI_TOKEN. Rollback:

  1. Issue a new API token in /admin → Settings → API Tokens.
  2. Update STRAPI_TOKEN in brisket’s deployment.
  3. Redeploy brisket.

Do not rotate APP_KEYS and ADMIN_JWT_SECRET together — admin sessions will hard-fail. Rotate APP_KEYS first (prepend new key, keep old until next deploy), then rotate ADMIN_JWT_SECRET separately.

Incident Triage Quick Reference

SymptomFirst checkSee
brisket marketing pages render with empty FAQ / hero copyIs the relevant single-type published? Hit /api/<plural>?populate=* with STRAPI_TOKEN.chuck-api
brisket gets 401 from chuckSTRAPI_TOKEN revoked or salt rotatedchuck-env
Admin login fails with Invalid tokenADMIN_JWT_SECRET rotatedchuck-env
Image upload returns 500R2 creds or bucketchuck-errors
Boot fails with ECONNREFUSEDPostgres unreachablechuck-errors
Admin returns CSP errors on uploaded imagesCF_PUBLIC_ACCESS_URL mismatchchuck-env

Anchor Docs