# Docker Compose para producción # Uso: docker compose -f docker-compose.prod.yml up -d # # Requisitos previos: # 1. Configurar .env con valores de producción (ver .env.example) # 2. Login al registry: docker login tea.millaguie.net # 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 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 # API Backend api: image: tea.millaguie.net/millaguie/trenes-backend:${IMAGE_TAG:-latest} container_name: trenes-api restart: unless-stopped command: ["node", "src/api/server.js"] 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:-change_me_in_production} LOG_LEVEL: info depends_on: postgres: condition: service_healthy redis: condition: service_healthy flyway: condition: service_completed_successfully healthcheck: test: ["CMD", "wget", "--quiet", "--tries=1", "--spider", "http://localhost:3000/health"] interval: 30s timeout: 10s retries: 3 networks: - trenes-network # Worker para polling GTFS-RT Vehicle Positions worker: image: tea.millaguie.net/millaguie/trenes-backend:${IMAGE_TAG:-latest} container_name: trenes-worker restart: unless-stopped command: ["node", "src/worker/gtfs-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_URL: https://gtfsrt.renfe.com/vehicle_positions.pb POLLING_INTERVAL: 30000 LOG_LEVEL: info depends_on: postgres: condition: service_healthy redis: condition: service_healthy flyway: condition: service_completed_successfully networks: - trenes-network # Worker para sincronización GTFS Static gtfs-static-syncer: image: tea.millaguie.net/millaguie/trenes-backend:${IMAGE_TAG:-latest} 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 flyway: condition: service_completed_successfully networks: - trenes-network # Worker para polling GTFS-RT Trip Updates trip-updates-poller: image: tea.millaguie.net/millaguie/trenes-backend:${IMAGE_TAG:-latest} 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 flyway: condition: service_completed_successfully networks: - trenes-network # Worker para polling GTFS-RT Service Alerts alerts-poller: image: tea.millaguie.net/millaguie/trenes-backend:${IMAGE_TAG:-latest} 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 flyway: condition: service_completed_successfully networks: - trenes-network # Worker para datos de flota Renfe renfe-fleet-poller: image: tea.millaguie.net/millaguie/trenes-backend:${IMAGE_TAG:-latest} 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 flyway: condition: service_completed_successfully networks: - trenes-network # Worker para refrescar vistas de analytics analytics-refresher: image: tea.millaguie.net/millaguie/trenes-backend:${IMAGE_TAG:-latest} 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 flyway: condition: service_completed_successfully networks: - trenes-network # Frontend frontend: image: tea.millaguie.net/millaguie/trenes-frontend:${IMAGE_TAG:-latest} 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 - certbot_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: - certbot_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 certbot_certs: external: true certbot_webroot: external: true networks: trenes-network: driver: bridge