Skip to content
BurnerByte

self-hosting

Production Deployment

Hardening BurnerByte for production use.

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 — Use pg_dump or continuous archiving (WAL)
  • MinIO — Use MinIO's built-in replication or backup the data volume
  • Redis — Ephemeral cache, no backup needed (reconciler rebuilds from PostgreSQL)

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