services: # PostgreSQL - Base de datos core (app) postgres-a76: image: postgres:18-alpine container_name: anexo76-postgres-a76 environment: POSTGRES_DB: anexo76_core POSTGRES_USER: postgres POSTGRES_PASSWORD: ${POSTGRES_APP_PASSWORD:-postgres} POSTGRES_INITDB_ARGS: "--encoding=UTF8" ports: - "5432:5432" volumes: - postgres_app_data:/var/lib/postgresql networks: - backend-net restart: unless-stopped healthcheck: test: [ "CMD-SHELL", "pg_isready -U postgres -d anexo76_core || exit 1" ] interval: 5s timeout: 3s retries: 10 start_period: 20s logging: driver: "json-file" options: max-size: "10m" max-file: "3" deploy: resources: limits: memory: 512M reservations: memory: 256M shm_size: 128mb # Backend - FastAPI backend: build: context: ./backend dockerfile: Dockerfile args: - BUILDKIT_INLINE_CACHE=1 image: anexo76-backend:latest container_name: anexo76-backend environment: - DEBUG=${DEBUG:-True} - ENVIRONMENT=${ENVIRONMENT:-development} - PYTHONUNBUFFERED=1 - PYTHONDONTWRITEBYTECODE=1 - CORE_DB_HOST=${CORE_DB_HOST:-postgres-a76} - CORE_DB_PORT=${CORE_DB_PORT:-5432} - CORE_DB_NAME=${CORE_DB_NAME:-anexo76_core} - CORE_DB_USER=${CORE_DB_USER:-postgres} - CORE_DB_PASSWORD=${POSTGRES_APP_PASSWORD:-postgres} # Keycloak — apunta al Keycloak del Hub (ya no tiene Keycloak propio) - CORS_ORIGINS=${CORS_ORIGINS:-http://localhost:5173,http://localhost:3000} - SITAR_API_URL=${SITAR_API_URL} - SITAR_API_USER=${SITAR_API_USER} - SITAR_API_PASSWORD=${SITAR_API_PASSWORD} - COVE_FIEL_HASH_KEY=${COVE_FIEL_HASH_KEY} - COVE_FIEL_HASH_IV=${COVE_FIEL_HASH_IV} - COVE_API_URL=${COVE_API_URL:-https://api.vu.aduanasoft.com} - COVE_API_VERIFY_SSL=${COVE_API_VERIFY_SSL:-False} - VALKEY_URL=${VALKEY_URL:-redis://valkey:6379/0} - CENTRAL_SERVER_URL=${CENTRAL_SERVER_URL:-""} - SYNC_SECRET_TOKEN=${SYNC_SECRET_TOKEN:-change-this-sync-token-in-production} - SPOKE_URLS=${SPOKE_URLS:-""} # Hub — URL interna para validación de licencias - HUB_URL=${HUB_URL:-http://host.docker.internal:8001} - HUB_ADMIN_EMAIL=${HUB_ADMIN_EMAIL:-} - HUB_ADMIN_PASSWORD=${HUB_ADMIN_PASSWORD:-} - APP_PUBLIC_URL=${APP_PUBLIC_URL:-http://localhost:3001} - CSV_IMPORT_STORAGE=${CSV_IMPORT_STORAGE:-minio} - S3_ENDPOINT_URL=${S3_ENDPOINT_URL:-http://minio:9000} - S3_ACCESS_KEY=${S3_ACCESS_KEY:-${MINIO_ROOT_USER:-minioadmin}} - S3_SECRET_KEY=${S3_SECRET_KEY:-${MINIO_ROOT_PASSWORD:-minioadmin}} - S3_BUCKET=${S3_BUCKET:-anexo76} - S3_REGION=${S3_REGION:-us-east-1} - S3_USE_SSL=${S3_USE_SSL:-false} - S3_FILE_STORAGE=${S3_FILE_STORAGE:-true} - S3_PRESIGNED_EXPIRES_SECONDS=${S3_PRESIGNED_EXPIRES_SECONDS:-3600} ports: - "8000:8000" depends_on: postgres-a76: condition: service_healthy volumes: - ./backend:/app - backend_cache:/app/__pycache__ - backend_uploads:/app/uploads networks: - backend-net - frontend-net - hub-net restart: unless-stopped command: [ "uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000", "--reload", "--log-level", "info" ] # El lifespan corre Alembic antes de servir; 1.ª subida a DB vacía puede tardar varios minutos (E2E/CI) healthcheck: test: [ "CMD-SHELL", "curl -f http://localhost:8000/api/health || exit 1" ] interval: 20s timeout: 10s retries: 20 start_period: 600s logging: driver: "json-file" options: max-size: "10m" max-file: "3" deploy: resources: limits: memory: 1024M reservations: memory: 512M # Frontend - SvelteKit frontend: build: context: ./frontend dockerfile: Dockerfile args: - BUILDKIT_INLINE_CACHE=1 image: anexo76-frontend:latest container_name: anexo76-frontend environment: - NODE_ENV=${NODE_ENV:-development} - VITE_API_URL=${VITE_API_URL:-http://localhost:8000/api/} - INTERNAL_API_URL=${INTERNAL_API_URL:-http://backend:8000/api/} - VITE_KEYCLOAK_URL=${VITE_KEYCLOAK_URL:-http://localhost:8085/kcauth} - VITE_KEYCLOAK_REALM=${VITE_KEYCLOAK_REALM:-master} - VITE_KEYCLOAK_CLIENT_ID=${VITE_KEYCLOAK_CLIENT_ID:-anexo76-frontend} # SSR server-side — usa URL interna del contenedor Keycloak (más rápido, sin salir a la LAN) - KEYCLOAK_URL=${KEYCLOAK_URL:-http://hub-keycloak:8080/kcauth} - KEYCLOAK_REALM=${KEYCLOAK_REALM:-master} - KEYCLOAK_CLIENT_ID=${KEYCLOAK_CLIENT_ID:-anexo76-frontend} # Hub — URL pública para el browser y URL interna para server-side - VITE_HUB_URL=${VITE_HUB_URL:-http://localhost:8001} - HUB_URL=${HUB_URL:-http://localhost:8001} - INTERNAL_HUB_URL=${INTERNAL_HUB_URL:-http://host.docker.internal:8001} # CORS / CSRF — trusted origins para svelte.config.js - CORS_ORIGINS=${CORS_ORIGINS:-http://localhost:5173,http://localhost:3001} - TRUSTED_ORIGINS=${TRUSTED_ORIGINS:-} # SvelteKit ORIGIN — evita que request.url.origin use la IP/puerto interno del contenedor - ORIGIN=${ORIGIN:-http://localhost:5173} ports: - "5173:5173" depends_on: backend: condition: service_healthy volumes: - ./frontend:/app - frontend_node_modules:/app/node_modules networks: - frontend-net - hub-net restart: unless-stopped command: [ "pnpm", "run", "dev", "--", "--host", "0.0.0.0" ] healthcheck: test: [ "CMD-SHELL", "wget --no-verbose --tries=1 --spider http://localhost:5173/ || exit 1" ] interval: 15s timeout: 5s retries: 5 start_period: 45s logging: driver: "json-file" options: max-size: "10m" max-file: "3" # Celery Worker celery_worker: build: ./backend container_name: worker command: celery -A core.celery_app worker --loglevel=info environment: - DEBUG=${DEBUG:-True} - ENVIRONMENT=${ENVIRONMENT:-development} - PYTHONUNBUFFERED=1 - PYTHONDONTWRITEBYTECODE=1 - VALKEY_URL=${VALKEY_URL:-redis://valkey:6379/0} - CENTRAL_SERVER_URL=${CENTRAL_SERVER_URL:-""} - SYNC_SECRET_TOKEN=${SYNC_SECRET_TOKEN:-change-this-sync-token-in-production} - SPOKE_URLS=${SPOKE_URLS:-""} - CORE_DB_HOST=${CORE_DB_HOST:-postgres-a76} - CORE_DB_PORT=${CORE_DB_PORT:-5432} - CORE_DB_NAME=${CORE_DB_NAME:-anexo76_core} - CORE_DB_USER=${CORE_DB_USER:-postgres} - CORE_DB_PASSWORD=${POSTGRES_APP_PASSWORD:-postgres} - VALKEY_URL=redis://valkey:6379/0 - SITAR_API_URL=${SITAR_API_URL} - SITAR_API_USER=${SITAR_API_USER} - SITAR_API_PASSWORD=${SITAR_API_PASSWORD} - COVE_FIEL_HASH_KEY=${COVE_FIEL_HASH_KEY} - COVE_FIEL_HASH_IV=${COVE_FIEL_HASH_IV} - COVE_API_URL=${COVE_API_URL:-https://api.vu.aduanasoft.com} - DODA_API_BASE_URL=${DODA_API_BASE_URL} - DODA_API_VERIFY_SSL=${DODA_API_VERIFY_SSL:-False} - CSV_IMPORT_STORAGE=${CSV_IMPORT_STORAGE:-minio} - S3_ENDPOINT_URL=${S3_ENDPOINT_URL:-http://minio:9000} - S3_ACCESS_KEY=${S3_ACCESS_KEY:-${MINIO_ROOT_USER:-minioadmin}} - S3_SECRET_KEY=${S3_SECRET_KEY:-${MINIO_ROOT_PASSWORD:-minioadmin}} - S3_BUCKET=${S3_BUCKET:-anexo76} - S3_REGION=${S3_REGION:-us-east-1} - S3_USE_SSL=${S3_USE_SSL:-false} - S3_FILE_STORAGE=${S3_FILE_STORAGE:-true} depends_on: backend: condition: service_healthy valkey: condition: service_started volumes: - ./backend:/app - backend_cache:/app/__pycache__ - backend_uploads:/app/uploads networks: - backend-net # Celery Beat celery_beat: build: ./backend container_name: celery_beat command: celery -A core.celery_app beat --loglevel=info environment: - VALKEY_URL=${VALKEY_URL:-redis://valkey:6379/0} - CENTRAL_SERVER_URL=${CENTRAL_SERVER_URL:-""} - SYNC_SECRET_TOKEN=${SYNC_SECRET_TOKEN:-change-this-sync-token-in-production} - SPOKE_URLS=${SPOKE_URLS:-""} - CORE_DB_HOST=${CORE_DB_HOST:-postgres-a76} - CORE_DB_PORT=${CORE_DB_PORT:-5432} - CORE_DB_NAME=${CORE_DB_NAME:-anexo76_core} - CORE_DB_USER=${CORE_DB_USER:-postgres} - CORE_DB_PASSWORD=${POSTGRES_APP_PASSWORD:-postgres} - SITAR_API_URL=${SITAR_API_URL} - SITAR_API_USER=${SITAR_API_USER} - SITAR_API_PASSWORD=${SITAR_API_PASSWORD} - COVE_FIEL_HASH_KEY=${COVE_FIEL_HASH_KEY} - COVE_FIEL_HASH_IV=${COVE_FIEL_HASH_IV} - COVE_API_URL=${COVE_API_URL:-https://api.vu.aduanasoft.com} - DODA_API_BASE_URL=${DODA_API_BASE_URL} - DODA_API_VERIFY_SSL=${DODA_API_VERIFY_SSL:-False} - CSV_IMPORT_STORAGE=${CSV_IMPORT_STORAGE:-minio} - S3_ENDPOINT_URL=${S3_ENDPOINT_URL:-http://minio:9000} - S3_ACCESS_KEY=${S3_ACCESS_KEY:-${MINIO_ROOT_USER:-minioadmin}} - S3_SECRET_KEY=${S3_SECRET_KEY:-${MINIO_ROOT_PASSWORD:-minioadmin}} - S3_BUCKET=${S3_BUCKET:-anexo76} - S3_REGION=${S3_REGION:-us-east-1} - S3_USE_SSL=${S3_USE_SSL:-false} - S3_FILE_STORAGE=${S3_FILE_STORAGE:-true} depends_on: backend: condition: service_healthy valkey: condition: service_started volumes: - ./backend:/app - backend_cache:/app/__pycache__ - backend_uploads:/app/uploads networks: - backend-net valkey: image: valkey/valkey:7.2 container_name: valkey restart: always ports: - "6379:6379" networks: - backend-net minio: image: minio/minio:RELEASE.2025-09-07T16-13-09Z container_name: anexo76-minio command: server /data --console-address ":9001" environment: MINIO_ROOT_USER: ${MINIO_ROOT_USER:-minioadmin} MINIO_ROOT_PASSWORD: ${MINIO_ROOT_PASSWORD:-minioadmin} ports: - "${MINIO_API_PORT:-9100}:9000" - "${MINIO_CONSOLE_PORT:-9101}:9001" volumes: - minio_data:/data networks: - backend-net restart: unless-stopped healthcheck: test: [ "CMD-SHELL", "curl -f http://127.0.0.1:9000/minio/health/live || exit 1" ] interval: 10s timeout: 5s retries: 5 start_period: 15s logging: driver: "json-file" options: max-size: "10m" max-file: "3" volumes: postgres_app_data: driver: local frontend_node_modules: driver: local backend_cache: driver: local backend_uploads: driver: local minio_data: driver: local networks: backend-net: driver: bridge frontend-net: driver: bridge hub-net: external: true name: aduanasoft-hub_default