version: "3.9" # ── Report Genius AI — Production Stack ────────────────────────────────────── # # Services: # api — FastAPI + Uvicorn (FAISS by default; optional Qdrant for async pipeline) # qdrant — Optional vector DB when VECTORSTORE_BACKEND=qdrant # temporal — Optional durable workflow server (ENABLE_TEMPORAL_WORKFLOW=true) # temporal-worker — Runs worker.py when Temporal is enabled # # Quick start: # cp .env.example .env # set OPENAI_API_KEY and TENANT_SECRET_KEY # docker compose up --build -d # open http://localhost:8000 # # To use PostgreSQL instead of SQLite, set DATABASE_URL in .env: # DATABASE_URL=postgresql+asyncpg://user:pass@postgres:5432/reportgenius # # Phase 2 + 3 (scale): # SCALE_OPTIMIZATION_PROFILE=true REDIS_URL=redis://redis:6379/0 \ # docker compose --profile redis --profile jobs up -d # ───────────────────────────────────────────────────────────────────────────── services: redis: image: redis:7-alpine container_name: report_genius_redis restart: unless-stopped ports: - "6379:6379" profiles: - redis jobs-worker: build: context: . dockerfile: Dockerfile target: production image: report-genius-ai:latest container_name: report_genius_jobs_worker restart: unless-stopped command: ["python", "jobs_worker.py"] environment: OPENAI_API_KEY: "${OPENAI_API_KEY}" TENANT_SECRET_KEY: "${TENANT_SECRET_KEY:-change-me-in-production}" DATABASE_URL: "${DATABASE_URL:-sqlite+aiosqlite:////data/dev.db}" FAISS_INDEX_PATH: "/data/faiss_index" UPLOAD_DIR: "/mnt/data/uploads" CACHE_DIR: "/tmp/section_cache" REDIS_URL: "${REDIS_URL:-redis://redis:6379/0}" ENABLE_JOB_QUEUE: "true" SCALE_OPTIMIZATION_PROFILE: "${SCALE_OPTIMIZATION_PROFILE:-true}" ENABLE_ASYNC_PIPELINE: "${ENABLE_ASYNC_PIPELINE:-false}" ENABLE_SPECULATIVE_EXECUTOR: "${ENABLE_SPECULATIVE_EXECUTOR:-false}" ENABLE_PROMPT_CACHING: "${ENABLE_PROMPT_CACHING:-false}" VECTORSTORE_BACKEND: "${VECTORSTORE_BACKEND:-faiss}" QDRANT_URL: "${QDRANT_URL:-http://qdrant:6333}" GENERATION_STALE_SWEEP_SECONDS: "${GENERATION_STALE_SWEEP_SECONDS:-120}" GENERATION_TIMEOUT_SECONDS: "${GENERATION_TIMEOUT_SECONDS:-3600}" volumes: - uploads:/mnt/data/uploads - section_cache:/tmp/section_cache - db_data:/data - faiss_index:/data/faiss_index depends_on: redis: condition: service_started profiles: - jobs qdrant: image: qdrant/qdrant:v1.12.5 container_name: report_genius_qdrant restart: unless-stopped ports: - "6333:6333" volumes: - qdrant_data:/qdrant/storage profiles: - qdrant api: build: context: . dockerfile: Dockerfile target: production image: report-genius-ai:latest container_name: report_genius_api restart: unless-stopped ports: - "8000:8000" environment: # Pin PORT=8000 so the container's CMD listens on 8000 and the existing # `localhost:8000` URL keeps working. The Dockerfile defaults to 7860 # (Hugging Face Spaces convention) when this env var is absent. PORT: "8000" OPENAI_API_KEY: "${OPENAI_API_KEY}" TENANT_SECRET_KEY: "${TENANT_SECRET_KEY:-change-me-in-production}" DATABASE_URL: "${DATABASE_URL:-sqlite+aiosqlite:////data/dev.db}" FAISS_INDEX_PATH: "/data/faiss_index" UPLOAD_DIR: "/mnt/data/uploads" CACHE_DIR: "/tmp/section_cache" MAX_CONTEXT_TOKENS: "${MAX_CONTEXT_TOKENS:-400}" MAX_OUTPUT_TOKENS: "${MAX_OUTPUT_TOKENS:-300}" RETRIEVAL_TOP_K: "${RETRIEVAL_TOP_K:-10}" RERANK_TOP_N: "${RERANK_TOP_N:-3}" MAX_CONCURRENT_INGESTS: "${MAX_CONCURRENT_INGESTS:-4}" RATE_LIMIT_GENERATE_RPM: "${RATE_LIMIT_GENERATE_RPM:-20}" RATE_LIMIT_READ_RPM: "${RATE_LIMIT_READ_RPM:-120}" DEV_MODE: "false" SCALE_OPTIMIZATION_PROFILE: "${SCALE_OPTIMIZATION_PROFILE:-false}" ENABLE_ASYNC_PIPELINE: "${ENABLE_ASYNC_PIPELINE:-false}" ENABLE_SPECULATIVE_EXECUTOR: "${ENABLE_SPECULATIVE_EXECUTOR:-false}" ENABLE_PROMPT_CACHING: "${ENABLE_PROMPT_CACHING:-false}" ENABLE_TEMPORAL_WORKFLOW: "${ENABLE_TEMPORAL_WORKFLOW:-false}" ENABLE_HYBRID_RETRIEVAL: "${ENABLE_HYBRID_RETRIEVAL:-true}" SEMANTIC_CACHE_ENABLED: "${SEMANTIC_CACHE_ENABLED:-true}" AGENTIC_INSPECTOR_WHEN_NOTES_ONLY: "${AGENTIC_INSPECTOR_WHEN_NOTES_ONLY:-false}" NOTES_ONLY_GENERATION: "${NOTES_ONLY_GENERATION:-true}" GENERATION_TIMEOUT_SECONDS: "${GENERATION_TIMEOUT_SECONDS:-3600}" GENERATION_STALE_SWEEP_SECONDS: "${GENERATION_STALE_SWEEP_SECONDS:-120}" TEMPORAL_HOST: "${TEMPORAL_HOST:-temporal:7233}" TEMPORAL_NAMESPACE: "${TEMPORAL_NAMESPACE:-default}" VECTORSTORE_BACKEND: "${VECTORSTORE_BACKEND:-faiss}" QDRANT_URL: "${QDRANT_URL:-http://qdrant:6333}" MAX_CONCURRENT_LLM_CALLS: "${MAX_CONCURRENT_LLM_CALLS:-10}" REDIS_URL: "${REDIS_URL:-}" ENABLE_JOB_QUEUE: "${ENABLE_JOB_QUEUE:-false}" volumes: - uploads:/mnt/data/uploads - section_cache:/tmp/section_cache - db_data:/data - faiss_index:/data/faiss_index depends_on: qdrant: condition: service_started required: false temporal: condition: service_started required: false healthcheck: test: ["CMD", "curl", "-fsS", "http://localhost:8000/health"] interval: 30s timeout: 10s retries: 3 start_period: 20s temporal: image: temporalio/auto-setup:1.25.2 container_name: report_genius_temporal restart: unless-stopped ports: - "7233:7233" environment: - DB=sqlite profiles: - temporal temporal-worker: build: context: . dockerfile: Dockerfile target: production image: report-genius-ai:latest container_name: report_genius_temporal_worker restart: unless-stopped command: ["python", "worker.py"] environment: OPENAI_API_KEY: "${OPENAI_API_KEY}" TENANT_SECRET_KEY: "${TENANT_SECRET_KEY:-change-me-in-production}" DATABASE_URL: "${DATABASE_URL:-sqlite+aiosqlite:////data/dev.db}" FAISS_INDEX_PATH: "/data/faiss_index" UPLOAD_DIR: "/mnt/data/uploads" CACHE_DIR: "/tmp/section_cache" ENABLE_TEMPORAL_WORKFLOW: "true" ENABLE_ASYNC_PIPELINE: "${ENABLE_ASYNC_PIPELINE:-false}" ENABLE_SPECULATIVE_EXECUTOR: "${ENABLE_SPECULATIVE_EXECUTOR:-false}" ENABLE_PROMPT_CACHING: "${ENABLE_PROMPT_CACHING:-false}" VECTORSTORE_BACKEND: "${VECTORSTORE_BACKEND:-faiss}" QDRANT_URL: "${QDRANT_URL:-http://qdrant:6333}" ENABLE_HYBRID_RETRIEVAL: "${ENABLE_HYBRID_RETRIEVAL:-true}" SEMANTIC_CACHE_ENABLED: "${SEMANTIC_CACHE_ENABLED:-true}" AGENTIC_INSPECTOR_WHEN_NOTES_ONLY: "${AGENTIC_INSPECTOR_WHEN_NOTES_ONLY:-false}" NOTES_ONLY_GENERATION: "${NOTES_ONLY_GENERATION:-true}" GENERATION_TIMEOUT_SECONDS: "${GENERATION_TIMEOUT_SECONDS:-3600}" GENERATION_STALE_SWEEP_SECONDS: "${GENERATION_STALE_SWEEP_SECONDS:-120}" TEMPORAL_HOST: "${TEMPORAL_HOST:-temporal:7233}" TEMPORAL_NAMESPACE: "${TEMPORAL_NAMESPACE:-default}" TEMPORAL_TASK_QUEUE: "${TEMPORAL_TASK_QUEUE:-reports}" MAX_CONCURRENT_LLM_CALLS: "${MAX_CONCURRENT_LLM_CALLS:-10}" volumes: - uploads:/mnt/data/uploads - section_cache:/tmp/section_cache - db_data:/data - faiss_index:/data/faiss_index depends_on: temporal: condition: service_started profiles: - temporal volumes: qdrant_data: driver: local uploads: driver: local section_cache: driver: local db_data: driver: local faiss_index: driver: local