name: podcast-intelligence x-backend-env: &backend-env APP_ENV: ${APP_ENV:-development} APP_DEBUG: ${APP_DEBUG:-true} APP_SECRET_KEY: ${APP_SECRET_KEY:-change-me-in-production} APP_ALLOWED_ORIGINS: ${APP_ALLOWED_ORIGINS:-http://localhost:3000} DEFAULT_WORKSPACE_ID: ${DEFAULT_WORKSPACE_ID:-00000000-0000-0000-0000-000000000001} DATABASE_URL: ${DATABASE_URL:-postgresql+psycopg://podcast:podcast@postgres:5432/podcast_intelligence} REDIS_URL: ${REDIS_URL:-redis://redis:6379/0} S3_ENDPOINT_URL: ${S3_ENDPOINT_URL:-http://minio:9000} S3_PUBLIC_ENDPOINT_URL: ${S3_PUBLIC_ENDPOINT_URL:-http://localhost:9000} S3_ACCESS_KEY: ${S3_ACCESS_KEY:-podcast} S3_SECRET_KEY: ${S3_SECRET_KEY:-podcast-secret} S3_BUCKET: ${S3_BUCKET:-podcast-media} S3_REGION: ${S3_REGION:-us-east-1} S3_SECURE: ${S3_SECURE:-false} PLAYBACK_URL_EXPIRES_SECONDS: ${PLAYBACK_URL_EXPIRES_SECONDS:-900} AUTH_MODE: ${AUTH_MODE:-dev} OIDC_ISSUER: ${OIDC_ISSUER:-} OIDC_AUDIENCE: ${OIDC_AUDIENCE:-} OIDC_JWKS_URL: ${OIDC_JWKS_URL:-} OIDC_WORKSPACE_CLAIM: ${OIDC_WORKSPACE_CLAIM:-workspace_id} AI_PROFILE: ${AI_PROFILE:-demo} TRANSCRIPTION_PROVIDER: ${TRANSCRIPTION_PROVIDER:-demo} EMBEDDING_PROVIDER: ${EMBEDDING_PROVIDER:-demo} LLM_PROVIDER: ${LLM_PROVIDER:-demo} OPENAI_API_KEY: ${OPENAI_API_KEY:-} OPENAI_BASE_URL: ${OPENAI_BASE_URL:-} OPENAI_TRANSCRIPTION_BASE_URL: ${OPENAI_TRANSCRIPTION_BASE_URL:-} OPENAI_EMBEDDING_BASE_URL: ${OPENAI_EMBEDDING_BASE_URL:-} OPENAI_LLM_BASE_URL: ${OPENAI_LLM_BASE_URL:-} OPENAI_TRANSCRIPTION_API_KEY: ${OPENAI_TRANSCRIPTION_API_KEY:-} OPENAI_EMBEDDING_API_KEY: ${OPENAI_EMBEDDING_API_KEY:-} OPENAI_LLM_API_KEY: ${OPENAI_LLM_API_KEY:-} OPENAI_TRANSCRIPTION_MODEL: ${OPENAI_TRANSCRIPTION_MODEL:-gpt-4o-transcribe-diarize} OPENAI_EMBEDDING_MODEL: ${OPENAI_EMBEDDING_MODEL:-text-embedding-3-small} OPENAI_LLM_MODEL: ${OPENAI_LLM_MODEL:-gpt-5.6-luna} OPENAI_LLM_API: ${OPENAI_LLM_API:-chat_completions} OPENAI_EMBEDDING_SEND_DIMENSIONS: ${OPENAI_EMBEDDING_SEND_DIMENSIONS:-false} OPENAI_REASONING_EFFORT: ${OPENAI_REASONING_EFFORT:-none} OPENAI_MAX_UPLOAD_BYTES: ${OPENAI_MAX_UPLOAD_BYTES:-25165824} TRANSCRIPTION_CHUNK_SECONDS: ${TRANSCRIPTION_CHUNK_SECONDS:-900} TRANSCRIPTION_CHUNK_BITRATE: ${TRANSCRIPTION_CHUNK_BITRATE:-64k} EMBEDDING_DIMENSION: ${EMBEDDING_DIMENSION:-1536} EMBEDDING_BATCH_SIZE: ${EMBEDDING_BATCH_SIZE:-4} STREAMING_STT_URL: ${STREAMING_STT_URL:-} STREAMING_STT_API_KEY: ${STREAMING_STT_API_KEY:-} STREAMING_STT_MODEL: ${STREAMING_STT_MODEL:-default} STREAMING_STT_LANGUAGE: ${STREAMING_STT_LANGUAGE:-pt} STREAMING_STT_FRAME_SECONDS: ${STREAMING_STT_FRAME_SECONDS:-10} STREAMING_STT_BATCH_SECONDS: ${STREAMING_STT_BATCH_SECONDS:-120} STREAMING_STT_OPEN_TIMEOUT_SECONDS: ${STREAMING_STT_OPEN_TIMEOUT_SECONDS:-10} STREAMING_STT_CLOSE_TIMEOUT_SECONDS: ${STREAMING_STT_CLOSE_TIMEOUT_SECONDS:-10} CODEX_BINARY: ${CODEX_BINARY:-codex} CODEX_MODEL: ${CODEX_MODEL:-} CODEX_TIMEOUT_SECONDS: ${CODEX_TIMEOUT_SECONDS:-300} CODEX_WORKDIR: ${CODEX_WORKDIR:-/tmp/podcast-intelligence-codex} SPOTIFY_CLIENT_ID: ${SPOTIFY_CLIENT_ID:-} SPOTIFY_CLIENT_SECRET: ${SPOTIFY_CLIENT_SECRET:-} HTTP_USER_AGENT: ${HTTP_USER_AGENT:-PodcastIntelligence/0.2 (+https://localhost)} MAX_REMOTE_FILE_BYTES: ${MAX_REMOTE_FILE_BYTES:-1073741824} MAX_AUDIO_DURATION_SECONDS: ${MAX_AUDIO_DURATION_SECONDS:-21600} DOWNLOAD_TIMEOUT_SECONDS: ${DOWNLOAD_TIMEOUT_SECONDS:-120} FFMPEG_BINARY: ${FFMPEG_BINARY:-ffmpeg} FFPROBE_BINARY: ${FFPROBE_BINARY:-ffprobe} AUDIO_SAMPLE_RATE: ${AUDIO_SAMPLE_RATE:-16000} AUDIO_CHANNELS: ${AUDIO_CHANNELS:-1} CHUNK_TARGET_TOKENS: ${CHUNK_TARGET_TOKENS:-600} CHUNK_OVERLAP_TOKENS: ${CHUNK_OVERLAP_TOKENS:-80} RETRIEVAL_TOP_K: ${RETRIEVAL_TOP_K:-10} RETRIEVAL_LEXICAL_WEIGHT: ${RETRIEVAL_LEXICAL_WEIGHT:-0.35} RETRIEVAL_VECTOR_WEIGHT: ${RETRIEVAL_VECTOR_WEIGHT:-0.65} services: postgres: image: pgvector/pgvector:pg17@sha256:d2ef61f42ef767baa5a1475393303cc235bcd92febd9d7014eddb48b41f3bad0 environment: POSTGRES_DB: ${POSTGRES_DB:-podcast_intelligence} POSTGRES_USER: ${POSTGRES_USER:-podcast} POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-podcast} ports: - "${HOST_BIND_ADDRESS:-127.0.0.1}:5432:5432" volumes: - postgres_data:/var/lib/postgresql/data healthcheck: test: [ "CMD-SHELL", "pg_isready -U ${POSTGRES_USER:-podcast} -d ${POSTGRES_DB:-podcast_intelligence}", ] interval: 5s timeout: 5s retries: 20 redis: image: redis:7.4-alpine@sha256:e7723ff73d963f5cc6d9c4643ea3d989527a402a319239054e9472a7fb9219a2 command: ["redis-server", "--appendonly", "yes"] ports: - "${HOST_BIND_ADDRESS:-127.0.0.1}:6379:6379" volumes: - redis_data:/data healthcheck: test: ["CMD", "redis-cli", "ping"] interval: 5s timeout: 3s retries: 20 minio: image: minio/minio:latest@sha256:14cea493d9a34af32f524e538b8346cf79f3321eff8e708c1e2960462bd8936e command: server /data --console-address ":9001" environment: MINIO_ROOT_USER: ${MINIO_ROOT_USER:-podcast} MINIO_ROOT_PASSWORD: ${MINIO_ROOT_PASSWORD:-podcast-secret} MINIO_API_CORS_ALLOW_ORIGIN: ${MINIO_API_CORS_ALLOW_ORIGIN:-http://localhost:3000} ports: - "${HOST_BIND_ADDRESS:-127.0.0.1}:9000:9000" - "${HOST_BIND_ADDRESS:-127.0.0.1}:9001:9001" volumes: - minio_data:/data healthcheck: test: ["CMD", "curl", "-f", "http://localhost:9000/minio/health/live"] interval: 5s timeout: 5s retries: 20 minio-init: image: minio/mc:latest@sha256:a7fe349ef4bd8521fb8497f55c6042871b2ae640607cf99d9bede5e9bdf11727 depends_on: minio: condition: service_healthy entrypoint: >- /bin/sh -c " mc alias set local http://minio:9000 ${MINIO_ROOT_USER:-podcast} ${MINIO_ROOT_PASSWORD:-podcast-secret}; mc mb --ignore-existing local/${S3_BUCKET:-podcast-media}; mc anonymous set none local/${S3_BUCKET:-podcast-media}; exit 0; " api: build: context: ./backend environment: *backend-env command: >- /bin/sh -c "alembic upgrade head && python -m podcast_intelligence.cli bootstrap && uvicorn podcast_intelligence.main:app --host 0.0.0.0 --port 8000" ports: - "${HOST_BIND_ADDRESS:-127.0.0.1}:8000:8000" depends_on: postgres: condition: service_healthy redis: condition: service_healthy minio-init: condition: service_completed_successfully healthcheck: test: ["CMD", "curl", "-f", "http://localhost:8000/health/ready"] interval: 10s timeout: 5s retries: 20 worker: build: context: ./backend environment: *backend-env command: celery -A podcast_intelligence.worker.celery_app:celery_app worker --loglevel=INFO --concurrency=2 depends_on: api: condition: service_healthy volumes: - processing_tmp:/tmp/podcast-intelligence beat: build: context: ./backend environment: *backend-env command: celery -A podcast_intelligence.worker.celery_app:celery_app beat --loglevel=INFO depends_on: api: condition: service_healthy mcp: build: context: ./backend environment: <<: *backend-env MCP_HOST: 0.0.0.0 MCP_PORT: 8001 command: python -m podcast_intelligence.mcp_server ports: - "${HOST_BIND_ADDRESS:-127.0.0.1}:8001:8001" depends_on: api: condition: service_healthy frontend: build: context: ./frontend args: VITE_API_URL: ${VITE_API_URL:-http://localhost:8000} VITE_MCP_URL: ${VITE_MCP_URL:-http://localhost:8001/mcp} ports: - "${HOST_BIND_ADDRESS:-127.0.0.1}:3000:3000" depends_on: api: condition: service_healthy volumes: postgres_data: redis_data: minio_data: processing_tmp: