Skip to content

Production Deployment Checklist

app.neemias.app and api.neemias.app are production environments. Local pnpm dev is development-only. This checklist covers the full production setup — Cloudflare Dashboard + code.


1. Environment Variables — Cloudflare Dashboard

These must be set manually in the Cloudflare Dashboard. They are never committed to the repository.

Cloudflare Workers (api.neemias.app)

Navigate to Workers & Pages → neemias → Settings → Variables.

VariableTypeRequiredDescription
AUTH_MODEPlain textYesSet to jwt. Default is dev (accepts any token — insecure).
AUTH_JWT_SECRETSecretYesGenerate with openssl rand -hex 32. Used to sign/verify JWT tokens.
ENVIRONMENTPlain textYesSet to production. Controls dev-only endpoints like /api/v1/_seed.

Optional (dev-mode tokens): If you need local development against the remote API, also set these plain-text variables:

  • AUTH_DEV_ADMIN_TOKEN — e.g. dev-admin-token
  • AUTH_DEV_CALLER_TOKEN — e.g. dev-caller-token
  • AUTH_DEV_COORDENACAO_TOKEN
  • AUTH_DEV_ADMINISTRATIVO_TOKEN
  • AUTH_DEV_VOLUNTARIO_TOKEN

Cloudflare Pages (app.neemias.app)

Navigate to Workers & Pages → neemias-app → Settings → Environment Variables.

VariableTypeRequiredDescription
DEPLOY_ENVPlain textYesSet to prod. Without this, the app's seed runs automatically. Frontend seed guard checks === \"prod\".

2. Deploy Pipeline

bash
# 1. Frontend — build + Pages auto-deploy
pnpm build:app
git add -A && git commit -m "..."
git push
# → Cloudflare Pages builds and deploys automatically

# 2. Worker — direct deploy
pnpm deploy:worker

What build:app does

  1. Runs scripts/generate-headers.ts (with NODE_ENV=production) — writes the CSP via app/csp.config.ts into app/public/_headers. In production, this uses 'unsafe-inline' CSP (required because Cloudflare injects inline <style> via font proxy and inline <script> via JS Challenge at the edge).
  2. Runs Vite production build (Rolldown bundler).
  3. Output goes to app/dist/. Cloudflare Pages serves from this directory.

What deploy:worker does

  1. Bundles workers/src/ via wrangler deploy.
  2. Applies wrangler.toml bindings (D1 database, R2 bucket).
  3. Deploys to api-neemias.shaveslavers.workers.dev (and custom domain).

3. DNS — Custom Domains

Configured in Cloudflare Dashboard → Workers & Pages → (select service) → Custom Domains.

DomainServiceType
app.neemias.appneemias-app (Pages)Frontend SPA
api.neemias.appneemias (Worker)Backend API

Both require an A/AAAA/CNAME record in the Cloudflare DNS zone.


4. Database Migrations

bash
# Local (development D1)
pnpm db:migrate:local

# Remote (production D1)
pnpm db:migrate:remote

Migrations live in migrations/0001_*.sql through migrations/00XX_*.sql. The Worker reads migrations_table in D1 to track which have been applied.


5. Seed Data

bash
# Populates dev D1 with demo data (only works with AUTH_MODE=dev)
curl -X POST https://api.neemias.app/api/v1/_seed \
  -H "Authorization: Bearer <DEV_TOKEN>"

⚠️ Never run seed in production — it creates default admin accounts. The seed endpoint is blocked when ENVIRONMENT=production.


6. First-Time Production Setup (step by step)

mermaid
flowchart TD
  A[Clone repo] --> B[Set env vars in Dashboard]
  B --> C{pnpm db:migrate:remote}
  C --> D{pnpm deploy:worker}
  D --> E[Verify API health]
  E --> F{pnpm build:app}
  F --> G[git push → Pages deploy]
  G --> H[Verify app login]
  H --> I((✅ Done))

Quick reference

StepCommand / ActionExpected result
1. Env varsCloudflare Dashboard → Workers → VariablesAUTH_MODE=jwt, AUTH_JWT_SECRET set
2. Env varsCloudflare Dashboard → Pages → VariablesDEPLOY_ENV=prod
3. Migrate DBpnpm db:migrate:remote"Executed N commands"
4. Deploy Workerpnpm deploy:worker"Published neemias"
5. Health checkcurl https://api.neemias.app/api/v1/health{"status":"ok"}
6. Build frontendpnpm build:app"built in Xs"
7. Deploy frontendgit pushPages build succeeds
8. Login testVisit app.neemias.appLogin page loads

7. Production Security Checklist

  • [ ] AUTH_MODE=jwt (not dev)
  • [ ] AUTH_JWT_SECRET is a strong random value stored as a Secret (not plain text)
  • [ ] ENVIRONMENT=production (blocks seed endpoint)
  • [ ] DEPLOY_ENV=prod (on Pages — blocks frontend seed, checked as === "prod" in code)
  • [ ] CSP uses production mode — no 'nonce-...' in CSP (nonces are dev-only)
  • [ ] CSP includes 'unsafe-inline' in style-src and script-src (required for Cloudflare font proxy + JS Challenge)
  • [ ] NODE_ENV=production was exported during pnpm build:app
  • [ ] scripts/pre-deploy-check.sh exits 0 (validates CSP mode + required origins)
  • [ ] scripts/validate-csp.sh passes (all required origins in connect-src)
  • [ ] frame-ancestors 'none' in CSP (prevents clickjacking)
  • [ ] Strict-Transport-Security: max-age=63072000 is active
  • [ ] No dev-mode tokens exposed in production
  • [ ] All D1 migrations applied (pnpm db:migrate:remote)
  • [ ] Rollback tag identified (previous v* tag for quick revert)
  • [ ] D1 database is backed up (export from Dashboard)
  • [ ] Visual smoke test: login page loads, styles render, no CSP console errors

8. Troubleshooting

SymptomLikely causeFix
Login fails, console shows CSP errors, app crashesDev-mode nonce leaked into production CSP — 'unsafe-inline' ignoredRun NODE_ENV=production pnpm build:app to regenerate _headers
Login page renders unstyled but no JS errorSame CSP issue — inline styles blockedSame fix: NODE_ENV=production pnpm build:app
Login fails, no CSP errorsAUTH_MODE=dev but no dev token setSet AUTH_MODE=jwt or configure dev tokens
Seed creates admin on prodENVIRONMENT not setSet ENVIRONMENT=production in Worker env vars
Pages build failsMissing VITE_BACKEND_URL envSet to https://api.neemias.app in Pages build settings
API returns 403JWT expired or invalidCheck AUTH_JWT_SECRET matches between Worker and client

Distribuído sob licença MIT.