Chuck Local Development
Chuck Local Development
How to run Chuck (Strapi 5 in apps/chuck/meat/) locally. The package
manager for this app is pnpm
(apps/chuck/meat/package.json → "packageManager": "pnpm@10.30.2"),
not yarn or npm.
Prerequisites
| Tool | Required | Why |
|---|---|---|
| Node.js | >= 18.0.0 <= 22.x.x | Strict engine in package.json. Node 24 is not supported — boot fails. Use nvm use 22. |
| pnpm | 10.30.2 (per packageManager) | Lockfile is pnpm-lock.yaml. |
| Docker | optional | For local Postgres. |
| Postgres 14+ | recommended | Production driver. SQLite works but is a fallback. |
First-Run Bootstrap
cd apps/chuck/meatpnpm installcp .env.example .envGenerate fresh secrets
The .env.example ships with committed dummy secrets (APP_KEYS,
API_TOKEN_SALT, ADMIN_JWT_SECRET, TRANSFER_TOKEN_SALT,
JWT_SECRET). They are visible in git history and must not be reused
outside throwaway local dev.
# Generate four 16-byte base64 keys for APP_KEYSfor i in 1 2 3 4; do node -e "console.log(require('crypto').randomBytes(16).toString('base64'))"; done
# And the salts / secretsnode -e "console.log(require('crypto').randomBytes(16).toString('base64'))" # API_TOKEN_SALTnode -e "console.log(require('crypto').randomBytes(16).toString('base64'))" # ADMIN_JWT_SECRETnode -e "console.log(require('crypto').randomBytes(16).toString('base64'))" # TRANSFER_TOKEN_SALTnode -e "console.log(require('crypto').randomBytes(16).toString('base64'))" # JWT_SECRETPaste them into .env. See chuck-env for the
full table of vars.
Postgres
Strapi defaults to Postgres in apps/chuck/meat/config/database.ts.
The .env.example includes a one-liner for a local Postgres container:
docker run --name foxy-postgres \ -e POSTGRES_USER=root -e POSTGRES_PASSWORD=root \ -e POSTGRES_DB=strapi \ -p 5432:5432 -d postgresThen in .env:
DATABASE_CLIENT=postgresDATABASE_URL=postgres://root:root@localhost:5432/strapiDATABASE_SSL=falseSQLite fallback
If you want zero-deps:
DATABASE_CLIENT=sqliteDATABASE_FILENAME=.tmp/data.dbThe DB file lives at apps/chuck/meat/.tmp/data.db. Schema migrations
run on every boot, so deleting the file is a clean reset.
R2 (Uploads)
For local dev you generally don’t need real R2 credentials — uploads will fail but everything else works. If you do need media working locally:
- Get an R2 token scoped to a dev bucket (Cloudflare dashboard).
- Fill
CF_ACCESS_KEY_ID,CF_ACCESS_SECRET,CF_ENDPOINT,CF_BUCKET,CF_PUBLIC_ACCESS_URLin.env.
CF_PUBLIC_ACCESS_URL is also used by CSP middleware
(apps/chuck/meat/config/middlewares.ts), so a wrong value will block
images in the admin panel even if the upload itself succeeds.
Email (SMTP)
Optional locally. Leave SMTP_* empty and the email plugin will fail
on send (acceptable for content work). If you need it, point at a
local SMTP catcher (e.g. Mailpit or mailhog/mailhog Docker image).
Run
cd apps/chuck/meatpnpm develop # dev mode with auto-reload + admin panel rebuildThis starts Strapi on HOST:PORT (.env defaults map to
http://localhost:1333). The admin panel is at /admin.
Other useful commands:
pnpm build # build admin panel for productionpnpm start # run already-built process (no auto-reload)pnpm strapi --helpAdmin User Bootstrap
On first boot (with an empty DB), open http://localhost:1333/admin.
Strapi prompts you to create the first admin in the browser. There
is no CLI for this in Strapi 5 — the form is the only path.
For an empty Postgres DB you only need to do this once. Subsequent
admin users are created from /admin → Settings → Users.
If you ever need to re-bootstrap with a wiped DB:
# Destructive — wipes everythingdocker rm -f foxy-postgres # if using dockerdocker run --name foxy-postgres ... postgres # recreate# Or for SQLite:rm -rf apps/chuck/meat/.tmp/data.dbAPI Token for Brisket Locally
To exercise the brisket → chuck path locally:
pnpm developchuck.- Create the first admin via the browser.
/admin→ Settings → API Tokens → “Create new API Token”.- Choose Read-only (or Full access for testing). Copy the token.
- In brisket’s
.env:STRAPI_URL=http://localhost:1333STRAPI_TOKEN=<paste>
Content Seeds
There is no checked-in content seed for chuck — content is created
in the admin panel and persisted in the DB. To bring local up to a
production-like state, restore a pg_dump from staging or use
pnpm strapi transfer:
# Pull from a deployed instance (interactive)cd apps/chuck/meatpnpm strapi transfer --from https://<src-host> --from-token <transfer-token>The transfer token is salted with TRANSFER_TOKEN_SALT
(chuck-env). Treat snapshots like prod data —
do not commit them.
TODO(@marty): document whether a sanitized seed dump exists in
shared storage, and where — no seed file or fixture is checked into
apps/chuck/ (find apps/chuck -iname 'seed*' -o -iname 'fixture*'
returns no matches).
Verifying the Install
# 1. Healthcurl -sf http://localhost:1333/_health -o /dev/null && echo health-ok
# 2. Public-or-tokened contentcurl -s http://localhost:1333/api/checkout-faq-section?populate=* \ -H "Authorization: Bearer $STRAPI_TOKEN" | jq '.data.id // .error'
# 3. Admin reachablecurl -sI http://localhost:1333/admin | head -1If all three return success, brisket can be pointed at this instance.
Troubleshooting
- “engine ‘node’ is incompatible” —
nvm use 22. pnpm: command not found—corepack enable && corepack prepare pnpm@10.30.2 --activate.- Boot exits with
Missing API_TOKEN_SALT— copy.env.exampleand fill it; pnpm does not load env from anywhere else. - Admin login loop —
APP_KEYSchanged since last login; clear cookies forlocalhost:1333. relation "..." does not exist— DB out of sync. With SQLite, delete.tmp/data.db. With Postgres, drop and recreate thestrapidatabase.- More in chuck-errors.
Anchor Docs
- API surface: chuck-api
- Env vars: chuck-env
- Errors: chuck-errors
- Runbook: chuck-runbook