# Application Stack - PHP Application with Nginx, Redis, Queue & Scheduler ## Overview Production-ready PHP application stack with multi-service architecture for high-performance web applications. **Features**: - PHP-FPM 8.3+ application runtime - Nginx web server with optimized configuration - Redis for caching, sessions, and queue backend - Dedicated queue worker for background job processing - Scheduler for cron job execution - SSL via Traefik with automatic Let's Encrypt certificates - Private Docker Registry integration - Health checks and automatic restart policies ## Services - **app** (PHP-FPM) - Application runtime handling PHP code execution - **nginx** (Nginx 1.25) - Web server proxying requests to PHP-FPM - **redis** (Redis 7) - Cache, session, and queue backend - **queue-worker** - Background job processor - **scheduler** - Cron job executor ## Prerequisites ### 1. Traefik Stack Running ```bash cd ../traefik docker compose up -d ``` ### 2. DNS Configuration Point `michaelschiemer.de` to your server IP (94.16.110.151) ### 3. Docker Registry Access ```bash # Login to private registry docker login registry.michaelschiemer.de # Verify access docker pull registry.michaelschiemer.de/michaelschiemer-app:latest ``` ### 4. Application Image Built ```bash # Build and push application image docker build -t registry.michaelschiemer.de/michaelschiemer-app:latest . docker push registry.michaelschiemer.de/michaelschiemer-app:latest ``` ### 5. Database Available Stack 5 (PostgreSQL/MySQL) must be running or external database configured. ## Configuration ### 1. Create Environment File ```bash cp .env.example .env ``` ### 2. Generate Passwords ```bash # Database password openssl rand -base64 32 # Redis password openssl rand -base64 32 ``` Update `.env`: ```env DB_PASS= REDIS_PASSWORD= ``` ### 3. Configure Database Connection **If using Stack 5 (PostgreSQL)**: ```env DB_HOST=postgres DB_PORT=5432 DB_NAME=michaelschiemer DB_USER=appuser ``` **If using Stack 2 Gitea MySQL**: ```env DB_HOST=mysql DB_PORT=3306 DB_NAME=michaelschiemer DB_USER=appuser ``` **If using external database**: ```env DB_HOST= DB_PORT= DB_NAME= DB_USER= ``` ### 4. Adjust Queue Worker Settings (Optional) ```env # Queue worker configuration QUEUE_WORKER_SLEEP=3 # Sleep between job checks (seconds) QUEUE_WORKER_TRIES=3 # Max attempts per job QUEUE_WORKER_TIMEOUT=60 # Max execution time per job (seconds) ``` ### 5. Configure Application Settings ```env APP_ENV=production # production, staging, or development APP_DEBUG=false # Enable debug mode (false for production) APP_URL=https://michaelschiemer.de ``` ## Deployment ### Initial Setup ```bash # Ensure Traefik is running docker network inspect traefik-public # Create .env file cp .env.example .env # Edit .env with generated passwords # Start application stack docker compose up -d # Check logs docker compose logs -f # Verify health docker compose ps ``` ### Verify Deployment ```bash # Test application endpoint curl https://michaelschiemer.de/health # Expected: HTTP 200 "healthy" # Check service status docker compose ps # All services should show "healthy" status # View logs docker compose logs app # Application logs docker compose logs nginx # Web server logs docker compose logs redis # Redis logs docker compose logs queue-worker # Queue worker logs docker compose logs scheduler # Scheduler logs ``` ## Usage ### Accessing the Application **Main Application**: - URL: https://michaelschiemer.de - SSL: Automatic via Traefik + Let's Encrypt - Auth: Configured in application ### Managing Services ```bash # Start stack docker compose up -d # Stop stack docker compose down # Restart specific service docker compose restart app docker compose restart nginx # View logs (follow mode) docker compose logs -f app docker compose logs -f queue-worker # Execute commands in app container docker compose exec app php console.php db:migrate docker compose exec app php console.php cache:clear # Access Redis CLI docker compose exec redis redis-cli -a ``` ### Queue Management ```bash # Monitor queue worker docker compose logs -f queue-worker # Restart queue worker (e.g., after code changes) docker compose restart queue-worker # Check queue status docker compose exec app php console.php queue:status # Process specific queue docker compose exec app php console.php queue:work --queue=emails # Clear failed jobs docker compose exec app php console.php queue:retry-failed ``` ### Scheduler Management ```bash # View scheduler logs docker compose logs -f scheduler # List scheduled tasks docker compose exec app php console.php scheduler:list # Run scheduler manually (for testing) docker compose exec app php console.php scheduler:run # Restart scheduler docker compose restart scheduler ``` ### Cache Management ```bash # Clear application cache docker compose exec app php console.php cache:clear # Clear specific cache tags docker compose exec app php console.php cache:forget user:123 # View cache statistics docker compose exec redis redis-cli -a INFO stats # Monitor cache in real-time docker compose exec redis redis-cli -a MONITOR ``` ## Integration with Other Stacks ### Stack 1: Traefik (Reverse Proxy) **Automatic SSL & Routing**: - Traefik labels in docker-compose.yml configure routing - SSL certificates automatically obtained via Let's Encrypt - Middleware chain applies security headers, rate limiting, etc. **Verify Integration**: ```bash # Check Traefik router docker exec traefik traefik healthcheck # View Traefik logs docker logs traefik | grep michaelschiemer.de ``` ### Stack 3: Docker Registry **Image Pulling**: - Stack pulls application image from private registry - Credentials configured via docker login **Update Application**: ```bash # Pull latest image docker compose pull app queue-worker scheduler # Recreate containers with new image docker compose up -d --force-recreate app queue-worker scheduler ``` ### Stack 5: Database (PostgreSQL or MySQL) **Connection**: - Database service must be on same Docker network or externally accessible - Connection configured via DB_HOST, DB_PORT, DB_NAME, DB_USER, DB_PASS **Run Migrations**: ```bash # Apply database migrations docker compose exec app php console.php db:migrate # Check migration status docker compose exec app php console.php db:status # Rollback migration docker compose exec app php console.php db:rollback ``` ### Stack 2: Gitea (Optional - Shared MySQL) **If using Gitea's MySQL**: ```env DB_HOST=mysql DB_PORT=3306 DB_NAME=michaelschiemer # Create separate database DB_USER=appuser # Create dedicated user ``` **Create Database**: ```bash docker exec mysql mysql -u root -p CREATE DATABASE michaelschiemer CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; CREATE USER 'appuser'@'%' IDENTIFIED BY ''; GRANT ALL PRIVILEGES ON michaelschiemer.* TO 'appuser'@'%'; FLUSH PRIVILEGES; ``` ## Backup & Recovery ### Manual Backup ```bash #!/bin/bash # backup-application.sh BACKUP_DIR="/backups/application" DATE=$(date +%Y%m%d_%H%M%S) mkdir -p $BACKUP_DIR # Backup application storage volume docker run --rm \ -v app-storage:/data \ -v $BACKUP_DIR:/backup \ alpine tar czf /backup/app-storage-$DATE.tar.gz -C /data . # Backup application logs docker run --rm \ -v app-logs:/data \ -v $BACKUP_DIR:/backup \ alpine tar czf /backup/app-logs-$DATE.tar.gz -C /data . # Backup Redis data (if persistence enabled) docker run --rm \ -v redis-data:/data \ -v $BACKUP_DIR:/backup \ alpine tar czf /backup/redis-data-$DATE.tar.gz -C /data . # Backup .env file cp .env $BACKUP_DIR/env-$DATE echo "Backup completed: $BACKUP_DIR/*-$DATE.*" ``` ### Restore from Backup ```bash # Stop stack docker compose down # Restore storage volume docker run --rm \ -v app-storage:/data \ -v /backups/application:/backup \ alpine tar xzf /backup/app-storage-YYYYMMDD_HHMMSS.tar.gz -C /data # Restore logs docker run --rm \ -v app-logs:/data \ -v /backups/application:/backup \ alpine tar xzf /backup/app-logs-YYYYMMDD_HHMMSS.tar.gz -C /data # Restore Redis data docker run --rm \ -v redis-data:/data \ -v /backups/application:/backup \ alpine tar xzf /backup/redis-data-YYYYMMDD_HHMMSS.tar.gz -C /data # Restore .env cp /backups/application/env-YYYYMMDD_HHMMSS .env # Start stack docker compose up -d ``` ### Automated Backups Add to crontab: ```bash # Daily backup at 2 AM 0 2 * * * /path/to/backup-application.sh # Keep only last 14 days 0 3 * * * find /backups/application -type f -mtime +14 -delete ``` ### Redis Persistence **Automatic Persistence** (configured in docker-compose.yml): - RDB snapshots: save 900 1, save 300 10, save 60 10000 - AOF (Append-Only File): appendonly yes, appendfsync everysec **Manual Redis Backup**: ```bash # Trigger manual save docker compose exec redis redis-cli -a SAVE # Export RDB file docker compose exec redis redis-cli -a BGSAVE docker cp redis:/data/dump.rdb ./redis-backup.rdb ``` ## Monitoring ### Health Checks ```bash # Check all service health docker compose ps # Application health curl https://michaelschiemer.de/health # Redis health docker compose exec redis redis-cli -a PING # Expected: PONG # PHP-FPM health docker compose exec app php-fpm-healthcheck ``` ### Log Management ```bash # View all logs docker compose logs # Follow logs for specific service docker compose logs -f app docker compose logs -f nginx docker compose logs -f queue-worker docker compose logs -f scheduler # View last 100 lines docker compose logs --tail=100 app # View logs since specific time docker compose logs --since 2024-01-01T00:00:00 app # Search logs docker compose logs app | grep ERROR docker compose logs nginx | grep 404 ``` ### Performance Metrics ```bash # Container resource usage docker stats app nginx redis queue-worker scheduler # Redis statistics docker compose exec redis redis-cli -a INFO stats docker compose exec redis redis-cli -a INFO memory # Redis slow log docker compose exec redis redis-cli -a SLOWLOG GET 10 # Nginx status (if enabled) docker compose exec nginx curl http://localhost/nginx_status ``` ### Application Monitoring ```bash # Application metrics endpoint (if implemented) curl https://michaelschiemer.de/metrics # Queue statistics docker compose exec app php console.php queue:stats # Cache hit rate docker compose exec app php console.php cache:stats ``` ## Troubleshooting ### Application Not Accessible ```bash # Check service status docker compose ps # Check Traefik routing docker exec traefik traefik healthcheck # View Nginx logs docker compose logs nginx # Test internal connectivity docker compose exec nginx curl http://app:9000/health ``` ### PHP-FPM Errors ```bash # View PHP-FPM logs docker compose logs app # Check PHP-FPM pool status docker compose exec app php-fpm-healthcheck # Restart PHP-FPM docker compose restart app # Check PHP configuration docker compose exec app php -i | grep error ``` ### Redis Connection Issues ```bash # Test Redis connection docker compose exec redis redis-cli -a PING # Check Redis logs docker compose logs redis # Verify Redis password grep REDIS_PASSWORD .env # Test connection from app docker compose exec app php console.php redis:test ``` ### Queue Worker Not Processing Jobs ```bash # Check queue worker status docker compose ps queue-worker # View queue worker logs docker compose logs queue-worker # Check if queue worker process is running docker compose exec queue-worker pgrep -f queue:work # Restart queue worker docker compose restart queue-worker # Check queue size docker compose exec redis redis-cli -a LLEN queues:default ``` ### Scheduler Not Running ```bash # Check scheduler status docker compose ps scheduler # View scheduler logs docker compose logs scheduler # Verify scheduler process docker compose exec scheduler pgrep -f scheduler:run # Restart scheduler docker compose restart scheduler # Test scheduler manually docker compose exec scheduler php console.php scheduler:run --once ``` ### High Memory Usage ```bash # Check container memory usage docker stats --no-stream # Redis memory usage docker compose exec redis redis-cli -a INFO memory # Adjust Redis max memory (in .env or docker-compose.yml) # --maxmemory 512mb (default) # Clear Redis cache if needed docker compose exec redis redis-cli -a FLUSHDB ``` ### Slow Response Times ```bash # Check Nginx access logs docker compose logs nginx | grep "request_time" # Enable PHP slow log (in php.ini or PHP-FPM pool config) # slowlog = /var/log/php-fpm-slow.log # request_slowlog_timeout = 5s # Check database query performance docker compose exec app php console.php db:explain # Check cache hit ratio docker compose exec redis redis-cli -a INFO stats ``` ### SSL Certificate Issues ```bash # Check Traefik certificate docker exec traefik cat /acme.json | grep michaelschiemer.de # Force certificate renewal (via Traefik) docker restart traefik # Test SSL openssl s_client -connect michaelschiemer.de:443 -servername michaelschiemer.de < /dev/null ``` ### Database Connection Errors ```bash # Test database connection from app docker compose exec app php console.php db:test # Verify database is accessible # If Stack 5 (PostgreSQL): docker exec postgres pg_isready # If Stack 2 (MySQL): docker exec mysql mysqladmin ping # Check database credentials in .env grep DB_ .env # Test connection manually docker compose exec app php -r "new PDO('mysql:host=mysql;dbname=michaelschiemer', 'appuser', 'password');" ``` ## Security ### Security Best Practices 1. **Environment Variables**: Never commit .env to version control 2. **Strong Passwords**: Use `openssl rand -base64 32` for all passwords 3. **Redis Password**: Always set REDIS_PASSWORD in production 4. **Database Access**: Use dedicated database user with minimal privileges 5. **File Permissions**: Ensure storage directories have correct ownership 6. **Updates**: Regularly update Docker images and dependencies 7. **Network Isolation**: app-internal network isolates services from external access ### Security Headers **Nginx Configuration** (`nginx/conf.d/default.conf`): - X-Frame-Options: SAMEORIGIN - X-Content-Type-Options: nosniff - X-XSS-Protection: 1; mode=block - Referrer-Policy: strict-origin-when-cross-origin **Traefik Middleware** (via default-chain@file): - HSTS - Additional security headers - Rate limiting ### Rate Limiting **Nginx Rate Limits** (configured in nginx/conf.d/default.conf): - API endpoints: 10 requests/second (burst 20) - General requests: 30 requests/second (burst 50) **Adjust Rate Limits**: ```nginx # Edit nginx/conf.d/default.conf limit_req_zone $binary_remote_addr zone=api_limit:10m rate=10r/s; limit_req_zone $binary_remote_addr zone=general_limit:10m rate=30r/s; ``` ### File Upload Security ```nginx # Client body size limit (nginx/conf.d/default.conf) client_max_body_size 100M; # Adjust based on requirements ``` ### Sensitive File Protection **Automatically Protected** (nginx/conf.d/default.conf): - .env, .git, .gitignore, .gitattributes - composer.json, composer.lock - package.json, package-lock.json - /storage (except /storage/public) ## Performance Tuning ### Redis Optimization **Memory Management**: ```yaml # docker-compose.yml --maxmemory 512mb # Adjust based on available RAM --maxmemory-policy allkeys-lru # Eviction policy ``` **Persistence**: ```yaml # Adjust RDB snapshot frequency --save 900 1 # After 900 sec if 1 key changed --save 300 10 # After 300 sec if 10 keys changed --save 60 10000 # After 60 sec if 10000 keys changed # AOF settings --appendonly yes --appendfsync everysec # or: always, no ``` **Connection Pooling**: ```php // In application code $redis = new Redis(); $redis->pconnect('redis', 6379); // Persistent connection ``` ### Nginx Optimization **Worker Processes** (add to nginx.conf if needed): ```nginx worker_processes auto; worker_connections 1024; ``` **Gzip Compression** (configured): - Level: 6 (balance between compression ratio and CPU usage) - Types: text/plain, text/css, text/javascript, application/json, etc. - Min length: 1024 bytes **Static File Caching** (configured): - Expires: 1 year for immutable assets - Cache-Control: public, immutable - Access log: disabled for static files **Buffer Tuning** (configured): ```nginx fastcgi_buffer_size 128k; fastcgi_buffers 256 16k; fastcgi_busy_buffers_size 256k; fastcgi_temp_file_write_size 256k; ``` ### PHP-FPM Optimization **Pool Configuration** (adjust in Dockerfile or php-fpm.conf): ```ini pm = dynamic pm.max_children = 50 pm.start_servers = 5 pm.min_spare_servers = 5 pm.max_spare_servers = 35 pm.max_requests = 500 ``` **OPcache** (enable in php.ini): ```ini opcache.enable=1 opcache.memory_consumption=128 opcache.interned_strings_buffer=8 opcache.max_accelerated_files=10000 opcache.revalidate_freq=2 ``` **Timeout Settings** (configured in nginx): ```nginx fastcgi_connect_timeout 60s; fastcgi_send_timeout 180s; fastcgi_read_timeout 180s; ``` ### Queue Worker Optimization **Worker Count**: ```bash # Run multiple queue workers for parallel processing docker compose up -d --scale queue-worker=3 ``` **Queue Configuration** (.env): ```env QUEUE_WORKER_SLEEP=3 # Lower = more responsive, higher = less CPU QUEUE_WORKER_TRIES=3 # Retry failed jobs QUEUE_WORKER_TIMEOUT=60 # Increase for long-running jobs ``` **Separate Queues**: ```bash # Start workers for specific queues docker compose exec app php console.php queue:work --queue=high-priority docker compose exec app php console.php queue:work --queue=emails docker compose exec app php console.php queue:work --queue=default ``` ### Database Query Optimization ```bash # Analyze slow queries docker compose exec app php console.php db:explain # Enable query logging docker compose exec app php console.php db:log enable # Cache query results docker compose exec app php console.php cache:queries ``` ## Scheduler Configuration ### Cron Jobs **Scheduler runs** `php console.php scheduler:run` continuously. **Define Scheduled Tasks** (in application code): ```php // Example: app/Console/Kernel.php protected function schedule(Schedule $schedule): void { // Run every minute $schedule->command('cache:clear')->everyMinute(); // Run hourly $schedule->command('reports:generate')->hourly(); // Run daily at 2 AM $schedule->command('cleanup:old-logs')->dailyAt('02:00'); // Run weekly on Sundays $schedule->command('backup:database')->weekly(); } ``` ### Scheduler Monitoring ```bash # View scheduler logs docker compose logs -f scheduler # List scheduled tasks docker compose exec app php console.php scheduler:list # Run scheduler manually (for testing) docker compose exec app php console.php scheduler:run --once ``` ## Update Stack ### Update Application Code ```bash # Build new image docker build -t registry.michaelschiemer.de/michaelschiemer-app:latest . # Push to registry docker push registry.michaelschiemer.de/michaelschiemer-app:latest # Pull and recreate containers docker compose pull docker compose up -d --force-recreate app queue-worker scheduler # Run migrations if needed docker compose exec app php console.php db:migrate ``` ### Update Nginx Configuration ```bash # Edit nginx configuration nano nginx/conf.d/default.conf # Test configuration docker compose exec nginx nginx -t # Reload Nginx docker compose exec nginx nginx -s reload # Or restart Nginx docker compose restart nginx ``` ### Update Stack Configuration ```bash # Pull latest images docker compose pull # Recreate containers docker compose up -d # Verify docker compose ps ``` ## Additional Resources - **Docker Compose Documentation**: https://docs.docker.com/compose/ - **Nginx Documentation**: https://nginx.org/en/docs/ - **Redis Documentation**: https://redis.io/documentation - **PHP-FPM Documentation**: https://www.php.net/manual/en/install.fpm.php - **Traefik v3 Documentation**: https://doc.traefik.io/traefik/ ## Stack Integration Summary **Depends On**: - Stack 1 (Traefik) - SSL and reverse proxy - Stack 3 (Docker Registry) - Application image storage - Stack 5 (Database) - Data persistence **Provides**: - PHP application runtime - Web server with SSL - Background job processing - Scheduled task execution - Caching infrastructure