Skip to content

Deployment walkthrough

This guide walks through deploying Confabulous step by step. For the full environment-variable reference, see Configuration. For real-world annotated configs, see Sample deployments.

  • Docker and Docker Compose v2+.
  • A server with at least 1 GB RAM and 1 CPU (VPS, home lab, cloud VM).
  • A domain name (optional but strongly recommended for HTTPS).

The repo ships a ready-to-run docker-compose.yml with safe localhost defaults, so the fastest path to a working instance is clone-and-run — handy for kicking the tires before customizing.

Clone the repo:

Terminal window
git clone https://github.com/ConfabulousDev/confab-web.git
cd confab-web

Start the stack:

Terminal window
docker compose up -d

This pulls the prebuilt image and starts the full stack — app, background worker, PostgreSQL, and MinIO — wiring up the database and storage bucket automatically.

Open the dashboard:

Visit http://localhost:8080 and log in with [email protected] / localdevpassword.

Connect the CLI:

Terminal window
curl -fsSL https://raw.githubusercontent.com/ConfabulousDev/confab/main/install.sh | bash
confab setup --backend-url http://localhost:8080

Start a Claude Code, Codex, or OpenCode session — it appears in the dashboard automatically.

The root docker-compose.yml reads every operator-facing value from a .env file next to it. You configure a real deployment by editing .env — you don’t edit the compose file.

Terminal window
cp .env.example .env

.env.example is organized by section (secrets, URLs, auth, team, smart recaps, email, …) with every variable documented. Uncomment and set what you need, then restart with docker compose up -d.

Terminal window
openssl rand -base64 32 # CSRF_SECRET_KEY (must be ≥ 32 chars)
openssl rand -base64 24 # each of POSTGRES_PASSWORD / MINIO_ROOT_USER / MINIO_ROOT_PASSWORD

Set them in .env:

Terminal window
CSRF_SECRET_KEY=<32+ char random>
POSTGRES_PASSWORD=<random>
MINIO_ROOT_USER=<random>
MINIO_ROOT_PASSWORD=<random>

These thread through every service automatically — the bundled Postgres and MinIO pick them up, and the app’s DATABASE_URL and S3 credentials are derived from them. The Quickstart defaults (confab / minioadmin) are only reachable on the Docker network, but default credentials are bad hygiene — replace them.

Terminal window
FRONTEND_URL=https://confab.example.com
BACKEND_URL=https://confab.example.com
ALLOWED_ORIGINS=https://confab.example.com
INSECURE_DEV_MODE=false

All three URLs are typically the same value. They may differ if you run the frontend and backend on separate domains.

Terminal window
ADMIN_BOOTSTRAP_EMAIL=[email protected]
ADMIN_BOOTSTRAP_PASSWORD=a-strong-password
SUPER_ADMIN_EMAILS=[email protected]

The bootstrap credentials create an admin user on first startup when no users exist.

To use a managed database (AWS RDS, DigitalOcean, Supabase, etc.) instead of the bundled Postgres:

  1. Set DATABASE_URL in .env (and MIGRATE_DATABASE_URL for a separate migration user):

    Terminal window
    DATABASE_URL=postgres://user:password@db-host:5432/confab?sslmode=require
  2. Remove the postgres service and postgres_data volume from docker-compose.yml.

To use AWS S3, DigitalOcean Spaces, Wasabi, or another S3-compatible provider instead of MinIO:

  1. Set the storage variables in .env:

    Terminal window
    S3_ENDPOINT=s3.amazonaws.com # or your provider's endpoint, no http(s):// prefix
    S3_USE_SSL=true
    AWS_ACCESS_KEY_ID=your-access-key
    AWS_SECRET_ACCESS_KEY=your-secret-key
    BUCKET_NAME=your-bucket-name
  2. Remove the minio, minio-setup services and minio_data volume from docker-compose.yml.

The compose file includes a Caddy reverse proxy behind a caddy profile. Caddy automatically provisions TLS certificates via Let’s Encrypt — no extra files to add, no port mappings to remove.

Set your domain in .env:

Terminal window
CONFAB_DOMAIN=confab.example.com
FRONTEND_URL=https://confab.example.com
BACKEND_URL=https://confab.example.com
ALLOWED_ORIGINS=https://confab.example.com
INSECURE_DEV_MODE=false

Point your DNS A record at your server’s IP, then start with the Caddy profile:

Terminal window
docker compose --profile caddy up -d

Caddy obtains a certificate for CONFAB_DOMAIN and reverse-proxies it to the app. The bundled Caddyfile handles TLS, gzip/zstd compression, and rotating access logs; edit it only if you need custom proxy behavior.

At least one authentication method must be enabled. You can enable multiple methods simultaneously. All of these go in .env.

The simplest option — recommended for single-user or small-team deployments. On by default (AUTH_PASSWORD_ENABLED=true).

Create an OAuth app at github.com/settings/developers:

  • Homepage URL: https://confab.example.com
  • Authorization callback URL: https://confab.example.com/auth/github/callback
Terminal window
GITHUB_CLIENT_ID=your-client-id
GITHUB_CLIENT_SECRET=your-client-secret
GITHUB_REDIRECT_URL=https://confab.example.com/auth/github/callback

Create OAuth credentials at console.cloud.google.com/apis/credentials:

  • Authorized redirect URI: https://confab.example.com/auth/google/callback
