Configuration
All environment variables and Docker images for self-hosted BuildBase.
Environment Variables
Installation (Required)
These are provided by the BuildBase dashboard when you create an Installation.
| Variable | Description |
|---|---|
INSTALLATION_API_KEY | Installation API key (from dashboard) |
INSTALLATION_ID | Installation ID (from dashboard) |
Server (Required)
| Variable | Description |
|---|---|
NODE_ENV | Set to production |
PORT | Server port (default: 3000) |
MONGO_CONNECTION_URL | MongoDB connection string |
REDIS_HOST | Redis hostname (default: localhost) |
REDIS_PORT | Redis port (default: 6379) |
SERVER_URL | Public URL of the tenant server. Enterprise single sign-on needs it public and on HTTPS: the WorkOS redirect URI is built from it (default: http://localhost:4101) |
APPLICATION_URL | Public URL of the client app (default: http://localhost:4100) |
AUTH_SERVER_URL | Public URL of the auth portal (default: http://localhost:4103) |
JWT_PASS | JWT signing secret |
DB_ENCRYPTION_KEY | Database field encryption key |
SECRET_KEY | General application secret |
OAUTH2_SECRET | OAuth2 token secret |
INSTALLATION_API_KEY | Installation API key (from BuildBase dashboard) |
INSTALLATION_ID | Installation ID (from BuildBase dashboard) |
Client App
| Variable | Description |
|---|---|
NEXT_PUBLIC_SERVER_URL | Tenant server URL |
NEXT_PUBLIC_DEFAULT_TENANT_SERVER_URL | Default tenant server URL |
NEXT_PUBLIC_INSTALLATION_ID | Installation ID (for org filtering) |
CORS
| Variable | Description |
|---|---|
CORS_WHITELISTED_DOMAINS | Comma-separated list of allowed origins |
The platform origins (console.buildbase.app) are always allowed by default.
Redis
| Variable | Description | Default |
|---|---|---|
REDIS_PASSWORD | Password (if required) | — |
REDIS_DB | Database number | 0 |
Optional Services
| Variable | Description |
|---|---|
TRUST_PROXY | How many proxies sit in front of the server and append to X-Forwarded-For. Defaults to 0 for self-hosted, which is right when the compose file publishes the port directly. The production compose sets 1 for its bundled nginx. Set it to the number of appending proxies if you put nginx, a load balancer or Cloudflare in front - too high and every per-IP rate limit is bypassable by a header the caller writes. |
GOOGLE_AUTH_CLIENT_ID | Google OAuth client ID (for Google vendor + Gmail sender) |
GOOGLE_AUTH_CLIENT_SECRET | Google OAuth client secret |
GOOGLE_STORAGE_ASSETS_BUCKET_NAME | GCS bucket for file uploads |
GOOGLE_STORAGE_STAGING_BUCKET_NAME | GCS bucket browsers upload to directly, before the server checks a file and makes it an asset. Turns on direct uploads up to 100 MB, file fields on forms and uploads by your app's users through the SDK. Unset, every upload goes through the server at 5 MB at most, and form file fields and SDK uploads answer 501. Its own bucket, in the assets bucket's location: soft delete off, a one-day delete lifecycle rule, CORS allowing PUT from any origin with the signed headers, and credentials that can sign URLs (a key file, or iam.serviceAccounts.signBlob on the service account) |
GOOGLE_STORAGE_FINALIZE_TOKEN | Shared secret in the Pub/Sub push URL for the staging bucket's finalize notifications (/api/webhooks/storage/finalize?token=...). Optional: unset answers 404 there, and an upload still completes through its own complete call or the 15-minute sweep, an hour later for a browser that closed after its upload |
GOOGLE_STORAGE_USAGE_LOG_BUCKET | GCS bucket the assets bucket writes its usage logs to. The hourly bandwidth job reads them to measure what each organization serves. Unset: bandwidth is not measured |
GOOGLE_STORAGE_USAGE_LOG_PREFIX | The log object prefix the assets bucket was given (--log-object-prefix). Must match, or the bandwidth job finds no logs (default: assets) |
EXPORTS_BUCKET | GCS bucket an organization owner's data export is written to and downloaded from. Unset, exports are off: the export routes answer 503 and nothing is written to another bucket. Its own bucket, in the assets bucket's location: a 7-day delete lifecycle rule, no retention policy, public access prevention, and credentials that can create, read, list and delete objects in it |
TENANT_STORAGE_GATE | The storage gate: off, shadow (log what it would refuse) or true. Self-hosted storage has no ceiling, so here it decides only whether a JSON body carrying a data: file over 16 KB is refused. Unset follows TENANT_FEATURE_GATE, which is off unless set |
TENANT_STORAGE_BILLING | Storage billing: off, shadow or true. No effect on a self-hosted install, where storage is not billed (default: off) |
TENANT_STORAGE_INTEGRITY | The weekly check of each organization's stored files against its asset records. Unset or shadow logs what it finds and changes nothing; true also sets aside a file no record accounts for (older than a day) and deletes it 7 days later if still unclaimed; off skips the check (default: shadow) |
TENANT_STORAGE_PURGE | Deleting an organization's files 30 days after its subscription lapsed: off, shadow (log what would be deleted) or true. A self-hosted organization never counts as lapsed, so in practice this has no effect here (default: off) |
GOOGLE_WEB_RISK_API_KEY | Google Web Risk API key for checking redirect destinations. Unset disables reputation checks; links still work. |
MAIL_TRACKING_SECRET | Signing key for email open/click tracking links. Derived from JWT_PASS when unset; set it to make the tracking key fully independent of the auth key. |
URL_SAFETY_LOG_ONLY | Record URL reputation verdicts without acting on them. Defaults to false, so setting GOOGLE_WEB_RISK_API_KEY starts refusing malicious destinations straight away. Set this to true first to measure the false-positive rate before enforcing. (default: false) |
URL_SAFETY_ENABLED | Master switch for URL reputation checks. Only the literal "false" turns them off, and every lookup then returns UNKNOWN. (default: true) |
URL_SAFETY_CLEAN_TTL_SECONDS | How long a clean verdict may be cached, in seconds, capped at 86400. A threat verdict ignores this and honours the expiry Google returns. (default: 3600) |
WEB_RISK_TIMEOUT_MS | Timeout for a single Web Risk lookup, in milliseconds. Lookups sit on a background path but must not hang a worker. (default: 5000) |
BREACHED_PASSWORD_CHECK | Refuse passwords that appear in known data breaches, on register, reset and change. Only the first five characters of the password's SHA-1 hash leave the server (the Pwned Passwords range API). Set it to off when the server cannot reach api.pwnedpasswords.com; otherwise every password waits out a 2-second timeout before it is allowed. (default: on) |
TURNSTILE_SITE_KEY | Cloudflare Turnstile site key for this install's own sign-in domain (AUTH_URL), used when an org turns bot protection on and has not saved keys of its own. Free from the Cloudflare dashboard; list your auth domain as a hostname on the widget. Leave it and TURNSTILE_SECRET_KEY unset and bot protection stays off unless an org adds its own keys. |
TURNSTILE_SECRET_KEY | The secret key paired with TURNSTILE_SITE_KEY. Only this server reads it, to check each solved challenge with Cloudflare, so the server needs to reach challenges.cloudflare.com. |
SMS_NUMBER_HOURLY_MAX | Phone sign-in codes sent to one phone number in an hour, across every org on this install. Counted before any account lookup, so a number with an account and one without run out alike. It guards your orgs' SMS bills, so DISABLE_RATE_LIMIT does not switch it off. (default: 5) |
SMS_NUMBER_DAILY_MAX | Phone sign-in codes sent to one phone number in a day. (default: 10) |
SMS_IP_HOURLY_MAX | Phone sign-in codes requested from one IP address in an hour. Stops one visitor cycling through many numbers to run up text message charges (SMS pumping). (default: 20) |
SMS_IP_DAILY_MAX | Phone sign-in codes requested from one IP address in a day. Each org's own daily cap is a console setting, not an environment variable. (default: 50) |
TENANT_MARKETPLACE | Selling through Stripe Connect (Billing, Selling and Item types), a kill switch. on opens it: non-production environments sell with Stripe test keys only, production may use live keys. off keeps it closed. test is legacy: Stripe test keys only, everywhere. Anything else reads as off (default: off) |
AFFILIATES_PAYPAL | Lets affiliate programs pay out by PayPal, from each app owner’s own PayPal Business account. on to allow it; anything else keeps it off (default: off) |
GOOGLE_STORAGE_DELIVERABLES_BUCKET_NAME | Google Cloud Storage bucket for the files sellers sell, served only through short-lived signed links (turn on public access prevention). Unset, file items answer FILES_NOT_SET_UP |
CLAMAV_HOST | Host of a ClamAV daemon (clamd) that scans each seller’s file before it can be sold. Unset, files are not scanned and may be sold only in a non-production environment (or anywhere while the legacy TENANT_MARKETPLACE=test is set) |
CLAMAV_PORT | Port of the ClamAV daemon (default: 3310) |
SHIPPING_EASYPOST | Lets an app owner connect their own EasyPost key for shipping labels and tracking. on to allow it; anything else keeps it off (manual shipments only) (default: off) |
SHIPPING_SHIPPO | Lets an app owner connect their own Shippo token for shipping labels and tracking. on to allow it; anything else keeps it off (manual shipments only) (default: off) |
NEW_RELIC_LICENSE_KEY | New Relic ingest license key for YOUR OWN account. Unset (default) disables all APM/log telemetry - nothing is collected or sent |
NEW_RELIC_APP_NAME | APM application name shown in New Relic (defaults to buildbase-server-selfhost) |
LOG_LEVEL | Server log verbosity: error, warn, info (default), http, verbose, debug or silly. Also the New Relic ingest throttle - with NEW_RELIC_LICENSE_KEY set, every logged line is an ingested line, so raise this to warn or error to cut volume without redeploying. An unrecognised value is refused at boot and info is used |
BULLMQ_DASHBOARD_PASSWORD | Password for the background-job dashboard at /api/admin/bullmq. Unset, it falls back to INSTALLATION_API_KEY and the server warns at every boot; set its own value so the two can be rotated apart (scripts/generate-selfhost-secrets.sh prints one) |
NEW_RELIC_BROWSER_ACCOUNT_ID | New Relic account ID for browser monitoring of the client and auth frontends (shared across apps). Unset (default) disables browser monitoring |
NEW_RELIC_BROWSER_LICENSE_KEY | New Relic browser license key (NRJS-…, shared across apps) for YOUR OWN account |
NEW_RELIC_BROWSER_APP_ID_CLIENT | New Relic browser application ID for the client (console) frontend |
NEW_RELIC_BROWSER_APP_ID_AUTH | New Relic browser application ID for the auth frontend |
Generating Secrets
All secret values should be unique, random strings. Generate them with:
openssl rand -hex 32Or generate all required secrets at once:
for i in JWT_PASS DB_ENCRYPTION_KEY SECRET_KEY OAUTH2_SECRET REDIS_PASSWORD BULLMQ_DASHBOARD_PASSWORD; do echo "$i=$(openssl rand -hex 32)"; doneDocker Images
| Image | Description | Port | Health Check |
|---|---|---|---|
buildbaseapp/tenant-server | Backend API server | 3000 | GET /api/ready |
buildbaseapp/client | Web dashboard (Next.js SSR) | 3000 | GET / |
buildbaseapp/auth | Auth portal (Next.js) | 3000 | GET /health |
All images are multi-arch (linux/amd64, linux/arm64), built on Node.js 22 Alpine. docker pull selects the correct variant for the host automatically. 32-bit ARM (linux/arm/v7) is not supported - see Supported architectures.
Architecture
Your end users' accounts and application data stay in your own MongoDB. The installation stays connected to BuildBase: the central server signs your team in to the console, manages your organizations, receives a status check-in every five minutes and is told about plan changes. Visitor IP addresses are sent to IPinfo for location lookups unless IP_INFO_MODE is set to disabled, and uploaded files go to a Google Cloud Storage bucket you own.