version: '3.8' # Docker Compose para producción # Uso: docker compose -f docker-compose.prod.yml up -d --build # # Requisitos previos: # 1. Configurar .env con valores de producción (ver .env.example) # 2. Configurar certificados SSL en ./nginx/ssl/ o usar certbot # 3. Configurar nginx/prod.conf con tu dominio services: # Base de datos PostgreSQL con extensión PostGIS postgres: image: postgis/postgis:16-3.4-alpine # IMPORTANTE: usar versión 16 para compatibilidad container_name: trenes-postgres restart: unless-stopped environment: POSTGRES_DB: ${POSTGRES_DB:-trenes} POSTGRES_USER: ${POSTGRES_USER:-trenes} POSTGRES_PASSWORD: ${POSTGRES_PASSWORD} PGDATA: /var/lib/postgresql/data/pgdata volumes: - postgres_data:/var/lib/postgresql/data - ./database/init:/docker-entrypoint-initdb.d healthcheck: test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER:-trenes} -d ${POSTGRES_DB:-trenes}"] interval: 10s timeout: 5s retries: 5 networks: - trenes-network # Redis para cache redis: image: redis:7-alpine container_name: trenes-redis restart: unless-stopped command: redis-server --appendonly yes volumes: - redis_data:/data healthcheck: test: ["CMD", "redis-cli", "ping"] interval: 10s timeout: 5s retries: 5 networks: - trenes-network # Flyway - Gestor de migraciones de base de datos flyway: image: flyway/flyway:10-alpine container_name: trenes-flyway command: migrate environment: FLYWAY_URL: jdbc:postgresql://postgres:5432/${POSTGRES_DB:-trenes} FLYWAY_USER: ${POSTGRES_USER:-trenes} FLYWAY_PASSWORD: ${POSTGRES_PASSWORD} FLYWAY_BASELINE_ON_MIGRATE: "true" FLYWAY_BASELINE_VERSION: "0" FLYWAY_SCHEMAS: public FLYWAY_LOCATIONS: filesystem:/flyway/sql FLYWAY_VALIDATE_ON_MIGRATE: "true" FLYWAY_OUT_OF_ORDER: "false" volumes: - ./database/migrations:/flyway/sql depends_on: postgres: condition: service_healthy networks: - trenes-network profiles: - migration # Worker para polling GTFS-RT Vehicle Positions worker: build: context: ./backend dockerfile: Dockerfile target: worker container_name: trenes-worker restart: unless-stopped environment: NODE_ENV: production DATABASE_URL: postgresql://${POSTGRES_USER:-trenes}:${POSTGRES_PASSWORD}@postgres:5432/${POSTGRES_DB:-trenes} REDIS_URL: redis://redis:6379 GTFS_RT_URL: https://gtfsrt.renfe.com/vehicle_positions.pb POLLING_INTERVAL: 30000 LOG_LEVEL: info depends_on: postgres: condition: service_healthy redis: condition: service_healthy networks: - trenes-network # Worker para sincronización GTFS Static gtfs-static-syncer: build: context: ./backend dockerfile: Dockerfile target: worker container_name: trenes-gtfs-static-syncer restart: unless-stopped command: node src/worker/gtfs-static-syncer.js environment: NODE_ENV: production DATABASE_URL: postgresql://${POSTGRES_USER:-trenes}:${POSTGRES_PASSWORD}@postgres:5432/${POSTGRES_DB:-trenes} REDIS_URL: redis://redis:6379 GTFS_STATIC_URL: https://data.renfe.com/dataset/horarios-trenes-largo-recorrido-ave/resource/horarios-trenes-largo-recorrido-ave-gtfs.zip SYNC_SCHEDULE: 0 3 * * * LOG_LEVEL: info volumes: - gtfs_static_data:/tmp/gtfs depends_on: postgres: condition: service_healthy redis: condition: service_healthy networks: - trenes-network # Worker para polling GTFS-RT Trip Updates trip-updates-poller: build: context: ./backend dockerfile: Dockerfile target: worker container_name: trenes-trip-updates-poller restart: unless-stopped command: node src/worker/trip-updates-poller.js environment: NODE_ENV: production DATABASE_URL: postgresql://${POSTGRES_USER:-trenes}:${POSTGRES_PASSWORD}@postgres:5432/${POSTGRES_DB:-trenes} REDIS_URL: redis://redis:6379 GTFS_RT_TRIP_UPDATES_URL: https://gtfsrt.renfe.com/trip_updates_cercanias.pb POLLING_INTERVAL: 30000 LOG_LEVEL: info depends_on: postgres: condition: service_healthy redis: condition: service_healthy networks: - trenes-network # Worker para polling GTFS-RT Service Alerts alerts-poller: build: context: ./backend dockerfile: Dockerfile target: worker container_name: trenes-alerts-poller restart: unless-stopped command: node src/worker/alerts-poller.js environment: NODE_ENV: production DATABASE_URL: postgresql://${POSTGRES_USER:-trenes}:${POSTGRES_PASSWORD}@postgres:5432/${POSTGRES_DB:-trenes} REDIS_URL: redis://redis:6379 GTFS_RT_ALERTS_URL: https://gtfsrt.renfe.com/alerts.pb POLLING_INTERVAL: 30000 LOG_LEVEL: info depends_on: postgres: condition: service_healthy redis: condition: service_healthy networks: - trenes-network # Worker para datos de flota Renfe renfe-fleet-poller: build: context: ./backend dockerfile: Dockerfile target: worker container_name: trenes-renfe-fleet-poller restart: unless-stopped command: node src/worker/renfe-fleet-poller.js environment: NODE_ENV: production DATABASE_URL: postgresql://${POSTGRES_USER:-trenes}:${POSTGRES_PASSWORD}@postgres:5432/${POSTGRES_DB:-trenes} REDIS_URL: redis://redis:6379 LOG_LEVEL: info depends_on: postgres: condition: service_healthy redis: condition: service_healthy networks: - trenes-network # Worker para refrescar vistas de analytics analytics-refresher: build: context: ./backend dockerfile: Dockerfile target: worker container_name: trenes-analytics-refresher restart: unless-stopped command: node src/worker/analytics-refresher.js environment: NODE_ENV: production DATABASE_URL: postgresql://${POSTGRES_USER:-trenes}:${POSTGRES_PASSWORD}@postgres:5432/${POSTGRES_DB:-trenes} REDIS_URL: redis://redis:6379 ANALYTICS_REFRESH_SCHEDULE: "*/15 * * * *" LOG_LEVEL: info depends_on: postgres: condition: service_healthy redis: condition: service_healthy networks: - trenes-network # API Backend api: build: context: ./backend dockerfile: Dockerfile target: api container_name: trenes-api restart: unless-stopped environment: NODE_ENV: production PORT: 3000 DATABASE_URL: postgresql://${POSTGRES_USER:-trenes}:${POSTGRES_PASSWORD}@postgres:5432/${POSTGRES_DB:-trenes} REDIS_URL: redis://redis:6379 CORS_ORIGIN: ${CORS_ORIGINS:-https://localhost} JWT_SECRET: ${JWT_SECRET} LOG_LEVEL: info depends_on: postgres: condition: service_healthy redis: condition: service_healthy healthcheck: test: ["CMD", "wget", "--quiet", "--tries=1", "--spider", "http://localhost:3000/health"] interval: 30s timeout: 10s retries: 3 networks: - trenes-network # Frontend # IMPORTANTE: Las variables VITE_* deben pasarse como build args, no como environment # ya que se procesan en tiempo de compilación, no en runtime frontend: build: context: ./frontend dockerfile: Dockerfile target: production args: VITE_API_URL: ${VITE_API_URL} VITE_WS_URL: ${VITE_WS_URL} container_name: trenes-frontend restart: unless-stopped depends_on: - api networks: - trenes-network # Nginx como reverse proxy con SSL nginx: image: nginx:alpine container_name: trenes-nginx restart: unless-stopped volumes: - ./nginx/prod.conf:/etc/nginx/conf.d/default.conf:ro - letsencrypt_certs:/etc/letsencrypt:ro - certbot_webroot:/var/www/certbot:ro ports: - "80:80" - "443:443" depends_on: - api - frontend healthcheck: test: ["CMD", "wget", "--quiet", "--tries=1", "--spider", "http://localhost/health"] interval: 30s timeout: 10s retries: 3 networks: - trenes-network # Certbot para renovación automática de certificados certbot: image: certbot/certbot container_name: trenes-certbot volumes: - letsencrypt_certs:/etc/letsencrypt - certbot_webroot:/var/www/certbot entrypoint: "/bin/sh -c 'trap exit TERM; while :; do certbot renew; sleep 12h & wait $${!}; done;'" networks: - trenes-network volumes: postgres_data: driver: local redis_data: driver: local gtfs_static_data: driver: local letsencrypt_certs: driver: local certbot_webroot: driver: local networks: trenes-network: driver: bridge