# Coolify production stack — self-contained: qdrant + ollama + ingestor + rag-mcp. # # Services kommunizieren nur über das compose-interne Netz. Öffentlich ist # allein der ingestor (Coolify-Domain → Port 8000) für den Nextcloud-Webhook. # qdrant + ollama bleiben intern. rag-mcp ist NICHT public (keine Domain) — # nur MetaMCP erreicht ihn über das dedizierte externe Netz "metamcp-net". # # Coolify rendert container_name/labels/COOLIFY_*-env/das Stack-Netz selbst. # Hier stehen nur Extra-Labels und das, was zusätzlich nötig ist. # # Voraussetzungen in Coolify: # - Pull-Credentials für gitea.jeanlucmakiola.de Registry hinterlegen # - Externes Netz einmalig anlegen: docker network create metamcp-net # und es in der MetaMCP-Compose ergänzen (networks: [metamcp-net]) # - Env-Vars setzen (Secrets wo markiert): # NEXTCLOUD_WEBDAV_URL # NEXTCLOUD_USER # NEXTCLOUD_APP_PASSWORD (Secret) # WEBHOOK_SECRET (Secret) # RAG_MCP_TOKEN (Secret — Bearer-Token für MetaMCP) # QDRANT_COLLECTION (optional, Default rag_thb_studium) # INGEST_ROOT (optional, Default Documents/THB) # LOG_LEVEL (optional, Default INFO) # - Traefik-Rate-Limit am ingestor-Router aktivieren: siehe README # ("MetaMCP & Härtung") — das Router-Binding-Label trägt die # env-spezifische Coolify-UUID und gehört daher in die Coolify-UI, # nicht ins versionierte Compose. services: qdrant: image: qdrant/qdrant:latest restart: unless-stopped volumes: - qdrant_data:/qdrant/storage ollama: image: ollama/ollama:latest restart: unless-stopped volumes: - ollama_data:/root/.ollama # Konstante ~2 Cores statt Peaks über alle Host-Cores. Bewusster # Trade-off: langsamerer Ingest, dafür predictable Last. cpus: "2.0" environment: OLLAMA_NUM_PARALLEL: "1" healthcheck: test: ["CMD", "ollama", "list"] interval: 10s timeout: 5s retries: 12 # One-shot: zieht das Embed-Modell in das ollama-Volume und beendet sich. # Idempotent — bei Redeploy mit warmem Volume nur Digest-Verifikation. # Verhindert den Startup-Crash des Ingestors bei fehlendem Modell. ollama-pull: image: ollama/ollama:latest restart: "no" depends_on: ollama: condition: service_healthy environment: OLLAMA_HOST: "http://ollama:11434" command: ["pull", "qwen3-embedding:0.6b"] ingestor: image: gitea.jeanlucmakiola.de/makiolaj/rag-ingestor:latest restart: unless-stopped pull_policy: always depends_on: qdrant: condition: service_started ollama: condition: service_healthy ollama-pull: condition: service_completed_successfully environment: # Coolify-Magie: macht den Ingestor öffentlich über den Coolify-Proxy # (inkl. Let's-Encrypt-TLS). Coolify generiert eine Domain; in der UI # auf die echte (z.B. ingest.jeanlucmakiola.de) überschreiben. Nextcloud # ruft nur diese öffentliche URL an — kein Coolify-Netz-Zugriff nötig. SERVICE_FQDN_INGESTOR_8000: / NEXTCLOUD_WEBDAV_URL: ${NEXTCLOUD_WEBDAV_URL} NEXTCLOUD_USER: ${NEXTCLOUD_USER} NEXTCLOUD_APP_PASSWORD: ${NEXTCLOUD_APP_PASSWORD} OLLAMA_URL: http://ollama:11434 OLLAMA_EMBED_MODEL: qwen3-embedding:0.6b QDRANT_URL: http://qdrant:6333 QDRANT_COLLECTION: ${QDRANT_COLLECTION:-rag_thb_studium} WEBHOOK_SECRET: ${WEBHOOK_SECRET} INGEST_ROOT: ${INGEST_ROOT:-Documents/THB} LOG_LEVEL: ${LOG_LEVEL:-INFO} # Extra-Label: definiert eine Traefik-Rate-Limit-Middleware (30 req/min, # Burst 15) gegen Spam des öffentlichen Webhooks. Das Binding an den # Coolify-Router (env-spezifische UUID) erfolgt in der Coolify-UI, s. README. labels: - "traefik.http.middlewares.rag-ratelimit.ratelimit.average=30" - "traefik.http.middlewares.rag-ratelimit.ratelimit.period=1m" - "traefik.http.middlewares.rag-ratelimit.ratelimit.burst=15" expose: - "8000" healthcheck: test: - CMD - python - -c - "import urllib.request,sys; sys.exit(0 if urllib.request.urlopen('http://localhost:8000/health').status==200 else 1)" interval: 15s timeout: 5s retries: 5 # MCP-Server für MetaMCP. Gleiches Image wie der ingestor (teilt den # Embed-Pfad → identische Vektoren wie beim Ingest), nur anderer command. # KEIN SERVICE_FQDN / kein expose → nicht public. Erreichbar ausschließlich # von MetaMCP über das externe Netz metamcp-net. Bearer-Token als zweite # Kontrolle hinter der Netz-Isolation. rag-mcp: image: gitea.jeanlucmakiola.de/makiolaj/rag-ingestor:latest restart: unless-stopped pull_policy: always command: ["python", "-m", "app.mcp_server"] depends_on: qdrant: condition: service_started ollama: condition: service_healthy ollama-pull: condition: service_completed_successfully environment: OLLAMA_URL: http://ollama:11434 OLLAMA_EMBED_MODEL: qwen3-embedding:0.6b QDRANT_URL: http://qdrant:6333 QDRANT_COLLECTION: ${QDRANT_COLLECTION:-rag_thb_studium} RAG_MCP_TOKEN: ${RAG_MCP_TOKEN} LOG_LEVEL: ${LOG_LEVEL:-INFO} # Vom MCP-Server ungenutzt, aber das geteilte Settings-Modell verlangt # diese Felder — harmlose Platzhalter, damit die Validierung durchläuft. NEXTCLOUD_WEBDAV_URL: http://unused NEXTCLOUD_USER: unused NEXTCLOUD_APP_PASSWORD: unused WEBHOOK_SECRET: unused networks: - default - metamcp-net volumes: qdrant_data: ollama_data: networks: # Coolify injiziert sein Stack-Netz als "default" automatisch. metamcp-net # ist ein eigens angelegtes externes Netz, dem nur rag-mcp und MetaMCP # beitreten — qdrant/ollama/ingestor bleiben außen vor. metamcp-net: external: true name: metamcp-net