services: # PostgreSQL must load the vector library so Hub (and Formbricks) can use the pgvector extension. postgres: image: pgvector/pgvector:pg18 volumes: - postgres:/var/lib/postgresql environment: - POSTGRES_DB=postgres - POSTGRES_USER=postgres - POSTGRES_PASSWORD=postgres ports: - 5432:5432 command: > postgres -c shared_preload_libraries=vector healthcheck: test: ["CMD-SHELL", "pg_isready -U postgres -d postgres || exit 1"] interval: 5s timeout: 3s retries: 30 start_period: 10s mailhog: image: arjenz/mailhog ports: - 8025:8025 - 1025:1025 valkey: image: valkey/valkey@sha256:12ba4f45a7c3e1d0f076acd616cb230834e75a77e8516dde382720af32832d6d command: "valkey-server --maxmemory-policy noeviction" ports: - 6379:6379 volumes: - valkey-data:/data rustfs-perms: image: busybox:1.36.1 user: "0:0" command: ["sh", "-c", "mkdir -p /data && chown -R 10001:10001 /data"] volumes: - rustfs-data:/data rustfs: image: rustfs/rustfs:1.0.0-alpha.93 depends_on: rustfs-perms: condition: service_completed_successfully command: /data environment: - RUSTFS_ACCESS_KEY=devrustfs - RUSTFS_SECRET_KEY=devrustfs123 - RUSTFS_ADDRESS=:9000 - RUSTFS_CONSOLE_ENABLE=true - RUSTFS_CONSOLE_ADDRESS=:9001 ports: - "9000:9000" # S3 API direct access - "9001:9001" # Web console volumes: - rustfs-data:/data rustfs-init: image: minio/mc@sha256:95b5f3f7969a5c5a9f3a700ba72d5c84172819e13385aaf916e237cf111ab868 depends_on: - rustfs environment: - RUSTFS_ADMIN_USER=devrustfs - RUSTFS_ADMIN_PASSWORD=devrustfs123 - RUSTFS_BUCKET_NAME=formbricks - RUSTFS_POLICY_NAME=formbricks-policy - RUSTFS_SERVICE_USER=devrustfs-service - RUSTFS_SERVICE_PASSWORD=devrustfs-service123 - RUSTFS_CORS_ALLOWED_ORIGINS=http://localhost:3000,http://127.0.0.1:3000,http://localhost:3001,http://127.0.0.1:3001,http://localhost:3002,http://127.0.0.1:3002 volumes: - ./docker/rustfs-init.sh:/usr/local/bin/rustfs-init.sh:ro entrypoint: - /bin/sh - /usr/local/bin/rustfs-init.sh # Create Hub's dedicated dev database before migrations run. Idempotent; runs on every compose up. hub-db-create: image: pgvector/pgvector:pg18 restart: "no" entrypoint: ["sh", "-c"] command: [ 'if [ "$$(psql "$$POSTGRES_DATABASE_URL" -tAc "SELECT 1 FROM pg_database WHERE datname = ''hub''")" != "1" ]; then psql "$$POSTGRES_DATABASE_URL" -v ON_ERROR_STOP=1 -c "CREATE DATABASE hub"; fi', ] environment: POSTGRES_DATABASE_URL: postgresql://postgres:postgres@postgres:5432/postgres?sslmode=disable depends_on: postgres: condition: service_healthy # Run Hub DB migrations (goose + river) before the API starts. Idempotent; runs on every compose up. # Hub image pinned via HUB_IMAGE_TAG so docker does not silently reuse a stale :latest cache. # Keep hub, hub-migrate, and any future hub-worker on the same tag — they share one image and # drift breaks migrations or job processing. hub-migrate: image: ghcr.io/formbricks/hub:${HUB_IMAGE_TAG:-0.3.0} restart: "no" entrypoint: ["sh", "-c"] command: [ 'if [ -x /usr/local/bin/goose ] && [ -x /usr/local/bin/river ]; then /usr/local/bin/goose -dir /app/migrations postgres "$$DATABASE_URL" up && /usr/local/bin/river migrate-up --database-url "$$DATABASE_URL"; else echo ''Migration tools (goose/river) not in image.''; exit 1; fi', ] environment: DATABASE_URL: ${HUB_DATABASE_URL:-postgresql://postgres:postgres@postgres:5432/hub?sslmode=disable} depends_on: hub-db-create: condition: service_completed_successfully # Formbricks Hub API (ghcr.io/formbricks/hub). Uses a dedicated local Hub database by default. hub: image: ghcr.io/formbricks/hub:${HUB_IMAGE_TAG:-0.3.0} depends_on: hub-migrate: condition: service_completed_successfully ports: - "8080:8080" environment: &hub-runtime-environment API_KEY: ${HUB_API_KEY:-dev-api-key} DATABASE_URL: ${HUB_DATABASE_URL:-postgresql://postgres:postgres@postgres:5432/hub?sslmode=disable} # Explicit Postgres env so migrations and any libpq fallback use the service host, not localhost PGHOST: postgres PGPORT: "5432" PGUSER: postgres PGPASSWORD: postgres PGDATABASE: hub PGSSLMODE: disable # Hub embeddings are disabled unless EMBEDDING_PROVIDER and EMBEDDING_MODEL are set. EMBEDDING_PROVIDER: ${EMBEDDING_PROVIDER:-} EMBEDDING_MODEL: ${EMBEDDING_MODEL:-} EMBEDDING_PROVIDER_API_KEY: ${EMBEDDING_PROVIDER_API_KEY:-} EMBEDDING_BASE_URL: ${EMBEDDING_BASE_URL:-} EMBEDDING_MAX_CONCURRENT: ${EMBEDDING_MAX_CONCURRENT:-5} EMBEDDING_MAX_ATTEMPTS: ${EMBEDDING_MAX_ATTEMPTS:-3} EMBEDDING_NORMALIZE: ${EMBEDDING_NORMALIZE:-false} EMBEDDING_GOOGLE_CLOUD_PROJECT: ${EMBEDDING_GOOGLE_CLOUD_PROJECT:-} EMBEDDING_GOOGLE_CLOUD_LOCATION: ${EMBEDDING_GOOGLE_CLOUD_LOCATION:-} GOOGLE_APPLICATION_CREDENTIALS: ${GOOGLE_APPLICATION_CREDENTIALS:-} # Hub worker processes async jobs enqueued by the API, including embeddings. hub-worker: image: ghcr.io/formbricks/hub:${HUB_IMAGE_TAG:-0.3.0} depends_on: hub-migrate: condition: service_completed_successfully postgres: condition: service_healthy entrypoint: ["/app/hub-worker"] healthcheck: disable: true environment: <<: *hub-runtime-environment cube: profiles: ["xm"] image: cubejs/cube:v1.6.6 env_file: - apps/web/.env depends_on: postgres: condition: service_healthy hub-migrate: condition: service_completed_successfully ports: - 4000:4000 - 4001:4001 # Cube Playground UI (dev only) environment: CUBEJS_DB_TYPE: postgres CUBEJS_DB_HOST: ${CUBEJS_DB_HOST:-postgres} CUBEJS_DB_NAME: ${CUBEJS_DB_NAME:-postgres} CUBEJS_DB_USER: ${CUBEJS_DB_USER:-postgres} CUBEJS_DB_PASS: ${CUBEJS_DB_PASS:-postgres} CUBEJS_DB_PORT: ${CUBEJS_DB_PORT:-5432} CUBEJS_DEV_MODE: "true" CUBEJS_API_SECRET: ${CUBEJS_API_SECRET:-} CUBEJS_JWT_ISSUER: ${CUBEJS_JWT_ISSUER:-formbricks-web} CUBEJS_JWT_AUDIENCE: ${CUBEJS_JWT_AUDIENCE:-formbricks-cube} CUBEJS_DEFAULT_API_SCOPES: meta,data CUBEJS_CACHE_AND_QUEUE_DRIVER: memory volumes: - ./docker/cube/cube.js:/cube/conf/cube.js:ro - ./docker/cube/schema:/cube/conf/model:ro restart: on-failure volumes: postgres: driver: local valkey-data: driver: local rustfs-data: driver: local