Production checklist
Build and run
Section titled “Build and run”-
Install and build on the server or in your build pipeline:
Terminal window npm installnpm run build # web app, docs and marketing siteThe API serves
apps/web/dist(orWEB_DIST). The docs and marketing sites are static folders to host wherever you like. -
Apply migrations with the same environment as the API (see Database):
Terminal window NODE_ENV=production npm run migrate -w @pdfeditor/api -
Start the API under a process manager of your choice, with
NODE_ENV=productionand the full configuration:Terminal window NODE_ENV=production npm startIt stops gracefully on
SIGTERM/SIGINT(finishes open requests, then closes database connections; it gives up after 10 seconds). -
Check
GET /api/health. It returns{"ok":true}when the API can query the database.
Checklist
Section titled “Checklist”-
NODE_ENV=production. This enforces the configuration check, turns off the development sign-in and/api/dev/*, and doesn’t auto-migrate unlessMIGRATE_ON_START=true. - HTTPS on the public URL, and
APP_URLset to exactly that URL. Teams only loadshttps://apps, and the Entra redirect URIs must match it. - Reverse proxy in front of the API that terminates TLS and forwards to
PORT. SetTRUST_PROXYto the number of proxies in front of the API (default1), so client IPs in the activity log and the rate limits are right. Don’t set it higher than the real number of hops, or clients can spoof their IP withX-Forwarded-For. Allow request bodies of at leastMAX_UPLOAD_MBthrough the proxy. -
SESSION_SECRET: at least 32 random characters (openssl rand -hex 32), stored as a secret and never changed casually (see Configuration). - Microsoft Entra registration with production redirect URIs (guide), and a reminder to rotate the client secret before it expires.
- Stripe live keys, prices created with
stripe:setup, webhook endpoint with the right events, customer portal enabled (guide). - Mailgun domain verified and
EMAIL_FROMon that domain (guide). - Audit log on insert-only and read-only roles, optionally in a separate database (guide).
- Backups of the main database (it contains every document sent for signature) and the audit database, with tested restores.
- First super admin created (guide).
- Teams package built for the production URL (guide).
- Monitoring: health check, and alerts on error-level logs, especially audit log write failed and background jobs failed.
- Contact form: a Cloudflare Turnstile widget for the marketing hostname,
TURNSTILE_SECRET_KEYandMARKETING_URLon the API, andPUBLIC_TURNSTILE_SITE_KEYin the marketing build. - Docs and marketing sites built with
PUBLIC_SITE_URL,PUBLIC_APP_URLand the otherPUBLIC_*values for production, and the marketing site serving/privacyand/terms(the Teams manifest links to them).
Background job
Section titled “Background job”The API process runs a job 10 seconds after it starts and then every hour. It:
- marks signing requests past their expiry date as Expired, and
- sends automatic reminders that are due.
The API writes JSON logs (pino) to stdout, one line per request plus errors. Authorization and X-Entra-Token headers are redacted. Set the level with LOG_LEVEL. Unexpected errors return Something went wrong on our side to the client with code server_error, and the details go to the log.