version: '3.8' services: server: extends: file: ./server/docker-compose.yaml service: server container_name: ${APP_NAME:-sebastian}_server_ee build: context: . dockerfile: Dockerfile.dev args: INCLUDE_EE: "true" environment: EDITION: enterprise DB_NAME: server PGBOSS_DATABASE: server DB_NAME_SERVER: server DB_USER_SERVER: app_user DB_USER_ADMIN: ${DB_USER_ADMIN:-postgres} VERSION: ${VERSION} APP_NAME: ${APP_NAME} APP_ENV: ${APP_ENV:-development} NODE_ENV: ${APP_ENV:-development} APP_EDITION: ${APP_EDITION:-enterprise} NEXT_PUBLIC_EDITION: ${NEXT_PUBLIC_EDITION:-enterprise} HOST: ${HOST} VERIFY_EMAIL_ENABLED: ${VERIFY_EMAIL_ENABLED:-false} REDIS_HOST: ${REDIS_HOST_DOCKER:-redis} REDIS_PORT: ${REDIS_PORT:-6379} DB_TYPE: ${DB_TYPE:-postgres} DB_HOST: ${PGBOUNCER_HOST_DOCKER:-pgbouncer} DB_PORT: ${PGBOUNCER_PORT:-6432} REQUIRE_HOCUSPOCUS: ${REQUIRE_HOCUSPOCUS:-false} LOG_LEVEL: ${LOG_LEVEL:-INFO} LOG_IS_FORMAT_JSON: ${LOG_IS_FORMAT_JSON:-false} LOG_IS_FULL_DETAILS: ${LOG_IS_FULL_DETAILS:-false} EMAIL_ENABLE: ${EMAIL_ENABLE:-false} EMAIL_FROM: ${EMAIL_FROM:-noreply@example.com} EMAIL_PORT: ${EMAIL_PORT:-587} EMAIL_USERNAME: ${EMAIL_USERNAME:-noreply@example.com} EMAIL_PROVIDER_TYPE: ${EMAIL_PROVIDER_TYPE:-smtp} RESEND_API_KEY: ${RESEND_API_KEY:-} OPENROUTER_API_KEY: ${OPENROUTER_API_KEY:-} OPENROUTER_API: ${OPENROUTER_API:-} NEXTAUTH_URL: ${NEXTAUTH_URL:-http://localhost:3000} NEXTAUTH_SECRET: ${NEXTAUTH_SECRET} NEXTAUTH_SESSION_EXPIRES: ${NEXTAUTH_SESSION_EXPIRES:-86400} COLLAB_PERSIST_API_KEY: ${COLLAB_PERSIST_API_KEY:-alga-collab-persist-dev} TEMPORAL_ADDRESS: ${TEMPORAL_ADDRESS:-temporal-dev:7233} TEMPORAL_NAMESPACE: ${TEMPORAL_NAMESPACE:-default} TEMPORAL_JOB_TASK_QUEUE: ${TEMPORAL_JOB_TASK_QUEUE:-alga-jobs} # Secret provider configuration (EE edition - for local dev, vault is disabled by default) # For production with vault, set SECRET_READ_CHAIN=env,filesystem,vault in your environment SECRET_READ_CHAIN: ${SECRET_READ_CHAIN:-env,filesystem} SECRET_WRITE_PROVIDER: ${SECRET_WRITE_PROVIDER:-filesystem} # Workflow configuration WORKFLOW_DISTRIBUTED_MODE: "true" WORKFLOW_REDIS_STREAM_PREFIX: "workflow:events:" WORKFLOW_REDIS_CONSUMER_GROUP: "workflow-workers" IMAP_WEBHOOK_SECRET: ${IMAP_WEBHOOK_SECRET:-} # NinjaOne Integration Configuration # These can be set via environment variables OR via secrets files (mounted at /run/secrets/) # The secret provider will check filesystem secrets first (if SECRET_READ_CHAIN includes 'filesystem') NINJAONE_CLIENT_ID: ${NINJAONE_CLIENT_ID:-} NINJAONE_CLIENT_SECRET: ${NINJAONE_CLIENT_SECRET:-} NINJAONE_REDIRECT_URI: ${NINJAONE_REDIRECT_URI:-} # Extension runner (Docker backend) + bundle storage (MinIO dev) RUNNER_BACKEND: ${RUNNER_BACKEND:-docker} RUNNER_BASE_URL: ${RUNNER_BASE_URL:-http://host.docker.internal:8085} RUNNER_DOCKER_HOST: ${RUNNER_DOCKER_HOST:-http://host.docker.internal:8085} RUNNER_PUBLIC_BASE: ${RUNNER_PUBLIC_BASE:-/runner} RUNNER_SERVICE_TOKEN: ${RUNNER_SERVICE_TOKEN:-local-runner-key} RUNNER_STORAGE_API_TOKEN: ${RUNNER_STORAGE_API_TOKEN:-local-runner-key} RUNNER_CONFIG_API_TOKEN: ${RUNNER_CONFIG_API_TOKEN:-local-runner-key} STORAGE_S3_ENDPOINT: ${STORAGE_S3_ENDPOINT:-http://host.docker.internal:9000} STORAGE_S3_REGION: ${STORAGE_S3_REGION:-us-east-1} STORAGE_S3_ACCESS_KEY: ${STORAGE_S3_ACCESS_KEY:-minioadmin} STORAGE_S3_SECRET_KEY: ${STORAGE_S3_SECRET_KEY:-minioadmin} STORAGE_S3_BUCKET: ${STORAGE_S3_BUCKET:-extensions} STORAGE_S3_BUNDLE_BUCKET: ${STORAGE_S3_BUNDLE_BUCKET:-extensions} STORAGE_S3_FORCE_PATH_STYLE: ${STORAGE_S3_FORCE_PATH_STYLE:-true} volumes: - type: bind source: ./secrets/db_password_server target: /run/secrets/db_password_server read_only: true # Mount ngrok volume to access ngrok URL for OAuth redirects and webhooks - ngrok-data:/app/ngrok:ro secrets: - postgres_password - db_password_server - db_password_hocuspocus - redis_password - email_password - crypto_key - token_secret_key - nextauth_secret - google_oauth_client_id - google_oauth_client_secret - alga_auth_key - ninjaone_client_id - ninjaone_client_secret networks: - app-network depends_on: postgres: condition: service_started pgbouncer: condition: service_started redis: condition: service_started hocuspocus: condition: service_started setup: condition: service_completed_successfully setup: build: context: . dockerfile: ee/setup/Dockerfile.dev container_name: ${APP_NAME:-sebastian}_setup_ee environment: EDITION: enterprise NODE_OPTIONS: --experimental-vm-modules DB_NAME_SERVER: server DB_USER_SERVER: app_user DB_USER_ADMIN: ${DB_USER_ADMIN:-postgres} PGBOSS_DATABASE: server VERSION: ${VERSION} APP_NAME: ${APP_NAME} APP_ENV: ${APP_ENV:-development} NODE_ENV: ${APP_ENV:-development} HOST: ${HOST} DB_TYPE: ${DB_TYPE:-postgres} DB_HOST: ${PGBOUNCER_HOST_DOCKER:-pgbouncer} DB_PORT: ${PGBOUNCER_PORT:-6432} LOG_LEVEL: ${LOG_LEVEL:-INFO} LOG_IS_FORMAT_JSON: ${LOG_IS_FORMAT_JSON:-false} LOG_IS_FULL_DETAILS: ${LOG_IS_FULL_DETAILS:-false} EMAIL_ENABLE: ${EMAIL_ENABLE:-false} EMAIL_FROM: ${EMAIL_FROM:-noreply@example.com} EMAIL_PORT: ${EMAIL_PORT:-587} EMAIL_USERNAME: ${EMAIL_USERNAME:-noreply@example.com} EMAIL_PROVIDER_TYPE: ${EMAIL_PROVIDER_TYPE:-smtp} RESEND_API_KEY: ${RESEND_API_KEY:-} NEXTAUTH_URL: ${NEXTAUTH_URL:-http://localhost:3000} NEXTAUTH_SESSION_EXPIRES: ${NEXTAUTH_SESSION_EXPIRES:-86400} # Secret provider configuration for setup (EE edition - for local dev, vault is disabled by default) # For production with vault, set SECRET_READ_CHAIN=env,filesystem,vault in your environment SECRET_READ_CHAIN: ${SECRET_READ_CHAIN:-env,filesystem} SECRET_WRITE_PROVIDER: ${SECRET_WRITE_PROVIDER:-filesystem} volumes: - type: bind source: ./setup/config.ini target: /opt/setup/config.ini read_only: true - type: bind source: ./ee/setup/entrypoint.sh target: /opt/setup/ee-entrypoint.sh read_only: true - type: bind source: ./secrets/postgres_password target: /run/secrets/postgres_password read_only: true - type: bind source: ./secrets/db_password_server target: /run/secrets/db_password_server read_only: true # Mount migrations/seeds from the current worktree so setup stays in sync with branch changes. # These are merged by /opt/setup/ee-entrypoint.sh into /app/server/migrations and /app/server/seeds. - type: bind source: ./server/migrations target: /app/server/migrations-ce read_only: true - type: bind source: ./ee/server/migrations target: /app/server/migrations-ee read_only: true - type: bind source: ./server/seeds target: /app/server/seeds-ce read_only: true - type: bind source: ./ee/server/seeds target: /app/server/seeds-ee read_only: true secrets: - postgres_password - db_password_server networks: - app-network depends_on: postgres: condition: service_started pgbouncer: condition: service_started entrypoint: ["/opt/setup/ee-entrypoint.sh"] workflow-worker: build: context: . dockerfile: services/workflow-worker/Dockerfile environment: EDITION: enterprise DB_NAME: server PGBOSS_DATABASE: server DB_NAME_SERVER: server DB_USER_SERVER: app_user DB_USER_ADMIN: ${DB_USER_ADMIN:-postgres} VERSION: ${VERSION} APP_NAME: ${APP_NAME} APP_ENV: ${APP_ENV:-development} NODE_ENV: ${APP_ENV:-development} HOST: ${HOST} REDIS_HOST: ${REDIS_HOST_DOCKER:-redis} REDIS_PORT: ${REDIS_PORT:-6379} DB_TYPE: ${DB_TYPE:-postgres} DB_HOST: ${PGBOUNCER_HOST_DOCKER:-pgbouncer} DB_PORT: ${PGBOUNCER_PORT:-6432} LOG_LEVEL: ${LOG_LEVEL:-INFO} LOG_IS_FORMAT_JSON: ${LOG_IS_FORMAT_JSON:-false} LOG_IS_FULL_DETAILS: ${LOG_IS_FULL_DETAILS:-false} # Secret provider configuration for workflow-worker (EE edition - for local dev, vault is disabled by default) # For production with vault, set SECRET_READ_CHAIN=env,filesystem,vault in your environment SECRET_READ_CHAIN: ${SECRET_READ_CHAIN:-env,filesystem} SECRET_WRITE_PROVIDER: ${SECRET_WRITE_PROVIDER:-filesystem} # Workflow-specific configuration # Run v2 runtime by default (legacy can be re-enabled via WORKFLOW_WORKER_MODE=all|legacy) WORKFLOW_WORKER_MODE: ${WORKFLOW_WORKER_MODE:-v2} WORKFLOW_DISTRIBUTED_MODE: "true" WORKFLOW_REDIS_STREAM_PREFIX: "workflow:events:" WORKFLOW_REDIS_CONSUMER_GROUP: "workflow-workers" WORKFLOW_REDIS_BATCH_SIZE: "10" WORKFLOW_REDIS_IDLE_TIMEOUT_MS: "60000" TEMPORAL_ADDRESS: ${TEMPORAL_ADDRESS:-temporal-dev:7233} TEMPORAL_NAMESPACE: ${TEMPORAL_NAMESPACE:-default} TEMPORAL_JOB_TASK_QUEUE: ${TEMPORAL_JOB_TASK_QUEUE:-alga-jobs} APPLICATION_URL: ${APPLICATION_URL:-http://localhost:3000} NEXTAUTH_URL: ${NEXTAUTH_URL:-http://localhost:3000} NEXTAUTH_SECRET: ${NEXTAUTH_SECRET:-local-nextauth-secret} ALGA_AUTH_KEY: ${ALGA_AUTH_KEY:-local-alga-auth-key} # WorkerServer listens on PORT (defaults to 4000) PORT: "4000" ports: # Expose a random port for health checks/monitoring - "4000" volumes: - type: bind source: ./secrets/db_password_server target: /run/secrets/db_password_server read_only: true - type: bind source: ./services/workflow-worker/entrypoint.sh target: /app/entrypoint.sh read_only: true entrypoint: ["/app/entrypoint.sh"] secrets: - postgres_password - db_password_server - redis_password - crypto_key - token_secret_key - nextauth_secret networks: - app-network depends_on: postgres: condition: service_started pgbouncer: condition: service_started redis: condition: service_started server: condition: service_started temporal-dev: condition: service_started # Enable scaling of worker instances deploy: replicas: ${WORKFLOW_WORKER_REPLICAS:-1} email-service: build: context: . dockerfile: services/email-service/Dockerfile environment: EDITION: enterprise DB_NAME: server DB_NAME_SERVER: server DB_USER_SERVER: app_user DB_USER_ADMIN: ${DB_USER_ADMIN:-postgres} VERSION: ${VERSION} APP_NAME: ${APP_NAME} APP_ENV: ${APP_ENV:-development} NODE_ENV: ${APP_ENV:-development} HOST: ${HOST} REDIS_HOST: ${REDIS_HOST:-redis} REDIS_PORT: ${REDIS_PORT:-6379} DB_TYPE: ${DB_TYPE:-postgres} DB_HOST: ${PGBOUNCER_HOST:-pgbouncer} DB_PORT: ${PGBOUNCER_PORT:-6432} LOG_LEVEL: ${LOG_LEVEL:-INFO} LOG_IS_FORMAT_JSON: ${LOG_IS_FORMAT_JSON:-false} LOG_IS_FULL_DETAILS: ${LOG_IS_FULL_DETAILS:-false} SECRET_READ_CHAIN: ${SECRET_READ_CHAIN:-env,filesystem} SECRET_WRITE_PROVIDER: ${SECRET_WRITE_PROVIDER:-filesystem} IMAP_PROVIDER_REFRESH_MS: ${IMAP_PROVIDER_REFRESH_MS:-60000} IMAP_POLL_INTERVAL_MS: ${IMAP_POLL_INTERVAL_MS:-30000} IMAP_LEASE_TTL_MS: ${IMAP_LEASE_TTL_MS:-120000} IMAP_MAX_CONNECTIONS_PER_TENANT: ${IMAP_MAX_CONNECTIONS_PER_TENANT:-5} IMAP_MAX_ATTACHMENT_BYTES: ${IMAP_MAX_ATTACHMENT_BYTES:-0} IMAP_FETCH_DELAY_MS: ${IMAP_FETCH_DELAY_MS:-0} IMAP_EVENT_CHANNEL_BY_TENANT: ${IMAP_EVENT_CHANNEL_BY_TENANT:-false} IMAP_OAUTH_AUTH_MECHANISM: ${IMAP_OAUTH_AUTH_MECHANISM:-XOAUTH2} IMAP_TLS_REJECT_UNAUTHORIZED: ${IMAP_TLS_REJECT_UNAUTHORIZED:-true} IMAP_WEBHOOK_URL: ${IMAP_WEBHOOK_URL:-http://server:3000/api/email/webhooks/imap} IMAP_WEBHOOK_TIMEOUT_MS: ${IMAP_WEBHOOK_TIMEOUT_MS:-10000} IMAP_WEBHOOK_MAX_ATTEMPTS: ${IMAP_WEBHOOK_MAX_ATTEMPTS:-3} IMAP_WEBHOOK_SECRET: ${IMAP_WEBHOOK_SECRET:-} volumes: - type: bind source: ./secrets/db_password_server target: /run/secrets/db_password_server read_only: true - type: bind source: ./secrets/tenants target: /run/secrets/tenants - type: bind source: ./services/email-service/entrypoint.sh target: /app/entrypoint.sh read_only: true entrypoint: ["/app/entrypoint.sh"] secrets: - postgres_password - db_password_server - redis_password - crypto_key - token_secret_key - nextauth_secret networks: - app-network depends_on: postgres: condition: service_started pgbouncer: condition: service_started redis: condition: service_started server: condition: service_started hocuspocus: extends: file: ./hocuspocus/docker-compose.yaml service: hocuspocus container_name: ${APP_NAME:-sebastian}_hocuspocus_ee build: context: . dockerfile: hocuspocus/Dockerfile environment: VERSION: ${VERSION} APP_NAME: ${APP_NAME} APP_ENV: ${APP_ENV:-development} NODE_ENV: ${APP_ENV:-development} HOST: ${HOST} REDIS_HOST: ${REDIS_HOST_DOCKER:-redis} REDIS_PORT: ${REDIS_PORT:-6379} DB_TYPE: ${DB_TYPE:-postgres} DB_HOST: ${DB_HOST_DOCKER:-postgres} DB_PORT: ${DB_PORT:-5432} AI_DOCUMENT_API_URL: ${AI_DOCUMENT_API_URL:-http://server:3000/api/v1/ai/document-assist} AI_DOCUMENT_API_KEY: ${AI_DOCUMENT_API_KEY:-} COLLAB_PERSIST_API_URL: ${COLLAB_PERSIST_API_URL:-http://server:3000/api/internal/collab/persist} COLLAB_PERSIST_API_KEY: ${COLLAB_PERSIST_API_KEY:-alga-collab-persist-dev} volumes: - type: bind source: ./secrets/db_password_server target: /run/secrets/db_password_hocuspocus read_only: true secrets: - postgres_password - db_password_server - redis_password networks: - app-network depends_on: redis: condition: service_started postgres: extends: file: docker-compose.base.yaml service: postgres environment: POSTGRES_DB: server VERSION: ${VERSION} APP_NAME: ${APP_NAME} APP_ENV: ${APP_ENV:-development} NODE_ENV: ${APP_ENV:-development} HOST: ${HOST} DB_TYPE: ${DB_TYPE:-postgres} DB_HOST: postgres DB_PORT: ${DB_PORT:-5432} secrets: - postgres_password redis: extends: file: docker-compose.base.yaml service: redis pgbouncer: extends: file: docker-compose.base.yaml service: pgbouncer temporal-dev: image: temporalio/auto-setup:1.24.2 ports: - "${EXPOSE_TEMPORAL_PORT:-7233}:7233" environment: - DB=postgres12 - DB_PORT=5432 - POSTGRES_SEEDS=postgres - POSTGRES_USER=postgres entrypoint: ["/bin/sh"] command: - -c - | export POSTGRES_PWD=$(cat /run/secrets/postgres_password) exec /etc/temporal/entrypoint.sh autosetup secrets: - postgres_password depends_on: postgres: condition: service_started networks: - app-network temporal-worker: image: temporal-worker-dev build: context: . dockerfile: ee/temporal-workflows/Dockerfile target: production working_dir: /app/ee/temporal-workflows entrypoint: ["docker-entrypoint.sh"] command: ["node", "dist/ee/temporal-workflows/src/worker.js"] environment: - LOG_LEVEL=INFO - SECRET_READ_CHAIN=filesystem,env - APPLICATION_URL=${APPLICATION_URL:-http://localhost:3000} - NEXTAUTH_URL=${NEXTAUTH_URL:-http://localhost:3000} - NEXTAUTH_SECRET=${NEXTAUTH_SECRET:-local-nextauth-secret} - ALGA_AUTH_KEY=${ALGA_AUTH_KEY:-local-alga-auth-key} - DB_HOST=${DB_HOST:-pgbouncer} - DB_PORT=${DB_PORT:-6432} - DB_NAME_SERVER=${DB_NAME_SERVER:-server} - DB_USER_SERVER=${DB_USER_SERVER:-app_user} - DB_USER_ADMIN=${DB_USER_ADMIN:-postgres} - TEMPORAL_ADDRESS=${TEMPORAL_ADDRESS:-temporal-dev:7233} - TEMPORAL_NAMESPACE=${TEMPORAL_NAMESPACE:-default} - TEMPORAL_TASK_QUEUE=${TEMPORAL_JOB_TASK_QUEUE:-alga-jobs} # Authored runtime queue ownership belongs to workflow-worker. - TEMPORAL_TASK_QUEUES=tenant-workflows,portal-domain-workflows,email-domain-workflows,alga-jobs - PORTAL_DOMAIN_BASE_VIRTUAL_SERVICE=${PORTAL_DOMAIN_BASE_VIRTUAL_SERVICE:-msp/alga-psa-vs} - REDIS_HOST=${REDIS_HOST:-redis} - REDIS_PORT=${REDIS_PORT:-6379} # Smoke-only: defaults to unset so the worker uses the real GDAP endpoints. Override in # the compose invocation or shell env to test self-tenant mode without CSP/GDAP. - ENTRA_DIRECT_SMOKE_SELF_TENANT_MODE=${ENTRA_DIRECT_SMOKE_SELF_TENANT_MODE:-} # Smoke-only: partitions the self-tenant /users response into N synthetic managed # tenants so cross-client bleed (Flow 2) can be exercised without CSP/GDAP. Format: # comma-separated `id|primary_domain|display_name` entries. - ENTRA_DIRECT_SMOKE_SYNTHETIC_TENANTS=${ENTRA_DIRECT_SMOKE_SYNTHETIC_TENANTS:-} # Smoke-only: forces accountEnabled=false for listed emails/UPNs so the # offboard → deactivate path (Flow 5) can be exercised without disabling # real users in Entra. Format: comma-separated email/UPN list. - ENTRA_DIRECT_SMOKE_DISABLED_USER_EMAILS=${ENTRA_DIRECT_SMOKE_DISABLED_USER_EMAILS:-} # Smoke-only: injects fake users into the /users response pinned to a # specific synthetic tenant bucket so Flow 7 (ambiguous match) can be # exercised without adding real users to Entra. Format: comma-separated # `objectId|upn|displayName|bucketIndex` entries. - ENTRA_DIRECT_SMOKE_EXTRA_USERS=${ENTRA_DIRECT_SMOKE_EXTRA_USERS:-} volumes: - type: bind source: ./secrets/tenants target: /run/secrets/tenants read_only: true - type: bind source: ./secrets/postgres_password target: /run/secrets/DB_PASSWORD_ADMIN read_only: true secrets: - db_password_server - postgres_password - alga_auth_key - nextauth_secret - redis_password - ninjaone_client_id - ninjaone_client_secret depends_on: temporal-dev: condition: service_started pgbouncer: condition: service_started redis: condition: service_started networks: - app-network temporal-ui: image: temporalio/ui:latest environment: - TEMPORAL_ADDRESS=temporal-dev:7233 ports: - "${EXPOSE_TEMPORAL_UI_PORT:-8088}:8080" depends_on: temporal-dev: condition: service_started networks: - app-network volumes: ngrok-data: external: true name: ${COMPOSE_PROJECT_NAME:-alga}_ngrok_data networks: app-network: driver: bridge