4.8 KiB
Deployment
This app is built to deploy to Render as a Web
Service via the included render.yaml blueprint.
Pre-flight checklist
Before deploying, confirm:
- CiviCRM has a working API key for a service user with sufficient privileges to read/write
Contact,Activity,Relationship, andOptionValue. - The
Primary Contact(or whatever valueFORM_CONTACT_RELATIONSHIPresolves to inconfig/form.ts) relationship type exists, with Individual on side A and Organization on side B. - The
Check-in (organizing)activity type exists with the six custom field groups attached (Check_in_data__organizing_,Stage_1,Stage_2,Stage_3,Stage_4,Stage_5). - The Framework Stage option group on the Organization contact contains all six in-scope values:
Inquiry,Organizing,Feasibility,Business feasibility,Store Implementation,Stabilize newly opened co-op.
Verify all of the above against your live CiviCRM by hitting /api/health (in development; production requires a token — see below).
First-time Render setup
-
Create a new Web Service from this repo on Render. Render will detect
render.yamland configure the service automatically. -
Set the secrets in the Render dashboard → Environment. The
render.yamldeclares them but marks themsync: falseso they're never echoed in the committed file:Variable Required? Purpose CIVI_BASE_URLyes e.g. https://crm.fci.coopCIVI_API_KEYyes Per-user API key. Generate via CiviCRM Contact → API Key field. CIVI_SITE_KEYyes From civicrm.settings.php(CIVICRM_SITE_KEY).CIVI_HTTP_AUTH_USERonly if Civi sits behind webserver-level Basic Auth HTTP Basic Auth username. CIVI_HTTP_AUTH_PASSonly if CIVI_HTTP_AUTH_USER is set HTTP Basic Auth password. HEALTH_TOKENrecommended Long random string (e.g. openssl rand -hex 32). Required to access/api/healthin production. If unset, that route returns 404.PUBLIC_ORIGINoptional e.g. https://check-in.fci.coop— used in absolute self-links if needed later. -
Trigger the first deploy. Render will run
npm ci && npm run buildthennpm run start. The platform health check hits/healthz(lightweight, no Civi dependency). -
Verify:
https://<your-render-url>/healthz→{"ok":true,"service":"coop-checkin"}https://<your-render-url>/api/health?token=<HEALTH_TOKEN>→ all checks should be green- Visit
https://<your-render-url>/?cid=<test-individual>&cs=<their-checksum>→ form loads with prefill
-
Custom domain (optional) — Render dashboard → Custom Domain → add
check-in.fci.coop. Update DNS to the provided CNAME. Render auto-issues a Let's Encrypt cert.
Security defaults
The app ships with these defaults; review and adjust per your threat model.
| Concern | Default |
|---|---|
| HTTPS | Required. Render terminates TLS for you. HSTS header is set with a 2-year max-age + preload. |
| CSP | Tight: same-origin scripts/connections; Google Fonts allowed for next/font; frame-ancestors 'none'; no third-party scripts. |
| Frame embedding | Denied (X-Frame-Options + CSP frame-ancestors). |
| Referrer | same-origin so the cid+cs query string never leaks via Referer to other origins. |
| Health endpoint | Production: gated behind HEALTH_TOKEN. Dev: unauthenticated. |
| Submit rate limit | 10 submissions per IP per minute (in-memory; resets on restart). |
| Error messages | Production responses say "Could not save" without details; full error logged server-side with secrets redacted. |
| Stub mode | Refuses to start if NODE_ENV=production and any of CIVI_BASE_URL/CIVI_API_KEY/CIVI_SITE_KEY is missing. |
What's intentionally NOT done
- CSRF tokens. The submit endpoint is gated by the per-contact CiviCRM checksum, which is not browser-cookie-based, so traditional CSRF doesn't apply. Add CSRF tokens if you ever introduce a session-cookie auth path.
- Multi-instance rate limiting. The in-memory token bucket is per-process. Acceptable for a single Render instance; switch to Redis or Render's edge rate-limit if you scale horizontally.
- WAF. None configured. Render's platform provides basic DDoS protection; add a WAF (Cloudflare in front of Render, or similar) if your threat model warrants it.
- i18n. English-only.
Rolling out updates
- Push to your default branch.
- Render auto-builds and deploys (per
autoDeploy: trueinrender.yaml). - Watch deploy logs for any health-check failures.
- After deploy: hit
/api/health?token=…to confirm all checks still pass against the live CRM.
Rollback
Render keeps the previous deploy available. From the Render dashboard → Deploys → previous deploy → Rollback to this deploy.