Fennec Runbook
Operational guide for shipping, rolling back, and unsticking the fennec admin SPA in Railway.
Deploy topology
- Service:
beef-fennecon Railway, regionus-east4-eqdc4a, 1 replica (apps/fennec/railway.json). - Builder:
DOCKERFILEagainstapps/fennec/Dockerfile. The Dockerfile builds the Vite bundle and runsserve -s dist -l 3000in the runner stage (seeapps/fennec/DockerfileCMD). Railway runtime isV2perapps/fennec/railway.json. - Healthcheck:
GET /with a 120s timeout. Restart policy:ON_FAILURE, max 10 retries. - CDN / edge: Railway’s built-in routing. There is no dedicated CloudFront / Cloudflare distribution for fennec configured in this repo. TODO(@law): confirm no upstream CDN exists at the platform layer; runbook below assumes Railway-only.
CI gates (before any deploy)
.github/workflows/fennec.yml runs on push/PR to main and release when
apps/fennec/** changes. Two required jobs:
| Job | Command | Working dir |
|---|---|---|
fennec-lint | pnpm lint | apps/fennec |
fennec-typecheck | pnpm tsc | apps/fennec |
pnpm lint runs ESLint with --max-warnings 0 — any warning fails CI. The
workflow does not include a build, deploy, or test step today — Railway
performs the production build during deploy.
Standard deploy
Railway watches the release branch (TODO(@law): confirm the watched
branch in the Railway service settings — .github/workflows/fennec.yml gates
both main and release, but the deploying branch is set in the Railway
service config, which is not in-repo).
- Merge to the deploy branch.
- Railway auto-builds against
apps/fennec/Dockerfile. - New revision becomes active when healthcheck
GET /returns 200. - Smoke-test:
- Open the fennec URL.
- Sign in with a known operator account.
- Verify
/dashboardloads (admin-gated) and/users/mereturns 200.
If the new revision fails healthcheck, Railway leaves the previous revision serving traffic.
Rollback
Two options, in order of preference:
- Railway revision rollback. From the
beef-fennecservice → Deployments → select the previous good deploy → “Redeploy”. This is instant and does not require a code change. - Git revert. Revert the offending commit on the deploy branch and let the workflow + Railway rebuild. Use this when the bad deploy already shipped a config change that revision rollback won’t undo.
When rolling back after an env-var change, redeploy explicitly — env values are baked into the SPA bundle (Vite), so rolling back code without rebuilding will keep the new env values in the old code.
Build cache
The fennec.yml workflow uses actions/setup-node@v4 with
cache: "pnpm" and cache-dependency-path: apps/fennec/pnpm-lock.yaml. To
force a clean install (when CI installs look corrupted):
- Bump
apps/fennec/pnpm-lock.yaml(e.g. viapnpm installand commit). - Or, in GitHub UI, delete the
pnpm-lock.yamlcache entry under Actions → Caches.
Railway has its own Docker build cache. To bust it: in the Railway service, Settings → “Reset Build Cache” (TODO(@law): confirm the exact UI label) and trigger a new deploy.
CDN flush
There is no dedicated external CDN configured for fennec (per the topology section above). If a stale-asset incident occurs, the likely culprits are browser cache or Railway edge:
- Browser: instruct affected operators to hard-reload (Cmd-Shift-R / Ctrl-F5). The bundle’s hashed asset names should make this rare.
- Railway edge: trigger a fresh deploy. Railway routes by deploy ID, so a redeploy invalidates whatever was cached at the edge.
- If a third-party CDN is added later: document the purge command here. TODO(@law): revisit once a CDN exists.
Common operational tasks
Update an env var
- Set the new value in Railway →
beef-fennec→ Variables. - Trigger a manual redeploy. The SPA must rebuild because Vite inlines
REACT_APP_*values. - Smoke test as above.
Change Clerk environment (dev → prod or vice versa)
- Update
REACT_APP_CLERK_PUBLISHABLE_KEYon the matching Railway env. - Coordinate with brain — its
CLERK_SECRET_KEYand webhooks must match the same Clerk instance. Seestandards/auth-model.md. - Redeploy fennec, then verify
GET /users/mesucceeds.
Add a new role-gated route
- Add the page under
apps/fennec/src/pages/. - Add a
<Route>inapps/fennec/src/App.tsx. - Wrap the element in
<PrivateRoute minRole={UserRole.X}>fromapps/fennec/src/components/PrivateRoute.tsx. - Lint, typecheck, deploy via standard flow.
Database access (Strapi backend)
The embedded Strapi uses the Neon sirloin database with schema=fennec
(see apps/fennec/backend/.env). Schema migrations are owned by Strapi’s
own lifecycle — do not run ad-hoc DDL against the schema.
Incident triage one-liner
fennec down → check Railway deploy → check brain availability →check Clerk status (status.clerk.com) → check Strapi backend deploy →DNS / cert sanityPair this runbook with fennec-oncall.md for paging context.