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 ​


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

9. IJCP Production Instance (ijcp.neemias.app) ​

The first real production instance (the app.neemias.app/api.neemias.app pair is the public demo — DEMO_MODE=true). Everything is dedicated to IJCP:

ResourceValueWhere
Workerapi-ijcpworkers/wrangler.ijcp.toml
Worker routeapi-ijcp.neemias.app/*[[routes]] in the toml (auto at deploy)
D1 databaseijcpwrangler d1 create ijcp
R2 bucketijcp-uploadswrangler r2 bucket create ijcp-uploads
Pages projectneemias-ijcpwrangler pages project create neemias-ijcp
Pages domainijcp.neemias.appPages → Custom Domains (or deploy workflow)
EmailRESEND_API_KEY (secret) + RESEND_FROM_EMAILResend (free tier) — domínio neemias.app verificado em resend.com
LicenseLICENSE var (ijcp, nucleus+volunteers)minted with pnpm sign-license (production keypair)

Env vars / secrets for api-ijcp:

VariableTypeValue
AUTH_MODEPlain textjwt (in toml [vars])
ENVIRONMENTPlain textproduction (in toml [vars])
ALLOWED_ORIGINSPlain texthttps://ijcp.neemias.app (in toml [vars])
ENABLED_MODULESPlain text* (in toml [vars])
LICENSEPlain textsigned JWT (in toml [vars])
RESEND_FROM_EMAILPlain textcontato@neemias.app (in toml [vars]; verify the domain at resend.com)
AUTH_JWT_SECRETSecretopenssl rand -hex 32 — set via wrangler secret put AUTH_JWT_SECRET --config workers/wrangler.ijcp.toml (first deploy only; rotation logs everyone out)
RESEND_API_KEYSecretResend API key — wrangler secret put RESEND_API_KEY --config workers/wrangler.ijcp.toml

Frontend build (must be done for IJCP, never for the demo):

bash
DEPLOY_ENV=production VITE_BACKEND_URL=https://api-ijcp.neemias.app VITE_DEMO_MODE=false pnpm build:app

VITE_DEMO_MODE=false overrides app/.env.production (which belongs to the demo instance). Deploys are automated by .github/workflows/deploy-ijcp.yml (tags ijcp-v* or manual) on the self-hosted runner; repo secrets: CLOUDFLARE_ACCOUNT_ID, CLOUDFLARE_API_TOKEN, IJCP_AUTH_JWT_SECRET.

Email (Resend): IJCP envia email transacional pela Resend (plano grátis, 100/dia) — RESEND_API_KEY é secret do Worker e o domínio contato@neemias.app deve estar verificado em resend.com/domains (passo de dashboard, único). O código também suporta o binding Cloudflare Email Service ([[send_email]]), que exige Workers Paid — trocar no futuro = adicionar o binding ao toml e remover RESEND_API_KEY.

Pre-created admin: created directly in the ijcp D1 (users + user_roles rows with the ADMIN role, v1 PBKDF2 hash that auto-upgrades to the two-pass format on first login).

Distribuído sob licença MIT.