version: '3.8' services: # PostgreSQL Database Service (Production) db: image: postgres:15-alpine container_name: market-data-db-prod environment: POSTGRES_USER: ${DB_USER:-postgres} POSTGRES_PASSWORD: ${DB_PASSWORD} POSTGRES_DB: ${DB_NAME:-financial_data} PGDATA: /var/lib/postgresql/data/pgdata volumes: - market_data_db_data_prod:/var/lib/postgresql/data - ./docker/init-db.sh:/docker-entrypoint-initdb.d/init-db.sh # No exposed ports in production (internal only) healthcheck: test: ["CMD-SHELL", "pg_isready -U ${DB_USER:-postgres} -d ${DB_NAME:-financial_data}"] interval: 10s timeout: 5s retries: 5 networks: - market-data-network restart: unless-stopped # Production security: run as non-root user: "999:999" # Node.js API Service (Production) api: build: context: . dockerfile: Dockerfile target: production container_name: market-data-api-prod environment: - NODE_ENV=production - PORT=3000 - DB_HOST=db - DB_PORT=5432 - DB_NAME=${DB_NAME:-financial_data} - DB_USER=${DB_USER:-postgres} - DB_PASSWORD=${DB_PASSWORD} - CORS_ORIGIN=${CORS_ORIGIN:-http://localhost:3000,https://beta-app.insightbull.io,https://app.insightbull.io} - JWT_SECRET=${JWT_SECRET} - LOG_LEVEL=${LOG_LEVEL:-info} # No volume mounts in production (code is baked into image) # Optional: mount logs volume for persistence volumes: - market_data_logs_prod:/app/logs # No exposed ports (accessed only through nginx) depends_on: db: condition: service_healthy networks: - market-data-network restart: unless-stopped healthcheck: test: ["CMD", "node", "-e", "require('http').get('http://localhost:3000/health', (r) => {process.exit(r.statusCode === 200 ? 0 : 1)})"] interval: 30s timeout: 10s start_period: 40s retries: 3 # Nginx Reverse Proxy (Production) nginx: image: nginx:alpine container_name: market-data-nginx-prod ports: - "80:80" - "443:443" volumes: - ./nginx/nginx.prod.conf:/etc/nginx/nginx.conf:ro - ./docker/nginx-entrypoint.sh:/docker-entrypoint-nginx.sh:ro # SSL certificates from certbot (shared volume) - certbot_etc:/etc/letsencrypt:ro - certbot_www:/var/www/certbot:ro - certbot_reload:/var/run/certbot-reload:rw environment: - DOMAIN_NAME=${DOMAIN_NAME:-default} depends_on: - api - certbot networks: - market-data-network restart: unless-stopped entrypoint: ["/bin/sh", "/docker-entrypoint-nginx.sh"] healthcheck: test: ["CMD", "wget", "--quiet", "--tries=1", "--spider", "http://localhost/health"] interval: 30s timeout: 10s retries: 3 # Certbot for SSL Certificate Management certbot: image: certbot/certbot:latest container_name: market-data-certbot volumes: - certbot_etc:/etc/letsencrypt - certbot_www:/var/www/certbot - certbot_logs:/var/log/letsencrypt - certbot_reload:/var/run/certbot-reload networks: - market-data-network restart: unless-stopped entrypoint: "/bin/sh -c 'trap exit TERM; while :; do certbot renew --quiet && touch /var/run/certbot-reload/reload 2>/dev/null || true; sleep 12h & wait $${!}; done;'" # Note: Initial certificate must be obtained using certbot-init service # Run: DOMAIN_NAME=yourdomain.com SSL_EMAIL=your@email.com docker-compose -f docker-compose.prod.yml run --rm certbot-init # Certbot Initialization Service (one-time use) certbot-init: image: certbot/certbot:latest container_name: market-data-certbot-init volumes: - certbot_etc:/etc/letsencrypt - certbot_www:/var/www/certbot - certbot_logs:/var/log/letsencrypt - ./docker/init-ssl.sh:/docker/init-ssl.sh:ro networks: - market-data-network entrypoint: /bin/sh command: /docker/init-ssl.sh environment: - DOMAIN_NAME=${DOMAIN_NAME} - SSL_EMAIL=${SSL_EMAIL} - SSL_STAGING=${SSL_STAGING:-0} volumes: market_data_db_data_prod: driver: local market_data_logs_prod: driver: local certbot_etc: driver: local certbot_www: driver: local certbot_logs: driver: local certbot_reload: driver: local networks: market-data-network: driver: bridge