Self-Hosting Budibase: Low-Code Platform for Internal Tools

Every company has That Spreadsheet. The one that started as a quick tracker and evolved into a mission-critical monster with 47 tabs, three people who understand the formulas, and a prayer that nobody accidentally deletes row 4.

Budibase turns those spreadsheets into actual applications — with forms, tables, automations, and role-based access — without requiring you to be a full-stack developer. It’s an open-source low-code platform that lets you build internal tools on top of your existing data sources (PostgreSQL, MySQL, REST APIs, Google Sheets, or its own built-in database).

The managed Budibase cloud plan works fine for small teams, but self-hosting removes user limits, keeps your data on your infrastructure, and eliminates the recurring subscription. If you’re building tools that touch sensitive business data, self-hosting is the obvious choice.

Budibase vs Other Low-Code Platforms

FeatureBudibaseRetoolAppsmithNocoDBBaserow
Open source✅ GPLv3❌ Proprietary✅ Apache 2.0✅ AGPLv3✅ MIT
Built-in database✅ BudibaseDB❌ External only❌ External only✅ (is the DB)✅ (is the DB)
External data sources✅ SQL, REST, S3✅ Extensive✅ SQL, REST, GraphQL⚠️ Limited⚠️ Limited
Automations✅ Built-in✅ Built-in⚠️ Basic✅ Webhooks✅ Webhooks
Form builder✅ Drag-and-drop✅ Drag-and-drop✅ Drag-and-drop⚠️ Basic⚠️ Basic
RBAC✅ App/table level✅ Granular✅ App level✅ View level✅ Table level
Self-host complexityMediumHardMediumEasyEasy
Resource usage~1.5GB RAMN/A (cloud)~1GB RAM~300MB RAM~500MB RAM
AI features✅ Built-in (LiteLLM)⚠️ Beta

Budibase sits in a sweet spot: more capable than spreadsheet replacements like NocoDB or Baserow, but simpler and self-hostable unlike Retool. If you need custom internal apps (not just fancy tables), Budibase delivers.

Prerequisites

  • A Linux server with Docker and Docker Compose installed (Ubuntu setup guide)
  • A domain name pointed at your server’s public IP
  • A reverse proxy (Caddy or Nginx) for HTTPS
  • At least 2GB RAM (Budibase runs multiple services: app server, worker, CouchDB, Redis, MinIO, and a proxy)
  • Basic familiarity with environment variables and Docker Compose

Docker Compose Setup

Budibase is a multi-service platform. The official stack includes an app server, background worker, CouchDB (database), Redis (caching), MinIO (object storage), and an Nginx-based proxy. Here’s a production-ready configuration:

First, create your project directory:

mkdir -p ~/budibase && cd ~/budibase

Create the environment file with secure credentials:

# .env
# Core secrets — generate unique values for each
API_ENCRYPTION_KEY=$(openssl rand -hex 32)
JWT_SECRET=$(openssl rand -hex 32)
INTERNAL_API_KEY=$(openssl rand -hex 32)

# CouchDB credentials
COUCH_DB_USER=budibase
COUCH_DB_PASSWORD=$(openssl rand -hex 16)

# MinIO credentials
MINIO_ACCESS_KEY=$(openssl rand -hex 12)
MINIO_SECRET_KEY=$(openssl rand -hex 24)

# Redis
REDIS_PASSWORD=$(openssl rand -hex 16)

# Main port — the proxy listens here
MAIN_PORT=10000

# Environment
BUDIBASE_ENVIRONMENT=PRODUCTION

Generate the actual .env file:

cat > .env << 'ENVFILE'
API_ENCRYPTION_KEY=
JWT_SECRET=
INTERNAL_API_KEY=
COUCH_DB_USER=budibase
COUCH_DB_PASSWORD=
MINIO_ACCESS_KEY=
MINIO_SECRET_KEY=
REDIS_PASSWORD=
MAIN_PORT=10000
BUDIBASE_ENVIRONMENT=PRODUCTION
ENVFILE

