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/data - ./scripts/postgres-app-entrypoint.sh:/docker-entrypoint-initdb.d/init-app.sh:ro 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 # PostgreSQL - Base de datos Keycloak postgres-keycloak: image: postgres:18-alpine container_name: anexo76-postgres-keycloak environment: POSTGRES_DB: keycloak POSTGRES_USER: postgres POSTGRES_PASSWORD: ${POSTGRES_KEYCLOAK_PASSWORD:-postgres} POSTGRES_INITDB_ARGS: "--encoding=UTF8" ports: - "5433:5432" volumes: - postgres_keycloak_data:/var/lib/postgresql/data - ./scripts/postgres-keycloak-entrypoint.sh:/docker-entrypoint-initdb.d/init-keycloak.sh:ro networks: - auth-net - backend-net restart: unless-stopped healthcheck: test: [ "CMD-SHELL", "pg_isready -U postgres -d keycloak || 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 # Keycloak - Servidor de autenticación keycloak: image: quay.io/keycloak/keycloak:26.4 container_name: anexo76-keycloak environment: KEYCLOAK_ADMIN: ${KEYCLOAK_ADMIN:-admin} KEYCLOAK_ADMIN_PASSWORD: ${KEYCLOAK_ADMIN_PASSWORD:-admin} KC_DB: postgres KC_DB_URL_HOST: postgres-keycloak KC_DB_URL_PORT: "5432" KC_DB_URL_DATABASE: keycloak KC_DB_URL: jdbc:postgresql://postgres-keycloak:5432/keycloak KC_DB_USERNAME: postgres KC_DB_PASSWORD: ${POSTGRES_KEYCLOAK_PASSWORD:-postgres} KC_DB_SCHEMA: public KC_HOSTNAME: localhost KC_HTTP_ENABLED: "true" KC_HOSTNAME_STRICT: "false" KC_HOSTNAME_STRICT_HTTPS: "false" KC_PROXY_HEADERS: "xforwarded" KC_HEALTH_ENABLED: "true" KC_METRICS_ENABLED: "true" KC_HOSTNAME_PATH: /kcauth KC_LOG_LEVEL: INFO JAVA_OPTS_APPEND: "-Xms256m -Xmx512m -XX:MetaspaceSize=96M -XX:MaxMetaspaceSize=256m -Djava.net.preferIPv4Stack=true" command: - start-dev - --http-relative-path=/kcauth - --db=postgres - --db-url-host=postgres-keycloak - --db-url-port=5432 - --db-url-database=keycloak - --db-username=postgres - --db-password=${POSTGRES_KEYCLOAK_PASSWORD:-postgres} - --http-enabled=true - --hostname-strict=false - --proxy-headers=xforwarded ports: - "8080:8080" - "9000:9000" depends_on: postgres-keycloak: condition: service_healthy volumes: - keycloak_data:/opt/keycloak/data networks: - auth-net - backend-net restart: unless-stopped healthcheck: test: [ "CMD-SHELL", "exec 3<>/dev/tcp/127.0.0.1/9000; echo -e 'GET /kcauth/health/ready HTTP/1.1\r host: 127.0.0.1\r Connection: close\r \r ' >&3; grep -q 'HTTP/1.1 200' <&3 || exit 1" ] interval: 10s timeout: 5s retries: 30 start_period: 90s logging: driver: "json-file" options: max-size: "10m" max-file: "3" deploy: resources: limits: memory: 768M reservations: memory: 512M # 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_SERVER_URL=${KEYCLOAK_SERVER_URL:-http://keycloak:8080/kcauth} - KEYCLOAK_REALM=${KEYCLOAK_REALM:-master} - KEYCLOAK_CLIENT_ID=${KEYCLOAK_CLIENT_ID:-anexo76-backend} - KEYCLOAK_CLIENT_SECRET=${KEYCLOAK_CLIENT_SECRET:-dev-secret} - 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} ports: - "8000:8000" depends_on: postgres-a76: condition: service_healthy keycloak: condition: service_healthy volumes: - ./backend:/app - backend_cache:/app/__pycache__ - backend_uploads:/app/uploads - ./scripts/backend-entrypoint.sh:/entrypoint.sh:ro networks: - backend-net - frontend-net restart: unless-stopped entrypoint: [ "/entrypoint.sh" ] command: [ "uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000", "--reload", "--log-level", "info" ] 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 # 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:8080/kcauth} - VITE_KEYCLOAK_REALM=${VITE_KEYCLOAK_REALM:-master} - VITE_KEYCLOAK_CLIENT_ID=${VITE_KEYCLOAK_CLIENT_ID:-anexo76-frontend} - KEYCLOAK_URL=${KEYCLOAK_URL:-http://keycloak:8080/kcauth} - KEYCLOAK_REALM=${KEYCLOAK_REALM:-master} - KEYCLOAK_CLIENT_ID=${KEYCLOAK_CLIENT_ID:-anexo76-backend} - KEYCLOAK_CLIENT_SECRET=${KEYCLOAK_CLIENT_SECRET:-zRU5NuvUFtBSOuh7Kdc372AItoWGLgz9} ports: - "5173:5173" depends_on: backend: condition: service_healthy entrypoint: [ "/frontend-entrypoint.sh" ] volumes: - ./frontend:/app - frontend_node_modules:/app/node_modules - ./scripts/frontend-entrypoint.sh:/frontend-entrypoint.sh:ro networks: - frontend-net - auth-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" deploy: resources: limits: memory: 1G reservations: memory: 512M # celery celery_worker: build: ./backend container_name: worker command: celery -A core.celery_app worker --loglevel=info environment: - VALKEY_URL=redis://valkey:6379/0 depends_on: - backend - valkey networks: - backend-net valkey: image: valkey/valkey:7.2 container_name: valkey restart: always ports: - "6379:6379" networks: - backend-net volumes: postgres_app_data: driver: local postgres_keycloak_data: driver: local keycloak_data: driver: local frontend_node_modules: driver: local backend_cache: driver: local backend_uploads: driver: local networks: backend-net: driver: bridge ipam: config: - subnet: 172.20.0.0/16 auth-net: driver: bridge ipam: config: - subnet: 172.21.0.0/16 frontend-net: driver: bridge ipam: config: - subnet: 172.22.0.0/16