Self-Hosted Deployment

Run on your infrastructure

Deploy the BuildBase server on your own infrastructure. Full data sovereignty with the same dashboard, SDK, and all platform modules. One Docker command to start.

Why self-host

Full control over data. Same platform features as shared cloud.

Data sovereignty

All data stays on your infrastructure. Nothing leaves your network. Full control over data residency and compliance.

Your security policies

Use your own firewall rules, VPN, and network policies. No shared infrastructure with other tenants.

Your database

Run your own MongoDB instance. Control backups, replication, disaster recovery, and data retention.

Central licensing only

BuildBase manages licensing and org management via the central server. Client, server, and auth portal run on your infrastructure. BuildBase never accesses your data.

One command setup

Public Docker image on Docker Hub. Copy the compose file, run one command, connect. Running in 30 seconds.

Run anywhere

AWS, GCP, Azure, DigitalOcean, Hetzner, on-premises, bare metal — anywhere Docker runs. No vendor lock-in.

Architecture

Split architecture. Data stays on your servers. Dashboard connects over HTTPS.

Your Infrastructure

Hosted by you — full control

Tenant Server — users, emails, workflows, billing

Client App — web dashboard for your team

Auth Portal — login, signup, password reset

MongoDB — your data, your backups

Redis — sessions, cache, queues

Central Server Only

Licensing and org management — nothing else

Org Management — create and manage organizations

Admin Auth — Google OAuth for org admins

ES256 Keys — secure token signing

Installation Keys — per-instance API keys

Licensing — installation verification

BuildBase never accesses your application data. The central server handles only licensing, org management, and authentication tokens.

All modules included

Every platform module works identically in self-hosted mode. Same SDK, same API, same dashboard.

Authentication
Billing & Subscriptions
Email Campaigns
Workflow Automation
Multi-Tenant Workspaces
SDK & API
User & Audience Management
Access Control (RBAC)
Credits & Usage Metering
Feature Flags
Push Notifications
Forms & Data Collection
Content Management
Analytics & Reporting
Webhooks & Events
Short Links
Collections & Custom Data
Slack & Team Notifications
Assets & Media

Every feature available in shared cloud works identically when self-hosted.

Deploy in three steps

Sign up to a running server in under 5 minutes.

1

Create org and installation

Sign up and select "Self-Hosted" when creating your organization. The setup wizard generates an Installation with a unique API key, a docker-compose file, and an env template.

2

Deploy with Docker

Copy the compose file and .env template to your server. Run docker compose up -d to start the tenant server, client app, auth portal, MongoDB, and Redis.

3

Connect and manage

Enter your server URL in the dashboard and verify the connection. Manage users, billing, emails, and workflows from the central dashboard.

Quick Start

One file. One command.

Server, client, auth, MongoDB, and Redis — all included. Copy, paste, run.

docker-compose.selfhost.yml
# Self-Hosted: MongoDB + Redis + Server + Client + Auth
# Save as docker-compose.selfhost.yml
# Usage: docker compose -f docker-compose.selfhost.yml --env-file .env.selfhost up -d

