name: leads-extractor services: postgres: image: postgres:18 restart: unless-stopped environment: POSTGRES_DB: ${POSTGRES_DB:-leads_extractor} POSTGRES_USER: ${POSTGRES_USER:-leads} POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-leads} healthcheck: test: ["CMD-SHELL", "pg_isready -U \"$${POSTGRES_USER}\" -d \"$${POSTGRES_DB}\""] interval: 5s timeout: 5s retries: 20 start_period: 10s volumes: - postgres_data:/var/lib/postgresql networks: - internal backend: build: context: ./backend dockerfile: Dockerfile restart: unless-stopped depends_on: postgres: condition: service_healthy environment: SERVER_HOST: 0.0.0.0 SERVER_PORT: 8080 FRONTEND_ORIGIN: ${FRONTEND_URL:-http://localhost:8090} DATABASE_URL: postgres://${POSTGRES_USER:-leads}:${POSTGRES_PASSWORD:-leads}@postgres:5432/${POSTGRES_DB:-leads_extractor} MEDIA_DIR: /data/media RUST_LOG: ${RUST_LOG:-backend=info} AUTH_USERNAME: ${AUTH_USERNAME} AUTH_PASSWORD: ${AUTH_PASSWORD} AUTH_SESSION_TTL_SECS: ${AUTH_SESSION_TTL_SECS:-43200} AUTH_COOKIE_SECURE: ${AUTH_COOKIE_SECURE:-false} AUTH_COOKIE_SAME_SITE: ${AUTH_COOKIE_SAME_SITE:-strict} RATE_LIMIT_ENABLED: ${RATE_LIMIT_ENABLED:-true} RATE_LIMIT_BURST_SIZE: ${RATE_LIMIT_BURST_SIZE:-120} RATE_LIMIT_PERIOD_MS: ${RATE_LIMIT_PERIOD_MS:-200} RATE_LIMIT_LOGIN_BURST_SIZE: ${RATE_LIMIT_LOGIN_BURST_SIZE:-5} RATE_LIMIT_LOGIN_PERIOD_SECS: ${RATE_LIMIT_LOGIN_PERIOD_SECS:-30} OPENAI_API_KEY: ${OPENAI_API_KEY} OPENAI_MODEL: ${OPENAI_MODEL:-gpt-5.6-luna} OPENAI_BASE_URL: ${OPENAI_BASE_URL:-https://api.openai.com/v1} OPENAI_TIMEOUT_SECS: ${OPENAI_TIMEOUT_SECS:-120} OPENAI_MAX_RETRIES: ${OPENAI_MAX_RETRIES:-6} OPENAI_INPUT_COST_PER_1M_USD: ${OPENAI_INPUT_COST_PER_1M_USD:-0.15} OPENAI_OUTPUT_COST_PER_1M_USD: ${OPENAI_OUTPUT_COST_PER_1M_USD:-0.60} OPENAI_MONTHLY_BUDGET_USD: ${OPENAI_MONTHLY_BUDGET_USD:-50.00} DATAIMPULSE_PROXY_ENABLED: ${DATAIMPULSE_PROXY_ENABLED:-true} DATAIMPULSE_PROXY_SCHEME: ${DATAIMPULSE_PROXY_SCHEME:-http} DATAIMPULSE_PROXY_HOST: ${DATAIMPULSE_PROXY_HOST:-gw.dataimpulse.com} DATAIMPULSE_PROXY_PORT: ${DATAIMPULSE_PROXY_PORT:-823} DATAIMPULSE_PROXY_USERNAME: ${DATAIMPULSE_PROXY_USERNAME} DATAIMPULSE_PROXY_PASSWORD: ${DATAIMPULSE_PROXY_PASSWORD} DATAIMPULSE_PROXY_COUNTRY: ${DATAIMPULSE_PROXY_COUNTRY:-br} DATAIMPULSE_PROXY_STICKY: ${DATAIMPULSE_PROXY_STICKY:-false} WORKER_CONCURRENCY: ${WORKER_CONCURRENCY:-8} BROWSER_CONCURRENCY: ${BROWSER_CONCURRENCY:-4} JOB_POLL_INTERVAL_MS: ${JOB_POLL_INTERVAL_MS:-750} REQUEST_TIMEOUT_SECS: ${REQUEST_TIMEOUT_SECS:-45} BROWSER_TIMEOUT_SECS: ${BROWSER_TIMEOUT_SECS:-75} BROWSER_NO_SANDBOX: ${BROWSER_NO_SANDBOX:-true} BROWSER_MIN_NAVIGATION_DELAY_MS: ${BROWSER_MIN_NAVIGATION_DELAY_MS:-2500} BROWSER_MAX_NAVIGATION_DELAY_MS: ${BROWSER_MAX_NAVIGATION_DELAY_MS:-15000} BROWSER_MACRO_PAUSE_EVERY_MIN: ${BROWSER_MACRO_PAUSE_EVERY_MIN:-15} BROWSER_MACRO_PAUSE_EVERY_MAX: ${BROWSER_MACRO_PAUSE_EVERY_MAX:-20} BROWSER_MACRO_PAUSE_MIN_SECS: ${BROWSER_MACRO_PAUSE_MIN_SECS:-30} BROWSER_MACRO_PAUSE_MAX_SECS: ${BROWSER_MACRO_PAUSE_MAX_SECS:-90} CRAWL_MAX_DEPTH: ${CRAWL_MAX_DEPTH:-5} CRAWL_MAX_PAGES_PER_INTERVIEWEE: ${CRAWL_MAX_PAGES_PER_INTERVIEWEE:-250} MAX_INTERVIEWEES_PER_RUN: ${MAX_INTERVIEWEES_PER_RUN:-0} volumes: - media_data:/data/media ports: - "${BACKEND_PORT:-8081}:8080" healthcheck: # /api/health exige sessão autenticada; 401 já indica que o servidor # está de pé e respondendo (só uma requisição de rede falhando é erro). test: ["CMD-SHELL", "curl -s -o /dev/null -w '%{http_code}' http://127.0.0.1:8080/api/health | grep -qE '^(200|401)$'"] interval: 10s timeout: 5s retries: 15 start_period: 10s networks: - internal frontend: build: context: ./frontend dockerfile: Dockerfile args: VITE_API_URL: ${BACKEND_URL:-http://localhost:8081} VITE_API_POLL_INTERVAL: ${VITE_API_POLL_INTERVAL:-6000} VITE_DEMO_MODE: ${VITE_DEMO_MODE:-false} restart: unless-stopped depends_on: backend: condition: service_healthy ports: - "${FRONTEND_PORT:-8080}:8080" networks: - internal networks: internal: volumes: postgres_data: media_data: