Skip to Content

Production Guide: Deploy Budibase with Docker Compose + Caddy + CouchDB + Redis + MinIO on Ubuntu

A practical, production-focused implementation guide for the open-source low-code platform with operational validation and recovery patterns.

Internal tools are the backbone of every operations team, yet most organizations either buy expensive SaaS platforms that charge per-user premiums, or they assign engineering sprints to build bespoke admin panels from scratch. Budibase is an open-source low-code platform that lets non-developers design data-driven applications with a visual builder while giving engineers full control over hosting, authentication, and data custody. It connects to PostgreSQL, MySQL, MongoDB, REST APIs, and Google Sheets, then generates reactive web apps with role-based access control, automation triggers, and customizable themes.

This guide deploys Budibase on Ubuntu with Docker Compose, Caddy for automatic HTTPS, CouchDB for application definitions and user metadata, Redis for caching and job queuing, and MinIO for object storage. By the end, you will have a production-ready low-code platform accessible at a public domain, with persistent data volumes, health checks, secure secrets handling, and a backup strategy that covers both the document store and the object storage layer.

Running Budibase on your own infrastructure means you retain full ownership of application schemas, user directories, audit logs, and file attachments. You avoid per-seat licensing, can enforce network-level isolation, and integrate directly with your existing identity provider without routing credentials through a third-party cloud.

Architecture and flow overview

Caddy sits at the edge and terminates TLS with automatically managed Let's Encrypt certificates. It reverse-proxies HTTPS traffic to the Budibase app container on its internal HTTP port. The app container serves the React frontend and the REST API, persists application definitions, screen layouts, datasource configurations, and user accounts in CouchDB, and enqueues background automation jobs in Redis. The worker container polls Redis for pending jobs, executes automation steps, handles data source queries, and writes results back to CouchDB. MinIO stores file attachments, CSV imports, template exports, and generated PDFs.

  • Edge: Caddy with automatic TLS and HTTP/2
  • App: Budibase apps container (frontend + API)
  • Worker: Budibase worker container (background jobs)
  • State: CouchDB for documents, Redis for queues, MinIO for objects
  • Control: Docker Compose with restart policies and health checks
  • Ops: Backup workflow covering CouchDB dumps and MinIO buckets

All services except Caddy remain inside an isolated Docker bridge network. CouchDB and Redis are unreachable from the public internet because they do not publish host ports. The app and worker containers share an internal network segment with the database and object storage, while Caddy is the only service bound to the host's public interfaces.

Prerequisites

  • Linux VM or server with Ubuntu 22.04/24.04 and sudo access
  • Docker Engine 24.x or newer with the Compose plugin
  • A DNS A record pointing your Budibase domain to the server IP
  • Firewall rules allowing TCP 22 (SSH) and TCP 443 (HTTPS)
  • Password manager entry for the initial admin credentials
  • Named owner assigned for platform operations and upgrades

Step-by-step deployment

1) Create directories and permissions

Use a fixed directory layout so runbooks, backup scripts, and monitoring checks reference the same paths every time.

sudo mkdir -p /opt/budibase/{compose,backups,plugins}
sudo mkdir -p /var/lib/budibase-couchdb
sudo mkdir -p /var/lib/budibase-minio
sudo mkdir -p /var/lib/budibase-redis
sudo chown -R 1000:1000 /var/lib/budibase-couchdb
sudo chown -R 1000:1000 /var/lib/budibase-minio
sudo chown -R 999:999 /var/lib/budibase-redis
sudo chown -R $USER:$USER /opt/budibase

If the copy button is unavailable in your browser/editor, manually copy the command block above.

2) Store secrets in an environment file

Keep credentials out of the compose file and out of version control. Restrict the file to root read access.

cat > /opt/budibase/.env.prod <<'EOF'
BUDIBASE_ENVIRONMENT=production
MAIN_PORT=10000
COUCH_DB_USER=budibase_couch
COUCH_DB_PASSWORD=$(openssl rand -base64 32)
REDIS_PASSWORD=$(openssl rand -base64 32)
MINIO_ACCESS_KEY=$(openssl rand -base64 24)
MINIO_SECRET_KEY=$(openssl rand -base64 32)
API_ENCRYPTION_KEY=$(openssl rand -base64 32)
JWT_SECRET=$(openssl rand -base64 48)
INTERNAL_API_KEY=$(openssl rand -base64 24)
[email protected]
BB_ADMIN_USER_PASSWORD=$(openssl rand -base64 24)
PLUGINS_DIR=/plugins
OFFLINE_MODE=
EOF
chmod 600 /opt/budibase/.env.prod

If the copy button is unavailable in your browser/editor, manually copy the command block above.

3) Configure the Caddy reverse proxy

Caddy handles TLS automatically and forwards traffic to the Budibase app container. Place the Caddyfile outside the compose directory so it can be updated independently.

cat > /opt/budibase/Caddyfile <<'EOF'
budibase.example.com {
    reverse_proxy app-service:4002
    header {
        X-Frame-Options DENY
        X-Content-Type-Options nosniff
        Referrer-Policy strict-origin-when-cross-origin
    }
    encode gzip zstd
}
EOF

If the copy button is unavailable in your browser/editor, manually copy the command block above.

4) Define the Docker Compose services

This compose file defines the core Budibase services. We omit the optional LiteLLM service to keep the stack focused and production-stable.

cat > /opt/budibase/compose/docker-compose.yml <<'EOF'
services:
  app-service:
    restart: unless-stopped
    image: budibase/apps:latest
    container_name: bbapps
    env_file:
      - /opt/budibase/.env.prod
    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: "true"
      REDIS_URL: redis-service:6379
      REDIS_PASSWORD: ${REDIS_PASSWORD}
      REDIS_USERNAME: ${REDIS_USERNAME:-}
      BB_ADMIN_USER_EMAIL: ${BB_ADMIN_USER_EMAIL}
      BB_ADMIN_USER_PASSWORD: ${BB_ADMIN_USER_PASSWORD}
      PLUGINS_DIR: ${PLUGINS_DIR}
      OFFLINE_MODE: ${OFFLINE_MODE:-}
    depends_on:
      - worker-service
      - redis-service
      - couchdb-service
      - minio-service
    volumes:
      - /opt/budibase/plugins:/plugins
    networks:
      - budibase

  worker-service:
    restart: unless-stopped
    image: budibase/worker:latest
    container_name: bbworker
    env_file:
      - /opt/budibase/.env.prod
    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}
      REDIS_USERNAME: ${REDIS_USERNAME:-}
      OFFLINE_MODE: ${OFFLINE_MODE:-}
    depends_on:
      - redis-service
      - minio-service
      - couchdb-service
    networks:
      - budibase

  couchdb-service:
    restart: unless-stopped
    image: budibase/database:2.1.0
    container_name: bbcouch
    env_file:
      - /opt/budibase/.env.prod
    environment:
      - COUCHDB_PASSWORD=${COUCH_DB_PASSWORD}
      - COUCHDB_USER=${COUCH_DB_USER}
      - TARGETBUILD=docker-compose
      - DATA_DIR=/data
    volumes:
      - /var/lib/budibase-couchdb:/data
    networks:
      - budibase
    healthcheck:
      test: ["CMD", "curl", "-f", "http://127.0.0.1:5984/_up"]
      interval: 30s
      timeout: 10s
      retries: 5

  redis-service:
    restart: unless-stopped
    image: redis:7-alpine
    container_name: bbredis
    env_file:
      - /opt/budibase/.env.prod
    command: redis-server --requirepass "${REDIS_PASSWORD}" --appendonly yes
    volumes:
      - /var/lib/budibase-redis:/data
    networks:
      - budibase
    healthcheck:
      test: ["CMD", "redis-cli", "--raw", "incr", "ping"]
      interval: 30s
      timeout: 10s
      retries: 5

  minio-service:
    restart: unless-stopped
    image: minio/minio:latest
    container_name: bbminio
    env_file:
      - /opt/budibase/.env.prod
    environment:
      MINIO_ACCESS_KEY: ${MINIO_ACCESS_KEY}
      MINIO_SECRET_KEY: ${MINIO_SECRET_KEY}
      MINIO_BROWSER: "off"
    command: server /data --console-address ":9001"
    volumes:
      - /var/lib/budibase-minio:/data
    networks:
      - budibase
    healthcheck:
      test: ["CMD", "sh", "-c", "curl -f http://127.0.0.1:9000/minio/health/live || exit 1"]
      interval: 30s
      timeout: 20s
      retries: 3

  caddy:
    restart: unless-stopped
    image: caddy:2-alpine
    container_name: bbcaddy
    ports:
      - "443:443"
      - "80:80"
    volumes:
      - /opt/budibase/Caddyfile:/etc/caddy/Caddyfile:ro
      - caddy_data:/data
      - caddy_config:/config
    networks:
      - budibase

networks:
  budibase:
    driver: bridge

volumes:
  caddy_data:
  caddy_config:
EOF

If the copy button is unavailable in your browser/editor, manually copy the command block above.

5) Start the stack and verify health

Pull images, create the network, and bring up services in dependency order. Wait for CouchDB and Redis health checks before trusting the app.

cd /opt/budibase/compose
docker compose pull
docker compose up -d
sleep 15
docker compose ps
docker compose logs --tail=50 app-service

If the copy button is unavailable in your browser/editor, manually copy the command block above.

6) Create the MinIO bucket for attachments

Budibase expects a bucket named budibase to exist in MinIO. Create it after the stack is healthy.

docker compose exec minio-service mc alias set local http://127.0.0.1:9000 "$MINIO_ACCESS_KEY" "$MINIO_SECRET_KEY"
docker compose exec minio-service mc mb local/budibase --ignore-existing
docker compose exec minio-service mc anonymous set download local/budibase

