services: # ============================================ # Nginx HTTPS Reverse Proxy # Enables secure context for microphone/crypto # Access via HTTPS on same ports as HTTP was before # ============================================ nginx: image: nginx:alpine container_name: breakpilot-pwa-nginx ports: - "443:443" # HTTPS Studio v2 (https://macmini/) - "80:80" # HTTP -> HTTPS redirect - "3000:3000" # HTTPS Admin Website (https://macmini:3000/) - "3002:3002" # HTTPS Admin v2 (https://macmini:3002/) - "8091:8091" # HTTPS Voice Service (wss://macmini:8091/) - "8000:8000" # HTTPS Backend API - "8086:8086" # HTTPS Klausur Service - "8089:8089" # HTTPS Edu-Search proxy (edu-search runs on 8088) - "8443:8443" # HTTPS Jitsi Meet (https://macmini:8443/) volumes: - ./nginx/conf.d:/etc/nginx/conf.d:ro - vault_certs:/etc/nginx/certs:ro depends_on: vault-agent: condition: service_started studio-v2: condition: service_started voice-service: condition: service_started backend: condition: service_started klausur-service: condition: service_started website: condition: service_started admin-v2: condition: service_started jitsi-web: condition: service_started extra_hosts: - "breakpilot-edu-search:host-gateway" networks: - breakpilot-pwa-network restart: unless-stopped # ============================================ # HashiCorp Vault - Secrets Management # Web UI: http://localhost:8200/ui # ============================================ vault: image: hashicorp/vault:1.15 container_name: breakpilot-pwa-vault cap_add: - IPC_LOCK ports: - "8200:8200" environment: - VAULT_DEV_ROOT_TOKEN_ID=${VAULT_DEV_TOKEN:-breakpilot-dev-token} - VAULT_DEV_LISTEN_ADDRESS=0.0.0.0:8200 - VAULT_ADDR=http://127.0.0.1:8200 volumes: - vault_data:/vault/data healthcheck: test: ["CMD", "vault", "status"] interval: 10s timeout: 5s retries: 3 start_period: 5s networks: - breakpilot-pwa-network restart: unless-stopped # Vault PKI Initialization - runs once to set up PKI and issue initial certs vault-init: image: hashicorp/vault:1.15 container_name: breakpilot-pwa-vault-init environment: - VAULT_ADDR=http://vault:8200 - VAULT_TOKEN=${VAULT_DEV_TOKEN:-breakpilot-dev-token} volumes: - ./vault/init-pki.sh:/init-pki.sh:ro - vault_agent_config:/vault/agent/data - vault_certs:/vault/certs entrypoint: ["/bin/sh", "-c"] command: - | echo "Waiting for Vault to be ready..." until vault status > /dev/null 2>&1; do sleep 1; done echo "Vault is ready. Running PKI initialization..." chmod +x /init-pki.sh /init-pki.sh echo "PKI initialization complete. Exiting." depends_on: vault: condition: service_healthy networks: - breakpilot-pwa-network restart: "no" # Vault Agent - manages certificate renewal for nginx vault-agent: image: hashicorp/vault:1.15 container_name: breakpilot-pwa-vault-agent environment: - VAULT_ADDR=http://vault:8200 volumes: - ./vault/agent/config.hcl:/vault/agent/config.hcl:ro - ./vault/agent/templates:/vault/agent/templates:ro - ./vault/agent/split-certs.sh:/vault/agent/split-certs.sh:ro - vault_agent_config:/vault/agent/data - vault_certs:/vault/certs entrypoint: ["vault", "agent", "-config=/vault/agent/config.hcl"] depends_on: vault: condition: service_healthy vault-init: condition: service_completed_successfully networks: - breakpilot-pwa-network restart: unless-stopped # PostgreSQL Database with PostGIS Extension # PostGIS is required for geo-service (OSM/Terrain features) postgres: image: postgis/postgis:16-3.4-alpine container_name: breakpilot-pwa-postgres ports: - "5432:5432" environment: POSTGRES_USER: breakpilot POSTGRES_PASSWORD: breakpilot123 POSTGRES_DB: breakpilot_db volumes: - breakpilot_pwa_data:/var/lib/postgresql/data - ./geo-service/scripts/init_postgis.sql:/docker-entrypoint-initdb.d/10-init-postgis.sql:ro healthcheck: test: ["CMD-SHELL", "pg_isready -U breakpilot -d breakpilot_db"] interval: 5s timeout: 5s retries: 5 networks: - breakpilot-pwa-network restart: unless-stopped # ============================================ # Valkey - Session Cache (Redis-Fork, BSD-3) # Redis-compatible, 100% Open Source # ============================================ valkey: image: valkey/valkey:8-alpine container_name: breakpilot-pwa-valkey ports: - "6379:6379" volumes: - valkey_data:/data command: valkey-server --appendonly yes --maxmemory 256mb --maxmemory-policy allkeys-lru healthcheck: test: ["CMD", "valkey-cli", "ping"] interval: 5s timeout: 3s retries: 5 networks: - breakpilot-pwa-network restart: unless-stopped # Go Consent Service consent-service: build: context: ./consent-service dockerfile: Dockerfile platform: linux/arm64 # Mac Mini Apple Silicon container_name: breakpilot-pwa-consent-service ports: - "8081:8081" environment: - DATABASE_URL=postgres://breakpilot:breakpilot123@postgres:5432/breakpilot_db?sslmode=disable - JWT_SECRET=${JWT_SECRET:-your-super-secret-jwt-key-change-in-production} - JWT_REFRESH_SECRET=${JWT_REFRESH_SECRET:-your-refresh-secret-key-change-in-production} - PORT=8081 - ENVIRONMENT=${ENVIRONMENT:-development} - ALLOWED_ORIGINS=http://localhost:8000,http://backend:8000 # Valkey Session Cache - VALKEY_URL=${VALKEY_URL:-redis://valkey:6379} - SESSION_TTL_HOURS=${SESSION_TTL_HOURS:-24} # E-Mail Konfiguration (Mailpit für Entwicklung) - SMTP_HOST=${SMTP_HOST:-mailpit} - SMTP_PORT=${SMTP_PORT:-1025} - SMTP_USERNAME=${SMTP_USERNAME:-} - SMTP_PASSWORD=${SMTP_PASSWORD:-} - SMTP_FROM_NAME=${SMTP_FROM_NAME:-BreakPilot} - SMTP_FROM_ADDR=${SMTP_FROM_ADDR:-noreply@breakpilot.app} - FRONTEND_URL=${FRONTEND_URL:-http://localhost:8000} depends_on: postgres: condition: service_healthy valkey: condition: service_healthy mailpit: condition: service_started networks: - breakpilot-pwa-network restart: unless-stopped # Python Backend backend: build: context: ./backend dockerfile: Dockerfile platform: linux/arm64 # Mac Mini Apple Silicon container_name: breakpilot-pwa-backend expose: - "8000" environment: - CONSENT_SERVICE_URL=http://consent-service:8081 - KLAUSUR_SERVICE_URL=http://klausur-service:8086 - TROCR_SERVICE_URL=${TROCR_SERVICE_URL:-http://host.docker.internal:18084} - CAMUNDA_URL=http://camunda:8080/engine-rest - DATABASE_URL=postgres://breakpilot:breakpilot123@postgres:5432/breakpilot_db?sslmode=disable - JWT_SECRET=${JWT_SECRET:-your-super-secret-jwt-key-change-in-production} - ENVIRONMENT=${ENVIRONMENT:-development} - ANTHROPIC_API_KEY=${ANTHROPIC_API_KEY:-} - DEBUG=${DEBUG:-false} # Alerts Agent (Google Alerts Monitoring) - ALERTS_AGENT_ENABLED=${ALERTS_AGENT_ENABLED:-true} # HashiCorp Vault - Secrets Management - VAULT_ADDR=${VAULT_ADDR:-http://vault:8200} - VAULT_TOKEN=${VAULT_TOKEN:-breakpilot-dev-token} - VAULT_SECRETS_PATH=${VAULT_SECRETS_PATH:-breakpilot} - USE_VAULT_SECRETS=${USE_VAULT_SECRETS:-true} # Keycloak Authentication (optional - wenn nicht gesetzt wird lokales JWT verwendet) - KEYCLOAK_SERVER_URL=${KEYCLOAK_SERVER_URL:-} - KEYCLOAK_REALM=${KEYCLOAK_REALM:-} - KEYCLOAK_CLIENT_ID=${KEYCLOAK_CLIENT_ID:-} - KEYCLOAK_CLIENT_SECRET=${KEYCLOAK_CLIENT_SECRET:-} - KEYCLOAK_VERIFY_SSL=${KEYCLOAK_VERIFY_SSL:-true} # Valkey Session Cache - VALKEY_URL=${VALKEY_URL:-redis://valkey:6379} - SESSION_TTL_HOURS=${SESSION_TTL_HOURS:-24} # vast.ai GPU Infrastructure - VAST_API_KEY=${VAST_API_KEY:-} - VAST_INSTANCE_ID=${VAST_INSTANCE_ID:-} - CONTROL_API_KEY=${CONTROL_API_KEY:-} - VAST_AUTO_SHUTDOWN=${VAST_AUTO_SHUTDOWN:-true} - VAST_AUTO_SHUTDOWN_MINUTES=${VAST_AUTO_SHUTDOWN_MINUTES:-30} # vLLM Backend - VLLM_BASE_URL=${VLLM_BASE_URL:-} - VLLM_ENABLED=${VLLM_ENABLED:-false} # Ollama Backend (lokal auf Mac Mini - DSGVO-konform) - OLLAMA_BASE_URL=${OLLAMA_BASE_URL:-http://host.docker.internal:11434} - OLLAMA_ENABLED=${OLLAMA_ENABLED:-true} - OLLAMA_DEFAULT_MODEL=${OLLAMA_DEFAULT_MODEL:-qwen2.5:14b} - OLLAMA_VISION_MODEL=${OLLAMA_VISION_MODEL:-qwen2.5vl:32b} - OLLAMA_CORRECTION_MODEL=${OLLAMA_CORRECTION_MODEL:-qwen2.5:14b} - OLLAMA_TIMEOUT=${OLLAMA_TIMEOUT:-180} # Breakpilot Drive Game API - GAME_USE_DATABASE=${GAME_USE_DATABASE:-true} - GAME_REQUIRE_AUTH=${GAME_REQUIRE_AUTH:-false} - GAME_REQUIRE_BILLING=${GAME_REQUIRE_BILLING:-false} - GAME_LLM_MODEL=${GAME_LLM_MODEL:-} # Compliance LLM Provider Configuration # Options: "anthropic" (cloud) or "self_hosted" (Ollama/local) - COMPLIANCE_LLM_PROVIDER=${COMPLIANCE_LLM_PROVIDER:-self_hosted} - SELF_HOSTED_LLM_URL=${SELF_HOSTED_LLM_URL:-http://host.docker.internal:11434} - SELF_HOSTED_LLM_MODEL=${SELF_HOSTED_LLM_MODEL:-llama3.1:70b} - COMPLIANCE_LLM_MAX_TOKENS=${COMPLIANCE_LLM_MAX_TOKENS:-4096} - COMPLIANCE_LLM_TEMPERATURE=${COMPLIANCE_LLM_TEMPERATURE:-0.3} - COMPLIANCE_LLM_TIMEOUT=${COMPLIANCE_LLM_TIMEOUT:-120} # E-Mail Konfiguration (Mailpit fuer Entwicklung) - SMTP_HOST=${SMTP_HOST:-mailpit} - SMTP_PORT=${SMTP_PORT:-1025} - SMTP_USERNAME=${SMTP_USERNAME:-} - SMTP_PASSWORD=${SMTP_PASSWORD:-} - SMTP_FROM_NAME=${SMTP_FROM_NAME:-BreakPilot} - SMTP_FROM_ADDR=${SMTP_FROM_ADDR:-noreply@breakpilot.app} extra_hosts: - "host.docker.internal:host-gateway" - "mac-mini:192.168.178.163" volumes: # Mount Docker socket for container monitoring (Mac Mini Control) - /var/run/docker.sock:/var/run/docker.sock:ro # Mount SBOM files for Security Dashboard - ./docs/sbom:/app/docs/sbom:ro # Mount Projekt-Verzeichnis fuer Test-Dashboard (echte Test-Discovery) - /Users/benjaminadmin/Projekte/breakpilot-pwa:/app/project:ro depends_on: - consent-service - valkey - mailpit networks: - breakpilot-pwa-network restart: unless-stopped # Automatic Database Backup (runs daily at 2 AM) backup: image: postgres:16-alpine container_name: breakpilot-pwa-backup volumes: - ./backups:/backups - postgres_data:/var/lib/postgresql/data:ro environment: - PGHOST=postgres - PGUSER=breakpilot - PGPASSWORD=breakpilot123 - PGDATABASE=breakpilot_db entrypoint: ["/bin/sh", "-c"] command: - | echo "Backup service started. Running backup every day at 2 AM..." while true; do # Berechne Sekunden bis 2 Uhr current_hour=$$(date +%H) current_min=$$(date +%M) current_sec=$$(date +%S) if [ $$current_hour -lt 2 ]; then sleep_hours=$$((2 - current_hour - 1)) else sleep_hours=$$((26 - current_hour - 1)) fi sleep_mins=$$((60 - current_min - 1)) sleep_secs=$$((60 - current_sec)) total_sleep=$$((sleep_hours * 3600 + sleep_mins * 60 + sleep_secs)) echo "Next backup in $$total_sleep seconds (at 2:00 AM)" sleep $$total_sleep # Backup erstellen TIMESTAMP=$$(date +"%Y%m%d_%H%M%S") BACKUP_FILE="/backups/breakpilot_$${TIMESTAMP}.sql.gz" echo "Creating backup: $$BACKUP_FILE" pg_dump | gzip > "$$BACKUP_FILE" if [ $$? -eq 0 ]; then echo "✓ Backup completed: $$BACKUP_FILE" # Alte Backups löschen (älter als 30 Tage) find /backups -name "breakpilot_*.sql.gz" -mtime +30 -delete echo "✓ Old backups cleaned up" else echo "✗ Backup failed!" fi done depends_on: postgres: condition: service_healthy networks: - breakpilot-pwa-network restart: unless-stopped profiles: - backup # Mailpit - Lokaler E-Mail-Server für Entwicklung # Web UI: http://localhost:8025 # SMTP: localhost:1025 mailpit: image: axllent/mailpit:latest container_name: breakpilot-pwa-mailpit ports: - "8025:8025" # Web UI - "1025:1025" # SMTP Server environment: - MP_SMTP_AUTH_ACCEPT_ANY=true - MP_SMTP_AUTH_ALLOW_INSECURE=true networks: - breakpilot-pwa-network restart: unless-stopped # Matrix Synapse - Schulkommunikation (E2EE Messenger) # Admin: http://localhost:8008/_synapse/admin synapse: image: matrixdotorg/synapse:latest container_name: breakpilot-pwa-synapse ports: - "8008:8008" # Client-Server API - "8448:8448" # Federation (optional für später) volumes: - synapse_data:/data environment: - SYNAPSE_SERVER_NAME=${MATRIX_SERVER_NAME:-breakpilot.local} - SYNAPSE_REPORT_STATS=no - SYNAPSE_NO_TLS=1 - SYNAPSE_ENABLE_REGISTRATION=no - SYNAPSE_LOG_LEVEL=INFO - UID=1000 - GID=1000 healthcheck: test: ["CMD-SHELL", "curl -fSs http://localhost:8008/health || exit 1"] interval: 30s timeout: 10s retries: 3 start_period: 30s networks: - breakpilot-pwa-network restart: unless-stopped # PostgreSQL für Matrix Synapse (separate DB) synapse-db: image: postgres:16-alpine container_name: breakpilot-pwa-synapse-db environment: POSTGRES_USER: synapse POSTGRES_PASSWORD: ${SYNAPSE_DB_PASSWORD:-synapse_secret_123} POSTGRES_DB: synapse POSTGRES_INITDB_ARGS: "--encoding=UTF8 --lc-collate=C --lc-ctype=C" volumes: - synapse_db_data:/var/lib/postgresql/data healthcheck: test: ["CMD-SHELL", "pg_isready -U synapse -d synapse"] interval: 5s timeout: 5s retries: 5 networks: - breakpilot-pwa-network restart: unless-stopped # Go School Service (Klausuren, Noten, Zeugnisse) school-service: build: context: ./school-service dockerfile: Dockerfile platform: linux/arm64 # Mac Mini Apple Silicon container_name: breakpilot-pwa-school-service ports: - "8084:8084" environment: - DATABASE_URL=postgres://breakpilot:breakpilot123@postgres:5432/breakpilot_db?sslmode=disable - JWT_SECRET=${JWT_SECRET:-your-super-secret-jwt-key-change-in-production} - PORT=8084 - ENVIRONMENT=${ENVIRONMENT:-development} - ALLOWED_ORIGINS=http://localhost:8000,http://backend:8000 - LLM_GATEWAY_URL=http://backend:8000/llm depends_on: postgres: condition: service_healthy networks: - breakpilot-pwa-network restart: unless-stopped # Embedding Service (ML-heavy operations) # Handles: embeddings, re-ranking, PDF extraction # Separated for faster klausur-service builds embedding-service: build: context: ./klausur-service/embedding-service dockerfile: Dockerfile platform: linux/arm64 # Mac Mini Apple Silicon container_name: breakpilot-pwa-embedding-service ports: - "8087:8087" environment: - EMBEDDING_BACKEND=${EMBEDDING_BACKEND:-local} - LOCAL_EMBEDDING_MODEL=${LOCAL_EMBEDDING_MODEL:-BAAI/bge-m3} - LOCAL_RERANKER_MODEL=${LOCAL_RERANKER_MODEL:-BAAI/bge-reranker-v2-m3} - PDF_EXTRACTION_BACKEND=${PDF_EXTRACTION_BACKEND:-auto} - OPENAI_API_KEY=${OPENAI_API_KEY:-} - COHERE_API_KEY=${COHERE_API_KEY:-} - LOG_LEVEL=${LOG_LEVEL:-INFO} volumes: - embedding_models:/root/.cache/huggingface deploy: resources: limits: memory: 4G healthcheck: test: ["CMD", "python", "-c", "import httpx; httpx.get('http://localhost:8087/health').raise_for_status()"] interval: 30s timeout: 10s start_period: 120s retries: 3 networks: - breakpilot-pwa-network restart: unless-stopped # Klausur-Service (Abitur/Vorabitur Klausurkorrektur) # React + FastAPI Microservice # Web UI: http://localhost:8086 klausur-service: build: context: ./klausur-service dockerfile: Dockerfile platform: linux/arm64 # Native ARM64 - PaddlePaddle 3.3.0 unterstützt ARM64 container_name: breakpilot-pwa-klausur-service expose: - "8086" environment: - JWT_SECRET=${JWT_SECRET:-your-super-secret-jwt-key-change-in-production} - BACKEND_URL=http://backend:8000 - SCHOOL_SERVICE_URL=http://school-service:8084 - ENVIRONMENT=${ENVIRONMENT:-development} # PostgreSQL for OCR Labeling & Metrics - DATABASE_URL=postgres://breakpilot:breakpilot123@postgres:5432/breakpilot_db # Embedding Service (ML operations) - EMBEDDING_SERVICE_URL=http://embedding-service:8087 # BYOEH Configuration - QDRANT_URL=http://qdrant:6333 - BYOEH_ENCRYPTION_ENABLED=true - BYOEH_CHUNK_SIZE=1000 - BYOEH_CHUNK_OVERLAP=200 # MinIO Configuration (RAG Document Storage) - MINIO_ENDPOINT=minio:9000 - MINIO_ACCESS_KEY=${MINIO_ROOT_USER:-breakpilot} - MINIO_SECRET_KEY=${MINIO_ROOT_PASSWORD:-breakpilot123} - MINIO_BUCKET=breakpilot-rag - MINIO_SECURE=false # Ollama LLM Configuration - OLLAMA_BASE_URL=${OLLAMA_BASE_URL:-http://host.docker.internal:11434} - OLLAMA_ENABLED=${OLLAMA_ENABLED:-true} - OLLAMA_DEFAULT_MODEL=${OLLAMA_DEFAULT_MODEL:-qwen2.5:14b} - OLLAMA_VISION_MODEL=${OLLAMA_VISION_MODEL:-qwen2.5vl:32b} - OLLAMA_CORRECTION_MODEL=${OLLAMA_CORRECTION_MODEL:-qwen2.5:14b} # PaddleOCR Service (x86_64 via Rosetta) - PADDLEOCR_SERVICE_URL=http://paddleocr-service:8095 # HashiCorp Vault - Anthropic API Key for Loesung E - VAULT_ADDR=http://vault:8200 - VAULT_TOKEN=${VAULT_DEV_TOKEN:-breakpilot-dev-token} - ANTHROPIC_API_KEY=${ANTHROPIC_API_KEY:-} volumes: - klausur_uploads:/app/uploads - eh_uploads:/app/eh-uploads - ocr_labeling:/app/ocr-labeling - paddle_models:/root/.paddlex # Persist PaddleOCR models across restarts - ./docs:/app/docs # NIBIS Abitur-Dateien depends_on: - backend - school-service - embedding-service - postgres - qdrant - minio - paddleocr-service - vault networks: - breakpilot-pwa-network healthcheck: test: ["CMD", "curl", "-f", "http://localhost:8086/health"] interval: 30s timeout: 30s retries: 3 start_period: 10s restart: unless-stopped # ============================================ # PaddleOCR Service - x86_64 via Rosetta # Runs in emulation to avoid ARM64 crashes # ============================================ paddleocr-service: build: context: ./paddleocr-service dockerfile: Dockerfile platform: linux/amd64 # Force x86_64 emulation via Rosetta container_name: breakpilot-pwa-paddleocr expose: - "8095" environment: - PADDLE_PDX_DISABLE_MODEL_SOURCE_CHECK=True volumes: - paddleocr_models:/root/.paddlex # Cache PaddleX models - paddleocr_models:/root/.paddleocr # Cache PaddleOCR 3.x models networks: - breakpilot-pwa-network healthcheck: test: ["CMD", "curl", "-f", "http://localhost:8095/health"] interval: 30s timeout: 60s retries: 5 start_period: 180s # Models need time to load in emulation restart: unless-stopped # Qdrant Vector Database (BYOEH - Erwartungshorizont RAG) # REST API: http://localhost:6333 # gRPC: localhost:6334 qdrant: image: qdrant/qdrant:v1.12.1 container_name: breakpilot-pwa-qdrant ports: - "6333:6333" - "6334:6334" volumes: - qdrant_data:/qdrant/storage environment: - QDRANT__SERVICE__GRPC_PORT=6334 healthcheck: test: ["CMD-SHELL", "bash -c '