services: database: image: postgres:16-alpine container_name: ipsextractor-db environment: POSTGRES_DB: ipsextractor POSTGRES_USER: ipsextractor POSTGRES_PASSWORD: ipsextractor volumes: - ipsextractor_db:/var/lib/postgresql/data healthcheck: test: ["CMD-SHELL", "pg_isready -U ipsextractor -d ipsextractor"] interval: 5s timeout: 5s retries: 15 start_period: 90s redis: image: redis:7-alpine container_name: ipsextractor-redis command: redis-server --maxmemory 512mb --maxmemory-policy allkeys-lru --save "" healthcheck: test: ["CMD", "redis-cli", "ping"] interval: 5s timeout: 3s retries: 10 db-migrations: build: context: ./services/db dockerfile: Dockerfile container_name: ipsextractor-db-migrations depends_on: database: condition: service_healthy environment: DATABASE_URL: postgresql+psycopg://ipsextractor:ipsextractor@database:5432/ipsextractor restart: on-failure extraction: build: context: ./services/extraction dockerfile: Dockerfile container_name: ipsextractor-extraction depends_on: database: condition: service_healthy redis: condition: service_healthy db-migrations: condition: service_completed_successfully environment: DATABASE_URL: postgresql+psycopg://ipsextractor:ipsextractor@database:5432/ipsextractor REDIS_URL: redis://redis:6379/0 ASYNC_QUEUE: ${ASYNC_QUEUE:-false} EXTRACTOR_BASE_URL: ${EXTRACTOR_BASE_URL:-https://api.openai.com/v1} EXTRACTOR_API_KEY: ${EXTRACTOR_API_KEY} EXTRACTOR_MODEL: ${EXTRACTOR_MODEL:-gpt-5.4-mini} NORMALIZER_BASE_URL: ${NORMALIZER_BASE_URL:-${EXTRACTOR_BASE_URL:-https://api.openai.com/v1}} NORMALIZER_API_KEY: ${NORMALIZER_API_KEY:-${EXTRACTOR_API_KEY}} NORMALIZER_MODEL: ${NORMALIZER_MODEL:-gpt-5.4-mini} EXTRACTOR_TIMEOUT_S: ${EXTRACTOR_TIMEOUT_S:-120} PDF_DPI: ${PDF_DPI:-150} PDF_THREAD_COUNT: ${PDF_THREAD_COUNT:-4} CONCURRENCY_LIMIT: ${CONCURRENCY_LIMIT:-50} ALLOWED_DOCUMENT_TYPES: ${ALLOWED_DOCUMENT_TYPES:-CC,TI,CE,RC,PA,PPT,PEP,EPT,NUIP} volumes: - ${IPS_DOCUMENT_ROOT:-./data}:/data - ips_queue:/tmp/ips_queue healthcheck: test: ["CMD", "python", "-c", "import urllib.request; urllib.request.urlopen('http://127.0.0.1:8001/health', timeout=5)"] interval: 10s timeout: 5s retries: 5 start_period: 30s # Escala horizontalmente: docker compose up --scale worker=4 worker: build: context: ./services/extraction dockerfile: Dockerfile command: python -m worker.main depends_on: database: condition: service_healthy redis: condition: service_healthy db-migrations: condition: service_completed_successfully deploy: replicas: ${WORKER_REPLICAS:-2} environment: DATABASE_URL: postgresql+psycopg://ipsextractor:ipsextractor@database:5432/ipsextractor REDIS_URL: redis://redis:6379/0 ASYNC_QUEUE: ${ASYNC_QUEUE:-false} EXTRACTOR_BASE_URL: ${EXTRACTOR_BASE_URL:-https://api.openai.com/v1} EXTRACTOR_API_KEY: ${EXTRACTOR_API_KEY} EXTRACTOR_MODEL: ${EXTRACTOR_MODEL:-gpt-5.4-mini} NORMALIZER_BASE_URL: ${NORMALIZER_BASE_URL:-${EXTRACTOR_BASE_URL:-https://api.openai.com/v1}} NORMALIZER_API_KEY: ${NORMALIZER_API_KEY:-${EXTRACTOR_API_KEY}} NORMALIZER_MODEL: ${NORMALIZER_MODEL:-gpt-5.4-mini} EXTRACTOR_TIMEOUT_S: ${EXTRACTOR_TIMEOUT_S:-120} PDF_DPI: ${PDF_DPI:-150} PDF_THREAD_COUNT: ${PDF_THREAD_COUNT:-4} CONCURRENCY_LIMIT: ${CONCURRENCY_LIMIT:-50} ALLOWED_DOCUMENT_TYPES: ${ALLOWED_DOCUMENT_TYPES:-CC,TI,CE,RC,PA,PPT,PEP,EPT,NUIP} WORKER_CONCURRENCY: ${WORKER_CONCURRENCY:-4} volumes: - ips_queue:/tmp/ips_queue # Código montado para que fixes del worker (p.ej. FOR UPDATE) apliquen sin rebuild. - ./services/extraction/worker:/app/worker - ./services/extraction/domain:/app/domain - ./services/extraction/core:/app/core - ./services/extraction/infrastructure:/app/infrastructure cleaning: build: context: ./services/cleaning dockerfile: Dockerfile container_name: ipsextractor-cleaning depends_on: database: condition: service_healthy db-migrations: condition: service_completed_successfully environment: DATABASE_URL: postgresql+psycopg://ipsextractor:ipsextractor@database:5432/ipsextractor poblacion_apta: build: context: ./services/poblacion_apta dockerfile: Dockerfile container_name: ipsextractor-poblacion-apta depends_on: database: condition: service_healthy db-migrations: condition: service_completed_successfully environment: DATABASE_URL: postgresql+psycopg://ipsextractor:ipsextractor@database:5432/ipsextractor count-api: build: context: ./services/services-count-facturas-medicas-serving dockerfile: Dockerfile container_name: ipsextractor-count-api restart: unless-stopped depends_on: db-migrations: condition: service_completed_successfully environment: PORT: "8080" # OCR_DPI: 150 reduce RAM (~4×) frente a 300; suficiente para contar facturas. OCR_DPI: ${OCR_DPI:-150} OCR_LANG: ${OCR_LANG:-spa} # Con varios lotes paralelos desde el desktop, limitar goroutines evita OOM. OCR_POOL_SIZE: ${OCR_POOL_SIZE:-4} SYNC_MAX_FILES: ${SYNC_MAX_FILES:-50} SYNC_PROCESS_CONCURRENCY: ${SYNC_PROCESS_CONCURRENCY:-4} MAX_PDF_SIZE_MB: ${MAX_PDF_SIZE_MB:-20} LOG_PIPELINE_TIMING: ${LOG_PIPELINE_TIMING:-false} volumes: - ./services/services-count-facturas-medicas-serving/models/latest:/app/models:ro healthcheck: test: ["CMD", "curl", "-f", "http://localhost:8080/health"] interval: 10s timeout: 5s retries: 5 start_period: 90s deploy: resources: limits: memory: ${COUNT_API_MEMORY:-4g} bot-descarga: build: context: ./services/bot_descarga dockerfile: Dockerfile container_name: ipsextractor-bot-descarga restart: unless-stopped depends_on: database: condition: service_healthy environment: BOT_API_TOKEN: ${BOT_API_TOKEN:-dev-token-change-me} BOT_API_HOST: 0.0.0.0 BOT_API_PORT: "8200" DATABASE_URL: postgresql+psycopg://ipsextractor:ipsextractor@database:5432/ipsextractor MINIO_ENDPOINT: ${MINIO_ENDPOINT:-https://s3.builker.xyz} MINIO_BUCKET: ${MINIO_BUCKET:-pys-cuentas-medicas} MINIO_ANONYMOUS: ${MINIO_ANONYMOUS:-1} MINIO_ACCESS_KEY: ${MINIO_ACCESS_KEY:-} MINIO_SECRET_KEY: ${MINIO_SECRET_KEY:-} MINIO_REGION: ${MINIO_REGION:-us-east-1} healthcheck: test: ["CMD", "curl", "-f", "http://127.0.0.1:8200/health"] interval: 10s timeout: 5s retries: 5 start_period: 20s api_gateway: build: context: ./services/api_gateway dockerfile: Dockerfile container_name: ipsextractor-api depends_on: extraction: condition: service_healthy cleaning: condition: service_started poblacion_apta: condition: service_started count-api: condition: service_healthy bot-descarga: condition: service_healthy environment: EXTRACTION_URL: http://extraction:8001 CLEANING_URL: http://cleaning:8002 POBLACION_APTA_URL: http://poblacion_apta:8003 COUNT_URL: http://count-api:8080 BOT_DESCARGA_URL: http://bot-descarga:8200 ports: # Único puerto expuesto al host. Los demás servicios solo son accesibles en la red Docker. - "0.0.0.0:8189:8000" healthcheck: test: ["CMD", "curl", "-f", "http://localhost:8000/health/live"] interval: 10s timeout: 5s retries: 5 start_period: 15s volumes: ipsextractor_db: ips_queue: