# Canonical self-host deploy stack for Confabulous. # # Eval / quickstart: docker compose up -d → dashboard at :8080 # Production: edit .env, then # docker compose --profile caddy up -d → HTTPS via Caddy # # Every operator-facing value is ${VAR:-default}, so this file runs with NO .env # (safe localhost defaults) and is customized entirely through a `.env` file next # to it — copy .env.example to .env and edit. The long tail of optional settings # (OAuth, OIDC, smart recap, email, …) is passed through from .env automatically # via `env_file` below, so you never edit this file to enable a feature. # # Default admin login (eval only): admin@local.dev / localdevpassword # # For local SOURCE development (hot-reload backend/frontend) use the dev-infra # stack instead: `make up` (docker-compose.infra.yml). x-logging: &default-logging driver: json-file options: max-size: "50m" max-file: "5" # Optional .env overlay: passes any KEY=VALUE in ./.env into the app/worker # containers, so optional features can be enabled from .env without editing this # file. `required: false` keeps zero-config `docker compose up` working with no .env. x-env-file: &env-file - path: .env required: false services: postgres: image: postgres:16-alpine restart: unless-stopped logging: *default-logging environment: POSTGRES_USER: confab POSTGRES_PASSWORD: ${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: # MinIO community images are frozen (upstream stopped publishing), and # quay.io/minio/minio now requires auth even for the frozen tag (locked # down 2026-09-24, see kata z6ts + #411). Using the Bitnami Legacy mirror # on Docker Hub instead, which still pulls anonymously. That image drops # root by default and can't write to /data, so it runs as root (uid 0). image: bitnamilegacy/minio:2025.7.23-debian-12-r5 user: "0" restart: unless-stopped logging: *default-logging command: server /data --console-address ":9001" environment: MINIO_ROOT_USER: ${MINIO_ROOT_USER:-minioadmin} MINIO_ROOT_PASSWORD: ${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: bitnamilegacy/minio:2025.7.23-debian-12-r5 logging: *default-logging depends_on: minio: condition: service_healthy entrypoint: > /bin/sh -c " mc alias set minio http://minio:9000 ${MINIO_ROOT_USER:-minioadmin} ${MINIO_ROOT_PASSWORD:-minioadmin}; mc mb minio/${BUCKET_NAME:-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: ${MIGRATE_DATABASE_URL:-${DATABASE_URL:-postgres://confab:${POSTGRES_PASSWORD:-confab}@postgres:5432/confab?sslmode=disable}} app: image: ghcr.io/confabulousdev/confab-web:latest restart: unless-stopped logging: *default-logging env_file: *env-file depends_on: migrate: condition: service_completed_successfully minio-setup: condition: service_completed_successfully ports: # Localhost-only by default: not reachable from the network without a # reverse proxy (see the `caddy` profile) or an explicit change. - "127.0.0.1:${PORT:-8080}:8080" environment: PORT: "8080" DATABASE_URL: ${DATABASE_URL:-postgres://confab:${POSTGRES_PASSWORD:-confab}@postgres:5432/confab?sslmode=disable} S3_ENDPOINT: ${S3_ENDPOINT:-minio:9000} S3_USE_SSL: "${S3_USE_SSL:-false}" AWS_ACCESS_KEY_ID: ${AWS_ACCESS_KEY_ID:-${MINIO_ROOT_USER:-minioadmin}} AWS_SECRET_ACCESS_KEY: ${AWS_SECRET_ACCESS_KEY:-${MINIO_ROOT_PASSWORD:-minioadmin}} BUCKET_NAME: ${BUCKET_NAME:-confab} FRONTEND_URL: ${FRONTEND_URL:-http://localhost:8080} BACKEND_URL: ${BACKEND_URL:-http://localhost:8080} ALLOWED_ORIGINS: ${ALLOWED_ORIGINS:-http://localhost:8080} CSRF_SECRET_KEY: ${CSRF_SECRET_KEY:-local-dev-csrf-secret-change-me-32chars} INSECURE_DEV_MODE: "${INSECURE_DEV_MODE:-true}" AUTH_PASSWORD_ENABLED: "${AUTH_PASSWORD_ENABLED:-true}" ADMIN_BOOTSTRAP_EMAIL: ${ADMIN_BOOTSTRAP_EMAIL:-admin@local.dev} ADMIN_BOOTSTRAP_PASSWORD: ${ADMIN_BOOTSTRAP_PASSWORD:-localdevpassword} SUPER_ADMIN_EMAILS: ${SUPER_ADMIN_EMAILS:-admin@local.dev} ENABLE_SHARE_CREATION: "${ENABLE_SHARE_CREATION:-true}" worker: image: ghcr.io/confabulousdev/confab-web:latest restart: unless-stopped logging: *default-logging command: ["./confab", "worker"] env_file: *env-file depends_on: migrate: condition: service_completed_successfully minio-setup: condition: service_completed_successfully environment: DATABASE_URL: ${DATABASE_URL:-postgres://confab:${POSTGRES_PASSWORD:-confab}@postgres:5432/confab?sslmode=disable} S3_ENDPOINT: ${S3_ENDPOINT:-minio:9000} S3_USE_SSL: "${S3_USE_SSL:-false}" AWS_ACCESS_KEY_ID: ${AWS_ACCESS_KEY_ID:-${MINIO_ROOT_USER:-minioadmin}} AWS_SECRET_ACCESS_KEY: ${AWS_SECRET_ACCESS_KEY:-${MINIO_ROOT_PASSWORD:-minioadmin}} BUCKET_NAME: ${BUCKET_NAME:-confab} WORKER_POLL_INTERVAL: ${WORKER_POLL_INTERVAL:-1m} WORKER_MAX_SESSIONS: "${WORKER_MAX_SESSIONS:-10}" # HTTPS via Caddy (Let's Encrypt). Opt-in: `docker compose --profile caddy up -d`. # Requires CONFAB_DOMAIN in .env and an A record pointing at this host. caddy: image: caddy:2-alpine restart: unless-stopped logging: *default-logging profiles: [caddy] ports: - "80:80" - "443:443" environment: CONFAB_DOMAIN: ${CONFAB_DOMAIN:-localhost} volumes: - ./Caddyfile:/etc/caddy/Caddyfile:ro - caddy_data:/data - caddy_config:/config - caddy_logs:/var/log/caddy depends_on: - app volumes: postgres_data: minio_data: caddy_data: caddy_config: caddy_logs: