version: "3.9" # --------------------------------------------------------------------- networks networks: kbnet: # internal overlay – NOT exposed driver: bridge # --------------------------------------------------------------------- secrets secrets: postgres_password: { file: ./secrets/postgres_password.txt } openai_key: { file: ./secrets/openai_key.txt } anthropic_key: { file: ./secrets/anthropic_key.txt } ssh_private_key: { file: ./secrets/ssh_private_key } ssh_public_key: { file: ./secrets/ssh_public_key } ssh_known_hosts: { file: ./secrets/ssh_known_hosts } # --------------------------------------------------------------------- volumes volumes: db_data: {} # Postgres qdrant_data: {} # Qdrant rabbitmq_data: {} # RabbitMQ # ------------------------------ X-templates ---------------------------- x-common-env: &env RABBITMQ_USER: kb RABBITMQ_HOST: rabbitmq CELERY_BROKER_PASSWORD: ${CELERY_BROKER_PASSWORD} QDRANT_HOST: qdrant DB_HOST: postgres DB_PORT: 5432 RABBITMQ_PORT: 5672 FILE_STORAGE_DIR: /app/memory_files TZ: "Etc/UTC" x-worker-base: &worker-base build: context: . dockerfile: docker/workers/Dockerfile restart: unless-stopped networks: [ kbnet ] security_opt: [ "no-new-privileges=true" ] depends_on: [ postgres, rabbitmq, qdrant ] env_file: [ .env ] environment: &worker-env <<: *env POSTGRES_PASSWORD_FILE: /run/secrets/postgres_password # DSNs are built in worker entrypoint from user + pw files QDRANT_URL: http://qdrant:6333 OPENAI_API_KEY_FILE: /run/secrets/openai_key ANTHROPIC_API_KEY_FILE: /run/secrets/anthropic_key secrets: [ postgres_password, openai_key, anthropic_key, ssh_private_key, ssh_public_key, ssh_known_hosts ] read_only: true tmpfs: - /tmp - /var/tmp - /home/kb/.ssh:uid=1000,gid=1000,mode=700 cap_drop: [ ALL ] volumes: - ./memory_files:/app/memory_files:rw logging: options: { max-size: "10m", max-file: "3" } user: kb # ================================ SERVICES ============================ services: # ----------------------------------------------------------------- data layer postgres: image: postgres:15 restart: unless-stopped networks: [ kbnet ] environment: <<: *env POSTGRES_USER: kb POSTGRES_PASSWORD_FILE: /run/secrets/postgres_password POSTGRES_DB: kb secrets: [ postgres_password ] volumes: - db_data:/var/lib/postgresql/data:rw healthcheck: test: [ "CMD-SHELL", "pg_isready -U kb" ] interval: 10s timeout: 5s retries: 5 security_opt: [ "no-new-privileges=true" ] migrate: build: context: . dockerfile: docker/migrations.Dockerfile networks: [kbnet] depends_on: postgres: condition: service_healthy environment: <<: *env POSTGRES_PASSWORD_FILE: /run/secrets/postgres_password secrets: [postgres_password] volumes: - ./db:/app/db:ro rabbitmq: image: rabbitmq:3.13-management restart: unless-stopped networks: [ kbnet ] environment: <<: *env RABBITMQ_DEFAULT_USER: "kb" RABBITMQ_DEFAULT_PASS: "${CELERY_BROKER_PASSWORD}" volumes: - rabbitmq_data:/var/lib/rabbitmq:rw healthcheck: test: [ "CMD", "rabbitmq-diagnostics", "ping" ] interval: 15s timeout: 5s retries: 5 security_opt: [ "no-new-privileges=true" ] qdrant: image: qdrant/qdrant:v1.14.0 restart: unless-stopped networks: [ kbnet ] volumes: - qdrant_data:/qdrant/storage:rw tmpfs: - /tmp - /var/tmp - /qdrant/snapshots:rw healthcheck: test: [ "CMD", "wget", "-q", "-T", "2", "-O", "-", "localhost:6333/ready" ] interval: 15s timeout: 5s retries: 5 security_opt: [ "no-new-privileges=true" ] cap_drop: [ ALL ] # ------------------------------------------------------------ API / gateway api: build: context: . dockerfile: docker/api/Dockerfile restart: unless-stopped networks: [kbnet] depends_on: [postgres, rabbitmq, qdrant] environment: <<: *env POSTGRES_PASSWORD_FILE: /run/secrets/postgres_password QDRANT_URL: http://qdrant:6333 secrets: [postgres_password] healthcheck: test: ["CMD-SHELL", "curl -fs http://localhost:8000/health || exit 1"] interval: 15s timeout: 5s retries: 5 ports: - "8000:8000" proxy: build: context: . dockerfile: docker/api/Dockerfile restart: unless-stopped networks: [kbnet] depends_on: [api] environment: <<: *env PROXY_EMAIL: "${PROXY_EMAIL}" PROXY_PASSWORD: "${PROXY_PASSWORD}" PROXY_REMOTE_SERVER: "http://api:8000" command: ["python", "/app/tools/simple_proxy.py", "--remote-server", "http://api:8000", "--email", "${PROXY_EMAIL}", "--password", "${PROXY_PASSWORD}", "--port", "8001"] volumes: - ./tools:/app/tools:ro ports: - "8001:8001" # ------------------------------------------------------------ Celery workers worker: <<: *worker-base environment: <<: *worker-env QUEUES: "email,ebooks,comic,blogs,forums,maintenance,notes" ingest-hub: <<: *worker-base build: context: . dockerfile: docker/ingest_hub/Dockerfile environment: <<: *worker-env volumes: - ./memory_files:/app/memory_files:rw tmpfs: - /tmp - /var/tmp - /var/log/supervisor - /var/run/supervisor deploy: { resources: { limits: { cpus: "0.5", memory: 512m } } } # ------------------------------------------------------------ watchtower (auto-update) # watchtower: # image: containrrr/watchtower # restart: unless-stopped # command: [ "--schedule", "0 0 4 * * *", "--cleanup" ] # volumes: [ "/var/run/docker.sock:/var/run/docker.sock:ro" ] # networks: [ kbnet ]