services: # ── Cache for SearXNG & LLM Gateway ── valkey: image: valkey/valkey:8-alpine command: valkey-server --save 30 1 --loglevel warning volumes: - valkey-data:/data restart: unless-stopped healthcheck: test: ["CMD", "valkey-cli", "ping"] interval: 10s timeout: 3s retries: 3 # ── Private search engine ── searxng: image: searxng/searxng:latest volumes: - ./searxng:/etc/searxng:rw environment: - SEARXNG_SECRET_KEY=${SEARXNG_SECRET_KEY} ports: - "0.0.0.0:8080:8080" depends_on: valkey: condition: service_healthy restart: unless-stopped # ── Vector database ── chromadb: image: chromadb/chroma:latest volumes: - chromadb-data:/chroma/chroma ports: - "0.0.0.0:8000:8000" environment: - IS_PERSISTENT=TRUE - ANONYMIZED_TELEMETRY=FALSE restart: unless-stopped # ── LLM API proxy ── llm-gateway: build: ./llm-gateway ports: - "0.0.0.0:4000:3000" volumes: - llm-gateway-data:/data - ./llm-gateway/configs/config.yaml:/etc/llm-gateway/config.yaml:ro environment: - SESSION_SECRET=${SESSION_SECRET} - ADMIN_USERNAME=${ADMIN_USERNAME} - ADMIN_PASSWORD=${ADMIN_PASSWORD} - OPENWEBUI_API_KEY=${OPENWEBUI_API_KEY} - PERSONAL_API_KEY=${PERSONAL_API_KEY} - DEEPINFRA_API_KEY=${DEEPINFRA_API_KEY} - SILICONFLOW_API_KEY=${SILICONFLOW_API_KEY} - OPENROUTER_API_KEY=${OPENROUTER_API_KEY} - GROQ_API_KEY=${GROQ_API_KEY} - CEREBRAS_API_KEY=${CEREBRAS_API_KEY} depends_on: valkey: condition: service_healthy restart: unless-stopped healthcheck: test: ["CMD", "wget", "-q", "-O", "/dev/null", "http://localhost:3000/health"] interval: 15s timeout: 5s retries: 5 start_period: 5s # ── Chat UI ── open-webui: image: ghcr.io/open-webui/open-webui:main volumes: - open-webui-data:/app/backend/data ports: - "0.0.0.0:3000:8080" environment: - OLLAMA_BASE_URL= - OPENAI_API_BASE_URL=http://llm-gateway:3000/v1 - OPENAI_API_KEY=${OPENWEBUI_API_KEY} - ENABLE_RAG_WEB_SEARCH=true - RAG_WEB_SEARCH_ENGINE=searxng - SEARXNG_QUERY_URL=http://searxng:8080/search?q=&format=json - CHROMA_HTTP_HOST=chromadb - CHROMA_HTTP_PORT=8000 - WEBUI_AUTH=true depends_on: llm-gateway: condition: service_healthy restart: unless-stopped # ── Cloudflare Tunnel (public HTTPS) ── cloudflared: image: cloudflare/cloudflared:latest command: tunnel run environment: - TUNNEL_TOKEN=${CLOUDFLARE_TUNNEL_TOKEN} restart: unless-stopped # ── Tailscale (private VPN access) ── tailscale: image: tailscale/tailscale:latest hostname: ai-proxy volumes: - tailscale-state:/var/lib/tailscale - /dev/net/tun:/dev/net/tun cap_add: - NET_ADMIN - SYS_MODULE environment: - TS_AUTHKEY=${TS_AUTHKEY} - TS_STATE_DIR=/var/lib/tailscale - TS_USERSPACE=false restart: unless-stopped network_mode: host # ═══════════════════════════════════════════════ # Monitoring stack # ═══════════════════════════════════════════════ # ── Metrics store (Prometheus-compatible) ── victoriametrics: image: victoriametrics/victoria-metrics:latest volumes: - victoriametrics-data:/victoria-metrics-data - ./monitoring/prometheus.yml:/etc/prometheus/prometheus.yml:ro command: - "-promscrape.config=/etc/prometheus/prometheus.yml" - "-retentionPeriod=90d" - "-storageDataPath=/victoria-metrics-data" ports: - "127.0.0.1:8428:8428" restart: unless-stopped # ── Host system metrics ── node-exporter: image: prom/node-exporter:latest pid: host volumes: - /proc:/host/proc:ro - /sys:/host/sys:ro - /:/rootfs:ro command: - "--path.procfs=/host/proc" - "--path.sysfs=/host/sys" - "--path.rootfs=/rootfs" - "--collector.filesystem.mount-points-exclude=^/(sys|proc|dev|host|etc)($$|/)" restart: unless-stopped # ── Valkey/Redis metrics ── redis-exporter: image: oliver006/redis_exporter:latest environment: - REDIS_ADDR=redis://valkey:6379 depends_on: valkey: condition: service_healthy restart: unless-stopped volumes: valkey-data: chromadb-data: llm-gateway-data: open-webui-data: tailscale-state: victoriametrics-data: