# --------------------------------------------------------------------------- # Shared env for the `full` application profile. These are LOCAL-DEV defaults # wired to the compose infra service names (postgres/rabbitmq/redis/qdrant). # # Secrets fall back to insecure dev values but can be overridden from a `.env` # file (see .env.example) — most importantly NUDGEBEE_ENCRYPTION_KEY, which # encrypts integration credentials at rest and MUST be identical across every # service. Change all of these before exposing the stack anywhere real. # --------------------------------------------------------------------------- x-app-common: &app-common # RabbitMQ (ignored by services that don't use MQ) RABBIT_MQ_HOST: rabbitmq RABBIT_MQ_PORT: '5672' RABBIT_MQ_USERNAME: guest RABBIT_MQ_PASSWORD: guest # Redis — used for the shared cache (and k8s-collector distributed locks). # Production runs CACHE_PROVIDER=redis whenever redis is enabled, so match it # here rather than falling back to per-process in-memory cache. Clients are # lazy (no dial until first use) and the compose redis has no auth, so empty # user/pass connect fine. CACHE_PROVIDER: redis REDIS_SERVER_HOST: redis REDIS_SERVER_PORT: '6379' REDIS_USER_NAME: '' REDIS_USER_PASSWORD: '' # Shared secrets — dev defaults; override via .env for anything real. # NUDGEBEE_ENCRYPTION_KEY must be 64 hex chars and identical everywhere. NUDGEBEE_ENCRYPTION_KEY: ${NUDGEBEE_ENCRYPTION_KEY:-0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef} RELAY_SERVER_SECRET_KEY: ${RELAY_SERVER_SECRET_KEY:-dev-relay-secret-change-me} # relay's RELAY_WORKSPACE_JWT_SECRET and llm-server's LLM_SERVER_JWT_SECRET # sign/verify the SAME workspace JWTs, so they must be identical (see the # comment at relay-server config.go). Both derive from one value on purpose — # this is not a copy-paste; a mismatch breaks workspace auth. RELAY_WORKSPACE_JWT_SECRET: ${RELAY_WORKSPACE_JWT_SECRET:-dev-jwt-secret-change-me} LLM_SERVER_JWT_SECRET: ${RELAY_WORKSPACE_JWT_SECRET:-dev-jwt-secret-change-me} ACTION_API_SERVER_TOKEN: ${ACTION_API_SERVER_TOKEN:-dev-action-token-change-me} # Inter-service URLs whose code defaults point at localhost/unknown ports. # SERVICE_API_SERVER_URL is intentionally left at its code default of # http://services-server:8000 and satisfied by a network alias on # api-server-services (see that service below). RELAY_SERVER_ENDPOINT: http://relay-server:8080 LLM_SERVER_URL: http://llm-server:8000 WORKFLOW_SERVER_URL: http://workflow-server:8000 TICKET_SERVICE_URL: http://ticket-server:8000 # llm-server reads TICKET_SERVER_URL (default :8080); ticket-server listens on # 8000 in compose, so point it there too. TICKET_SERVER_URL: http://ticket-server:8000 NOTIFICATION_SERVICE_URL: http://notifications-server:8080 ML_SERVICE_URL: http://ml-k8s-server:9999 # Postgres over the compose network uses no TLS NUDGEBEE_DB_SSL_ENABLED: 'false' # Full Postgres DSN for the compose Postgres. Each service reads it under a # DIFFERENT variable name (see per-service env blocks). x-db-url: &db-url postgres://postgres:postgrespassword@postgres:5432/nudgebee?sslmode=disable networks: nudgebee: volumes: pg_data: rabbitmq_data: redis_data: qdrant_data: services: # ---------- Infra ---------- postgres: image: ghcr.io/nudgebee/postgres:16 networks: [nudgebee] restart: unless-stopped ports: - "5432:5432" volumes: - pg_data:/var/lib/postgresql/data environment: POSTGRES_PASSWORD: postgrespassword POSTGRES_DB: nudgebee healthcheck: test: ["CMD-SHELL", "pg_isready -U postgres -d nudgebee"] interval: 5s timeout: 5s retries: 10 rabbitmq: image: ghcr.io/nudgebee/rabbitmq:3-management networks: [nudgebee] restart: unless-stopped ports: - "5672:5672" - "15672:15672" volumes: - rabbitmq_data:/var/lib/rabbitmq environment: RABBITMQ_DEFAULT_USER: guest RABBITMQ_DEFAULT_PASS: guest redis: image: ghcr.io/nudgebee/redis:7-alpine networks: [nudgebee] restart: unless-stopped ports: - "6379:6379" volumes: - redis_data:/data qdrant: image: ghcr.io/nudgebee/qdrant:v1.18.3 networks: [nudgebee] restart: unless-stopped ports: - "6333:6333" - "6334:6334" volumes: - qdrant_data:/qdrant/storage temporal: image: temporalio/auto-setup:1.29.1 networks: [nudgebee] restart: unless-stopped ports: - "7233:7233" depends_on: - postgres environment: DB: postgres12 DB_PORT: "5432" POSTGRES_SEEDS: postgres POSTGRES_USER: postgres POSTGRES_PWD: postgrespassword DBNAME: temporal VISIBILITY_DBNAME: temporal_visibility temporal-ui: image: temporalio/ui:2.44.0 networks: [nudgebee] restart: unless-stopped ports: - "8233:8080" depends_on: - temporal environment: TEMPORAL_ADDRESS: temporal:7233 TEMPORAL_CORS_ORIGINS: http://localhost:3000 # ---------- Migrations (one-shot) ---------- # Runs by default alongside infra: `docker compose up -d` brings up # pg/redis/rabbit/qdrant/temporal AND applies migrations. Idempotent — # re-running against an up-to-date DB is a no-op (golang-migrate tracker). # ClickHouse step auto-skips (no CLICKHOUSE_ENABLED). MIGRATE_SKIP_PLAYBOOK=1 # because local infra-only flows have no services-server to receive the # playbook-load curl; the cron registers itself on first services-server boot. migrations: build: ./api-server/migrations networks: [nudgebee] restart: "no" depends_on: postgres: condition: service_healthy rabbitmq: condition: service_started environment: APP_DATABASE_URL: postgres://postgres:postgrespassword@postgres:5432/nudgebee?sslmode=disable RABBIT_MQ_HOST: rabbitmq RABBIT_MQ_USERNAME: guest RABBIT_MQ_PASSWORD: guest MIGRATE_SKIP_PLAYBOOK: "1" # ---------- Go services ---------- # Each service uses dual mode: docker compose up tries the published # ghcr.io image first; falls back to local build from `build.context` # if the image isn't pullable (or you've never pushed). To force a # local rebuild during development: # docker compose build # Then docker compose up will pick up the locally-built image instead. api-server-services: profiles: [full] image: ghcr.io/nudgebee/services-server:edge build: context: ./api-server/services # Every service defaults its backend calls to http://services-server:8000 # (the SaaS service name). This alias makes that default resolve to this # container, so event ingestion and RPC work without per-service overrides. networks: nudgebee: aliases: - services-server ports: - "8000:8000" environment: <<: *app-common APP_DATABASE_URL: *db-url env_file: - path: ./api-server/services/.env required: false depends_on: - postgres - rabbitmq - redis ticket-server: profiles: [full] image: ghcr.io/nudgebee/ticket-server:edge build: context: ./ticket-server networks: [nudgebee] ports: - "8001:8000" environment: <<: *app-common APP_DATABASE_URL: *db-url PORT: '8000' depends_on: - postgres - rabbitmq - redis cloud-collector: profiles: [full] image: ghcr.io/nudgebee/cloud-collector-server:edge build: context: ./collector-server/cloud-collector networks: [nudgebee] ports: - "8002:8000" environment: <<: *app-common CLOUD_COLLECTOR_SERVER_DB_URL: *db-url depends_on: - postgres - rabbitmq - redis relay-server: profiles: [full] image: ghcr.io/nudgebee/relay-server:edge build: context: ./collector-server/k8s-collector/relay-server networks: [nudgebee] ports: - "8004:8080" environment: <<: *app-common COLLECTOR_DB_URL: *db-url RELAY_HTTP_PORT: '8080' depends_on: - postgres - rabbitmq - redis llm-server: profiles: [full] image: ghcr.io/nudgebee/llm-server:edge build: context: ./llm/llm-server networks: [nudgebee] ports: - "8005:8000" environment: <<: *app-common LLM_SERVER_DB_URL: *db-url # Vector search is proxied through the rag-server, not Qdrant directly. RAG_SERVER_URL: http://rag-server:9999 depends_on: - postgres - rabbitmq - qdrant - redis # TODO: code-analysis is intentionally NOT a long-running compose service. # System tracks per-account code-analysis instances and the design is for # llm-server to launch one dynamically per account on demand. This block # only exists as a placeholder for the time when that wiring lands. # code-analysis: # image: code-analysis # build locally — no published image # networks: [nudgebee] # ports: # - "8006:8080" workflow-server: profiles: [full] image: ghcr.io/nudgebee/workflow-server:edge build: context: ./runbook-server networks: [nudgebee] ports: - "8007:8000" environment: <<: *app-common # runbook/workflow tables live in the shared nudgebee DB AUTO_PILOT_DATABASE_URL: *db-url TEMPORAL_GRPC_ENDPOINT: temporal:7233 API_PORT: '8000' depends_on: - postgres - rabbitmq - temporal - redis # ---------- Python services ---------- k8s-collector-app: profiles: [full] image: ghcr.io/nudgebee/k8s-collector:edge build: context: ./collector-server/k8s-collector/app networks: [nudgebee] ports: # Container port is 5000, but publishing on host :5000 collides with macOS # AirPlay Receiver. Publish on host 8003 to both avoid that and stay in the # app-service 800x range (8003 is the free slot after cloud-collector's # 8002). Override with K8S_COLLECTOR_HOST_PORT. - "${K8S_COLLECTOR_HOST_PORT:-8003}:5000" environment: <<: *app-common COLLECTOR_DB_URL: *db-url # ClickHouse is optional (disabled), but the settings class requires the # keys to be present, so provide inert stubs. CLICKHOUSE_ENABLED: 'false' CLICKHOUSE_HOST: '' CLICKHOUSE_USER: '' CLICKHOUSE_PASSWORD: '' depends_on: - postgres - rabbitmq - redis rag-server: profiles: [full] image: ghcr.io/nudgebee/rag-server:edge build: context: ./llm/rag-server networks: [nudgebee] ports: - "9998:9999" environment: <<: *app-common APP_DATABASE_URL: *db-url # Use the standalone Qdrant server instead of embedded on-disk mode QDRANT_URL: http://qdrant:6333 depends_on: - qdrant - postgres ml-k8s-server: profiles: [full] image: ghcr.io/nudgebee/ml-k8s-server:edge build: context: ./ml-k8s-server networks: [nudgebee] ports: - "9999:9999" - "8081:8081" environment: <<: *app-common ML_INFERENCE_DATABASE_URL: *db-url depends_on: - postgres notifications-server: profiles: [full] image: ghcr.io/nudgebee/notifications:edge build: context: ./notifications-server networks: [nudgebee] ports: - "8090:8080" environment: <<: *app-common NOTIFICATION_DB_URL: *db-url depends_on: - postgres - rabbitmq - redis # ---------- Frontend ---------- app: profiles: [full] image: ghcr.io/nudgebee/app:edge build: context: ./app networks: [nudgebee] ports: - "3000:3000" depends_on: - api-server-services env_file: - path: ./app/.env required: false environment: SERVICE_API_SERVER_URL: http://api-server-services:8000 LLM_SERVER_URL: http://llm-server:8000 # Server-side API routes decrypt integration creds — must match the key # used by every backend service (see x-app-common above). NUDGEBEE_ENCRYPTION_KEY: ${NUDGEBEE_ENCRYPTION_KEY:-0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef}