# 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 (override build from base) image: localhost:5000/framework:latest # Build section removed - production-base.yml has no build sections # Production restart policy restart: always # Note: Port mappings removed - Traefik handles all external routing # No direct port bindings needed, Traefik proxies traffic to this container # 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 # Note: no-new-privileges prevents PHP-FPM from switching to www-data # We need to allow privilege escalation for PHP-FPM user switching # security_opt: # - no-new-privileges:true cap_drop: - ALL cap_add: - CHOWN - DAC_OVERRIDE - NET_BIND_SERVICE # Required for binding to ports 80/443 - SETGID # Required for PHP-FPM to switch to www-data - SETUID # Required for PHP-FPM to switch to www-data # 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 (override build from base) image: localhost:5000/framework:latest # Build section removed - production-base.yml has no build sections # 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 # Load environment variables from .env file (generated by Ansible) # Use absolute path to ensure .env is found regardless of working directory env_file: - /home/deploy/deployment/stacks/production/.env 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-write for storage/var directories) - /home/deploy/michaelschiemer/current:/var/www/html:rw # Database service removed - using external PostgreSQL Stack (deployment/stacks/postgresql/) # Connection via app-internal network using docker-compose.postgres-override.yml # Redis service removed - using external Redis Stack (deployment/stacks/redis/) # Connection via app-internal network using docker-compose.redis-override.yml queue-worker: # Use pre-built image from registry (override build from base) image: localhost:5000/framework:latest # Build section removed - production-base.yml has no build sections # 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" # Override entrypoint to skip PHP-FPM/Nginx startup - queue-worker only needs PHP CLI entrypoint: "" # Worker command - direct PHP execution command: ["php", "/var/www/html/worker.php"] # Production volumes volumes: # Mount application code from rsync deployment (read-write for storage/var directories) - /home/deploy/michaelschiemer/current:/var/www/html:rw # Load environment variables from .env file (generated by Ansible) # Use absolute path to ensure .env is found regardless of working directory env_file: - /home/deploy/deployment/stacks/production/.env 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: php: condition: service_healthy # Note: PostgreSQL and Redis are external services, connection via app-internal network # php-test service removed for production (test profiles not used in production) php-test: image: localhost:5000/framework:latest # Build section removed - production-base.yml has no build sections profiles: - never # Disable php-test in production # Scheduler (Cron Jobs) scheduler: # Use pre-built image from registry (override build from base if exists) image: localhost:5000/framework:latest # Build section removed - production-base.yml has no build sections 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" # Override entrypoint to skip PHP-FPM/Nginx startup - scheduler only needs PHP CLI entrypoint: "" # Scheduler command - direct PHP execution command: php console.php scheduler:run # Production volumes volumes: # Mount application code from rsync deployment (read-write for storage/var directories) - /home/deploy/michaelschiemer/current:/var/www/html:rw # Load environment variables from .env file (generated by Ansible) # Use absolute path to ensure .env is found regardless of working directory env_file: - /home/deploy/deployment/stacks/production/.env 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: php: condition: service_healthy # Note: PostgreSQL and Redis are external services, 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/