# --------------------------------------------------------------------- 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 redis_data: {} # Redis # ------------------------------ X-templates ---------------------------- x-common-env: &env REDIS_HOST: redis REDIS_PORT: 6379 REDIS_DB: 0 REDIS_PASSWORD: ${REDIS_PASSWORD} QDRANT_HOST: qdrant DB_HOST: postgres DB_PORT: 5432 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, redis, 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 VOYAGE_API_KEY: ${VOYAGE_API_KEY} DISCORD_COLLECTOR_SERVER_URL: ingest-hub DISCORD_COLLECTOR_PORT: 8003 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 redis: image: redis:7.2-alpine restart: unless-stopped networks: [ kbnet ] command: ["redis-server", "--save", "", "--appendonly", "no", "--requirepass", "${REDIS_PASSWORD}"] volumes: - redis_data:/data:rw healthcheck: test: [ "CMD", "redis-cli", "--pass", "${REDIS_PASSWORD}", "ping" ] interval: 15s timeout: 5s retries: 5 security_opt: [ "no-new-privileges=true" ] cap_drop: [ ALL ] user: redis 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", "bash", "-c", "exec 3<>/dev/tcp/localhost/6333 && echo -e 'GET /readyz HTTP/1.0\\r\\n\\r\\n' >&3 && timeout 2 cat <&3 | grep -q 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 args: SERVER_URL: "${SERVER_URL:-http://localhost:8000}" SESSION_COOKIE_NAME: "${SESSION_COOKIE_NAME:-session_id}" restart: unless-stopped networks: [kbnet] depends_on: [postgres, redis, qdrant] environment: <<: *env POSTGRES_PASSWORD_FILE: /run/secrets/postgres_password QDRANT_URL: http://qdrant:6333 SERVER_URL: "${SERVER_URL:-http://localhost:8000}" VITE_SERVER_URL: "${SERVER_URL:-http://localhost:8000}" STATIC_DIR: "/app/static" VOYAGE_API_KEY: ${VOYAGE_API_KEY} ENABLE_BM25_SEARCH: false OPENAI_API_KEY_FILE: /run/secrets/openai_key ANTHROPIC_API_KEY_FILE: /run/secrets/anthropic_key secrets: [postgres_password, openai_key, anthropic_key] volumes: - ./memory_files:/app/memory_files:rw healthcheck: test: ["CMD-SHELL", "curl -fs http://localhost:8000/health || exit 1"] interval: 15s timeout: 5s retries: 5 ports: - "8000:8000" # ------------------------------------------------------------ Celery workers worker: <<: *worker-base environment: <<: *worker-env QUEUES: "backup,email,ebooks,discord,comic,blogs,forums,maintenance,notes,scheduler" ingest-hub: <<: *worker-base build: context: . dockerfile: docker/ingest_hub/Dockerfile environment: <<: *worker-env DISCORD_BOT_TOKEN: ${DISCORD_BOT_TOKEN} DISCORD_NOTIFICATIONS_ENABLED: ${DISCORD_NOTIFICATIONS_ENABLED:-true} DISCORD_COLLECTOR_ENABLED: true 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 } } } # ------------------------------------------------------------ database backups backup: image: postgres:15 # Has pg_dump, wget, curl networks: [kbnet] depends_on: [postgres, qdrant] env_file: [ .env ] environment: <<: *worker-env secrets: [postgres_password] volumes: - ./tools/backup_databases.sh:/backup.sh:ro entrypoint: ["/bin/bash"] command: ["/backup.sh"] profiles: [backup] # Only start when explicitly called security_opt: ["no-new-privileges=true"] # ------------------------------------------------------------ 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 ]