Terminal window
GOOGLE_CLIENT_ID=your-client-id
GOOGLE_CLIENT_SECRET=your-client-secret
GOOGLE_REDIRECT_URL=https://confab.example.com/auth/google/callback

Works with Keycloak, Okta, Auth0, Azure AD, and any OpenID Connect provider that supports OIDC Discovery (/.well-known/openid-configuration). All four variables must be set:

Terminal window
OIDC_ISSUER_URL=https://your-idp.example.com
OIDC_CLIENT_ID=your-client-id
OIDC_CLIENT_SECRET=your-client-secret
OIDC_REDIRECT_URL=https://confab.example.com/auth/oidc/callback
OIDC_DISPLAY_NAME=SSO # Controls button text ("Continue with ...")

For an internal-only instance with no public signups, two variables lock the deployment down. Set both in .env for a fully closed instance.

Restrict who can log in (applies to password, OAuth, and OIDC):

Terminal window
ALLOWED_EMAIL_DOMAINS=company.com,partner.com

Block new registrations (existing users keep working; new sign-ups are rejected):

Terminal window
MAX_USERS=0
VariableWhat it does
SHARE_ALL_SESSIONS_TO_AUTHENTICATEDSet to true to make every session visible to all authenticated users. Useful for small teams that want full transparency. See Sharing.
ENABLE_SHARE_CREATIONSet to true to allow users to create external share links.
MAX_USERSMaximum registered users (default 50). Set to 0 to block new registrations.
SUPER_ADMIN_EMAILSComma-separated emails with access to the admin panel at /admin/users.
ENABLE_ORG_ANALYTICSSet to true to expose org-wide per-user analytics (/admin/...) to every authenticated user — same visibility model as SHARE_ALL_SESSIONS_TO_AUTHENTICATED. See Organization analytics for the privacy implications.

AI-powered session summaries using the Anthropic API. Requires an Anthropic API key. Add to .env:

Terminal window
SMART_RECAP_ENABLED=true
ANTHROPIC_API_KEY=sk-ant-xxxxxxxxxxxx
SMART_RECAP_MODEL=claude-haiku-4-5-20251001
SMART_RECAP_QUOTA_LIMIT=500 # Monthly per-user generation limit

The bundled worker service precomputes recaps in the background. See Configuration for advanced worker tuning options.

8. Email (optional, for share invitations)

Section titled “8. Email (optional, for share invitations)”

Sign up at resend.com and add to .env:

Terminal window
RESEND_API_KEY=re_xxxxxxxxxxxx
EMAIL_FROM_ADDRESS=[email protected]

See Configuration for additional email settings (rate limits, display name, support email).

When a new version is released:

Terminal window
# 1. Pull the latest images
docker compose pull
# 2. Run database migrations
docker compose run --rm migrate
# 3. Restart services with the new images
docker compose up -d

Migrations are idempotent — safe to run multiple times. The migrate service exits after completion. If you run with HTTPS, keep --profile caddy on the up command.

Before exposing your instance to the internet:

  • INSECURE_DEV_MODE is false.
  • CSRF_SECRET_KEY is a unique random string of 32+ characters.
  • POSTGRES_PASSWORD and MINIO_ROOT_USER / MINIO_ROOT_PASSWORD are random values, not the Quickstart defaults.
  • ALLOWED_ORIGINS contains only your domain.
  • HTTPS is enforced (via the Caddy profile or another reverse proxy).
  • Bootstrap credentials (ADMIN_BOOTSTRAP_*) are removed after setup.
  • Database uses SSL (sslmode=require in DATABASE_URL) if external.
  • OAuth secrets are production values, not development/test credentials.

For a comprehensive security review, see backend/SECURITY.md in the repo.

ALLOWED_ORIGINS must exactly match the URL in your browser’s address bar, including the scheme (https://) and port (if non-standard). No trailing slash.

OAuth callback fails with “redirect URI mismatch”

Section titled “OAuth callback fails with “redirect URI mismatch””

The redirect URL in your OAuth provider’s settings must exactly match the environment variable (GITHUB_REDIRECT_URL, GOOGLE_REDIRECT_URL, or OIDC_REDIRECT_URL), including the scheme and path.

  • S3_ENDPOINT must not include http:// or https:// — just the host and port (e.g. minio:9000).
  • Set S3_USE_SSL to false for local MinIO, true for external providers.
  • Ensure the bucket exists. The minio-setup service creates it automatically for local MinIO.

At least one auth method must be configured. Set AUTH_PASSWORD_ENABLED=true or configure an OAuth/OIDC provider.

Server refuses to start (“insecure default in production mode”)

Section titled “Server refuses to start (“insecure default in production mode”)”

The instance detected production intent (an https:// URL, or INSECURE_DEV_MODE not true) while still using the template default CSRF_SECRET_KEY or ADMIN_BOOTSTRAP_PASSWORD. Set unique values for both — or, for local evaluation only, set INSECURE_DEV_MODE=true and use an http://localhost URL.

Without HTTPS, you must set INSECURE_DEV_MODE=true. In production, use HTTPS and ensure INSECURE_DEV_MODE is false.

  • Verify DATABASE_URL is correct and the Postgres server is reachable from the Docker network.
  • If using the bundled Postgres, ensure the postgres service is healthy: docker compose ps.

Set PORT in .env to a free port — the published localhost port follows it automatically.