version: '3.8' services: # Backend API Service api: build: context: ./backend dockerfile: Dockerfile container_name: overseas-appointment-api restart: unless-stopped ports: - "3000:3000" environment: - NODE_ENV=production - PORT=3000 - DB_HOST=mysql - DB_PORT=3306 - DB_NAME=${DB_NAME:-overseas_appointment} - DB_USER=${DB_USER:-app_user} - DB_PASSWORD=${DB_PASSWORD:-app_password} - REDIS_HOST=redis - REDIS_PORT=6379 - REDIS_PASSWORD=${REDIS_PASSWORD:-} - JWT_SECRET=${JWT_SECRET:-change-this-in-production} - JWT_EXPIRES_IN=${JWT_EXPIRES_IN:-7d} - JWT_REFRESH_EXPIRES_IN=${JWT_REFRESH_EXPIRES_IN:-30d} - WECHAT_APP_ID=${WECHAT_APP_ID:-} - WECHAT_APP_SECRET=${WECHAT_APP_SECRET:-} - UPLOAD_PATH=/app/uploads - MAX_FILE_SIZE=${MAX_FILE_SIZE:-5242880} - RATE_LIMIT_WINDOW=${RATE_LIMIT_WINDOW:-60000} - RATE_LIMIT_MAX=${RATE_LIMIT_MAX:-100} - LOG_LEVEL=${LOG_LEVEL:-info} volumes: - api_uploads:/app/uploads - api_logs:/app/logs depends_on: mysql: condition: service_healthy redis: condition: service_healthy networks: - app-network healthcheck: test: ["CMD", "node", "-e", "require('http').get('http://localhost:3000/health', (r) => process.exit(r.statusCode === 200 ? 0 : 1))"] interval: 30s timeout: 10s retries: 3 start_period: 40s # MySQL Database Service mysql: image: mysql:8.0 container_name: overseas-appointment-mysql restart: unless-stopped ports: - "3306:3306" environment: - MYSQL_ROOT_PASSWORD=${MYSQL_ROOT_PASSWORD:-root_password} - MYSQL_DATABASE=${DB_NAME:-overseas_appointment} - MYSQL_USER=${DB_USER:-app_user} - MYSQL_PASSWORD=${DB_PASSWORD:-app_password} command: --default-authentication-plugin=mysql_native_password --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci volumes: - mysql_data:/var/lib/mysql - ./docker/mysql/init:/docker-entrypoint-initdb.d - ./docker/mysql/conf.d:/etc/mysql/conf.d networks: - app-network healthcheck: test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-u", "root", "-p${MYSQL_ROOT_PASSWORD:-root_password}"] interval: 10s timeout: 5s retries: 5 start_period: 30s # Redis Cache Service redis: image: redis:7-alpine container_name: overseas-appointment-redis restart: unless-stopped ports: - "6379:6379" command: redis-server --appendonly yes ${REDIS_PASSWORD:+--requirepass $REDIS_PASSWORD} volumes: - redis_data:/data networks: - app-network healthcheck: test: ["CMD", "redis-cli", "ping"] interval: 10s timeout: 5s retries: 5 start_period: 10s # Nginx Reverse Proxy nginx: image: nginx:alpine container_name: overseas-appointment-nginx restart: unless-stopped ports: - "80:80" - "443:443" volumes: - ./docker/nginx/nginx.conf:/etc/nginx/nginx.conf:ro - ./docker/nginx/conf.d:/etc/nginx/conf.d:ro - ./docker/nginx/ssl:/etc/nginx/ssl:ro - nginx_logs:/var/log/nginx depends_on: - api networks: - app-network networks: app-network: driver: bridge volumes: mysql_data: driver: local redis_data: driver: local api_uploads: driver: local api_logs: driver: local nginx_logs: driver: local