version: '3.8' services: # PostgreSQL 数据库 postgres: image: postgres:16-alpine container_name: photography-postgres environment: POSTGRES_DB: photography POSTGRES_USER: postgres POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-photography_prod_2024} POSTGRES_INITDB_ARGS: "--auth-local=md5 --auth-host=md5" volumes: - postgres_data:/var/lib/postgresql/data - ./init-db.sql:/docker-entrypoint-initdb.d/init-db.sql:ro networks: - photography-network ports: - "5432:5432" restart: unless-stopped healthcheck: test: ["CMD-SHELL", "pg_isready -U postgres"] interval: 10s timeout: 5s retries: 5 deploy: resources: limits: memory: 512M reservations: memory: 256M # Redis 缓存 redis: image: redis:7-alpine container_name: photography-redis command: redis-server --appendonly yes --requirepass ${REDIS_PASSWORD:-redis_prod_2024} volumes: - redis_data:/data networks: - photography-network ports: - "6379:6379" restart: unless-stopped healthcheck: test: ["CMD", "redis-cli", "ping"] interval: 10s timeout: 5s retries: 5 deploy: resources: limits: memory: 256M reservations: memory: 128M # 后端 API 服务 api: build: context: ../.. dockerfile: configs/docker/Dockerfile target: production container_name: photography-api environment: - ENV=production - DATABASE_HOST=postgres - DATABASE_PORT=5432 - DATABASE_NAME=photography - DATABASE_USER=postgres - DATABASE_PASSWORD=${POSTGRES_PASSWORD:-photography_prod_2024} - REDIS_HOST=redis - REDIS_PORT=6379 - REDIS_PASSWORD=${REDIS_PASSWORD:-redis_prod_2024} - JWT_SECRET=${JWT_SECRET:-photography-jwt-secret-prod-2024} - FILE_UPLOAD_MAX_SIZE=10485760 - CORS_ORIGINS=https://photography.iriver.top,https://admin.photography.iriver.top volumes: - upload_data:/app/uploads - ./logs:/app/logs networks: - photography-network ports: - "8080:8080" depends_on: postgres: condition: service_healthy redis: condition: service_healthy restart: unless-stopped healthcheck: test: ["CMD", "curl", "-f", "http://localhost:8080/api/v1/health"] interval: 30s timeout: 10s retries: 3 deploy: resources: limits: memory: 512M reservations: memory: 256M # Nginx 反向代理 nginx: image: nginx:alpine container_name: photography-nginx volumes: - ./nginx.conf:/etc/nginx/nginx.conf:ro - ./ssl:/etc/nginx/ssl:ro - upload_data:/usr/share/nginx/html/uploads:ro networks: - photography-network ports: - "80:80" - "443:443" depends_on: - api restart: unless-stopped healthcheck: test: ["CMD", "curl", "-f", "http://localhost/api/v1/health"] interval: 30s timeout: 10s retries: 3 # 监控服务 (可选) prometheus: image: prom/prometheus:latest container_name: photography-prometheus command: - '--config.file=/etc/prometheus/prometheus.yml' - '--storage.tsdb.path=/prometheus' - '--web.console.libraries=/etc/prometheus/console_libraries' - '--web.console.templates=/etc/prometheus/consoles' - '--storage.tsdb.retention.time=200h' - '--web.enable-lifecycle' volumes: - ./prometheus.yml:/etc/prometheus/prometheus.yml:ro - prometheus_data:/prometheus networks: - photography-network ports: - "9090:9090" restart: unless-stopped # 日志收集 (可选) grafana: image: grafana/grafana:latest container_name: photography-grafana environment: - GF_SECURITY_ADMIN_PASSWORD=${GRAFANA_PASSWORD:-admin123} - GF_USERS_ALLOW_SIGN_UP=false volumes: - grafana_data:/var/lib/grafana - ./grafana/provisioning:/etc/grafana/provisioning:ro networks: - photography-network ports: - "3000:3000" depends_on: - prometheus restart: unless-stopped networks: photography-network: driver: bridge volumes: postgres_data: driver: local redis_data: driver: local upload_data: driver: local prometheus_data: driver: local grafana_data: driver: local