# Fill in random secrets
sed -i "s/^API_ENCRYPTION_KEY=.*/API_ENCRYPTION_KEY=$(openssl rand -hex 32)/" .env
sed -i "s/^JWT_SECRET=.*/JWT_SECRET=$(openssl rand -hex 32)/" .env
sed -i "s/^INTERNAL_API_KEY=.*/INTERNAL_API_KEY=$(openssl rand -hex 32)/" .env
sed -i "s/^COUCH_DB_PASSWORD=.*/COUCH_DB_PASSWORD=$(openssl rand -hex 16)/" .env
sed -i "s/^MINIO_ACCESS_KEY=.*/MINIO_ACCESS_KEY=$(openssl rand -hex 12)/" .env
sed -i "s/^MINIO_SECRET_KEY=.*/MINIO_SECRET_KEY=$(openssl rand -hex 24)/" .env
sed -i "s/^REDIS_PASSWORD=.*/REDIS_PASSWORD=$(openssl rand -hex 16)/" .env

Now the Docker Compose file:

# docker-compose.yml
services:
  app-service:
    restart: unless-stopped
    image: budibase/apps
    container_name: bbapps
    environment:
      SELF_HOSTED: 1
      COUCH_DB_URL: http://${COUCH_DB_USER}:${COUCH_DB_PASSWORD}@couchdb-service:5984
      WORKER_URL: http://worker-service:4003
      MINIO_URL: http://minio-service:9000
      MINIO_ACCESS_KEY: ${MINIO_ACCESS_KEY}
      MINIO_SECRET_KEY: ${MINIO_SECRET_KEY}
      INTERNAL_API_KEY: ${INTERNAL_API_KEY}
      BUDIBASE_ENVIRONMENT: ${BUDIBASE_ENVIRONMENT}
      PORT: 4002
      API_ENCRYPTION_KEY: ${API_ENCRYPTION_KEY}
      JWT_SECRET: ${JWT_SECRET}
      LOG_LEVEL: info
      ENABLE_ANALYTICS: "false"
      REDIS_URL: redis-service:6379
      REDIS_PASSWORD: ${REDIS_PASSWORD}
    depends_on:
      - worker-service
      - redis-service

  worker-service:
    restart: unless-stopped
    image: budibase/worker
    container_name: bbworker
    environment:
      SELF_HOSTED: 1
      PORT: 4003
      CLUSTER_PORT: ${MAIN_PORT}
      API_ENCRYPTION_KEY: ${API_ENCRYPTION_KEY}
      JWT_SECRET: ${JWT_SECRET}
      MINIO_ACCESS_KEY: ${MINIO_ACCESS_KEY}
      MINIO_SECRET_KEY: ${MINIO_SECRET_KEY}
      MINIO_URL: http://minio-service:9000
      APPS_URL: http://app-service:4002
      COUCH_DB_USERNAME: ${COUCH_DB_USER}
      COUCH_DB_PASSWORD: ${COUCH_DB_PASSWORD}
      COUCH_DB_URL: http://${COUCH_DB_USER}:${COUCH_DB_PASSWORD}@couchdb-service:5984
      INTERNAL_API_KEY: ${INTERNAL_API_KEY}
      REDIS_URL: redis-service:6379
      REDIS_PASSWORD: ${REDIS_PASSWORD}
    depends_on:
      - redis-service
      - minio-service

  minio-service:
    restart: unless-stopped
    image: minio/minio
    volumes:
      - minio_data:/data
    environment:
      MINIO_ACCESS_KEY: ${MINIO_ACCESS_KEY}
      MINIO_SECRET_KEY: ${MINIO_SECRET_KEY}
      MINIO_BROWSER: "off"
    command: server /data --console-address ":9001"
    healthcheck:
      test: "timeout 5s bash -c ':> /dev/tcp/127.0.0.1/9000' || exit 1"
      interval: 30s
      timeout: 20s
      retries: 3

  proxy-service:
    restart: unless-stopped
    ports:
      - "${MAIN_PORT}:10000"
    container_name: bbproxy
    image: budibase/proxy
    environment:
      - PROXY_RATE_LIMIT_WEBHOOKS_PER_SECOND=10
      - PROXY_RATE_LIMIT_API_PER_SECOND=50
      - APPS_UPSTREAM_URL=http://app-service:4002
      - WORKER_UPSTREAM_URL=http://worker-service:4003
      - MINIO_UPSTREAM_URL=http://minio-service:9000
      - COUCHDB_UPSTREAM_URL=http://couchdb-service:5984
      - RESOLVER=127.0.0.11
    depends_on:
      - minio-service
      - worker-service
      - couchdb-service

  couchdb-service:
    restart: unless-stopped
    image: budibase/couchdb
    environment:
      - COUCHDB_PASSWORD=${COUCH_DB_PASSWORD}
      - COUCHDB_USER=${COUCH_DB_USER}
    volumes:
      - couchdb_data:/opt/couchdb/data

  redis-service:
    restart: unless-stopped
    image: redis:7-alpine
    command: redis-server --requirepass "${REDIS_PASSWORD}"
    volumes:
      - redis_data:/data

volumes:
  couchdb_data:
    driver: local
  minio_data:
    driver: local
  redis_data:
    driver: local

Start the stack:

docker compose up -d

The first startup takes a minute or two as all services initialize. Watch the logs:

docker compose logs -f --tail=50

Once everything is healthy, Budibase will be accessible at http://your-server-ip:10000.

Reverse Proxy Configuration

Don’t expose Budibase directly. Put it behind a reverse proxy with HTTPS.

Add to your Caddyfile:

b}udibraesvee.rysoeu_rpdroomxayinl.occoamlh{ost:10000

Nginx

server {
    listen 443 ssl http2;
    server_name budibase.yourdomain.com;

    ssl_certificate /etc/letsencrypt/live/budibase.yourdomain.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/budibase.yourdomain.com/privkey.pem;

    client_max_body_size 50M;

    location / {
        proxy_pass http://localhost:10000;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;

        # WebSocket support (needed for real-time features)
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
    }
}

Initial Setup

  1. Navigate to https://budibase.yourdomain.com in your browser
  2. You’ll see the admin account creation screen
  3. Enter your email, password, and organization name
  4. Click Create admin user

That’s it — you’re in. No verification email, no phone number, no waiting for approval.

Building Your First App

Budibase shines when you need a quick internal tool. Here’s a practical example: an inventory tracker.

Create the App

  1. Click Create new app from the dashboard
  2. Choose Start from scratch (or pick a template)
  3. Name it “Inventory Tracker”

Set Up the Data

Click Data in the left sidebar. You have two options:

Option A: Use BudibaseDB (built-in)

  1. Click BudibaseDBCreate new table
  2. Name it “Items”
  3. Add columns:
    • name (Text) — the item name
    • category (Options) — define categories like Electronics, Furniture, etc.
    • quantity (Number) — current stock
    • location (Text) — where it’s stored
    • last_updated (Date) — auto-populated

Option B: Connect an external database

  1. Click Add source → choose PostgreSQL, MySQL, REST API, etc.
  2. Enter connection details
  3. Budibase will auto-discover your tables and relationships

Build the Interface

Switch to the Design tab:

  1. Add a Table component linked to your Items data
  2. Enable inline editing, sorting, and filtering
  3. Add a Form component for creating new items
  4. Use Conditional display to show different views based on user role

Add Automations

Click Automate in the sidebar:

  1. Trigger: When a row is updated in “Items”
  2. Condition: If quantity drops below 10
  3. Action: Send an email notification to the team or hit a webhook

Automations can also run on schedules (daily inventory reports), app actions (button clicks), or external webhooks.

Connecting External Data Sources

One of Budibase’s strengths is pulling data from where it already lives:

  • PostgreSQL / MySQL / MariaDB / MSSQL — full CRUD operations
  • MongoDB — read and write to collections
  • REST API — connect to any HTTP endpoint
  • Google Sheets — use spreadsheets as a data source
  • Airtable — import your existing bases
  • S3-compatible storage — file attachments via MinIO or AWS S3
  • Oracle Database — enterprise data source support

For each external source, Budibase generates queries automatically but also lets you write custom queries when you need more control.

User Management and RBAC

Budibase has three built-in roles:

  • Admin — full platform access, manage users, apps, and settings
  • Power — can create and edit apps
  • Basic — can use apps they’ve been given access to

Within each app, you can further restrict access at the screen and component level. For example, only show the “Delete” button to Power users, or restrict a data table to show only rows owned by the current user.

SSO Integration

For production deployments, configure SSO under Settings → Auth:

  • OpenID Connect — works with Authentik, Keycloak, Auth0, etc.
  • Google OAuth — quick setup for Google Workspace teams
  • SCIM — automatic user provisioning (Premium feature)

If you’re already running Authentik or Authelia, plug Budibase into your existing identity provider.

Backup Strategy

Budibase stores data across multiple services. A complete backup needs to cover all of them:

#!/bin/bash
# backup-budibase.sh
BACKUP_DIR="/backups/budibase/$(date +%Y-%m-%d)"
mkdir -p "$BACKUP_DIR"

# Stop services for consistent backup
cd ~/budibase
docker compose stop app-service worker-service

# Backup CouchDB data
docker compose exec -T couchdb-service \
  tar czf - /opt/couchdb/data > "$BACKUP_DIR/couchdb.tar.gz"

# Backup MinIO data
docker run --rm \
  -v budibase_minio_data:/data \
  -v "$BACKUP_DIR":/backup \
  alpine tar czf /backup/minio.tar.gz /data

# Backup Redis (optional — mostly cache, but persistent data exists)
docker run --rm \
  -v budibase_redis_data:/data \
  -v "$BACKUP_DIR":/backup \
  alpine tar czf /backup/redis.tar.gz /data

# Backup .env file (contains your secrets!)
cp ~/budibase/.env "$BACKUP_DIR/env.backup"

# Restart services
docker compose start app-service worker-service

echo "Backup completed: $BACKUP_DIR"

Budibase also supports app-level export: go to Settings within any app and click Export. This creates a portable file you can import into another Budibase instance.

Performance Tuning

Memory Allocation

The default stack is hungry. If you’re running on a 2GB server, consider these tweaks in your docker-compose.yml:

services:
  couchdb-service:
    # ... existing config ...
    deploy:
      resources:
        limits:
          memory: 512M

  redis-service:
    # ... existing config ...
    command: redis-server --requirepass "${REDIS_PASSWORD}" --maxmemory 128mb --maxmemory-policy allkeys-lru

Disable Analytics

Already done in our compose file (ENABLE_ANALYTICS: "false"), but double-check under Settings → Organisation → Analytics in the Budibase UI.

CouchDB Compaction

CouchDB can grow large over time. Set up periodic compaction:

# Run weekly via cron
curl -X POST http://admin:password@localhost:5984/_compact \
  -H "Content-Type: application/json"

Troubleshooting

Services Won’t Start

Check that all environment variables are set:

docker compose config

Missing secrets (especially API_ENCRYPTION_KEY or JWT_SECRET) will cause silent failures.

“502 Bad Gateway” on First Access

The app and worker services take 30-60 seconds to initialize. Check their status:

docker compose ps
docker compose logs app-service --tail=20

CouchDB Clustering Errors

If you see CouchDB cluster warnings in the logs, they’re usually harmless for single-node setups. The Budibase CouchDB image handles single-node configuration automatically.

High Memory Usage

CouchDB and MinIO are the biggest consumers. If memory is tight:

  1. Ensure CouchDB compaction is running
  2. Set Redis maxmemory as shown above
  3. Consider using an external PostgreSQL database instead of BudibaseDB (CouchDB) for large datasets

App Export/Import Fails

Large apps with many attachments can timeout during export. Use the file-system backup approach instead of the UI export for production backups.

Updating Budibase

cd ~/budibase
docker compose pull
docker compose down
docker compose up -d

Budibase handles database migrations automatically on startup. Still, always back up before updating — especially across major versions.

Check the Budibase changelog before upgrading to catch any breaking changes.

Conclusion

Budibase fills a real gap: it’s powerful enough to replace simple internal tools that would otherwise require a developer, but self-hostable and open-source so your data stays yours. The learning curve is gentle — if you can build a spreadsheet, you can build a Budibase app.

Start with one pain point. That inventory tracker nobody wants to maintain in Excel. The onboarding form that’s still a PDF. The customer lookup tool that’s three browser tabs and a prayer. Build it in Budibase, deploy it on your own server, and move on to the next one.

The platform is actively developed, with new features landing regularly. The community is helpful, the documentation is solid, and the self-hosted experience is first-class. For internal tooling needs, Budibase is one of the best open-source options available.