BuildButler

Self-Hosted Installation

Run the full BuildButler stack on your own infrastructure via npm or Docker.

Run the full BuildButler stack — dashboard, API, and AI assistant — on your own infrastructure. No cloud account required.

Requirements

  • Node.js 22+ (npm) or Docker
  • PostgreSQL 15+ — a single PostgreSQL server is enough; BuildButler creates both required databases automatically on first run
  • A BuildButler license key (BB-XXXX-XXXX-XXXX-XXXX) — obtain one at buildbutler.dev

Install via npm

npm install -g @buildbutler/server

Create a .env file:

DATABASE_URL=postgresql://user:password@localhost/buildbutler
JWT_SECRET=<run: openssl rand -hex 32>
BUILDBUTLER_LICENSE_KEY=BB-XXXX-XXXX-XXXX-XXXX

Start the server:

buildbutler

Open http://localhost:3000 and register your first account.


Install via Docker

docker run -d \
  --name buildbutler \
  -p 3000:3000 \
  -e DATABASE_URL="postgresql://user:password@your-db-host/buildbutler" \
  -e JWT_SECRET="$(openssl rand -hex 32)" \
  -e BUILDBUTLER_LICENSE_KEY="BB-XXXX-XXXX-XXXX-XXXX" \
  ghcr.io/buildbutler/buildbutler:latest

If PostgreSQL is running on your local machine, use host.docker.internal instead of localhost.


Database setup

BuildButler needs two PostgreSQL databases — one for org data (builds, tests, dashboards) and one for the control plane (users, sessions, API keys). Both are created automatically on first startup.

The control database defaults to the same host as DATABASE_URL with _control appended:

DATABASE_URLAuto-derived control DB
postgresql://host/buildbutlerpostgresql://host/buildbutler_control

Set CONTROL_DATABASE_URL explicitly only if the control database needs to live on a different server.

Schema migrations also run automatically on every startup — no manual migration step needed.


Docker Compose (with PostgreSQL)

For a production setup with SSL, see Docker Compose with Caddy in the SSL Setup section below.

services:
  postgres:
    image: postgres:16-alpine
    restart: unless-stopped
    environment:
      POSTGRES_USER: buildbutler
      POSTGRES_PASSWORD: changeme
      POSTGRES_DB: buildbutler
    volumes:
      - postgres_data:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U buildbutler"]
      interval: 5s
      timeout: 5s
      retries: 5
 
  buildbutler:
    image: ghcr.io/buildbutler/buildbutler:latest
    restart: unless-stopped
    ports:
      - "3000:3000"
    environment:
      DATABASE_URL: postgresql://buildbutler:changeme@postgres/buildbutler
      JWT_SECRET: "replace-with-output-of-openssl-rand-hex-32"
      BUILDBUTLER_LICENSE_KEY: "BB-XXXX-XXXX-XXXX-XXXX"
    depends_on:
      postgres:
        condition: service_healthy
 
volumes:
  postgres_data:
docker compose up -d
docker compose logs -f buildbutler

Configuration

Required

VariableDescription
DATABASE_URLPostgreSQL connection string
JWT_SECRETRandom secret for signing auth tokens — generate with openssl rand -hex 32

Optional

VariableDescription
CONTROL_DATABASE_URLControl plane database. Defaults to DATABASE_URL host with _control suffix.
BUILDBUTLER_LICENSE_KEYLicense key. Without one the server runs with a 7-day grace period then restricts data ingest.
PORTPort to listen on (default: 3000)
ALLOWED_ORIGINSComma-separated CORS origins — set to your public URL when behind a reverse proxy
RESEND_API_KEYResend API key for password reset and invite emails
LLM_ENCRYPTION_KEY32-char key to encrypt stored AI provider API keys — generate with openssl rand -hex 16

Object storage (optional)

Artifact uploads require an S3-compatible object store. If not configured, artifact endpoints return 503 but all other features work.

VariableDescription
S3_BUCKETBucket name
S3_ENDPOINTOverride endpoint for MinIO, Cloudflare R2, etc. Leave unset for AWS S3.
S3_ACCESS_KEYAccess key ID
S3_SECRET_KEYSecret access key
S3_REGIONRegion (default: us-east-1, use auto for R2)

CLI flags

FlagDescription
--port <number>Override the port
--noauthDisable authentication — all visitors have full admin access. For local dev or VPN-isolated installs only.

Reverse proxy

Caddy (automatic TLS):

buildbutler.yourcompany.com {
    reverse_proxy localhost:3000
}

nginx:

server {
    listen 443 ssl;
    server_name buildbutler.yourcompany.com;
 
    location / {
        proxy_pass http://localhost:3000;
        proxy_set_header Host $host;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        client_max_body_size 100m;
    }
}

