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: - "5939:5432" volumes: - postgres_app_data:/var/lib/postgresql/data 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: image: dev.aduanasoft.com/anexo76/backend:latest container_name: anexo76-backend environment: - DEBUG=${DEBUG:-True} - ENVIRONMENT=${ENVIRONMENT:-production} - 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 — fuente única de autenticación: Workspace (workspace.aduanasoft.com) - KEYCLOAK_SERVER_URL=${KEYCLOAK_SERVER_URL:-https://workspace.aduanasoft.com/kcauth} - KEYCLOAK_REALM=${KEYCLOAK_REALM:-master} - KEYCLOAK_CLIENT_ID=${KEYCLOAK_CLIENT_ID:-anexo76-frontend} - KEYCLOAK_CLIENT_SECRET=${KEYCLOAK_CLIENT_SECRET:-} - CORS_ORIGINS=${CORS_ORIGINS:-https://anexo76-dev.aduanasoft.com,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=${HUB_URL:-https://workspace.aduanasoft.com} - HUB_ADMIN_EMAIL=${HUB_ADMIN_EMAIL:-} - HUB_ADMIN_PASSWORD=${HUB_ADMIN_PASSWORD:-} - APP_PUBLIC_URL=${APP_PUBLIC_URL:-https://anexo76-dev.aduanasoft.com} - SMTP_HOST=${SMTP_HOST:-smtp.gmail.com} - SMTP_PORT=${SMTP_PORT:-587} - SMTP_USER=${SMTP_USER:-} - SMTP_PASSWORD=${SMTP_PASSWORD:-} - SMTP_FROM_NAME=${SMTP_FROM_NAME:-Sistema Anexo76} - SMTP_USE_TLS=${SMTP_USE_TLS:-true} - 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: - "3467:8000" depends_on: postgres-a76: condition: service_healthy minio: condition: service_healthy volumes: - backend_uploads:/app/uploads - backend_layouts:/app/layouts networks: - backend-net - frontend-net restart: unless-stopped command: [ "gunicorn", "main:app", "-k", "uvicorn.workers.UvicornWorker", "-w", "${WEB_CONCURRENCY:-1}", "-b", "0.0.0.0:8000", "--log-level", "info", "--forwarded-allow-ips", "*" ] healthcheck: test: [ "CMD-SHELL", "curl -f http://localhost:8000/api/health || exit 1" ] interval: 15s timeout: 5s retries: 5 start_period: 60s logging: driver: "json-file" options: max-size: "10m" max-file: "3" deploy: resources: limits: memory: 512M reservations: memory: 256M # celery celery_worker: image: dev.aduanasoft.com/anexo76/backend:latest container_name: worker command: celery -A core.celery_app worker --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} - COVE_API_VERIFY_SSL=${COVE_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 - valkey volumes: - backend_layouts:/app/layouts networks: - backend-net celery_beat: image: dev.aduanasoft.com/anexo76/backend:latest 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} - COVE_API_VERIFY_SSL=${COVE_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 - valkey volumes: - backend_layouts:/app/layouts networks: - backend-net restart: unless-stopped valkey: image: valkey/valkey:7.2 container_name: valkey restart: always ports: - "6579: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" # Frontend - SvelteKit frontend: image: dev.aduanasoft.com/anexo76/frontend:latest container_name: anexo76-frontend environment: - NODE_ENV=${NODE_ENV:-production} - VITE_API_URL=${VITE_API_URL:-https://anexo76-dev.aduanasoft.com/api} - INTERNAL_API_URL=${INTERNAL_API_URL:-http://backend:8000/api/} - INTERNAL_HUB_URL=https://workspace.aduanasoft.com - HUB_URL=https://workspace.aduanasoft.com # VITE_HUB_URL: URL pública del Hub para el browser (bakeada en build, pero también se lee # en runtime por $env/dynamic/private en workspace-auth.ts). El root .env puede tener # localhost; isDevOnlyUrl() lo descarta y cae a HUB_URL (correcto arriba). - VITE_HUB_URL=https://workspace.aduanasoft.com # Keycloak — fuente única de autenticación: Workspace (workspace.aduanasoft.com) # VITE_KEYCLOAK_URL: URL pública que el browser usa para el flujo OIDC. # Debe ser el mismo Keycloak donde el usuario tiene su sesión de Workspace. - VITE_KEYCLOAK_URL=${VITE_KEYCLOAK_URL:-https://workspace.aduanasoft.com/kcauth} - VITE_KEYCLOAK_REALM=${VITE_KEYCLOAK_REALM:-master} - VITE_KEYCLOAK_CLIENT_ID=${VITE_KEYCLOAK_CLIENT_ID:-anexo76-frontend} # KEYCLOAK_URL: URL que usa SvelteKit server-side para intercambiar código por tokens. - KEYCLOAK_URL=${KEYCLOAK_URL:-https://workspace.aduanasoft.com/kcauth} - KEYCLOAK_REALM=${KEYCLOAK_REALM:-master} - KEYCLOAK_CLIENT_ID=${KEYCLOAK_CLIENT_ID:-anexo76-frontend} - KEYCLOAK_CLIENT_SECRET=${KEYCLOAK_CLIENT_SECRET:-} # SvelteKit ORIGIN — determina url.origin en load functions y el flag secure de cookies. # ⚠️ Docker Compose carga el .env raíz automáticamente. Si ese .env tiene # ORIGIN=http://localhost:5173 (valor dev), sobreescribe el default de abajo. # SITE_URL es el fallback que usa el código cuando url.origin es localhost. - ORIGIN=${ORIGIN:-https://anexo76-dev.aduanasoft.com} # SITE_URL: hardcoded — no depende del .env raíz. # resolveSystemBaseUrl() lo usa cuando ORIGIN tiene localhost (root .env de dev en prod). - SITE_URL=https://anexo76-dev.aduanasoft.com ports: - "5111:5173" depends_on: backend: condition: service_healthy networks: - frontend-net - backend-net restart: unless-stopped command: [ "pnpm", "start" ] 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" deploy: resources: limits: memory: 1G reservations: memory: 512M volumes: postgres_app_data: driver: local frontend_node_modules: driver: local backend_cache: driver: local backend_uploads: driver: local backend_layouts: driver: local minio_data: driver: local networks: backend-net: driver: bridge frontend-net: driver: bridge