# version: '3.8' # This is obsolete in newer Docker Compose versions services: # ============================================================================= # Reverse Proxy & SSL Termination # ============================================================================= traefik: image: traefik:v3.0 container_name: traefik restart: unless-stopped command: - "--api.dashboard=true" - "--api.insecure=false" - "--providers.docker=true" - "--providers.docker.exposedbydefault=false" - "--entrypoints.web.address=:80" - "--entrypoints.websecure.address=:443" - "--certificatesresolvers.letsencrypt.acme.tlschallenge=true" - "--certificatesresolvers.letsencrypt.acme.email=${LETSENCRYPT_EMAIL}" - "--certificatesresolvers.letsencrypt.acme.storage=/letsencrypt/acme.json" - "--global.sendanonymoususage=false" # HTTP to HTTPS redirect - "--entrypoints.web.http.redirections.entrypoint.to=websecure" - "--entrypoints.web.http.redirections.entrypoint.scheme=https" ports: - "80:80" - "443:443" volumes: - /var/run/docker.sock:/var/run/docker.sock:ro - traefik_letsencrypt:/letsencrypt labels: - "traefik.enable=true" # Dashboard - "traefik.http.routers.dashboard.rule=Host(`traefik.${BOT_DOMAIN}`)" - "traefik.http.routers.dashboard.tls=true" - "traefik.http.routers.dashboard.tls.certresolver=letsencrypt" - "traefik.http.routers.dashboard.service=api@internal" - "traefik.http.routers.dashboard.middlewares=auth" - "traefik.http.middlewares.auth.basicauth.users=admin:$$2y$$10$$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi" healthcheck: test: ["CMD", "traefik", "healthcheck"] interval: 30s timeout: 10s retries: 3 # ============================================================================= # Database Services # ============================================================================= postgres: image: postgres:15-alpine container_name: postgres restart: unless-stopped environment: POSTGRES_DB: digital_store_bot POSTGRES_USER: botuser POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-secure_password} POSTGRES_INITDB_ARGS: "--encoding=UTF-8 --locale=C" # Performance settings POSTGRES_SHARED_BUFFERS: 256MB POSTGRES_EFFECTIVE_CACHE_SIZE: 1GB POSTGRES_MAINTENANCE_WORK_MEM: 64MB volumes: - postgres_data:/var/lib/postgresql/data - ./scripts/init-db.sql:/docker-entrypoint-initdb.d/init-db.sql:ro ports: - "127.0.0.1:5433:5432" # 注释掉避免端口冲突 # command: ["postgres", "-c", "config_file=/etc/postgresql/postgresql.conf"] # 移除自定义配置文件 healthcheck: test: ["CMD-SHELL", "pg_isready -U botuser -d digital_store_bot"] interval: 30s timeout: 10s retries: 3 start_period: 60s redis: image: redis:7-alpine container_name: redis restart: unless-stopped command: redis-server --appendonly yes --maxmemory 512mb --maxmemory-policy allkeys-lru --save 900 1 --save 300 10 --save 60 10000 volumes: - redis_data:/data ports: - "127.0.0.1:6380:6379" # 注释掉避免端口冲突 healthcheck: test: ["CMD", "redis-cli", "ping"] interval: 30s timeout: 10s retries: 3 # ============================================================================= # Application Services # ============================================================================= bot: build: context: . dockerfile: Dockerfile target: production container_name: digital_store_bot restart: unless-stopped env_file: - .env environment: - DATABASE_URL=postgresql+asyncpg://botuser:${POSTGRES_PASSWORD:-secure_password}@postgres:5432/digital_store_bot - REDIS_URL=redis://redis:6379/0 - SERVICE_TYPE=bot volumes: - ./data:/app/data:rw - ./logs:/app/logs:rw - ./locales:/app/locales:ro - ./static:/app/static:ro depends_on: postgres: condition: service_healthy redis: condition: service_healthy labels: - "traefik.enable=true" # Webhook endpoint - "traefik.http.routers.bot.rule=Host(`${BOT_DOMAIN}`) && (PathPrefix(`/webhook`) || PathPrefix(`/api/webhook`))" - "traefik.http.routers.bot.tls=true" - "traefik.http.routers.bot.tls.certresolver=letsencrypt" - "traefik.http.services.bot.loadbalancer.server.port=8000" # Health check endpoint - "traefik.http.routers.bot-health.rule=Host(`${BOT_DOMAIN}`) && Path(`/health`)" - "traefik.http.routers.bot-health.tls=true" - "traefik.http.routers.bot-health.tls.certresolver=letsencrypt" healthcheck: test: ["CMD", "curl", "-f", "http://localhost:8000/health"] interval: 30s timeout: 10s retries: 3 start_period: 60s deploy: resources: limits: memory: 512M reservations: memory: 256M admin: build: context: . dockerfile: Dockerfile target: production container_name: admin_panel restart: unless-stopped env_file: - .env environment: - DATABASE_URL=postgresql+asyncpg://botuser:${POSTGRES_PASSWORD:-secure_password}@postgres:5432/digital_store_bot - REDIS_URL=redis://redis:6379/0 - SERVICE_TYPE=admin command: ["python", "-m", "src.presentation.web.admin_panel"] volumes: - ./data:/app/data:rw - ./logs:/app/logs:rw - ./templates:/app/templates:ro - ./static:/app/static:ro depends_on: postgres: condition: service_healthy redis: condition: service_healthy bot: condition: service_healthy labels: - "traefik.enable=true" - "traefik.http.routers.admin.rule=Host(`admin.${BOT_DOMAIN}`)" - "traefik.http.routers.admin.tls=true" - "traefik.http.routers.admin.tls.certresolver=letsencrypt" - "traefik.http.services.admin.loadbalancer.server.port=8080" # Security headers - "traefik.http.middlewares.admin-headers.headers.customRequestHeaders.X-Forwarded-Proto=https" - "traefik.http.middlewares.admin-headers.headers.customResponseHeaders.X-Content-Type-Options=nosniff" - "traefik.http.middlewares.admin-headers.headers.customResponseHeaders.X-Frame-Options=DENY" - "traefik.http.middlewares.admin-headers.headers.customResponseHeaders.X-XSS-Protection=1; mode=block" - "traefik.http.routers.admin.middlewares=admin-headers" deploy: resources: limits: memory: 256M reservations: memory: 128M # ============================================================================= # Background Services # ============================================================================= scheduler: build: context: . dockerfile: Dockerfile target: production container_name: task_scheduler restart: unless-stopped env_file: - .env environment: - DATABASE_URL=postgresql+asyncpg://botuser:${POSTGRES_PASSWORD:-secure_password}@postgres:5432/digital_store_bot - REDIS_URL=redis://redis:6379/0 - SERVICE_TYPE=scheduler volumes: - ./data:/app/data:rw - ./logs:/app/logs:rw depends_on: postgres: condition: service_healthy redis: condition: service_healthy deploy: resources: limits: memory: 256M reservations: memory: 128M # ============================================================================= # Monitoring Services (Optional) # ============================================================================= prometheus: image: prom/prometheus:latest container_name: prometheus restart: unless-stopped user: "nobody" 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' - '--web.external-url=https://metrics.${BOT_DOMAIN}' volumes: - ./monitoring/prometheus.yml:/etc/prometheus/prometheus.yml:ro - prometheus_data:/prometheus labels: - "traefik.enable=true" - "traefik.http.routers.prometheus.rule=Host(`metrics.${BOT_DOMAIN}`)" - "traefik.http.routers.prometheus.tls=true" - "traefik.http.routers.prometheus.tls.certresolver=letsencrypt" - "traefik.http.services.prometheus.loadbalancer.server.port=9090" - "traefik.http.routers.prometheus.middlewares=prometheus-auth" - "traefik.http.middlewares.prometheus-auth.basicauth.users=admin:$$2y$$10$$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi" profiles: - monitoring grafana: image: grafana/grafana:latest container_name: grafana restart: unless-stopped user: "472" environment: - GF_SECURITY_ADMIN_PASSWORD=${GRAFANA_PASSWORD:-admin123} - GF_USERS_ALLOW_SIGN_UP=false - GF_SERVER_ROOT_URL=https://grafana.${BOT_DOMAIN} - GF_SECURITY_COOKIE_SECURE=true - GF_SECURITY_COOKIE_SAMESITE=strict - GF_ANALYTICS_REPORTING_ENABLED=false - GF_ANALYTICS_CHECK_FOR_UPDATES=false volumes: - grafana_data:/var/lib/grafana - ./monitoring/grafana/provisioning:/etc/grafana/provisioning:ro - ./monitoring/grafana/dashboards:/var/lib/grafana/dashboards:ro depends_on: - prometheus labels: - "traefik.enable=true" - "traefik.http.routers.grafana.rule=Host(`grafana.${BOT_DOMAIN}`)" - "traefik.http.routers.grafana.tls=true" - "traefik.http.routers.grafana.tls.certresolver=letsencrypt" - "traefik.http.services.grafana.loadbalancer.server.port=3000" profiles: - monitoring # ============================================================================= # Log Aggregation (Optional) # ============================================================================= loki: image: grafana/loki:latest container_name: loki restart: unless-stopped command: -config.file=/etc/loki/local-config.yaml volumes: - ./monitoring/loki/loki-config.yml:/etc/loki/local-config.yaml:ro - loki_data:/loki labels: - "traefik.enable=true" - "traefik.http.routers.loki.rule=Host(`logs.${BOT_DOMAIN}`)" - "traefik.http.routers.loki.tls=true" - "traefik.http.routers.loki.tls.certresolver=letsencrypt" - "traefik.http.services.loki.loadbalancer.server.port=3100" profiles: - logging promtail: image: grafana/promtail:latest container_name: promtail restart: unless-stopped volumes: - ./monitoring/promtail/promtail-config.yml:/etc/promtail/config.yml:ro - ./logs:/var/log/app:ro - /var/log:/var/log/host:ro command: -config.file=/etc/promtail/config.yml depends_on: - loki profiles: - logging volumes: postgres_data: driver: local redis_data: driver: local traefik_letsencrypt: driver: local prometheus_data: driver: local grafana_data: driver: local loki_data: driver: local networks: default: name: digital_store_network driver: bridge ipam: config: - subnet: 172.20.0.0/16