Production checklist
What to configure and verify before running ChatAI 1.1 in production.
Work through this list before pointing real traffic at a ChatAI instance. Every variable is described in the environment reference; start from .env.production.example, keep the file chmod 600, and never commit it.
Database
- Postgres with the
vectorextension (Docker Compose usespgvector/pgvector:pg17; Neon works too). -
DATABASE_URLset. On Neon, also setDATABASE_URL_UNPOOLEDto the direct URL of the same endpoint for migrations and workers. - Automated backups or point-in-time recovery enabled, and a restore tested once. See Backup and upgrade.
- Migrations run before the new version serves traffic: the Docker entrypoint does it by default, or the VPS deploy script runs them as a one-off step with
RUN_MIGRATIONS=0on the app.
Authentication
-
BETTER_AUTH_SECRETis at least 32 random bytes (openssl rand -base64 32) and unique to this instance. -
BETTER_AUTH_URLis the exact public HTTPS origin browsers use (no path, no trailing slash). - Password-reset email configured with
RESEND_API_KEYandEMAIL_FROM; otherwise reset links only appear in server logs. - GitHub OAuth (optional) uses a callback on the same origin.
-
SEED_DEMO_ON_STARTis unset. The demo account must never exist in production.
Secrets and encryption
-
ENCRYPTION_KEYset (32 bytes) if any assistant will store its own provider key, and backed up somewhere other than the server. Losing it makes stored keys unreadable. - Provider keys (
AI_API_KEY,VOICE_OPENAI_API_KEY,POLAR_*, object storage) live only in server-side environment variables, never inNEXT_PUBLIC_*. - Logs and error trackers don't capture request bodies or environment dumps.
Admin access
-
ADMIN_USER_IDSlists only the operators who need/api/admin/*, or stays empty. See Billing and usage.
Usage and billing
-
HOSTED_USAGE_ENFORCEMENTchosen deliberately:shadow(default) to meter without blocking,enforcefor a hosted service selling plans, oroff. - With
enforce, plans are assigned or Polar is connected; otherwise every account gets Free limits. - With Polar:
POLAR_SERVER=production, production Product IDs, and the webhook registered at/api/webhooks/polarwithPOLAR_WEBHOOK_SECRET.
Voice (preview)
Skip this section to run without Voice; text chat is unaffected.
-
VOICE_OPENAI_API_KEYset, andVOICE_PROVIDERleft atgpt-live. - One long-lived Node.js web instance (or sticky routing by Voice session id); not serverless. See Voice in production.
- Platform stop grace of at least 15 seconds (the bundled Compose files set
stop_grace_period: 20s). -
VOICE_MAX_CONCURRENT_SESSIONSset below your provider organization's concurrent-session limit.
Object storage and recordings
Only needed if owners will record Voice audio.
- Private S3-compatible bucket (no public-read policy) and
OBJECT_STORAGE_*credentials limited to that bucket. -
VOICE_RECORDING_SPOOL_DIRon persistent disk (Docker:/app/uploads/voice-spool). - Recording stays off by default per assistant. Owners turn on Record Voice audio in Settings → Privacy, choose a Recording retention, and visitors must accept the recording notice for every session.
Widget security
For each production assistant, in Settings → Security:
- Allowed domains lists the sites that embed the widget. An empty list accepts any origin.
- Per-visitor and per-assistant rate limits fit expected traffic (instance defaults:
WIDGET_RATE_LIMIT_PER_VISITOR_PER_MINUTE=20,WIDGET_RATE_LIMIT_PER_ASSISTANT_PER_MINUTE=120). - Require widget signing turned on for embeds you control server-side.
Privacy and retention
- Each assistant's Store visitor conversations, Retention and Anonymize visitor identifiers settings match your privacy policy. When conversations aren't stored, Voice sessions aren't stored either.
- Your privacy notice tells visitors about AI chat and, if enabled, Voice transcripts and recordings.
Network and TLS
- TLS terminated at a reverse proxy (the VPS setup uses Caddy) and
X-Forwarded-Protoforwarded. - Proxy allows long-lived connections for streaming chat and doesn't strip CORS headers on
/api/v1/*. Public API routes send permissive CORS headers by design; access is controlled by API keys, the domain allowlist and rate limits. - Only ports 80 and 443 are exposed publicly; Postgres is not reachable from the internet.
Container image
- Running an image built from a release tag (for example
git checkout v1.1.0 && docker build), pushed to your own registry under an immutable tag. Build with--build-arg NEXT_PUBLIC_APP_URL=…andNEXT_PUBLIC_DOCS_URL=…for your origins; theghcr.io/nightzeros/chataiimage is built for the hosted service. -
GET /api/healthreturns{"status":"ok","db":"ok"}after deploy.
Before go-live
- Sign up, create an assistant, upload a document and get a grounded answer.
- Embed the widget on an allowed domain and confirm a different domain is refused.
- If Voice is enabled, complete a short call from a real browser over HTTPS.
- Review the Upgrading to 1.1 notes if this instance previously ran 1.0.