# ============================================================================== # Production Docker Swarm Stack with Traefik Load Balancer # ============================================================================== # Usage: docker stack deploy -c docker-compose.prod.yml framework # # This is a STANDALONE file - no merging with docker-compose.yml required. # All services are production-ready with Swarm deployment configurations. # ============================================================================== version: '3.8' # ============================================================================== # Services # ============================================================================== services: # ---------------------------------------------------------------------------- # Traefik - Reverse Proxy & Load Balancer # ---------------------------------------------------------------------------- traefik: image: traefik:v2.10 command: # API & Dashboard - "--api.dashboard=true" - "--api.insecure=true" # Docker Swarm Provider - "--providers.docker=true" - "--providers.docker.swarmMode=true" - "--providers.docker.exposedByDefault=false" - "--providers.docker.network=framework_traefik-public" # Entrypoints - "--entrypoints.web.address=:80" - "--entrypoints.websecure.address=:443" # HTTP → HTTPS Redirect - "--entrypoints.web.http.redirections.entryPoint.to=websecure" - "--entrypoints.web.http.redirections.entryPoint.scheme=https" # Access Logs - "--accesslog=true" - "--accesslog.filepath=/var/log/traefik/access.log" # Metrics - "--metrics.prometheus=true" ports: - target: 80 published: 80 mode: host - target: 443 published: 443 mode: host - target: 8080 published: 8080 protocol: tcp mode: host volumes: - /var/run/docker.sock:/var/run/docker.sock:ro - ./ssl:/ssl:ro - traefik-logs:/var/log/traefik networks: - traefik-public deploy: placement: constraints: - node.role == manager labels: # Dashboard - "traefik.enable=true" - "traefik.http.routers.traefik.rule=Host(`traefik.localhost`)" - "traefik.http.routers.traefik.service=api@internal" - "traefik.http.routers.traefik.entrypoints=websecure" - "traefik.http.routers.traefik.tls=true" - "traefik.http.services.traefik.loadbalancer.server.port=8080" # ---------------------------------------------------------------------------- # Web - PHP Application (Nginx + PHP-FPM) # ---------------------------------------------------------------------------- web: image: 94.16.110.151:5000/framework:latest environment: # Application - APP_ENV=production - APP_DEBUG=false - APP_NAME=Michael Schiemer - APP_TIMEZONE=Europe/Berlin - APP_LOCALE=de # Database - DB_DRIVER=pgsql - DB_HOST=db - DB_PORT=5432 - DB_DATABASE=framework_prod - DB_USERNAME=postgres - DB_CHARSET=utf8 # Redis (Sessions & Cache) - REDIS_HOST=redis - REDIS_PORT=6379 - SESSION_DRIVER=redis - CACHE_DRIVER=redis # Security - SECURITY_ALLOWED_HOSTS=localhost,michaelschiemer.de,www.michaelschiemer.de - SECURITY_RATE_LIMIT_PER_MINUTE=60 - SESSION_LIFETIME=1800 - FORCE_HTTPS=true # Performance - OPCACHE_ENABLED=true # Analytics - ANALYTICS_ENABLED=true - ANALYTICS_TRACK_PAGE_VIEWS=true secrets: - db_password - app_key - vault_encryption_key - shopify_webhook_secret - rapidmail_password volumes: - storage-logs:/var/www/html/storage/logs:rw - storage-uploads:/var/www/html/storage/uploads:rw networks: - traefik-public - backend healthcheck: test: ["CMD", "curl", "-f", "http://localhost/health"] interval: 30s timeout: 10s retries: 3 start_period: 40s deploy: replicas: 3 update_config: parallelism: 1 delay: 10s failure_action: rollback order: start-first rollback_config: parallelism: 1 delay: 5s restart_policy: condition: on-failure delay: 5s max_attempts: 3 labels: # Traefik Configuration - "traefik.enable=true" # HTTP Router - "traefik.http.routers.web.rule=Host(`michaelschiemer.de`) || Host(`www.michaelschiemer.de`)" - "traefik.http.routers.web.entrypoints=websecure" - "traefik.http.routers.web.tls=true" # Load Balancer - "traefik.http.services.web.loadbalancer.server.port=80" - "traefik.http.services.web.loadbalancer.sticky.cookie=true" - "traefik.http.services.web.loadbalancer.sticky.cookie.name=PHPSESSID" - "traefik.http.services.web.loadbalancer.sticky.cookie.secure=true" - "traefik.http.services.web.loadbalancer.sticky.cookie.httpOnly=true" # Health Check - "traefik.http.services.web.loadbalancer.healthcheck.path=/health" - "traefik.http.services.web.loadbalancer.healthcheck.interval=30s" - "traefik.http.services.web.loadbalancer.healthcheck.timeout=5s" # ---------------------------------------------------------------------------- # Database - PostgreSQL # ---------------------------------------------------------------------------- db: image: postgres:16-alpine environment: - POSTGRES_DB=framework_prod - POSTGRES_USER=postgres - POSTGRES_PASSWORD_FILE=/run/secrets/db_password - POSTGRES_INITDB_ARGS=-E UTF8 --locale=C - PGDATA=/var/lib/postgresql/data/pgdata secrets: - db_password volumes: - db-data:/var/lib/postgresql/data networks: - backend healthcheck: test: ["CMD-SHELL", "pg_isready -U postgres -d framework_prod"] interval: 10s timeout: 5s retries: 5 start_period: 30s deploy: placement: constraints: - node.role == manager restart_policy: condition: on-failure delay: 5s max_attempts: 3 resources: limits: memory: 1G cpus: '1.0' reservations: memory: 512M cpus: '0.5' # ---------------------------------------------------------------------------- # Redis - Cache & Sessions # ---------------------------------------------------------------------------- redis: image: redis:7-alpine command: > redis-server --appendonly yes --appendfsync everysec --maxmemory 256mb --maxmemory-policy allkeys-lru volumes: - redis-data:/data networks: - backend healthcheck: test: ["CMD", "redis-cli", "ping"] interval: 10s timeout: 3s retries: 3 start_period: 10s deploy: placement: constraints: - node.role == manager restart_policy: condition: on-failure delay: 5s resources: limits: memory: 256M cpus: '0.5' reservations: memory: 128M cpus: '0.25' # ---------------------------------------------------------------------------- # Queue Worker - Background Jobs # ---------------------------------------------------------------------------- queue-worker: image: 94.16.110.151:5000/framework:latest command: ["php", "/var/www/html/worker.php"] environment: - APP_ENV=production - APP_DEBUG=false - DB_HOST=db - DB_DATABASE=framework_prod - REDIS_HOST=redis - WORKER_DEBUG=false - WORKER_SLEEP_TIME=100000 - WORKER_MAX_JOBS=1000 secrets: - db_password - app_key volumes: - storage-logs:/var/www/html/storage/logs:rw - storage-queue:/var/www/html/storage/queue:rw networks: - backend deploy: replicas: 2 restart_policy: condition: on-failure delay: 5s max_attempts: 3 resources: limits: memory: 512M reservations: memory: 256M stop_grace_period: 30s # ---------------------------------------------------------------------------- # Prometheus - Metrics Collection # ---------------------------------------------------------------------------- prometheus: image: prom/prometheus:latest command: - '--config.file=/etc/prometheus/prometheus.yml' - '--storage.tsdb.path=/prometheus' - '--web.console.libraries=/usr/share/prometheus/console_libraries' - '--web.console.templates=/usr/share/prometheus/consoles' - '--storage.tsdb.retention.time=30d' volumes: - prometheus-data:/prometheus - ./prometheus/prometheus.yml:/etc/prometheus/prometheus.yml:ro networks: - traefik-public - backend deploy: placement: constraints: - node.role == manager restart_policy: condition: on-failure resources: limits: memory: 512M cpus: '0.5' reservations: memory: 256M cpus: '0.25' labels: - "traefik.enable=true" - "traefik.http.routers.prometheus.rule=Host(`prometheus.michaelschiemer.de`)" - "traefik.http.routers.prometheus.entrypoints=websecure" - "traefik.http.routers.prometheus.tls=true" - "traefik.http.services.prometheus.loadbalancer.server.port=9090" # ---------------------------------------------------------------------------- # Grafana - Metrics Visualization # ---------------------------------------------------------------------------- grafana: image: grafana/grafana:latest environment: - GF_SECURITY_ADMIN_PASSWORD__FILE=/run/secrets/grafana_admin_password - GF_USERS_ALLOW_SIGN_UP=false - GF_SERVER_ROOT_URL=https://grafana.michaelschiemer.de - GF_INSTALL_PLUGINS= secrets: - grafana_admin_password volumes: - grafana-data:/var/lib/grafana networks: - traefik-public - backend deploy: placement: constraints: - node.role == manager restart_policy: condition: on-failure resources: limits: memory: 512M cpus: '0.5' reservations: memory: 256M cpus: '0.25' labels: - "traefik.enable=true" - "traefik.http.routers.grafana.rule=Host(`grafana.michaelschiemer.de`)" - "traefik.http.routers.grafana.entrypoints=websecure" - "traefik.http.routers.grafana.tls=true" - "traefik.http.services.grafana.loadbalancer.server.port=3000" # ---------------------------------------------------------------------------- # Portainer - Container Management # ---------------------------------------------------------------------------- portainer: image: portainer/portainer-ce:latest command: -H unix:///var/run/docker.sock volumes: - /var/run/docker.sock:/var/run/docker.sock - portainer-data:/data networks: - traefik-public deploy: placement: constraints: - node.role == manager restart_policy: condition: on-failure resources: limits: memory: 256M cpus: '0.25' reservations: memory: 128M cpus: '0.1' labels: - "traefik.enable=true" - "traefik.http.routers.portainer.rule=Host(`portainer.michaelschiemer.de`)" - "traefik.http.routers.portainer.entrypoints=websecure" - "traefik.http.routers.portainer.tls=true" - "traefik.http.services.portainer.loadbalancer.server.port=9000" # ============================================================================== # Networks # ============================================================================== networks: traefik-public: driver: overlay attachable: true backend: driver: overlay internal: true # ============================================================================== # Volumes # ============================================================================== volumes: traefik-logs: storage-logs: storage-uploads: storage-queue: db-data: redis-data: prometheus-data: grafana-data: portainer-data: # ============================================================================== # Secrets (to be created before deployment) # ============================================================================== secrets: db_password: external: true app_key: external: true vault_encryption_key: external: true shopify_webhook_secret: external: true rapidmail_password: external: true grafana_admin_password: external: true