Then set ALLOWED_ORIGINS=https://buildbutler.yourcompany.com.


SSL Setup

Caddy handles TLS automatically via Let's Encrypt — no certificate management needed.

# Install Caddy, then create a Caddyfile:
buildbutler.yourcompany.com {
    reverse_proxy localhost:3000
}

Start Caddy and it fetches and renews the certificate automatically.

nginx + Certbot

# Get a certificate
certbot --nginx -d buildbutler.yourcompany.com

nginx config:

server {
    listen 443 ssl;
    server_name buildbutler.yourcompany.com;
 
    ssl_certificate /etc/letsencrypt/live/buildbutler.yourcompany.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/buildbutler.yourcompany.com/privkey.pem;
 
    location / {
        proxy_pass http://localhost:3000;
        proxy_set_header Host $host;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        client_max_body_size 100m;
    }
}

Docker Compose with Caddy

A complete production setup with PostgreSQL, BuildButler, and Caddy handling TLS automatically.

docker-compose.yml:

services:
  caddy:
    image: caddy:2-alpine
    restart: unless-stopped
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./Caddyfile:/etc/caddy/Caddyfile
      - caddy_data:/data
    depends_on:
      - buildbutler
 
  buildbutler:
    image: ghcr.io/buildbutler/buildbutler:latest
    restart: unless-stopped
    environment:
      DATABASE_URL: postgresql://buildbutler:changeme@postgres/buildbutler
      JWT_SECRET: "replace-with-output-of-openssl-rand-hex-32"
      BUILDBUTLER_LICENSE_KEY: "BB-XXXX-XXXX-XXXX-XXXX"
      ALLOWED_ORIGINS: "https://buildbutler.yourcompany.com"
    depends_on:
      postgres:
        condition: service_healthy
 
  postgres:
    image: postgres:16-alpine
    restart: unless-stopped
    environment:
      POSTGRES_USER: buildbutler
      POSTGRES_PASSWORD: changeme
      POSTGRES_DB: buildbutler
    volumes:
      - postgres_data:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U buildbutler"]
      interval: 5s
      timeout: 5s
      retries: 5
 
volumes:
  caddy_data:
  postgres_data:

Caddyfile:

buildbutler.yourcompany.com {
    reverse_proxy buildbutler:3000
}

Start everything:

docker compose up -d
docker compose logs -f buildbutler

Caddy fetches and renews the TLS certificate automatically. Replace buildbutler.yourcompany.com with your actual domain — it must be publicly reachable for Let's Encrypt to issue a certificate.

Using a company-issued certificate

If your organisation provides its own TLS certificate (e.g. from an internal CA or a purchased cert), you can use it with Caddy instead of Let's Encrypt.

Mount the certificate files into the Caddy container and reference them in the Caddyfile:

docker-compose.yml — add a volume mount for your certs:

  caddy:
    image: caddy:2-alpine
    restart: unless-stopped
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./Caddyfile:/etc/caddy/Caddyfile
      - ./certs:/etc/caddy/certs:ro
      - caddy_data:/data
    depends_on:
      - buildbutler

Place your certificate files in a certs/ folder next to docker-compose.yml:

your-project/
├── docker-compose.yml
├── Caddyfile
└── certs/
    ├── cert.pem
    └── key.pem

Caddyfile — point to the certificate files:

buildbutler.yourcompany.com {
    tls /etc/caddy/certs/cert.pem /etc/caddy/certs/key.pem
    reverse_proxy buildbutler:3000
}

If you are not using Docker and running Caddy directly on the host, the paths are local filesystem paths:

buildbutler.yourcompany.com {
    tls /etc/ssl/certs/buildbutler.pem /etc/ssl/private/buildbutler.key
    reverse_proxy localhost:3000
}

When tls is set to explicit file paths, Caddy uses those files and does not contact Let's Encrypt. Certificate renewal is your responsibility — replace the files and run caddy reload (or restart the container) when the cert is renewed.


Upgrading

npm:

npm update -g @buildbutler/server
buildbutler

Docker:

docker pull ghcr.io/buildbutler/buildbutler:latest
docker compose up -d

Migrations run automatically on startup. BuildButler displays an upgrade banner in the sidebar when a newer version is available.


AI assistant

The AI assistant requires an external LLM provider key.

  1. Go to Settings → AI
  2. Choose your provider: OpenAI, Anthropic, or Google
  3. Enter your API key and select a model

Recommended models:

ProviderModel
Anthropicclaude-sonnet-4-6
OpenAIgpt-4o-mini
Googlegemini-2.0-flash

Set LLM_ENCRYPTION_KEY before saving any API keys — without it keys are stored in plaintext.