# CHLOVA — compose de PRODUCTION (déploiement GitOps via Portainer). # # Différences avec ../infra/docker-compose.yml (dev local) : # • Aucun `env_file` : le clone git ne contient PAS de .env. TOUS les secrets # et réglages arrivent par les VARIABLES DE STACK Portainer (interpolation # ${VAR}). L'agent ne voit jamais de secret en clair (CLAUDE.md). # • Réseau Traefik réel du homelab = `proxy` (external), pas `traefik-public`. # • certresolver réel = `letsencrypt` (cf. stack proxy), pas `le`. # • Le backend rejoint `proxy` pour joindre n8n en interne (http://n8n:5678). # # Cible : environnement Portainer `vps-pogoo-002` (endpoint 11). # Build : Portainer clone ce dépôt et build l'image sur le VPS (GitOps). # → chemin compose = infra/docker-compose.prod.yml ; contexte build = racine. # # Phase 1 (lecture seule) : CHLOVA_PHASE=1, PORTAINER_READ_ONLY=true. # Voir docs/deploy.md pour la procédure complète + variables de stack à fournir. name: chlova services: # ── Ollama : proxy authentifié vers les modèles cloud (ollama.com) ────── ollama: image: ollama/ollama:0.30.10 # >= 0.30 requis pour les modèles :cloud restart: unless-stopped environment: OLLAMA_API_KEY: ${OLLAMA_API_KEY:?OLLAMA_API_KEY requis} OLLAMA_HOST: 0.0.0.0:11434 volumes: - ollama-data:/root/.ollama networks: - chlova-internal - chlova-egress # AUCUN port publié : Ollama n'a pas d'auth native, jamais exposé. # ── MCP Portainer (passerelle HTTP portainer/portainer-mcp) — read-only P1 ─ # Parle à l'API Portainer (pas au socket Docker). Le backend s'y connecte en # HTTP sur :17717/mcp avec le secret de passerelle (Bearer) + la clé API # restreinte de chlova (X-Portainer-API-Key). mcp-portainer: image: portainer/portainer-mcp:2.42.6 restart: unless-stopped environment: PORTAINER_URL: ${PORTAINER_URL:-http://portainer:9000} # interne : Portainer sur le même hôte (local), réseau proxy PORTAINER_MCP_AUTH_TOKEN: ${PORTAINER_MCP_AUTH_TOKEN:?requis} # secret de passerelle (partagé avec le backend) PORTAINER_MCP_ALLOWED_HOSTS: mcp-portainer:17717 # hôte par lequel le backend appelle PORTAINER_MCP_DANGEROUSLY_ALLOW_PLAINTEXT_HTTP: "1" # HTTP interne (réseau Docker privé) PORTAINER_TLS_VERIFY: "0" # Portainer interne en HTTP : pas de TLS PORTAINER_READ_ONLY: ${PORTAINER_READ_ONLY:-true} # P1 : GET/HEAD seulement (défense en profondeur) networks: - chlova-internal # joignable par le backend - proxy # joint le serveur Portainer (http://portainer:9000) # AUCUN port publié. # ── Backend CHLOVA : SEULE surface, cerveau (boucle agent) ────────────── backend: build: context: .. # racine du dépôt (image = API + SPA web) dockerfile: orchestrator/Dockerfile image: chlova/backend:0.2.0 restart: unless-stopped environment: # — Runtime / phase — CHLOVA_ENV: ${CHLOVA_ENV:-production} CHLOVA_LOG_LEVEL: ${CHLOVA_LOG_LEVEL:-info} CHLOVA_PHASE: ${CHLOVA_PHASE:-1} # 1 = lecture seule CHLOVA_DB_PATH: ${CHLOVA_DB_PATH:-/app/data/chlova.db} # — Ollama (cloud proxy) — OLLAMA_BASE_URL: ${OLLAMA_BASE_URL:-http://ollama:11434} OLLAMA_API_KEY: ${OLLAMA_API_KEY:?requis} OLLAMA_MODEL: ${OLLAMA_MODEL:-qwen3:cloud} # — MCP n8n (natif, interne via réseau proxy) — MCP_N8N_URL: ${MCP_N8N_URL:-http://n8n-chlova:5678/mcp-server/http} MCP_N8N_AUTH_TOKEN: ${MCP_N8N_AUTH_TOKEN:?requis} # — MCP Portainer (passerelle) — MCP_PORTAINER_URL: ${MCP_PORTAINER_URL:-http://mcp-portainer:17717/mcp} PORTAINER_MCP_AUTH_TOKEN: ${PORTAINER_MCP_AUTH_TOKEN:?requis} # secret de passerelle (= côté sidecar) PORTAINER_API_KEY: ${PORTAINER_API_KEY:?requis} # clé API Portainer restreinte de chlova PORTAINER_READ_ONLY: ${PORTAINER_READ_ONLY:-true} # — Alertes (Phase 3) : vide = log-only — ALERT_WEBHOOK_URL: ${ALERT_WEBHOOK_URL:-} # — API/UI (surface exposée) : login fort — CHLOVA_ADMIN_USER: ${CHLOVA_ADMIN_USER:?requis} CHLOVA_ADMIN_PASSWORD_HASH: ${CHLOVA_ADMIN_PASSWORD_HASH:?requis} CHLOVA_TOTP_SECRET: ${CHLOVA_TOTP_SECRET:?requis} CHLOVA_JWT_SECRET: ${CHLOVA_JWT_SECRET:?requis} # — Auto-extension (Phase 5) : off par défaut — CHLOVA_AUTOEXT_ENABLED: ${CHLOVA_AUTOEXT_ENABLED:-false} CHLOVA_REPO_ROOT: ${CHLOVA_REPO_ROOT:-/app/repo} volumes: - chlova-data:/app/data # SQLite (table assets, P2+) depends_on: - ollama - mcp-portainer networks: - chlova-internal - proxy # joint Traefik + n8n (réseau homelab) labels: traefik.enable: "true" traefik.docker.network: proxy traefik.http.routers.chlova.rule: Host(`${CHLOVA_DOMAIN:-chlova.pogoo.app}`) traefik.http.routers.chlova.entrypoints: websecure traefik.http.routers.chlova.tls: "true" traefik.http.routers.chlova.tls.certresolver: letsencrypt traefik.http.services.chlova.loadbalancer.server.port: "8080" networks: chlova-internal: internal: true # aucune route vers l'extérieur chlova-egress: driver: bridge # sortie contrôlée (Ollama → ollama.com) proxy: name: proxy external: true # réseau Traefik existant du homelab (cf. stacks proxy/n8n) volumes: ollama-data: chlova-data: