-
Environment Variables Set
APP_ENV=productionAPP_DEBUG=falseAPP_KEYgenerated (Laravel)- Database credentials secure
-
Security Hardened
- SSL Certificates configured (
CERTBOT_ENABLED=true) -
PHP_DISABLE_FUNCTIONSset (optional but recommended) - Firewall rules configured (only 80, 443 exposed)
- Strong database passwords
- No debug/development extensions (Xdebug disabled)
- SSL Certificates configured (
-
Performance Optimized
-
OPCACHE_VALIDATE_TIMESTAMPS=0(production) -
OPCACHE_JIT=tracingenabled - FPM pool sized for your server (see formulas below)
- Laravel optimizations:
LARAVEL_OPTIMIZE_ON_BOOT=true
-
-
Monitoring & Logging
- Health checks enabled (
HEALTHCHECK_ENABLED=true) - Log aggregation configured (stdout/stderr)
- Error tracking (Sentry, Bugsnag, etc.)
- Health checks enabled (
-
Backup Strategy
- Database backups automated
- Volume backups scheduled (
/etc/letsencrypt, uploaded files) - Backup restore tested
environment:
APP_ENV: production
APP_DEBUG: "false"
# Disable dangerous PHP functions
PHP_DISABLE_FUNCTIONS: "exec,passthru,shell_exec,system,proc_open,popen,curl_exec,curl_multi_exec,parse_ini_file,show_source"environment:
# OPcache - Maximum Performance
OPCACHE_ENABLE: "1"
OPCACHE_MEMORY_CONSUMPTION: 256 # Increase if many files
OPCACHE_MAX_ACCELERATED_FILES: 20000
OPCACHE_VALIDATE_TIMESTAMPS: "0" # IMPORTANT: Disable for production
OPCACHE_JIT: tracing
OPCACHE_JIT_BUFFER_SIZE: 100M
# Realpath Cache - Critical for Laravel
REALPATH_CACHE_SIZE: 4096k
REALPATH_CACHE_TTL: 600
# PHP-FPM Pool Sizing (see formulas below)
FPM_PM_TYPE: dynamic
FPM_PM_MAX_CHILDREN: 50 # Adjust based on RAM!
FPM_PM_START_SERVERS: 10
FPM_PM_MIN_SPARE_SERVERS: 5
FPM_PM_MAX_SPARE_SERVERS: 15
FPM_PM_MAX_REQUESTS: 500 # Prevent memory leaksAvailable RAM for PHP = Total RAM - (System + Database + Other services)
FPM_PM_MAX_CHILDREN = Available RAM / Average Memory per Request
# Example: 4GB Server
# - System: 512MB
# - MySQL: 1GB
# - Other: 512MB
# = Available: 2GB
# If average PHP request uses 64MB:
FPM_PM_MAX_CHILDREN = 2048MB / 64MB = 32
# Conservative settings:
FPM_PM_MAX_CHILDREN: 25
FPM_PM_START_SERVERS: 5
FPM_PM_MIN_SPARE_SERVERS: 3
FPM_PM_MAX_SPARE_SERVERS: 10
Use when you know exact traffic patterns and want maximum performance:
FPM_PM_TYPE: static
FPM_PM_MAX_CHILDREN: 50 # All children always runningWarning: Static mode uses more RAM but has zero overhead for spawning workers.
Good for VPS with limited RAM:
FPM_PM_TYPE: ondemand
FPM_PM_MAX_CHILDREN: 50
FPM_PM_PROCESS_IDLE_TIMEOUT: 10s # Kill idle workers after 10sMinimum:
CERTBOT_ENABLED: "true"
APP_URL: https://yourdomain.com/ # Auto-configures SSL
CERTBOT_EMAIL: admin@yourdomain.comAuto-Renewal: Cron runs 2x daily (00:00, 12:00) - certificates renew automatically.
HTTP β HTTPS Redirect: Automatic when certificates exist.
The image handles permissions automatically:
/var/www:755owned bywww-data:www-data- PHP-FPM socket:
0660(www-data:www-data, nginx in www-data group) - Session directory:
1733(sticky bit, safe for multi-user)
Never:
- Run container as root in production
- Use
chmod 777on application directories - Disable SELinux/AppArmor without good reason
PHP_DISABLE_FUNCTIONS: "exec,passthru,shell_exec,system,proc_open,popen,curl_exec,curl_multi_exec,parse_ini_file,show_source"Note: Some packages (e.g., Laravel Excel) may need proc_open. Test before deploying!
DO NOT commit:
.envfiles with real credentials- Database passwords
- API keys
Use:
- Docker secrets (Swarm)
- Kubernetes secrets
- Vault/AWS Secrets Manager
# Check health check script
docker exec <container> /usr/local/bin/healthcheck.sh
# View detailed status
docker inspect <container> | jq '.[0].State.Health'Common causes:
- PHP-FPM crashed (check:
docker logs <container>) - Nginx config error (check:
docker exec <container> nginx -t) - Database connection failed (check Laravel
.env)
# Check FPM pool status
curl http://localhost/status?full
# Check active workers and memory
docker exec <container> ps aux | grep php-fpmSolutions:
- Reduce
FPM_PM_MAX_CHILDREN - Enable
FPM_PM_MAX_REQUESTS(default: 500) to recycle workers - Check for memory leaks in application code
# Check OPcache status
docker exec <container> php -r "print_r(opcache_get_status());"Symptoms:
oom_restartsincreasingcache_fullerrors
Solutions:
OPCACHE_MEMORY_CONSUMPTION: 512 # Increase from 256
OPCACHE_MAX_ACCELERATED_FILES: 50000 # If many files# Check FPM slow log
docker exec <container> tail -f /var/log/php-fpm/slow.logFPM logs requests > 5s by default.
Solutions:
- Optimize database queries
- Add indexes
- Enable Laravel query caching
- Check
FPM_REQUEST_SLOWLOG_TIMEOUTsetting
# Check certificates exist
docker exec <container> ls -la /etc/letsencrypt/live/
# Check Nginx SSL config
docker exec <container> cat /etc/nginx/http.d/default.conf | grep ssl
# Check Certbot logs
docker exec <container> certbot certificatesCommon issues:
CERTBOT_DOMAINSdoesn't match actual domain- Port 80 blocked (Certbot needs it for validation)
- Rate limit hit (Let's Encrypt: 50 certs/week)
Built-in health check at /health:
curl http://localhost/health
# Response: healthyAvailable at /status (localhost only):
# Summary
curl http://localhost/status
# Full details
curl http://localhost/status?full
curl http://localhost/status?jsonMetrics:
active processes- Currently processing requestsidle processes- Waiting for requeststotal processes- active + idlemax children reached- Pool exhausted (increaseFPM_PM_MAX_CHILDREN)
All logs go to stdout/stderr (Docker best practice):
# Follow all logs
docker logs -f <container>
# Filter PHP errors
docker logs <container> 2>&1 | grep -i error
# Filter slow requests
docker logs <container> 2>&1 | grep -i slowExport metrics to Prometheus/Grafana:
- PHP-FPM Exporter: Use
hipages/php-fpm_exporter - Nginx Exporter: Use
nginx-prometheus-exporter - Application Metrics: Laravel Telescope, Horizon
# 1. Pull new image
docker-compose pull
# 2. Recreate only changed services
docker-compose up -d
# 3. Run migrations (if needed)
docker-compose exec app php artisan migrate --force
# 4. Clear OPcache (force reload)
docker-compose exec app kill -USR2 1 # Reload PHP-FPMNote: Container restart clears OPcache automatically (OPCACHE_VALIDATE_TIMESTAMPS=0).
# Rolling update (zero downtime)
docker service update --image ghcr.io/rene-roscher/php:8.3 app
# Force update (pull new image)
docker service update --force --image ghcr.io/rene-roscher/php:8.3 appstrategy:
type: RollingUpdate
rollingUpdate:
maxUnavailable: 0
maxSurge: 1-
Database (daily)
docker exec <db-container> mysqldump -u root -p${PASS} ${DB} | gzip > backup-$(date +%F).sql.gz
-
Uploaded Files (daily/hourly depending on upload frequency)
tar czf storage-$(date +%F).tar.gz ./storage/app/public/ -
SSL Certificates (weekly, or after renewal)
docker run --rm -v certbot:/data -v $(pwd):/backup alpine tar czf /backup/certbot-$(date +%F).tar.gz /data
-
Environment Config (on change)
# Store encrypted in version control gpg -c .env.production git add .env.production.gpg
# 1. Stop containers
docker-compose down
# 2. Restore database
docker-compose up -d database
docker exec -i <db-container> mysql -u root -p${PASS} ${DB} < backup.sql
# 3. Restore files
tar xzf storage-backup.tar.gz -C ./
# 4. Restore certificates
docker run --rm -v certbot:/data -v $(pwd):/backup alpine tar xzf /backup/certbot-backup.tar.gz -C /
# 5. Start application
docker-compose up -dExpected performance with recommended settings:
| Server | RAM | FPM Children | Requests/sec | Response Time |
|---|---|---|---|---|
| Small VPS | 2GB | 20 | ~100 | <100ms |
| Medium | 4GB | 50 | ~250 | <50ms |
| Large | 8GB+ | 100+ | ~500+ | <30ms |
Note: Actual performance depends on application code, database, and caching.
- Laravel Performance Optimization
- PHP-FPM Tuning Guide
- Nginx Performance Tuning
- Let's Encrypt Rate Limits
- Docker Security Best Practices
- Check logs:
docker logs <container> - Run health check:
docker exec <container> /usr/local/bin/healthcheck.sh - Check documentation: README.md
- Report issues: GitHub Issues