self-hosting
Production Deployment
Hardening BurnerByte for production use.
Checklist
- Set a strong, random
JWT_SECRET(64+ characters) - Set
ENCRYPTION_KEYto 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_KEYworks too when running the binary directly, but Docker Compose forwards only the unprefixed name.) - Change default database and MinIO passwords
- Configure
FRONTEND_URLandAPI_BASE_URLwith your actual domain - Set
BB_CORS_ALLOWED_ORIGINSto 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_PROXIESto 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/metricsgate all read the same resolved value - Prometheus metrics are on by default at
/metricsand answer only loopback and RFC1918-private clients, so scrape from inside your network. Your proxy should still overwrite inboundX-Forwarded-For/X-Real-IPas a matter of hygiene - Set
BB_METRICS_TOKENif/metricsis reachable from anywhere but your scrape host. The loopback/private-address check still rests on your proxy overwriting inboundX-Forwarded-ForandX-Real-IP; a bearer token rests on nothing but itself. Prometheus sends it withbearer_tokenin the scrape config - Consider
BB_SECURITY_REQUIRE_EMAIL_VERIFICATION=trueon 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=falseunless a non-browser client needs?token=on the WebSocket endpoints. The browser client uses the one-time/ws/ticketflow; a token in a URL is captured by proxy access logs and browser history - Set
LOG_LEVEL=infoandLOG_FORMAT=json
Minimal Production Environment
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=trueObject storage is not optional above one replica. With BB_MINIO_ENDPOINT
unset, the process falls back to local-filesystem attachments under
./data/attachments and the only signal is a minio unavailable, using local filesystem for attachments warning in the log. Each API and smtpd instance then
has its own private copy, so an attachment written by one replica is a 404 from
another.
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/staticcan 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_dumpor 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:
git clone https://github.com/AmJaradat01/burnerbyte.gitcd burnerbytecp .env.example .env # fill in the values from the section abovedocker compose up -d --buildSet 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.
If you put a proxy in front, name it in BB_RATE_LIMIT_TRUSTED_PROXIES.
Otherwise every request appears to come from the proxy's own address and all
per-client rate limiting collapses onto one bucket. Forwarded headers from
anywhere else are ignored by design — a client cannot choose the address it is
rate-limited, allowlisted or audited under.