# ========================================================= # BreakPilot Lehrer — KI-Lehrerplattform (Coolify) # ========================================================= # Requires: breakpilot-core must be running # Deployed via Coolify. SSL termination handled by Traefik. # External services (managed separately in Coolify): # - PostgreSQL, Qdrant, S3-compatible storage # ========================================================= networks: breakpilot-network: external: true name: breakpilot-network volumes: klausur_uploads: eh_uploads: ocr_labeling: paddle_models: lehrer_backend_data: opensearch_data: services: # ========================================================= # FRONTEND # ========================================================= admin-lehrer: build: context: ./admin-lehrer dockerfile: Dockerfile args: NEXT_PUBLIC_API_URL: ${NEXT_PUBLIC_API_URL:-https://api-lehrer.breakpilot.ai} NEXT_PUBLIC_OLD_ADMIN_URL: ${NEXT_PUBLIC_OLD_ADMIN_URL:-} NEXT_PUBLIC_KLAUSUR_SERVICE_URL: ${NEXT_PUBLIC_KLAUSUR_SERVICE_URL:-https://klausur.breakpilot.ai} NEXT_PUBLIC_VOICE_SERVICE_URL: ${NEXT_PUBLIC_VOICE_SERVICE_URL:-wss://voice.breakpilot.ai} container_name: bp-lehrer-admin expose: - "3000" volumes: - lehrer_backend_data:/app/data environment: NODE_ENV: production BACKEND_URL: http://backend-lehrer:8001 CONSENT_SERVICE_URL: http://bp-core-consent-service:8081 KLAUSUR_SERVICE_URL: http://klausur-service:8086 OLLAMA_URL: ${OLLAMA_URL:-} depends_on: backend-lehrer: condition: service_started labels: - "traefik.enable=true" - "traefik.http.routers.admin-lehrer.rule=Host(`admin-lehrer.breakpilot.ai`)" - "traefik.http.routers.admin-lehrer.entrypoints=https" - "traefik.http.routers.admin-lehrer.tls=true" - "traefik.http.routers.admin-lehrer.tls.certresolver=letsencrypt" - "traefik.http.services.admin-lehrer.loadbalancer.server.port=3000" restart: unless-stopped networks: - breakpilot-network studio-v2: build: context: ./studio-v2 dockerfile: Dockerfile args: NEXT_PUBLIC_VOICE_SERVICE_URL: ${NEXT_PUBLIC_VOICE_SERVICE_URL:-wss://voice.breakpilot.ai} NEXT_PUBLIC_KLAUSUR_SERVICE_URL: ${NEXT_PUBLIC_KLAUSUR_SERVICE_URL:-https://klausur.breakpilot.ai} container_name: bp-lehrer-studio-v2 expose: - "3001" environment: NODE_ENV: production BACKEND_URL: http://backend-lehrer:8001 depends_on: - backend-lehrer labels: - "traefik.enable=true" - "traefik.http.routers.studio.rule=Host(`app.breakpilot.ai`)" - "traefik.http.routers.studio.entrypoints=https" - "traefik.http.routers.studio.tls=true" - "traefik.http.routers.studio.tls.certresolver=letsencrypt" - "traefik.http.services.studio.loadbalancer.server.port=3001" restart: unless-stopped networks: - breakpilot-network website: build: context: ./website dockerfile: Dockerfile args: NEXT_PUBLIC_BILLING_API_URL: ${NEXT_PUBLIC_BILLING_API_URL:-https://api-core.breakpilot.ai} NEXT_PUBLIC_APP_URL: ${NEXT_PUBLIC_APP_URL:-https://app.breakpilot.ai} NEXT_PUBLIC_KLAUSUR_SERVICE_URL: ${NEXT_PUBLIC_KLAUSUR_SERVICE_URL:-https://klausur.breakpilot.ai} NEXT_PUBLIC_VOICE_SERVICE_URL: ${NEXT_PUBLIC_VOICE_SERVICE_URL:-wss://voice.breakpilot.ai} NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY: ${NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY:-} container_name: bp-lehrer-website expose: - "3000" environment: NODE_ENV: production VAST_API_KEY: ${VAST_API_KEY:-} CONTROL_API_KEY: ${CONTROL_API_KEY:-} BACKEND_URL: http://backend-lehrer:8001 CONSENT_SERVICE_URL: http://bp-core-consent-service:8081 EDU_SEARCH_URL: ${EDU_SEARCH_URL:-} EDU_SEARCH_API_KEY: ${EDU_SEARCH_API_KEY:-} depends_on: - backend-lehrer labels: - "traefik.enable=true" - "traefik.http.routers.website.rule=Host(`www.breakpilot.ai`)" - "traefik.http.routers.website.entrypoints=https" - "traefik.http.routers.website.tls=true" - "traefik.http.routers.website.tls.certresolver=letsencrypt" - "traefik.http.services.website.loadbalancer.server.port=3000" restart: unless-stopped networks: - breakpilot-network # ========================================================= # BACKEND # ========================================================= backend-lehrer: build: context: ./backend-lehrer dockerfile: Dockerfile container_name: bp-lehrer-backend user: "0:0" expose: - "8001" volumes: - lehrer_backend_data:/app/data environment: PORT: 8001 DATABASE_URL: postgresql+asyncpg://${POSTGRES_USER}:${POSTGRES_PASSWORD}@${POSTGRES_HOST}:${POSTGRES_PORT:-5432}/${POSTGRES_DB}?options=-csearch_path%3Dlehrer,core,public JWT_SECRET: ${JWT_SECRET} ENVIRONMENT: production CONSENT_SERVICE_URL: http://bp-core-consent-service:8081 KLAUSUR_SERVICE_URL: http://klausur-service:8086 TROCR_SERVICE_URL: ${TROCR_SERVICE_URL:-} CAMUNDA_URL: ${CAMUNDA_URL:-} VALKEY_URL: redis://bp-core-valkey:6379/0 SESSION_TTL_HOURS: ${SESSION_TTL_HOURS:-24} ANTHROPIC_API_KEY: ${ANTHROPIC_API_KEY:-} DEBUG: "false" ALERTS_AGENT_ENABLED: ${ALERTS_AGENT_ENABLED:-false} VAST_API_KEY: ${VAST_API_KEY:-} VAST_INSTANCE_ID: ${VAST_INSTANCE_ID:-} CONTROL_API_KEY: ${CONTROL_API_KEY:-} OLLAMA_BASE_URL: ${OLLAMA_BASE_URL:-} OLLAMA_ENABLED: ${OLLAMA_ENABLED:-false} OLLAMA_DEFAULT_MODEL: ${OLLAMA_DEFAULT_MODEL:-} OLLAMA_VISION_MODEL: ${OLLAMA_VISION_MODEL:-} OLLAMA_CORRECTION_MODEL: ${OLLAMA_CORRECTION_MODEL:-} OLLAMA_TIMEOUT: ${OLLAMA_TIMEOUT:-120} GAME_USE_DATABASE: ${GAME_USE_DATABASE:-true} GAME_REQUIRE_AUTH: ${GAME_REQUIRE_AUTH:-true} GAME_REQUIRE_BILLING: ${GAME_REQUIRE_BILLING:-true} GAME_LLM_MODEL: ${GAME_LLM_MODEL:-} SMTP_HOST: ${SMTP_HOST} SMTP_PORT: ${SMTP_PORT:-587} SMTP_USERNAME: ${SMTP_USERNAME} SMTP_PASSWORD: ${SMTP_PASSWORD} SMTP_FROM_NAME: ${SMTP_FROM_NAME:-BreakPilot} SMTP_FROM_ADDR: ${SMTP_FROM_ADDR:-noreply@breakpilot.ai} RAG_SERVICE_URL: http://bp-core-rag-service:8097 labels: - "traefik.enable=true" - "traefik.http.routers.backend-lehrer.rule=Host(`api-lehrer.breakpilot.ai`)" - "traefik.http.routers.backend-lehrer.entrypoints=https" - "traefik.http.routers.backend-lehrer.tls=true" - "traefik.http.routers.backend-lehrer.tls.certresolver=letsencrypt" - "traefik.http.services.backend-lehrer.loadbalancer.server.port=8001" restart: unless-stopped networks: - breakpilot-network # ========================================================= # MICROSERVICES # ========================================================= klausur-service: build: context: ./klausur-service dockerfile: Dockerfile container_name: bp-lehrer-klausur-service expose: - "8086" volumes: - klausur_uploads:/app/uploads - eh_uploads:/app/eh-uploads - ocr_labeling:/app/ocr-labeling - paddle_models:/root/.paddlex environment: JWT_SECRET: ${JWT_SECRET} BACKEND_URL: http://backend-lehrer:8001 SCHOOL_SERVICE_URL: http://school-service:8084 ENVIRONMENT: production DATABASE_URL: postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@${POSTGRES_HOST}:${POSTGRES_PORT:-5432}/${POSTGRES_DB} EMBEDDING_SERVICE_URL: http://bp-core-embedding-service:8087 QDRANT_URL: ${QDRANT_URL} MINIO_ENDPOINT: ${S3_ENDPOINT} MINIO_ACCESS_KEY: ${S3_ACCESS_KEY} MINIO_SECRET_KEY: ${S3_SECRET_KEY} MINIO_BUCKET: ${S3_BUCKET:-breakpilot-rag} MINIO_SECURE: ${S3_SECURE:-true} PADDLEOCR_SERVICE_URL: ${PADDLEOCR_SERVICE_URL:-} ANTHROPIC_API_KEY: ${ANTHROPIC_API_KEY:-} OLLAMA_BASE_URL: ${OLLAMA_BASE_URL:-} OLLAMA_ENABLED: ${OLLAMA_ENABLED:-false} OLLAMA_DEFAULT_MODEL: ${OLLAMA_DEFAULT_MODEL:-} OLLAMA_VISION_MODEL: ${OLLAMA_VISION_MODEL:-} OLLAMA_CORRECTION_MODEL: ${OLLAMA_CORRECTION_MODEL:-} RAG_SERVICE_URL: http://bp-core-rag-service:8097 depends_on: school-service: condition: service_started healthcheck: test: ["CMD", "curl", "-f", "http://127.0.0.1:8086/health"] interval: 30s timeout: 30s retries: 3 start_period: 10s labels: - "traefik.enable=true" - "traefik.http.routers.klausur.rule=Host(`klausur.breakpilot.ai`)" - "traefik.http.routers.klausur.entrypoints=https" - "traefik.http.routers.klausur.tls=true" - "traefik.http.routers.klausur.tls.certresolver=letsencrypt" - "traefik.http.services.klausur.loadbalancer.server.port=8086" restart: unless-stopped networks: - breakpilot-network school-service: build: context: ./school-service dockerfile: Dockerfile container_name: bp-lehrer-school-service expose: - "8084" environment: DATABASE_URL: postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@${POSTGRES_HOST}:${POSTGRES_PORT:-5432}/${POSTGRES_DB} JWT_SECRET: ${JWT_SECRET} PORT: 8084 ENVIRONMENT: production ALLOWED_ORIGINS: "*" LLM_GATEWAY_URL: http://backend-lehrer:8001/llm restart: unless-stopped networks: - breakpilot-network # ========================================================= # EDU SEARCH # ========================================================= opensearch: image: opensearchproject/opensearch:2.11.1 container_name: bp-lehrer-opensearch environment: - cluster.name=edu-search-cluster - node.name=opensearch-node1 - discovery.type=single-node - bootstrap.memory_lock=true - "OPENSEARCH_JAVA_OPTS=-Xms512m -Xmx512m" - OPENSEARCH_INITIAL_ADMIN_PASSWORD=${OPENSEARCH_PASSWORD:-Admin123!} - plugins.security.disabled=true ulimits: memlock: soft: -1 hard: -1 nofile: soft: 65536 hard: 65536 volumes: - opensearch_data:/usr/share/opensearch/data healthcheck: test: ["CMD-SHELL", "curl -s http://localhost:9200 >/dev/null || exit 1"] interval: 30s timeout: 10s retries: 5 start_period: 60s restart: unless-stopped networks: - breakpilot-network edu-search-service: build: context: ./edu-search-service dockerfile: Dockerfile container_name: bp-lehrer-edu-search expose: - "8088" environment: PORT: 8088 OPENSEARCH_URL: http://opensearch:9200 OPENSEARCH_USERNAME: admin OPENSEARCH_PASSWORD: ${OPENSEARCH_PASSWORD:-Admin123!} INDEX_NAME: bp_documents_v1 EDU_SEARCH_API_KEY: ${EDU_SEARCH_API_KEY:-} USER_AGENT: "BreakpilotEduCrawler/1.0 (+contact: security@breakpilot.com)" RATE_LIMIT_PER_SEC: "0.2" MAX_DEPTH: "4" MAX_PAGES_PER_RUN: "500" DB_HOST: ${POSTGRES_HOST} DB_PORT: ${POSTGRES_PORT:-5432} DB_USER: ${POSTGRES_USER} DB_PASSWORD: ${POSTGRES_PASSWORD} DB_NAME: ${POSTGRES_DB} DB_SSLMODE: disable STAFF_CRAWLER_EMAIL: crawler@breakpilot.de depends_on: opensearch: condition: service_healthy healthcheck: test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:8088/v1/health"] interval: 30s timeout: 3s start_period: 10s retries: 3 restart: unless-stopped networks: - breakpilot-network