If the copy button is unavailable in your browser/editor, manually copy the command block above.

7) Configure automated backups

Back up CouchDB with its native dump tool and mirror the MinIO data directory. Store archives on separate storage or a remote target.

cat > /opt/budibase/backups/backup-budibase.sh <<'EOF'
#!/usr/bin/env bash
set -euo pipefail
TS=$(date +%F-%H%M)
mkdir -p /opt/budibase/backups/archive
tar -czf /opt/budibase/backups/archive/budibase-couchdb-$TS.tgz -C /var/lib/budibase-couchdb .
tar -czf /opt/budibase/backups/archive/budibase-minio-$TS.tgz -C /var/lib/budibase-minio .
find /opt/budibase/backups/archive -type f -mtime +14 -delete
echo "Backup completed: budibase-couchdb-$TS.tgz"
EOF
chmod +x /opt/budibase/backups/backup-budibase.sh

If the copy button is unavailable in your browser/editor, manually copy the command block above.

Configuration and secrets handling

Use least privilege from day one. Keep administrative access limited to named team members and remove shared credentials. Rotate the initial admin password after first login and track rotation dates in your security calendar. If your organization requires SSO, plan it as an explicit follow-up change with rollback steps.

For secrets, prefer an external secret manager when available. If not, use encrypted disk plus strict file permissions and treat environment files as sensitive assets in backups. Never place API tokens directly into application exports or version control. If automation pipelines render provisioning files, redact secrets from CI logs and artifact retention.

To reduce drift, keep Caddyfile and Compose definitions under version control. Use pull requests for changes, include screenshots in reviews, and require a named approver from operations. This creates a paper trail for incident retrospectives and compliance checks.

Also define capacity guardrails early: target query latency for large datasources, max concurrent automation runs, and plugin approval process. These controls prevent slow degradation that only appears under load. Add lightweight dashboards that observe the low-code platform itself so failures are visible before users complain.

Verification

Verification should be more than "the page loads." Confirm service state, health endpoint output, certificate validity, and backup artifact creation. Keep expected outputs in your runbook and capture evidence after each change.

  • All containers show healthy status in docker compose ps
  • CouchDB responds with 200 OK on /_up
  • Redis accepts authenticated PING
  • Budibase web UI loads at the HTTPS domain without certificate warnings
  • MinIO bucket budibase exists and is writable
  • Backup script produces non-empty archives with current timestamps

If any check fails, stop rollout and execute rollback before proceeding to broader changes.

Common issues and fixes

App container exits on startup

Review environment variable syntax and verify CouchDB is healthy before the app starts. Check docker compose logs app-service for connection refused errors.

Login works but apps do not save

Usually indicates write-permission mismatch in CouchDB or MinIO storage. Reapply ownership and restart the affected service.

File uploads fail silently

Confirm the MinIO bucket exists and the app service environment variables point to the correct MinIO URL and credentials.

Worker job queue grows without processing

Check Redis memory and authentication. Verify the worker container can reach Redis and CouchDB on the internal Docker network.

Backups exist but restore fails

Most restore failures come from undocumented file paths or permission mismatches. Include a monthly restore drill and verify ownership after extraction.

Service not available after reboot

Validate Docker restart policies. Ensure the data mount paths exist before Compose tries to attach volumes.

FAQ

Is Budibase suitable for production internal tools?

Yes for many teams, provided you add health checks, backups, and operational monitoring. The self-hosted edition is designed for production use with proper infrastructure.

Why CouchDB instead of PostgreSQL?

Budibase uses CouchDB for document-oriented app metadata and revision history. This is by design and should not be swapped without understanding the migration path.

Should MinIO be exposed publicly?

No. Keep MinIO on the internal Docker network and let the app service communicate directly. Only Caddy should listen on public ports.

What is a safe upgrade process?

Pin image tags, back up before upgrading, test in staging, and verify health endpoints after each change.

Can we start with .env files and migrate to a secret manager later?

Yes. Begin with strict permissions, then migrate to a dedicated secret manager as operational maturity increases.

How many admins should the platform have?

Keep it minimal: typically two named maintainers and one break-glass account controlled by policy.

What should a handoff runbook include?

Startup commands, verification checklist, backup and restore procedures, escalation contacts, and rollback steps.

Can I use an external S3-compatible store instead of MinIO?

Yes. Update the MinIO URL and credentials environment variables to point at your external endpoint, ensuring the bucket and permissions are pre-configured.

Internal links

Talk to us

If you want this implemented with hardened defaults, observability, and tested recovery playbooks, our team can help.

Contact Us

Production Guide: Deploy Jitsi Meet with Docker Compose + Caddy on Ubuntu
Self-hosted video conferencing with Docker Compose, Caddy, and Jitsi Meet on Ubuntu.