Production deployment was failing because docker-compose.production.yml had build: sections but no image: references. This caused Docker Compose to attempt building on the server, which failed because the docker/ directory doesn't exist in the deployment location. Changes: - Add image: git.michaelschiemer.de:5000/framework:latest to web, php, and queue-worker services - Removed build: section from php service (no longer needed) - Remove test comment from ShowHome.php The deployment script's sed command (line 1259-1260 in build-image.yml) now successfully finds and updates the image: tags with the correct version from the registry. Related to: Production deployment error "docker/php: no such file or directory"
430 lines
12 KiB
YAML
430 lines
12 KiB
YAML
# 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/
|