Skip to content
BurnerByte

Production Deployment

The hardening checklist before BurnerByte faces the internet: secrets, TLS, CORS, trusted proxies, rate limits, backups, and what does and does not scale.

Checklist

  • Set a strong, random JWT_SECRET (64+ characters)
  • Set ENCRYPTION_KEY to a 32-byte hex key (openssl rand -hex 32) — encrypts SSO secrets, SMTP passwords and storage credentials at rest. The first-run installer generates one for you; set it explicitly when you configure through the environment instead, or those credentials are stored in plain text. (BB_ENCRYPTION_KEY works too when running the binary directly, but Docker Compose forwards only the unprefixed name.)
  • Change default database and MinIO passwords
  • Configure FRONTEND_URL and API_BASE_URL with your actual domain
  • Set BB_CORS_ALLOWED_ORIGINS to your frontend domain only
  • Enable TLS on your reverse proxy
  • Configure outbound SMTP (mailer) for invites and password resets
  • Set up DNS records for your email domain (MX + TXT)
  • Review rate limiting settings
  • Set BB_RATE_LIMIT_TRUSTED_PROXIES to your reverse proxy's CIDR if you run behind one. Left empty (the default), forwarded headers are ignored entirely and the client IP is always the peer address — correct for a directly-exposed deployment. Getting this wrong now costs accuracy, not safety: rate limits, the API-key IP allowlist, the audit trail and the /metrics gate all read the same resolved value
  • Prometheus metrics are on by default at /metrics and answer only loopback and RFC1918-private clients, so scrape from inside your network. Your proxy should still overwrite inbound X-Forwarded-For / X-Real-IP as a matter of hygiene
  • Set BB_METRICS_TOKEN if /metrics is reachable from anywhere but your scrape host. The loopback/private-address check still rests on your proxy overwriting inbound X-Forwarded-For and X-Real-IP; a bearer token rests on nothing but itself. Prometheus sends it with bearer_token in the scrape config
  • Consider BB_SECURITY_REQUIRE_EMAIL_VERIFICATION=true on a new deployment. It refuses password logins until the address is confirmed, and it is off by default only because enabling it on an existing instance locks out every account created beforehand
  • Leave BB_SECURITY_ALLOW_TOKEN_QUERY_PARAM=false unless a non-browser client needs ?token= on the WebSocket endpoints. The browser client uses the one-time /ws/ticket flow; a token in a URL is captured by proxy access logs and browser history
  • Set LOG_LEVEL=info and LOG_FORMAT=json

Minimal Production Environment

bash
DATABASE_URL=postgres://user:pass@db:5432/burnerbyte?sslmode=requireREDIS_URL=redis://:password@redis:6379/0JWT_SECRET=<random-64-char-string>ENCRYPTION_KEY=<64-char-hex-string>FRONTEND_URL=https://app.example.comAPI_BASE_URL=https://api.example.comBB_SMTP_HOSTNAME=mail.example.comBB_CORS_ALLOWED_ORIGINS=https://app.example.comBB_RATE_LIMIT_ENABLED=trueBB_SERVER_MAX_BODY_SIZE=5242880 BB_MINIO_ENDPOINT=s3.example.comBB_MINIO_ACCESS_KEY=<access-key>BB_MINIO_SECRET_KEY=<secret-key>BB_MINIO_BUCKET=burnerbyteBB_MINIO_USE_SSL=true
Careful

Scaling

  • API server — Request handling is stateless and horizontally scalable, but read the worker note below before running more than one instance
  • SMTP server — Stateless, can run multiple instances (each needs port 25/2525)
  • Frontend — Next.js output: 'standalone': a Node server (node server.js, port 3000), not a static export. Run several instances behind a load balancer; only .next/static can be fronted by a CDN.
  • Workers — Every API process starts all seven background workers (cleanup, reconciler, dns_recheck, webhook_retry, analytics, invite_expiry, admin_stats). There is no leader election and no flag to start an API without them, so a second replica runs a second copy of each job: duplicate DNS lookups, duplicate webhook retries, duplicate analytics rollups and duplicate expiry emails. Run exactly one API replica until leader election exists.

Backups

  • PostgreSQL — pg_dump, or continuous archiving (WAL) if you need point-in-time recovery.
  • Object storage — MinIO's own replication, or a copy of the miniodata volume. Take it before the database dump, so every storage_key in the dump refers to an object the backup already has.
  • Attachment fallback — the attachments volume is separate, and holds bodies written while MinIO was unreachable. Check whether it is empty before deciding you can skip it.
  • Redis — ephemeral cache; the reconciler rebuilds it from PostgreSQL.
  • ENCRYPTION_KEY — not a store, but a dump restored without it leaves every stored credential undecryptable.

Backup and Restore has the commands, the ordering rule and the restore procedure. Rolling back a release means restoring a dump, so an untested restore is not a rollback plan — see Upgrading.

Deploying

Docker Compose is the supported deployment path. The same docker-compose.yml that runs locally runs in production — point it at your own Postgres, Redis and S3-compatible storage instead of the bundled ones:

bash
git clone https://github.com/AmJaradat01/burnerbyte.gitcd burnerbytecp .env.example .env    # fill in the values from the section abovedocker compose up -d --build

Set EXTERNAL_DATABASE_URL and EXTERNAL_REDIS_URL in .env to use managed services, and drop the postgres, redis and minio services from the Compose file once nothing points at them.

The repository ships no provider-specific provisioning scripts. Reverse proxy, TLS, firewall and host hardening are yours to configure.

Careful