Files
michaelschiemer/deployment/stacks/application
Michael Schiemer 24cbbccf4c feat: update deployment configuration and encrypted env loader
- Update Ansible playbooks and roles for application deployment
- Add new Gitea/Traefik troubleshooting playbooks
- Update Docker Compose configurations (base, local, staging, production)
- Enhance EncryptedEnvLoader with improved error handling
- Add deployment scripts (autossh setup, migration, secret testing)
- Update CI/CD workflows and documentation
- Add Semaphore stack configuration
2025-11-02 20:38:06 +01:00
..

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

cd ../traefik
docker compose up -d

2. DNS Configuration

Point michaelschiemer.de to your server IP (94.16.110.151)

3. Docker Registry Access

# Login to private registry
docker login registry.michaelschiemer.de

# Verify access
docker pull registry.michaelschiemer.de/michaelschiemer-app:latest

4. Application Image Built

# 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

cp .env.example .env

2. Generate Passwords

# Database password
openssl rand -base64 32

# Redis password
openssl rand -base64 32

Update .env:

DB_PASS=<generated-database-password>
REDIS_PASSWORD=<generated-redis-password>

3. Configure Database Connection

If using Stack 5 (PostgreSQL):

DB_HOST=postgres
DB_PORT=5432
DB_NAME=michaelschiemer
DB_USER=appuser

If using Stack 2 Gitea MySQL:

DB_HOST=mysql
DB_PORT=3306
DB_NAME=michaelschiemer
DB_USER=appuser

If using external database:

DB_HOST=<external-host>
DB_PORT=<port>
DB_NAME=<database>
DB_USER=<username>

4. Adjust Queue Worker Settings (Optional)

# 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

APP_ENV=production           # production, staging, or development
APP_DEBUG=false             # Enable debug mode (false for production)
APP_URL=https://michaelschiemer.de

Deployment

Initial Setup

# 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

# 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:

Managing Services

# 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 <REDIS_PASSWORD>

Queue Management

# 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

# 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

# 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 <REDIS_PASSWORD> INFO stats

# Monitor cache in real-time
docker compose exec redis redis-cli -a <REDIS_PASSWORD> 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:

# 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:

# 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:

# 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:

DB_HOST=mysql
DB_PORT=3306
DB_NAME=michaelschiemer  # Create separate database
DB_USER=appuser          # Create dedicated user

Create Database:

docker exec mysql mysql -u root -p
CREATE DATABASE michaelschiemer CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
CREATE USER 'appuser'@'%' IDENTIFIED BY '<password>';
GRANT ALL PRIVILEGES ON michaelschiemer.* TO 'appuser'@'%';
FLUSH PRIVILEGES;

Backup & Recovery

Manual Backup

#!/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

# 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:

# 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:

# Trigger manual save
docker compose exec redis redis-cli -a <REDIS_PASSWORD> SAVE

# Export RDB file
docker compose exec redis redis-cli -a <REDIS_PASSWORD> BGSAVE
docker cp redis:/data/dump.rdb ./redis-backup.rdb

Monitoring

Health Checks

# Check all service health
docker compose ps

# Application health
curl https://michaelschiemer.de/health

# Redis health
docker compose exec redis redis-cli -a <REDIS_PASSWORD> PING
# Expected: PONG

# PHP-FPM health
docker compose exec app php-fpm-healthcheck

Log Management

# 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

# Container resource usage
docker stats app nginx redis queue-worker scheduler

# Redis statistics
docker compose exec redis redis-cli -a <REDIS_PASSWORD> INFO stats
docker compose exec redis redis-cli -a <REDIS_PASSWORD> INFO memory

# Redis slow log
docker compose exec redis redis-cli -a <REDIS_PASSWORD> SLOWLOG GET 10

# Nginx status (if enabled)
docker compose exec nginx curl http://localhost/nginx_status

Application Monitoring

# 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

# 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

# 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

# Test Redis connection
docker compose exec redis redis-cli -a <REDIS_PASSWORD> 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

# 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 <REDIS_PASSWORD> LLEN queues:default

Scheduler Not Running

# 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

# Check container memory usage
docker stats --no-stream

# Redis memory usage
docker compose exec redis redis-cli -a <REDIS_PASSWORD> 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 <REDIS_PASSWORD> FLUSHDB

Slow Response Times

# 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 <REDIS_PASSWORD> INFO stats

SSL Certificate Issues

# 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

# 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:

# 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

# 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:

# docker-compose.yml
--maxmemory 512mb              # Adjust based on available RAM
--maxmemory-policy allkeys-lru # Eviction policy

Persistence:

# 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:

// In application code
$redis = new Redis();
$redis->pconnect('redis', 6379); // Persistent connection

Nginx Optimization

Worker Processes (add to nginx.conf if needed):

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):

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):

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):

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):

fastcgi_connect_timeout 60s;
fastcgi_send_timeout 180s;
fastcgi_read_timeout 180s;

Queue Worker Optimization

Worker Count:

# Run multiple queue workers for parallel processing
docker compose up -d --scale queue-worker=3

Queue Configuration (.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:

# 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

# 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):

// 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

# 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

# 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

# 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

# Pull latest images
docker compose pull

# Recreate containers
docker compose up -d

# Verify
docker compose ps

Additional Resources

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