Files
michaelschiemer/docker-compose.prod.yml

467 lines
13 KiB
YAML

# ==============================================================================
# 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