# Production Environment Override # Usage: docker-compose -f docker-compose.base.yml -f docker-compose.production.yml --env-file .env.production up -d # # This file overrides base configuration with production-specific settings: # - Stricter resource limits # - Production restart policies (always) # - JSON logging with proper rotation # - No host mounts (security) # - Production PostgreSQL configuration # - Certbot for SSL certificates # - Production port mappings (80, 443 for Let's Encrypt) services: web: # Use pre-built image from registry image: git.michaelschiemer.de:5000/framework:latest # Production restart policy restart: always # Production port mappings for Let's Encrypt ports: - "80:80" # HTTP for ACME challenge - "443:443" # HTTPS for production traffic # Override volumes - use Let's Encrypt certificates and mount application code volumes: - certbot-conf:/etc/letsencrypt:ro - certbot-www:/var/www/certbot:ro # Application code via rsync deployment - /home/deploy/michaelschiemer/current:/var/www/html:ro environment: - APP_ENV=production - APP_DEBUG=false # Security hardening security_opt: - no-new-privileges:true cap_drop: - ALL cap_add: - CHOWN - DAC_OVERRIDE - NET_BIND_SERVICE # Required for binding to ports 80/443 # Stricter health checks for production healthcheck: test: ["CMD", "curl", "-f", "https://localhost/health"] interval: 15s timeout: 5s retries: 5 start_period: 30s # JSON logging with rotation logging: driver: json-file options: max-size: "10m" max-file: "5" compress: "true" labels: "service,environment" # Production resource limits (Nginx is lightweight) deploy: resources: limits: memory: 512M cpus: '1.0' reservations: memory: 256M cpus: '0.5' depends_on: php: condition: service_healthy certbot: condition: service_started php: # Use pre-built image from registry image: git.michaelschiemer.de:5000/framework:latest # Production restart policy restart: always # Override user setting - container must start as root for gosu to work # The entrypoint script will use gosu to switch to appuser after setup user: "root" # Security hardening (applied after gosu switches to appuser) security_opt: - no-new-privileges:true cap_drop: - ALL cap_add: - CHOWN - DAC_OVERRIDE environment: - APP_ENV=production - APP_DEBUG=false - PHP_MEMORY_LIMIT=512M - PHP_MAX_EXECUTION_TIME=30 # Disable Xdebug in production - XDEBUG_MODE=off # Use Docker Secrets via *_FILE pattern (Framework supports this automatically) - DB_PASSWORD_FILE=/run/secrets/db_user_password - REDIS_PASSWORD_FILE=/run/secrets/redis_password - APP_KEY_FILE=/run/secrets/app_key - VAULT_ENCRYPTION_KEY_FILE=/run/secrets/vault_encryption_key secrets: - db_user_password - redis_password - app_key - vault_encryption_key # Stricter health checks healthcheck: test: ["CMD", "php", "-v"] interval: 15s timeout: 5s retries: 5 start_period: 30s # JSON logging logging: driver: json-file options: max-size: "10m" max-file: "10" compress: "true" labels: "service,environment" # Production resource limits deploy: resources: limits: memory: 1G cpus: '2.0' reservations: memory: 512M cpus: '1.0' # Production volumes volumes: # Mount application code from rsync deployment (read-only) - /home/deploy/michaelschiemer/current:/var/www/html:ro # Mount storage directory as writable volume (overlays the read-only code mount) - storage:/var/www/html/storage:rw # Mount .env file from shared directory (production environment variables) - /home/deploy/michaelschiemer/shared/.env.production:/var/www/html/.env:ro # Database service removed - using external PostgreSQL Stack (deployment/stacks/postgresql/) # Connection via app-internal network using docker-compose.postgres-override.yml redis: # Production restart policy restart: always # Use Docker Secrets for Redis password environment: REDIS_PASSWORD_FILE: /run/secrets/redis_password secrets: - redis_password # Security hardening security_opt: - no-new-privileges:true # Don't set user here - we need root to read Docker Secrets in entrypoint # Redis will run as root, but this is acceptable for this use case cap_drop: - ALL # Use entrypoint script to inject password from Docker Secret into config # Note: Script runs as root to read Docker Secrets, then starts Redis entrypoint: ["/bin/sh", "-c"] command: - | # Read password from Docker Secret (as root) REDIS_PASSWORD=$$(cat /run/secrets/redis_password 2>/dev/null || echo '') # Start Redis with all settings as command line arguments (no config file to avoid conflicts) if [ -n "$$REDIS_PASSWORD" ]; then exec redis-server \ --bind 0.0.0.0 \ --dir /data \ --save 900 1 \ --save 300 10 \ --save 60 10000 \ --appendonly yes \ --requirepass "$$REDIS_PASSWORD" else exec redis-server \ --bind 0.0.0.0 \ --dir /data \ --save 900 1 \ --save 300 10 \ --save 60 10000 \ --appendonly yes fi # Production resource limits deploy: resources: limits: memory: 512M cpus: '1.0' reservations: memory: 256M cpus: '0.5' # Stricter health checks healthcheck: test: ["CMD", "redis-cli", "--raw", "incr", "ping"] interval: 10s timeout: 3s retries: 5 start_period: 10s # JSON logging logging: driver: json-file options: max-size: "10m" max-file: "5" compress: "true" labels: "service,environment" queue-worker: # Use pre-built image from registry image: git.michaelschiemer.de:5000/framework:latest # Production restart policy restart: always # Override user setting - container must start as root for gosu to work # The entrypoint script will use gosu to switch to appuser after setup user: "root" # No entrypoint override - queue-worker runs worker.php directly # Worker command - direct PHP execution command: ["php", "/var/www/html/worker.php"] # Production volumes volumes: # Mount application code from rsync deployment (read-only) - /home/deploy/michaelschiemer/current:/var/www/html:ro # Mount storage directory as writable volume (overlays the read-only code mount) - storage:/var/www/html/storage:rw # Mount var directory as writable volume for cache and logs (overlays read-only code mount) - var-data:/var/www/html/var:rw # Mount .env file from shared directory (production environment variables) - /home/deploy/michaelschiemer/shared/.env.production:/var/www/html/.env:ro environment: - APP_ENV=production - WORKER_DEBUG=false - WORKER_SLEEP_TIME=100000 - WORKER_MAX_JOBS=10000 # Use Docker Secrets via *_FILE pattern (Framework supports this automatically) - DB_PASSWORD_FILE=/run/secrets/db_user_password - REDIS_PASSWORD_FILE=/run/secrets/redis_password - APP_KEY_FILE=/run/secrets/app_key - VAULT_ENCRYPTION_KEY_FILE=/run/secrets/vault_encryption_key secrets: - db_user_password - redis_password - app_key - vault_encryption_key # Production resource limits deploy: resources: limits: memory: 2G cpus: '2.0' reservations: memory: 1G cpus: '1.0' # Note: replicas removed due to conflict with container_name # To scale queue workers, use separate docker-compose service definitions # JSON logging logging: driver: json-file options: max-size: "20m" max-file: "10" compress: "true" labels: "service,environment" # Graceful shutdown for long-running jobs stop_grace_period: 60s # Wait for dependencies to be healthy before starting depends_on: redis: condition: service_healthy php: condition: service_healthy # Note: PostgreSQL (postgres) is external service, connection via app-internal network # Scheduler (Cron Jobs) scheduler: # Use same build as php service (has application code copied) image: git.michaelschiemer.de:5000/framework:latest container_name: scheduler # Production restart policy restart: always # Override user setting - container must start as root for gosu to work # The entrypoint script will use gosu to switch to appuser after setup user: "root" # Scheduler command - direct PHP execution command: php console.php scheduler:run # Production volumes volumes: # Mount application code from rsync deployment (read-only) - /home/deploy/michaelschiemer/current:/var/www/html:ro # Mount storage directory as writable volume (overlays the read-only code mount) - storage:/var/www/html/storage:rw # Mount var directory as writable volume for cache and logs (overlays read-only code mount) - var-data:/var/www/html/var:rw # Mount .env file from shared directory (production environment variables) - /home/deploy/michaelschiemer/shared/.env.production:/var/www/html/.env:ro environment: - TZ=Europe/Berlin - APP_ENV=production - APP_DEBUG=false # Use Docker Secrets via *_FILE pattern (Framework supports this automatically) - DB_PASSWORD_FILE=/run/secrets/db_user_password - REDIS_PASSWORD_FILE=/run/secrets/redis_password - APP_KEY_FILE=/run/secrets/app_key - VAULT_ENCRYPTION_KEY_FILE=/run/secrets/vault_encryption_key secrets: - db_user_password - redis_password - app_key - vault_encryption_key # Production resource limits deploy: resources: limits: memory: 512M cpus: '0.5' reservations: memory: 256M cpus: '0.25' # Health checks healthcheck: test: ["CMD-SHELL", "php -r 'exit(0);' && test -f /var/www/html/console.php || exit 1"] interval: 60s timeout: 10s retries: 3 start_period: 30s # JSON logging logging: driver: json-file options: max-size: "10m" max-file: "5" compress: "true" labels: "service,environment" # Graceful shutdown stop_grace_period: 30s # Wait for dependencies to be healthy before starting depends_on: redis: condition: service_healthy php: condition: service_healthy # Note: PostgreSQL (postgres) is external service, connection via app-internal network # Certbot Sidecar Container for Let's Encrypt certbot: image: certbot/certbot:latest container_name: certbot restart: always volumes: # Share certificates with Nginx - certbot-conf:/etc/letsencrypt - certbot-www:/var/www/certbot # Logs for debugging - certbot-logs:/var/log/letsencrypt # Auto-renewal every 12 hours entrypoint: "/bin/sh -c 'trap exit TERM; while :; do certbot renew --webroot -w /var/www/certbot --quiet; sleep 12h & wait $${!}; done;'" networks: - frontend # JSON logging logging: driver: json-file options: max-size: "5m" max-file: "3" compress: "true" labels: "service,environment" networks: cache: internal: true # Cache network is internal in production volumes: # Let's Encrypt SSL Certificates certbot-conf: driver: local certbot-www: driver: local certbot-logs: driver: local # Application storage volume (single volume for entire storage directory) storage: driver: local # Database volume removed - using external PostgreSQL Stack # PostgreSQL data is managed by deployment/stacks/postgresql/