services:
  # ── Infrastructure ──────────────────────────────────────────
  mongodb:
    image: mongo:7.0
    restart: unless-stopped
    security_opt:
      - no-new-privileges:true
    cap_drop:
      - ALL
    cap_add:
      - SETUID
      - SETGID
      - DAC_OVERRIDE
    volumes:
      - mongodb_data:/data/db
    networks:
      - db
    healthcheck:
      test: ["CMD", "mongosh", "--eval", "db.adminCommand('ping')"]
      interval: 10s
      timeout: 5s
      start_period: 20s
      retries: 3
    deploy:
      resources:
        limits:
          memory: 1024M
          cpus: "1.0"
          pids: 256
    logging:
      driver: json-file
      options:
        max-size: "100m"
        max-file: "3"

  redis:
    image: redis:7.4-alpine
    restart: unless-stopped
    read_only: true
    cap_drop:
      - ALL
    cap_add:
      - SETUID
      - SETGID
    command: redis-server --appendonly yes --maxmemory-policy noeviction --maxmemory 256mb --requirepass ${REDIS_PASSWORD:-redispass}
    tmpfs:
      - /tmp
    volumes:
      - redis_data:/data
    networks:
      - db
    healthcheck:
      test: ["CMD", "redis-cli", "-a", "${REDIS_PASSWORD:-redispass}", "ping"]
      interval: 10s
      timeout: 5s
      retries: 3
    deploy:
      resources:
        limits:
          memory: 256M
          cpus: "0.5"
          pids: 64
    logging:
      driver: json-file
      options:
        max-size: "50m"
        max-file: "3"

  # ── Backend ─────────────────────────────────────────────────
  tenant-server:
    image: buildbaseapp/tenant-server:latest
    restart: unless-stopped
    read_only: true
    security_opt:
      - no-new-privileges:true
    cap_drop:
      - ALL
    tmpfs:
      - /tmp
      - /var/log
    ports:
      - "${TENANT_SERVER_PORT:-4101}:3000"
    environment:
      - NODE_ENV=production
      - PORT=3000
      # Keep the Node heap below the 1024M container limit — the image
      # default is sized for larger hosts and would get OOM-killed here.
      - NODE_OPTIONS=--max-old-space-size=768
      - SERVER_URL=${TENANT_SERVER_URL:-http://localhost:4101}
      - APPLICATION_URL=${CLIENT_URL:-http://localhost:4100}
      - AUTH_SERVER_URL=${AUTH_URL:-http://localhost:4103}
      - MONGO_CONNECTION_URL=mongodb://mongodb:27017/
      - REDIS_HOST=redis
      - REDIS_PORT=6379
      - REDIS_PASSWORD=${REDIS_PASSWORD:-redispass}
      - INSTALLATION_API_KEY=${INSTALLATION_API_KEY:-}
      - INSTALLATION_ID=${INSTALLATION_ID:-}
      - JWT_PASS=${JWT_PASS:-localdev_jwt_secret_do_not_use_in_production}
      - OAUTH2_SECRET=${OAUTH2_SECRET:-localdev_oauth2_secret_do_not_use_in_production}
      - DB_ENCRYPTION_KEY=${DB_ENCRYPTION_KEY:-localdev_db_encryption_key_do_not_use_in_production}
      - SECRET_KEY=${SECRET_KEY:-localdev_secret_key_do_not_use_in_production}
      - CORS_WHITELISTED_DOMAINS=${CLIENT_URL:-http://localhost:4100},${AUTH_URL:-http://localhost:4103}
    depends_on:
      mongodb:
        condition: service_healthy
      redis:
        condition: service_healthy
    networks:
      - db
      - app
    healthcheck:
      test: ["CMD", "wget", "-qO-", "http://127.0.0.1:3000/api/ready"]
      interval: 15s
      timeout: 5s
      start_period: 45s
      retries: 3
    deploy:
      resources:
        limits:
          memory: 1024M
          cpus: "1.0"
          pids: 256
    logging:
      driver: json-file
      options:
        max-size: "100m"
        max-file: "5"

  # ── Frontend ────────────────────────────────────────────────
  client:
    image: buildbaseapp/client:latest
    restart: unless-stopped
    # NOTE: read_only must NOT be set on client or auth. Their entrypoints
    # rewrite __NEXT_PUBLIC_*__ URL placeholders in the JS bundles at
    # startup; a read-only filesystem makes that rewrite fail silently and
    # the app calls the literal placeholder string instead of your URL.
    security_opt:
      - no-new-privileges:true
    cap_drop:
      - ALL
    tmpfs:
      - /tmp
      - /var/cache/nginx
      - /var/run
    ports:
      - "${CLIENT_PORT:-4100}:3000"
    environment:
      - NEXT_PUBLIC_SERVER_URL=${TENANT_SERVER_URL:-http://localhost:4101}
      - NEXT_PUBLIC_DEFAULT_TENANT_SERVER_URL=${TENANT_SERVER_URL:-http://localhost:4101}
      - NEXT_PUBLIC_INSTALLATION_ID=${INSTALLATION_ID:-}
    networks:
      - app
    healthcheck:
      test: ["CMD", "wget", "-qO-", "http://127.0.0.1:3000/"]
      interval: 15s
      timeout: 5s
      start_period: 30s
      retries: 3
    deploy:
      resources:
        limits:
          memory: 256M
          cpus: "0.5"
          pids: 128
    logging:
      driver: json-file
      options:
        max-size: "50m"
        max-file: "3"

  auth:
    image: buildbaseapp/auth:latest
    restart: unless-stopped
    security_opt:
      - no-new-privileges:true
    cap_drop:
      - ALL
    tmpfs:
      - /tmp
      - /var/cache/nginx:uid=1001,gid=1001
      - /var/run:uid=1001,gid=1001
    ports:
      - "${AUTH_PORT:-4103}:3000"
    environment:
      - NEXT_PUBLIC_SERVER_URL=${TENANT_SERVER_URL:-http://localhost:4101}
    networks:
      - app
    healthcheck:
      test: ["CMD", "wget", "-qO-", "http://127.0.0.1:3000/health"]
      interval: 15s
      timeout: 5s
      start_period: 30s
      retries: 3
    deploy:
      resources:
        limits:
          memory: 256M
          cpus: "0.5"
          pids: 128
    logging:
      driver: json-file
      options:
        max-size: "50m"
        max-file: "3"

volumes:
  mongodb_data:
  redis_data:

# Network segmentation: databases isolated from frontends.
# Only tenant-server bridges both networks.
networks:
  db:
    driver: bridge
    internal: true
  app:
    driver: bridge

Shared Cloud vs Self-Hosted

Same platform, same features. Different hosting.

Shared Cloud
Self-Hosted
All platform modules
User & audience dataIsolated per orgYour servers
DatabasePer-org databaseYour MongoDB
Redis / cachingPre-configuredYour Redis
Dashboard UIPre-configuredHosted by you
SDK & APISame SDKSame SDK
Authentication tokensCentral serverES256 signed, verified locally
Emails & campaignsPre-configuredYour infrastructure
Workflows & jobsPre-configuredYour workers
File storageCloud storageYour storage
SSL & networkingPre-configuredYou control
UpdatesAutomaticdocker compose pull
Setup timeInstant~5 minutes
Security

Security

Built for compliance-sensitive deployments.

  • ES256 (ECDSA P-256) asymmetric token signing — private key never leaves central server
  • Per-organization key pairs with key rotation support
  • Audience-locked tokens with 2-minute expiry and replay protection (jti)
  • Per-installation API keys with HMAC-SHA256 request signing
  • Installation-scoped access — each instance only sees its assigned orgs
  • All sensitive database fields encrypted at rest (AES-256-CBC)
  • No user passwords or PII flow through the central server
  • Central server URL pinned via read-only lock file in Docker images
  • Read-only tenant-server filesystem prevents runtime code tampering
  • Brute-force protection with rate limiting on all auth endpoints
  • SSRF protection on outbound requests (blocks private IPs, cloud metadata)
  • Non-root Docker container with dumb-init signal handling
  • Network segmentation — databases on internal-only network, unreachable from frontends
  • All capabilities dropped (cap_drop: ALL) with minimal cap_add where required
  • PID limits on all containers to prevent fork bomb attacks
  • CPU and memory limits on all containers to prevent resource exhaustion
  • Docker socket mounted read-only for autoheal (production template)
  • Privilege escalation blocked (no-new-privileges) on every container

Docker images

Three official images on Docker Hub. No source code, no secrets, no source maps.

ImageDescriptionPort
buildbaseapp/tenant-serverBackend API server3000
buildbaseapp/clientWeb dashboard (Next.js)3000
buildbaseapp/authAuth portal (Next.js)3000

Platform: linux/amd64, linux/arm64 · Base: Node.js 22 Alpine · Non-root user · Graceful shutdown via dumb-init

Requirements

Minimal requirements. The quick-start compose includes MongoDB and Redis.

ServerLinux (Ubuntu 20.04+ recommended)
Memory2 GB RAM minimum
CPU2 vCPU minimum
DockerDocker Engine 20+ and Docker Compose v2
DatabaseMongoDB 7+ (included in quick start)
CacheRedis 7+ (included in quick start)
NetworkPublic IP or domain (for production). Localhost works for testing.

Frequently Asked Questions

What are the requirements to self-host?

A Linux server with 2 GB RAM, Docker, and a public IP or domain. MongoDB and Redis are included in the quick-start compose file.

Does the dashboard still work?

Yes. The dashboard at console.buildbase.app connects to your self-hosted server over HTTPS. Your data stays on your server.

How does authentication work between central and my server?

The central server signs an ES256 token with your org's private key. Your server verifies it using the public key. No secrets travel over the network.

Can I use localhost for testing?

Yes. The setup wizard verifies connections from your browser, so localhost:3000, 192.168.x.x, and LAN IPs all work. Use a public domain with HTTPS for production.

How do I update to a new version?

Run `docker compose pull` and `docker compose up -d`. The production compose file with 2 replicas supports zero-downtime updates.

Can I run multiple organizations on one server?

Yes. One installation serves multiple organizations. They share the same server, database, and Redis while data remains isolated in separate databases.

Is the Docker image open source?

The image is published on Docker Hub at buildbaseapp/tenant-server. It contains compiled JavaScript only — no source code or source maps.

How do backups work?

You manage your own MongoDB backups. Use mongodump, MongoDB Atlas automated backups, or any preferred tool. Daily backups with point-in-time recovery are recommended.

Can I switch from cloud to self-hosted?

Yes. Create a self-hosted organization and migrate your data. Both shared and self-hosted orgs can run under the same account.

Is there an SLA for self-hosted?

Self-hosted uptime depends on your infrastructure. The dashboard has its own SLA. For production, use the production compose file with 2 replicas, Nginx, and auto-heal.

Get started

Create a self-hosted organization and deploy in minutes.