# ============================================================================= # DidiBrain — Atomic + Postgres pgvector # ============================================================================= # This compose stack runs: # - postgres (pgvector/pgvector:pg16) on port 5434 # - atomic-server (kenforthewin/atomic-server:latest) on port 8080 # # Atomic is configured to use Postgres as the data backend (atoms, embeddings, # tags, etc.) while keeping the SQLite registry for tokens & global settings # in the local volume at /data. # # AI provider config (BGE-M3 endpoint, Qwen 397B router) is NOT set here — # it lives in Atomic's settings table and is bootstrapped post-startup by # `scripts/02_bootstrap_atomic.py` which calls PUT /api/settings. # ============================================================================= services: postgres: image: pgvector/pgvector:pg16 container_name: didibrain-postgres environment: POSTGRES_USER: ${POSTGRES_USER:-atomic} POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-atomic_dev_changeme} POSTGRES_DB: ${POSTGRES_DB:-atomic} ports: - "${POSTGRES_PORT:-5434}:5432" volumes: - didibrain-pg-data:/var/lib/postgresql/data healthcheck: test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER:-atomic} -d ${POSTGRES_DB:-atomic}"] interval: 3s timeout: 5s retries: 10 start_period: 5s networks: - didi-network restart: unless-stopped atomic-server: image: ghcr.io/kenforthewin/atomic-server:latest container_name: didibrain-atomic # Override the upstream image's hard-coded `--db-path` (legacy SQLite mode) # so we can use the modern `--data-dir` + `--storage postgres` flow. entrypoint: ["atomic-server", "--data-dir", "/data"] command: ["serve", "--bind", "0.0.0.0", "--port", "8080"] environment: ATOMIC_STORAGE: postgres ATOMIC_DATABASE_URL: postgres://${POSTGRES_USER:-atomic}:${POSTGRES_PASSWORD:-atomic_dev_changeme}@postgres:5432/${POSTGRES_DB:-atomic} RUST_LOG: "atomic_core=info,atomic_server=info,warn" # PUBLIC_URL is required for OAuth/MCP discovery — fine to leave empty # for local dev (we'll set it on the real server). PUBLIC_URL: "" ports: # 8080 is taken on this dev box (aria-frontend); use 8088 externally, # the container itself still listens on 8080 internally. - "8088:8080" volumes: - didibrain-atomic-data:/data depends_on: postgres: condition: service_healthy healthcheck: test: ["CMD", "curl", "-f", "http://localhost:8080/health"] interval: 5s timeout: 3s retries: 10 start_period: 15s networks: - didi-network restart: unless-stopped # Atomic-server makes outbound HTTPS calls to the LLM router and BGE # endpoint. Since those live on the host network (10.11.10.x and # localhost:14011), Linux containers need to reach them. Two options: # 1. Use host.docker.internal:14011 for the router (Docker Desktop # maps this to the host on Win/Mac). # 2. Use the actual host IP for 10.11.10.x targets (Docker Desktop # routes through WSL2's NAT, so 10.x is reachable directly). # We rely on (2) being true for the BGE endpoint and (1) for the LLM # router. The bootstrap script will configure both. extra_hosts: - "host.docker.internal:host-gateway" brain-api: # DidiBrain's HTTP service (speaks Didi's web-module contract). # Build context is the didibrain/ root one level up, so shared/ and # extractor/ get COPY'd in by the Dockerfile. build: context: .. dockerfile: brain_api/Dockerfile image: didibrain-api:latest container_name: didibrain-api # Load the full host .env (single source of truth for endpoints), # then override the atomic URL to use Docker's internal DNS. Everything # else — BGE, LLM router, model names — stays exactly as on the host. env_file: - ../.env environment: # Inside the compose network, atomic-server is reachable by its # service name on its internal port (8080), NOT the host-mapped 8088. ATOMIC_URL: http://atomic-server:8080 # brain_api connects directly to postgres for the verification cache. # Inside the network, postgres is at 'postgres:5432' (not 5434/localhost). POSTGRES_HOST: postgres POSTGRES_INTERNAL_PORT: "5432" # AI platform dashboard — RuntimeConfigClient polls /api/config every 30s # for live overrides on atom_* and log_level. Empty/unset = polling # disabled (settings/constants used as-is). DASHBOARD_URL: ${DASHBOARD_URL:-http://didiAI-dashboard:51300} # OTel — traces to OTel Collector → Jaeger OTEL_EXPORTER_OTLP_ENDPOINT: ${OTEL_EXPORTER_OTLP_ENDPOINT:-http://didi-otel-collector:4317} OTEL_SERVICE_NAME: didibrain-api volumes: # Mount the host-seeded taxonomy map so background extraction (which # instantiates TagResolver directly, bypassing the app lifespan # refresh) can find canonical tag UUIDs. - ../shared/_tag_ids.json:/app/shared/_tag_ids.json:ro ports: - "8090:8090" depends_on: postgres: condition: service_healthy atomic-server: condition: service_healthy healthcheck: test: ["CMD", "curl", "-fsS", "http://localhost:8090/health"] interval: 10s timeout: 5s retries: 5 start_period: 20s networks: - didi-network restart: unless-stopped # BGE endpoint (10.11.10.15) and LLM router (10.11.10.17) live on the # VPN-routed internal network. Docker Desktop on Windows routes these # through WSL2's NAT just like the atomic-server container already does, # so no special extra_hosts entries are needed for them. scheduler: # Phase C — feeder (RSS hourly), auditor (daily LLM audit), and breaking # news watcher in a single container. Imports brain_api.* directly for # DB access (same Postgres pool, separate process). build: context: .. dockerfile: scheduler/Dockerfile image: didibrain-scheduler:latest container_name: didibrain-scheduler env_file: - ../.env environment: # Same as brain-api — keeps the auditor's DB connection consistent. ATOMIC_URL: http://atomic-server:8080 POSTGRES_HOST: postgres POSTGRES_INTERNAL_PORT: "5432" # Default brain target uses Docker DNS to reach the brain-api service # in this same compose stack. Override per environment if needed. SCHED_BRAIN_API_URL: http://brain-api:8090 SCHED_LOG_LEVEL: INFO depends_on: postgres: condition: service_healthy brain-api: condition: service_healthy healthcheck: test: ["CMD-SHELL", "test -f /tmp/scheduler.healthy && test \"$$(($$(date +%s) - $$(stat -c %Y /tmp/scheduler.healthy)))\" -lt 300"] interval: 60s timeout: 10s retries: 3 start_period: 60s networks: - didi-network restart: unless-stopped volumes: didibrain-pg-data: name: didibrain-pg-data didibrain-atomic-data: name: didibrain-atomic-data networks: didi-network: external: true # single shared network for all DIDI + AI platform stacks