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.
Prerequisites
Section titled “Prerequisites”- 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).
1. Quickstart compose file
Section titled “1. Quickstart compose file”The fastest path to a working instance — handy for kicking the tires on a fresh server before customizing.
Create a project directory:
mkdir confabulous && cd confabulousCreate docker-compose.yml:
# Caps each container's logs at 250 MB (5 × 50 MB) so they can't fill the# host disk. Referenced on every service via `*default-logging`.x-logging: &default-logging driver: json-file options: max-size: "50m" max-file: "5"
services: postgres: image: postgres:16-alpine restart: unless-stopped logging: *default-logging environment: POSTGRES_USER: confab POSTGRES_PASSWORD: confab POSTGRES_DB: confab volumes: - postgres_data:/var/lib/postgresql/data healthcheck: test: ["CMD-SHELL", "pg_isready -U confab"] interval: 5s timeout: 3s retries: 5
minio: image: minio/minio:latest restart: unless-stopped logging: *default-logging command: server /data --console-address ":9001" environment: MINIO_ROOT_USER: minioadmin MINIO_ROOT_PASSWORD: minioadmin volumes: - minio_data:/data healthcheck: test: ["CMD", "curl", "-f", "http://localhost:9000/minio/health/live"] interval: 5s timeout: 3s retries: 5
minio-setup: image: minio/mc:latest logging: *default-logging depends_on: minio: condition: service_healthy entrypoint: > /bin/sh -c " /usr/bin/mc alias set minio http://minio:9000 minioadmin minioadmin; /usr/bin/mc mb minio/confab --ignore-existing; exit 0; "
migrate: image: ghcr.io/confabulousdev/confab-web:latest logging: *default-logging depends_on: postgres: condition: service_healthy command: ["./migrate_db.sh"] environment: DATABASE_URL: postgres://confab:confab@postgres:5432/confab?sslmode=disable
app: image: ghcr.io/confabulousdev/confab-web:latest restart: unless-stopped logging: *default-logging depends_on: migrate: condition: service_completed_successfully minio-setup: condition: service_completed_successfully ports: - "127.0.0.1:8080:8080" environment: PORT: 8080 DATABASE_URL: postgres://confab:confab@postgres:5432/confab?sslmode=disable S3_ENDPOINT: minio:9000 S3_USE_SSL: "false" AWS_ACCESS_KEY_ID: minioadmin AWS_SECRET_ACCESS_KEY: minioadmin BUCKET_NAME: confab FRONTEND_URL: http://localhost:8080 BACKEND_URL: http://localhost:8080 ALLOWED_ORIGINS: http://localhost:8080 CSRF_SECRET_KEY: local-dev-csrf-secret-change-me-32chars AUTH_PASSWORD_ENABLED: "true" ADMIN_BOOTSTRAP_PASSWORD: localdevpassword ENABLE_SHARE_CREATION: "true" INSECURE_DEV_MODE: "true"
worker: image: ghcr.io/confabulousdev/confab-web:latest restart: unless-stopped logging: *default-logging command: ["./confab", "worker"] depends_on: migrate: condition: service_completed_successfully minio-setup: condition: service_completed_successfully environment: DATABASE_URL: postgres://confab:confab@postgres:5432/confab?sslmode=disable S3_ENDPOINT: minio:9000 S3_USE_SSL: "false" AWS_ACCESS_KEY_ID: minioadmin AWS_SECRET_ACCESS_KEY: minioadmin BUCKET_NAME: confab WORKER_POLL_INTERVAL: 1m WORKER_MAX_SESSIONS: "10"
volumes: postgres_data: minio_data:Start the stack:
docker compose up -dOpen the dashboard:
Visit http://localhost:8080 and log in with [email protected] / localdevpassword.
Connect the CLI:
curl -fsSL https://raw.githubusercontent.com/ConfabulousDev/confab/main/install.sh | bashconfab setup --backend-url http://localhost:8080Start a Claude Code or Codex session — it appears in the dashboard automatically.
2. Production setup
Section titled “2. Production setup”Once you’re ready to run on real infrastructure, customize the environment variables in your docker-compose.yml.
Generate secrets
Section titled “Generate secrets”Replace the placeholder CSRF key with a random value:
openssl rand -base64 32Set the result as CSRF_SECRET_KEY in the app service. Choose a strong admin password and update ADMIN_BOOTSTRAP_EMAIL and ADMIN_BOOTSTRAP_PASSWORD.
The bundled postgres and minio services still ship with their Quickstart defaults (confab / minioadmin). Both are only reachable on the docker network, so exposure is bounded — but default credentials are bad hygiene. Generate replacements:
openssl rand -base64 24Put them in a .env file next to docker-compose.yml:
POSTGRES_PASSWORD=<random>MINIO_ROOT_USER=<random>MINIO_ROOT_PASSWORD=<random>Reference each variable as ${VAR} wherever the literal appears in docker-compose.yml. For example:
# docker-compose.yml (excerpt)postgres: environment: POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}app: environment: DATABASE_URL: postgres://confab:${POSTGRES_PASSWORD}@postgres:5432/confab?sslmode=disableRepeat for MINIO_ROOT_USER and MINIO_ROOT_PASSWORD in the minio, minio-setup, app, and worker services.
Remove or set to "false":
INSECURE_DEV_MODE: "false"Set public URLs
Section titled “Set public URLs”Update the URL variables in the app service to your domain:
FRONTEND_URL: https://confab.example.comBACKEND_URL: https://confab.example.comALLOWED_ORIGINS: https://confab.example.comAll three are typically the same value. They may differ if you run the frontend and backend on separate domains.
External PostgreSQL (optional)
Section titled “External PostgreSQL (optional)”To use a managed database (AWS RDS, DigitalOcean, Supabase, etc.) instead of the bundled Postgres:
-
Update
DATABASE_URLin both theappandworkerservices:DATABASE_URL: postgres://user:password@db-host:5432/confab?sslmode=require -
Update
DATABASE_URLin themigrateservice to match (or useMIGRATE_DATABASE_URLfor a separate admin user). -
Remove the
postgresservice andpostgres_datavolume from the compose file.
External S3 storage (optional)
Section titled “External S3 storage (optional)”To use AWS S3, DigitalOcean Spaces, Wasabi, or another S3-compatible provider instead of MinIO:
-
Update the storage variables in both the
appandworkerservices:S3_ENDPOINT: s3.amazonaws.com # or your provider's endpointS3_USE_SSL: "true"AWS_ACCESS_KEY_ID: your-access-keyAWS_SECRET_ACCESS_KEY: your-secret-keyBUCKET_NAME: your-bucket-name -
Remove the
minio,minio-setupservices andminio_datavolume from the compose file.
3. HTTPS with Caddy
Section titled “3. HTTPS with Caddy”Caddy automatically provisions TLS certificates via Let’s Encrypt. Add it to your compose stack for zero-config HTTPS.
Add a Caddy service to your docker-compose.yml:
caddy: image: caddy:2-alpine restart: unless-stopped logging: *default-logging # defined in the Quickstart compose above ports: - "80:80" - "443:443" volumes: - ./Caddyfile:/etc/caddy/Caddyfile:ro - caddy_data:/data - caddy_config:/config - caddy_logs:/var/log/caddy depends_on: - appAdd the volumes to the volumes: section at the bottom:
volumes: postgres_data: minio_data: caddy_data: caddy_config: caddy_logs:Remove the port mapping from the app service (Caddy handles external traffic):
app: # Remove this line: # ports: # - "127.0.0.1:8080:8080"Create a Caddyfile in the same directory:
confab.example.com { reverse_proxy app:8080
log { output file /var/log/caddy/access.log { roll_size 50mb roll_keep 5 roll_keep_for 168h # 7 days } }
encode gzip zstd}Replace confab.example.com with your domain. The log block rotates access logs in the caddy_logs volume by size and age; encode gzip zstd enables response compression.
Update environment variables in the app service:
FRONTEND_URL: https://confab.example.comBACKEND_URL: https://confab.example.comALLOWED_ORIGINS: https://confab.example.comINSECURE_DEV_MODE: "false"Point your DNS A record to your server’s IP, then restart:
docker compose up -dCaddy will automatically obtain a TLS certificate for your domain.
4. Authentication
Section titled “4. Authentication”At least one authentication method must be enabled. You can enable multiple methods simultaneously.
Password auth
Section titled “Password auth”The simplest option — recommended for single-user or small-team deployments.
AUTH_PASSWORD_ENABLED: "true"ADMIN_BOOTSTRAP_PASSWORD: a-strong-passwordThe bootstrap credentials create an admin user on first startup when no users exist. Remove them from the compose file after initial setup.
GitHub OAuth
Section titled “GitHub OAuth”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
Add to the app service:
GITHUB_CLIENT_ID: your-client-idGITHUB_CLIENT_SECRET: your-client-secretGITHUB_REDIRECT_URL: https://confab.example.com/auth/github/callbackGoogle OAuth
Section titled “Google OAuth”Create OAuth credentials at console.cloud.google.com/apis/credentials:
- Authorized redirect URI:
https://confab.example.com/auth/google/callback
Add to the app service:
GOOGLE_CLIENT_ID: your-client-idGOOGLE_CLIENT_SECRET: your-client-secretGOOGLE_REDIRECT_URL: https://confab.example.com/auth/google/callbackGeneric OIDC
Section titled “Generic OIDC”Works with Keycloak, Okta, Auth0, Azure AD, and any OpenID Connect provider that supports OIDC Discovery (/.well-known/openid-configuration).
Add to the app service:
OIDC_ISSUER_URL: https://your-idp.example.comOIDC_CLIENT_ID: your-client-idOIDC_CLIENT_SECRET: your-client-secretOIDC_REDIRECT_URL: https://confab.example.com/auth/oidc/callbackOIDC_DISPLAY_NAME: SSO # Controls button text ("Continue with ...")All four variables (OIDC_ISSUER_URL, OIDC_CLIENT_ID, OIDC_CLIENT_SECRET, OIDC_REDIRECT_URL) must be set to enable OIDC.
5. Single-tenant / single-org lockdown
Section titled “5. Single-tenant / single-org lockdown”For an internal-only instance with no public signups, two variables lock the deployment down. Set both for a fully closed instance.
Restrict who can log in (applies to password, OAuth, and OIDC):
ALLOWED_EMAIL_DOMAINS: company.com,partner.comBlock new registrations (existing users keep working; new sign-ups are rejected):
MAX_USERS: "0"6. Team settings
Section titled “6. Team settings”| Variable | What it does |
|---|---|
SHARE_ALL_SESSIONS_TO_AUTHENTICATED | Set to "true" to make every session visible to all authenticated users. Useful for small teams that want full transparency. See Sharing. |
ENABLE_SHARE_CREATION | Set to "true" to allow users to create external share links. |
MAX_USERS | Maximum registered users (default 50). Set to "0" to block new registrations. |
SUPER_ADMIN_EMAILS | Comma-separated emails with access to the admin panel at /admin/users. |
ENABLE_ORG_ANALYTICS | Set 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 in backend/API.md for the privacy implications. |
7. Smart recaps (optional)
Section titled “7. Smart recaps (optional)”AI-powered session summaries using the Anthropic API. Requires an Anthropic API key.
Add to both the app and worker services:
SMART_RECAP_ENABLED: "true"ANTHROPIC_API_KEY: sk-ant-xxxxxxxxxxxxSMART_RECAP_MODEL: claude-haiku-4-5-20251001SMART_RECAP_QUOTA_LIMIT: "500" # Monthly generation limitThe worker service (already in the quickstart compose file) 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:
RESEND_API_KEY: re_xxxxxxxxxxxxSee Configuration for additional email settings (rate limits, display name, support email).
9. Upgrading
Section titled “9. Upgrading”When a new version is released:
# 1. Pull the latest imagesdocker compose pull
# 2. Run database migrationsdocker compose run --rm migrate
# 3. Restart services with the new imagesdocker compose up -dMigrations are idempotent — safe to run multiple times. The migrate service exits after completion.
10. Security checklist
Section titled “10. Security checklist”Before exposing your instance to the internet:
-
INSECURE_DEV_MODEis unset or"false". -
CSRF_SECRET_KEYis a unique random string of 32+ characters. -
POSTGRES_PASSWORDandMINIO_ROOT_USER/MINIO_ROOT_PASSWORDare random values, not the Quickstart defaults. -
ALLOWED_ORIGINScontains only your domain. - HTTPS is enforced (via Caddy or another reverse proxy).
- Bootstrap credentials (
ADMIN_BOOTSTRAP_*) are removed after setup. - Database uses SSL (
sslmode=requireinDATABASE_URL) if external. - OAuth secrets are production values, not development/test credentials.
For a comprehensive security review, see backend/SECURITY.md in the repo.
Troubleshooting
Section titled “Troubleshooting”CORS errors in the browser console
Section titled “CORS errors in the browser console”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 / MinIO connection errors
Section titled “S3 / MinIO connection errors”S3_ENDPOINTmust not includehttp://orhttps://— just the host and port (e.g.minio:9000).- Set
S3_USE_SSLto"false"for local MinIO,"true"for external providers. - Ensure the bucket exists. The
minio-setupservice creates it automatically for local MinIO.
”No authentication methods enabled”
Section titled “”No authentication methods enabled””At least one auth method must be configured. Set AUTH_PASSWORD_ENABLED: "true" or configure an OAuth/OIDC provider.
Cookies not persisting / login loop
Section titled “Cookies not persisting / login loop”Without HTTPS, you must set INSECURE_DEV_MODE: "true". In production, use HTTPS and ensure INSECURE_DEV_MODE is unset or "false".
Database connection refused
Section titled “Database connection refused”- Verify
DATABASE_URLis correct and the Postgres server is reachable from the Docker network. - If using the bundled Postgres, ensure the
postgresservice is healthy:docker compose ps.
Port 8080 already in use
Section titled “Port 8080 already in use”Change PORT in the app service and update the port mapping to match:
ports: - "127.0.0.1:3000:3000"environment: PORT: "3000"