Appearance
Deployment
Who this is for: Platform operators and DevOps
What you'll achieve: Host SchoolRota in a UK-friendly environment with migrations and docs
Hosting guidance
Not for shared cPanel. Prefer:
- Fly.io / Railway / small UK VPS
- Azure if a trust requires it
Target region: UK-first (eu-west-2 or equivalent) for school data residency.
Application runtime
| Process | Purpose |
|---|---|
Next.js web (apps/web) | Tenant portals, operator UI, marketing, Stripe, tRPC, SSE |
Optional worker (apps/worker) | BullMQ background jobs when Redis is available |
Local default database is SQLite. Production staging on Fly uses SQLite on a volume (single machine). Postgres for scaled production — see repository docs/BILLING_AND_HOSTING.md.
Environment variables
Copy from .env.example and set production values:
| Variable | Notes |
|---|---|
DATABASE_URL | SQLite file or Postgres URL |
OPERATOR_API_TOKEN | Required for /operator in production |
OPERATOR_AUTH_DISABLED | 1 for local dev only |
SESSION_SECRET | Long random string |
APP_URL | Public base URL |
EMAIL_FROM | From header |
EMAIL_PROVIDER | console or resend |
RESEND_API_KEY | When using Resend |
STRIPE_* | Self-serve signup (see BILLING_AND_HOSTING.md) |
REDIS_URL | Optional acceleration; durable calendar jobs still require cron recovery |
INTEGRATION_ENCRYPTION_KEY | Required for Outlook (and future) integration secrets — min 16 characters |
CRON_SECRET | Required for /api/cron/calendar-sync (fail-closed without it) |
Outlook calendar sync
When enabling Outlook calendar for pilot schools:
- Set
INTEGRATION_ENCRYPTION_KEYandCRON_SECRETin production secrets. - Schedule
POST /api/cron/calendar-syncwithAuthorization: Bearer {CRON_SECRET}(required for correctness). Redis +apps/workeraccelerates jobs but does not replace cron recovery. - Operator enables
outlook_calendarfor the tenant. - School admin completes Entra app registration and Admin integration setup.
Pending sync jobs recover automatically when the worker or cron endpoint runs (even if Redis was down at publish time).
fly volumes create schoolrota_data --region lhr --size 1fly secrets set OPERATOR_API_TOKEN=... APP_URL=https://... SESSION_SECRET=...fly deploy- SSH: run migrations against
/data/schoolrota.sqlite - Create first tenant via
/operatoror CLI - Smoke-test login → setup → publish → export
Migrating from Dutylead (pre-rebrand)
If you already run the old dutylead Fly app:
- Fly app — create
fly apps create schoolrota(or rename the existing app) and updatefly.toml/ CI deploy target. - Volume / database — on the mounted volume, rename the SQLite file if needed:bashOr keep
flyctl ssh console -a schoolrota -C "mv /data/dutylead.sqlite /data/schoolrota.sqlite"DATABASE_URL=file:/data/dutylead.sqlitein Fly secrets until you cut over. - DNS — point
schoolrota.com,*.schoolrota.com, anddocs.schoolrota.comat Fly (or your host). - Secrets — set
APP_URL=https://schoolrota.com,EMAIL_FROM=SchoolRota <noreply@schoolrota.com>, and update Stripe webhook URL tohttps://schoolrota.com/api/billing/webhook. - IdP / SCIM — new schools should use
SchoolRota-*group names; legacyDutylead-*groups still map correctly. - Outlook — existing synced events keep working (legacy extension id supported); new syncs use
com.schoolrota.calendar.
Deploy checklist (generic)
- Set secrets and
APP_URL - Run migrations:
pnpm db:migrate - Seed only if creating the demo/bootstrap data is intentional
- Start web (and worker if Redis is configured)
- Provision the first real tenant via
/operatoror CLI - Smoke-test login → setup → build day → publish → export
- Confirm email delivery path is not
consolein production
Documentation site
This VitePress site builds to static files:
bash
pnpm docs:buildOutput: docs-site/.vitepress/dist
Deploy options:
- GitHub Pages — see
.github/workflows/docs.yml - Cloudflare Pages / Netlify — point at the same build command and output directory
- Custom domain such as
docs.schoolrota.com
Preview locally:
bash
pnpm docs:dev
# or after build:
pnpm docs:previewRelated
- Provisioning tenants
- Local setup
- Engineering spec:
docs/SYSTEM_PLAN.mdin the repository
