Files
kxkm_clown/docker-compose.yml
T
Codex Local c5f921243e feat: wire vLLM + TEI embedding backend
- LLM_API_KEY support across all LLM calls
- TEI embedding server (bge-m3, :9500)
- Hierarchical AGENTS.md documentation
- CLAUDE.md updated for new architecture
2026-04-08 09:41:51 +02:00

414 lines
14 KiB
YAML

# ---------------------------------------------------------------------------
# KXKM_Clown — Docker Compose
# ---------------------------------------------------------------------------
# Usage:
# docker compose up -d # postgres only
# docker compose --profile v1 up -d # V1 app + postgres
# docker compose --profile v2 up -d # V2 api + worker + postgres
# docker compose --profile v1 --profile v2 up -d # V1 + V2 + postgres
# docker compose --profile ollama up -d # include Ollama-compatible embeddings backend
#
# By default, the primary LLM runtime is expected on the host (LLM_URL, port 11434).
# OLLAMA_URL is retained for embeddings/RAG compatibility.
# ---------------------------------------------------------------------------
x-common-env: &common-env
LLM_URL: "${LLM_URL:-http://host.docker.internal:11434}"
LLM_MODEL: "${LLM_MODEL:-qwen-14b-awq}"
OLLAMA_URL: "${OLLAMA_URL:-http://host.docker.internal:11434}"
DATABASE_URL: postgres://kxkm:kxkm@postgres:5432/kxkm_clown
NODE_ENV: production
x-extra-hosts: &extra-hosts
- "host.docker.internal:host-gateway"
services:
# -------------------------------------------------------------------------
# V1 — main Express + WebSocket server
# -------------------------------------------------------------------------
app:
build: .
restart: unless-stopped
profiles: [v1]
extra_hosts: *extra-hosts
ports:
- "${APP_PORT:-3333}:3333"
environment:
<<: *common-env
PORT: "3333"
ADMIN_BOOTSTRAP_TOKEN: "${ADMIN_BOOTSTRAP_TOKEN:-}"
ADMIN_TOKEN: "${ADMIN_TOKEN:-kxkm}"
ADMIN_ALLOWED_SUBNETS: "${ADMIN_ALLOWED_SUBNETS:-}"
WEB_SEARCH_API_BASE: "${WEB_SEARCH_API_BASE:-}"
OWNER_NICK: "${OWNER_NICK:-}"
MAX_GENERAL_RESPONDERS: "${MAX_GENERAL_RESPONDERS:-4}"
CHAT_PAUSED: "${CHAT_PAUSED:-0}"
volumes:
- app-data:/app/data
depends_on:
postgres:
condition: service_healthy
healthcheck:
test: ["CMD-SHELL", "node -e \"fetch('http://localhost:3333/api/ping').then(r=>{process.exit(r.ok?0:1)}).catch(()=>process.exit(1))\""]
interval: 15s
timeout: 5s
retries: 3
start_period: 10s
# -------------------------------------------------------------------------
# V2 — API server (Express, TypeScript) — main chat interface
# Serves both the REST/WebSocket API and the React web build.
# -------------------------------------------------------------------------
api:
build: .
restart: unless-stopped
profiles: [v2]
network_mode: host
command: ["node", "apps/api/dist/server.js"]
volumes:
- ./data:/app/data
- ./scripts:/app/scripts:ro
- /home/kxkm/openDAW/packages/app/studio/dist:/app/daw:ro
environment:
LLM_URL: "${LLM_URL:-http://localhost:11434}"
LLM_MODEL: "${LLM_MODEL:-qwen-14b-awq}"
OLLAMA_URL: "http://localhost:11434"
DATABASE_URL: "postgres://kxkm:kxkm@localhost:5432/kxkm_clown"
NODE_ENV: production
V2_API_PORT: "${API_PORT:-3333}"
WEB_DIST_PATH: "/app/apps/web/dist"
DAW_DIST_PATH: "/app/daw"
ADMIN_BOOTSTRAP_TOKEN: "${ADMIN_BOOTSTRAP_TOKEN:-}"
ADMIN_TOKEN: "${ADMIN_TOKEN:-kxkm}"
ADMIN_ALLOWED_SUBNETS: "${ADMIN_ALLOWED_SUBNETS:-}"
TTS_ENABLED: "1"
KOKORO_URL: "http://127.0.0.1:9201"
PYTHON_BIN: "python3"
SCRIPTS_DIR: "/app/scripts"
PIPER_VOICE_DIR: "/app/data/piper-voices"
COQUI_TOS_AGREED: "1"
VISION_MODEL: "qwen3-vl:8b"
LIGHTRAG_URL: "http://localhost:9621"
TTS_URL: "http://localhost:9100"
DOCLING_URL: "http://localhost:9400"
RERANKER_URL: "http://localhost:9500"
RAG_CHUNK_SIZE: "${RAG_CHUNK_SIZE:-500}"
RAG_MIN_SIMILARITY: "${RAG_MIN_SIMILARITY:-0.3}"
RAG_MAX_RESULTS: "${RAG_MAX_RESULTS:-3}"
RAG_EMBEDDING_MODEL: "${RAG_EMBEDDING_MODEL:-nomic-embed-text}"
depends_on:
postgres:
condition: service_healthy
healthcheck:
test: ["CMD-SHELL", "node -e \"fetch('http://localhost:3333/api/v2/health').then(r=>{process.exit(r.ok?0:1)}).catch(()=>process.exit(1))\""]
interval: 15s
timeout: 5s
retries: 3
start_period: 10s
# -------------------------------------------------------------------------
# V2 — Worker (poll loop, GPU access for training)
# -------------------------------------------------------------------------
worker:
build: .
restart: unless-stopped
profiles: [v2]
network_mode: host
command: ["node", "apps/worker/dist/index.js"]
environment:
LLM_URL: "${LLM_URL:-http://localhost:11434}"
LLM_MODEL: "${LLM_MODEL:-qwen-14b-awq}"
OLLAMA_URL: "http://localhost:11434"
DATABASE_URL: "postgres://kxkm:kxkm@localhost:5432/kxkm_clown"
NODE_ENV: production
PYTHON_BIN: "${PYTHON_BIN:-/home/kxkm/venv/bin/python3}"
SCRIPTS_DIR: "${SCRIPTS_DIR:-/app/scripts}"
TRAINING_TIMEOUT_MS: "${TRAINING_TIMEOUT_MS:-3600000}"
volumes:
- /home/kxkm/venv:/home/kxkm/venv:ro
deploy:
resources:
reservations:
devices:
- driver: nvidia
count: all
capabilities: [gpu]
depends_on:
postgres:
condition: service_healthy
# -------------------------------------------------------------------------
# PostgreSQL 16
# -------------------------------------------------------------------------
postgres:
image: postgres:16-alpine
restart: unless-stopped
environment:
POSTGRES_USER: kxkm
POSTGRES_PASSWORD: kxkm
POSTGRES_DB: kxkm_clown
volumes:
- pg-data:/var/lib/postgresql/data
ports:
- "${PG_PORT:-5432}:5432"
healthcheck:
test: ["CMD-SHELL", "pg_isready -U kxkm -d kxkm_clown"]
interval: 5s
timeout: 3s
retries: 5
start_period: 5s
# -------------------------------------------------------------------------
# Ollama-compatible embeddings backend (optional, use --profile ollama)
# -------------------------------------------------------------------------
ollama:
image: ollama/ollama:latest
restart: unless-stopped
profiles: [ollama]
ports:
- "${OLLAMA_PORT:-11434}:11434"
volumes:
- ollama-data:/root/.ollama
# GPU passthrough — uncomment if NVIDIA GPU available:
# deploy:
# resources:
# reservations:
# devices:
# - driver: nvidia
# count: all
# capabilities: [gpu]
# -------------------------------------------------------------------------
# SearXNG — self-hosted privacy search engine
# Replaces DuckDuckGo Lite scraping with structured JSON API.
# Usage: set WEB_SEARCH_API_BASE=http://localhost:8080/search in .env
# -------------------------------------------------------------------------
searxng:
image: searxng/searxng:latest
restart: unless-stopped
profiles: [v2]
ports:
- "${SEARXNG_PORT:-8080}:8080"
volumes:
- ./ops/v2/searxng:/etc/searxng:ro
environment:
SEARXNG_BASE_URL: "http://localhost:8080/"
healthcheck:
test: ["CMD", "wget", "-q", "--spider", "http://localhost:8080/"]
interval: 30s
timeout: 5s
retries: 3
start_period: 10s
# -------------------------------------------------------------------------
# Discord Pharmacius Bot — bridges KXKM chat ↔ Discord
# Usage: DISCORD_BOT_TOKEN=xxx DISCORD_CHANNEL_ID=yyy docker compose --profile discord up -d
# -------------------------------------------------------------------------
discord-bot:
build: .
restart: unless-stopped
profiles: [discord]
network_mode: host
command: ["node", "scripts/discord-pharmacius.js"]
environment:
DISCORD_BOT_TOKEN: "${DISCORD_BOT_TOKEN:-}"
DISCORD_CHANNEL_ID: "${DISCORD_CHANNEL_ID:-}"
KXKM_WS_URL: "ws://localhost:3333/ws"
depends_on:
api:
condition: service_healthy
# -------------------------------------------------------------------------
# Discord Voice Bot — STT → Personas → TTS in voice channels
# Usage: DISCORD_BOT_TOKEN=xxx DISCORD_VOICE_CHANNEL=yyy docker compose --profile discord-voice up -d
# -------------------------------------------------------------------------
discord-voice:
build: .
restart: unless-stopped
profiles: [discord-voice]
network_mode: host
command: ["node", "scripts/discord-voice.js"]
environment:
DISCORD_BOT_TOKEN: "${DISCORD_BOT_TOKEN:-}"
DISCORD_VOICE_CHANNEL: "${DISCORD_VOICE_CHANNEL_ID_2:-}"
KXKM_WS_URL: "ws://localhost:3333/ws"
PYTHON_BIN: "${PYTHON_BIN:-/home/kxkm/venv/bin/python3}"
SCRIPTS_DIR: "/app/scripts"
volumes:
- /home/kxkm/venv:/home/kxkm/venv:ro
depends_on:
api:
condition: service_healthy
# -------------------------------------------------------------------------
# Chatterbox TTS — GPU-accelerated voice synthesis (Docker)
# Usage: docker compose --profile v2 up -d
# API: POST /tts, POST /v1/audio/speech
# -------------------------------------------------------------------------
chatterbox:
image: ghcr.io/devnen/chatterbox-tts-server:latest
restart: unless-stopped
profiles: [v2]
ports:
- "${CHATTERBOX_PORT:-9200}:8004"
volumes:
- ./data/voice-samples:/app/voices:ro
deploy:
resources:
reservations:
devices:
- driver: nvidia
count: 1
capabilities: [gpu]
healthcheck:
test: ["CMD-SHELL", "python3 -c \"import urllib.request; urllib.request.urlopen('http://localhost:8004/get_predefined_voices')\""]
interval: 30s
timeout: 10s
retries: 3
start_period: 60s
# -------------------------------------------------------------------------
# TTS Sidecar — Proxy to Chatterbox + Piper fallback
# Runs on host network, proxies /synthesize to Chatterbox :9200
# -------------------------------------------------------------------------
tts-sidecar:
build: .
restart: unless-stopped
profiles: [v2]
network_mode: host
command: ["python3", "scripts/tts-server.py", "--port", "9100", "--backend", "chatterbox-remote"]
environment:
CHATTERBOX_URL: "http://127.0.0.1:9200"
PIPER_VOICE_DIR: "/app/data/piper-voices"
KXKM_VOICE_SAMPLES_DIR: "/app/data/voice-samples"
volumes:
- ./data:/app/data
- ./scripts:/app/scripts:ro
- /home/kxkm/openDAW/packages/app/studio/dist:/app/daw:ro
depends_on:
chatterbox:
condition: service_healthy
# -------------------------------------------------------------------------
# Docling Serve — Document parsing (PDF, DOCX, PPTX → structured text)
# API: POST /v1/convert/source
# UI: http://localhost:9400/ui (if DOCLING_SERVE_ENABLE_UI=1)
# -------------------------------------------------------------------------
docling:
image: ghcr.io/docling-project/docling-serve:latest
restart: unless-stopped
profiles: [v2]
ports:
- "9400:5001"
environment:
DOCLING_SERVE_ENABLE_UI: "1"
healthcheck:
test: ["CMD-SHELL", "python3 -c \"import urllib.request; urllib.request.urlopen('http://localhost:5001/health')\""]
interval: 30s
timeout: 10s
retries: 3
start_period: 120s
# -------------------------------------------------------------------------
# LightRAG — Graph RAG server
# API: POST /query, POST /documents/text, GET /health
# Web UI: http://localhost:9621
# -------------------------------------------------------------------------
lightrag:
build:
context: .
dockerfile_inline: |
FROM python:3.12-slim
RUN pip install --no-cache-dir 'lightrag-hku[api]'
EXPOSE 9621
CMD ["lightrag-server", "--host", "0.0.0.0", "--port", "9621"]
restart: unless-stopped
profiles: [v2]
network_mode: host
environment:
LLM_MODEL: "qwen3:8b"
EMBEDDING_MODEL: "nomic-embed-text"
EMBEDDING_DIM: "768"
OLLAMA_HOST: "${OLLAMA_URL:-http://localhost:11434}"
LLM_BINDING: ollama
EMBEDDING_BINDING: ollama
RAG_DIR: "/data/lightrag"
command: >
lightrag-server
--host 0.0.0.0
--port 9621
--working-dir /data/lightrag
--llm-binding ollama
--embedding-binding ollama
volumes:
- ./data/lightrag:/data/lightrag
healthcheck:
test: ["CMD-SHELL", "python3 -c \"import urllib.request; urllib.request.urlopen('http://localhost:9621/health')\""]
interval: 30s
timeout: 10s
retries: 3
start_period: 30s
# -------------------------------------------------------------------------
# Prometheus — metrics collection
# Usage: docker compose --profile monitoring up -d
# -------------------------------------------------------------------------
prometheus:
image: prom/prometheus:latest
restart: unless-stopped
profiles: [monitoring]
extra_hosts: *extra-hosts
ports:
- "9090:9090"
volumes:
- ./ops/monitoring/prometheus.yml:/etc/prometheus/prometheus.yml:ro
- prometheus-data:/prometheus
command:
- "--config.file=/etc/prometheus/prometheus.yml"
- "--storage.tsdb.retention.time=30d"
- "--web.enable-lifecycle"
# -------------------------------------------------------------------------
# Grafana — dashboards & visualization
# Usage: docker compose --profile monitoring up -d
# Default login: admin / admin
# -------------------------------------------------------------------------
grafana:
image: grafana/grafana:latest
restart: unless-stopped
profiles: [monitoring]
ports:
- "3000:3000"
environment:
GF_SECURITY_ADMIN_PASSWORD: "${GRAFANA_PASSWORD:-kxkm}"
GF_USERS_ALLOW_SIGN_UP: "false"
volumes:
- grafana-data:/var/lib/grafana
- ./ops/monitoring/grafana/provisioning:/etc/grafana/provisioning:ro
- ./ops/monitoring/grafana/dashboards:/var/lib/grafana/dashboards:ro
depends_on:
- prometheus
# --- Text Embeddings Inference (dedicated embedding server) -----------------
# Usage: docker compose --profile embeddings up -d
# Exposes OpenAI-compatible /v1/embeddings on port 9500
# -------------------------------------------------------------------------
tei:
image: ghcr.io/huggingface/text-embeddings-inference:cpu-1.6
container_name: kxkm-tei
restart: unless-stopped
profiles: [v2, embeddings]
ports:
- "9500:80"
volumes:
- tei-models:/data
command: --model-id BAAI/bge-m3 --port 80
volumes:
app-data:
pg-data:
ollama-data:
tei-models:
prometheus-data:
grafana-data: