309 lines
10 KiB
YAML
309 lines
10 KiB
YAML
# Docker Registry: registry.michaelschiemer.de (HTTPS via Traefik)
|
|
|
|
services:
|
|
# PHP-FPM Application Runtime
|
|
app:
|
|
image: git.michaelschiemer.de:5000/framework:latest
|
|
container_name: app
|
|
restart: unless-stopped
|
|
networks:
|
|
- app-internal
|
|
environment:
|
|
- TZ=Europe/Berlin
|
|
- APP_ENV=${APP_ENV:-production}
|
|
- APP_DEBUG=${APP_DEBUG:-false}
|
|
- APP_URL=${APP_URL:-https://michaelschiemer.de}
|
|
- APP_KEY=${APP_KEY:-}
|
|
# Git Repository (optional - if set, container will clone/pull code on start)
|
|
- GIT_REPOSITORY_URL=${GIT_REPOSITORY_URL:-}
|
|
- GIT_BRANCH=${GIT_BRANCH:-main}
|
|
- GIT_TOKEN=${GIT_TOKEN:-}
|
|
- GIT_USERNAME=${GIT_USERNAME:-}
|
|
- GIT_PASSWORD=${GIT_PASSWORD:-}
|
|
# Database
|
|
- DB_HOST=${DB_HOST:-postgres}
|
|
- DB_PORT=${DB_PORT:-5432}
|
|
- DB_DATABASE=${DB_DATABASE}
|
|
- DB_USERNAME=${DB_USERNAME}
|
|
- DB_PASSWORD=${DB_PASSWORD}
|
|
# Redis
|
|
- REDIS_HOST=redis
|
|
- REDIS_PORT=6379
|
|
- REDIS_PASSWORD=${REDIS_PASSWORD}
|
|
# Cache
|
|
- CACHE_DRIVER=redis
|
|
- CACHE_PREFIX=${CACHE_PREFIX:-app}
|
|
# Session
|
|
- SESSION_DRIVER=redis
|
|
- SESSION_LIFETIME=${SESSION_LIFETIME:-120}
|
|
# Queue
|
|
- QUEUE_DRIVER=redis
|
|
- QUEUE_CONNECTION=default
|
|
volumes:
|
|
- app-code:/var/www/html
|
|
- app-storage:/var/www/html/storage
|
|
- app-logs:/var/www/html/storage/logs
|
|
- /etc/timezone:/etc/timezone:ro
|
|
- /etc/localtime:/etc/localtime:ro
|
|
healthcheck:
|
|
test: ["CMD-SHELL", "true"]
|
|
interval: 30s
|
|
timeout: 10s
|
|
retries: 3
|
|
start_period: 40s
|
|
depends_on:
|
|
redis:
|
|
condition: service_started
|
|
|
|
# Nginx Web Server
|
|
# Uses same image as app - clones code from Git if GIT_REPOSITORY_URL is set, then runs nginx
|
|
nginx:
|
|
image: git.michaelschiemer.de:5000/framework:latest
|
|
container_name: nginx
|
|
restart: unless-stopped
|
|
networks:
|
|
- traefik-public
|
|
- app-internal
|
|
environment:
|
|
- TZ=Europe/Berlin
|
|
- APP_ENV=${APP_ENV:-production}
|
|
- APP_DEBUG=${APP_DEBUG:-false}
|
|
# Git Repository (same as app - will clone code on start)
|
|
- GIT_REPOSITORY_URL=${GIT_REPOSITORY_URL:-}
|
|
- GIT_BRANCH=${GIT_BRANCH:-main}
|
|
- GIT_TOKEN=${GIT_TOKEN:-}
|
|
- GIT_USERNAME=${GIT_USERNAME:-}
|
|
- GIT_PASSWORD=${GIT_PASSWORD:-}
|
|
volumes:
|
|
- ./nginx/conf.d:/etc/nginx/conf.d:ro
|
|
- app-storage:/var/www/html/storage:ro
|
|
- /etc/timezone:/etc/timezone:ro
|
|
- /etc/localtime:/etc/localtime:ro
|
|
# Use custom entrypoint that ensures code is available then starts nginx only (no PHP-FPM)
|
|
entrypoint: ["/bin/sh", "-c"]
|
|
command:
|
|
- |
|
|
# Ensure code is available in /var/www/html (from image or Git)
|
|
GIT_TARGET_DIR="/var/www/html"
|
|
|
|
# If storage is mounted but code is missing, copy from image's original location
|
|
if [ ! -d "$$GIT_TARGET_DIR/public" ] && [ -d "/var/www/html.orig" ]; then
|
|
echo "?? [nginx] Copying code from image..."
|
|
# Copy everything except storage (which is a volume mount)
|
|
find /var/www/html.orig -mindepth 1 -maxdepth 1 ! -name "storage" -exec cp -r {} "$$GIT_TARGET_DIR/" \; 2>/dev/null || true
|
|
fi
|
|
|
|
if [ -n "$$GIT_REPOSITORY_URL" ]; then
|
|
# Configure Git to be non-interactive
|
|
export GIT_TERMINAL_PROMPT=0
|
|
export GIT_ASKPASS=echo
|
|
|
|
# Determine authentication method
|
|
|
|
|
|
if [ -n "$$GIT_TOKEN" ]; then
|
|
GIT_URL_WITH_AUTH=$$(echo "$$GIT_REPOSITORY_URL" | sed "s|https://|https://$${GIT_TOKEN}@|")
|
|
elif [ -n "$$GIT_USERNAME" ] && [ -n "$$GIT_PASSWORD" ]; then
|
|
GIT_URL_WITH_AUTH=$$(echo "$$GIT_REPOSITORY_URL" | sed "s|https://|https://$${GIT_USERNAME}:$${GIT_PASSWORD}@|")
|
|
else
|
|
echo "⚠️ [nginx] No Git credentials provided (GIT_TOKEN or GIT_USERNAME/GIT_PASSWORD). Using image contents."
|
|
GIT_URL_WITH_AUTH=""
|
|
fi
|
|
|
|
|
|
if [ -n "$$GIT_URL_WITH_AUTH" ] && [ ! -d "$$GIT_TARGET_DIR/.git" ]; then
|
|
echo "?? [nginx] Cloning repository from $$GIT_REPOSITORY_URL (branch: $${GIT_BRANCH:-main})..."
|
|
# Remove only files/dirs that are not storage (which is a volume mount)
|
|
# Clone into a temporary directory first, then move contents
|
|
TEMP_CLONE="$${GIT_TARGET_DIR}.tmp"
|
|
rm -rf "$$TEMP_CLONE" 2>/dev/null || true
|
|
if git clone --branch "$${GIT_BRANCH:-main}" --depth 1 "$$GIT_URL_WITH_AUTH" "$$TEMP_CLONE"; then
|
|
# Remove only files/dirs that are not storage (which is a volume mount)
|
|
find "$$GIT_TARGET_DIR" -mindepth 1 -maxdepth 1 ! -name "storage" -exec rm -rf {} \\; 2>/dev/null || true
|
|
# Move contents from temp directory to target (preserving storage)
|
|
find "$$TEMP_CLONE" -mindepth 1 -maxdepth 1 ! -name "." ! -name ".." -exec mv {} "$$GIT_TARGET_DIR/" \\; 2>/dev/null || true
|
|
rm -rf "$$TEMP_CLONE" 2>/dev/null || true
|
|
echo "✅ [nginx] Repository cloned successfully"
|
|
else
|
|
echo "? Git clone failed. Using image contents."
|
|
rm -rf "$$TEMP_CLONE" 2>/dev/null || true
|
|
fi
|
|
else
|
|
echo "?? [nginx] Pulling latest changes..."
|
|
cd "$$GIT_TARGET_DIR"
|
|
git fetch origin "$${GIT_BRANCH:-main}" || true
|
|
git reset --hard "origin/$${GIT_BRANCH:-main}" || true
|
|
git clean -fd || true
|
|
fi
|
|
if [ -f "$$GIT_TARGET_DIR/composer.json" ]; then
|
|
echo "?? [nginx] Installing dependencies..."
|
|
cd "$$GIT_TARGET_DIR"
|
|
composer install --no-dev --optimize-autoloader --no-interaction --no-scripts || true
|
|
composer dump-autoload --optimize --classmap-authoritative || true
|
|
fi
|
|
echo "? [nginx] Git sync completed"
|
|
else
|
|
echo "?? [nginx] GIT_REPOSITORY_URL not set, using code from image"
|
|
fi
|
|
|
|
# Start nginx only (no PHP-FPM)
|
|
echo "?? [nginx] Starting nginx..."
|
|
exec nginx -g "daemon off;"
|
|
labels:
|
|
- "traefik.enable=true"
|
|
# HTTP Router
|
|
- "traefik.http.routers.app.rule=Host(`${APP_DOMAIN:-michaelschiemer.de}`)"
|
|
- "traefik.http.routers.app.entrypoints=websecure"
|
|
- "traefik.http.routers.app.tls=true"
|
|
- "traefik.http.routers.app.tls.certresolver=letsencrypt"
|
|
# Service
|
|
- "traefik.http.services.app.loadbalancer.server.port=80"
|
|
# Middleware
|
|
- "traefik.http.routers.app.middlewares=default-chain@file"
|
|
# Network
|
|
- "traefik.docker.network=traefik-public"
|
|
healthcheck:
|
|
test: ["CMD-SHELL", "curl -f http://127.0.0.1/health || exit 1"]
|
|
interval: 30s
|
|
timeout: 10s
|
|
retries: 3
|
|
start_period: 10s
|
|
depends_on:
|
|
app:
|
|
condition: service_started
|
|
|
|
# Redis Cache/Session/Queue Backend
|
|
redis:
|
|
image: redis:7-alpine
|
|
container_name: redis
|
|
restart: unless-stopped
|
|
networks:
|
|
- app-internal
|
|
environment:
|
|
- TZ=Europe/Berlin
|
|
command: >
|
|
redis-server
|
|
--requirepass ${REDIS_PASSWORD}
|
|
--maxmemory 512mb
|
|
--maxmemory-policy allkeys-lru
|
|
--save 900 1
|
|
--save 300 10
|
|
--save 60 10000
|
|
--appendonly yes
|
|
--appendfsync everysec
|
|
volumes:
|
|
- redis-data:/data
|
|
- /etc/timezone:/etc/timezone:ro
|
|
- /etc/localtime:/etc/localtime:ro
|
|
healthcheck:
|
|
test: ["CMD", "redis-cli", "--raw", "incr", "ping"]
|
|
interval: 30s
|
|
timeout: 10s
|
|
retries: 3
|
|
start_period: 10s
|
|
|
|
# Queue Worker (Background Jobs)
|
|
queue-worker:
|
|
image: git.michaelschiemer.de:5000/framework:latest
|
|
container_name: queue-worker
|
|
restart: unless-stopped
|
|
networks:
|
|
- app-internal
|
|
environment:
|
|
- TZ=Europe/Berlin
|
|
- APP_ENV=${APP_ENV:-production}
|
|
- APP_DEBUG=${APP_DEBUG:-false}
|
|
# Database
|
|
- DB_HOST=${DB_HOST:-postgres}
|
|
- DB_PORT=${DB_PORT:-5432}
|
|
- DB_DATABASE=${DB_DATABASE}
|
|
- DB_USERNAME=${DB_USERNAME}
|
|
- DB_PASSWORD=${DB_PASSWORD}
|
|
# Redis
|
|
- REDIS_HOST=redis
|
|
- REDIS_PORT=6379
|
|
- REDIS_PASSWORD=${REDIS_PASSWORD}
|
|
# Queue
|
|
- QUEUE_DRIVER=redis
|
|
- QUEUE_CONNECTION=default
|
|
- QUEUE_WORKER_SLEEP=${QUEUE_WORKER_SLEEP:-3}
|
|
- QUEUE_WORKER_TRIES=${QUEUE_WORKER_TRIES:-3}
|
|
- QUEUE_WORKER_TIMEOUT=${QUEUE_WORKER_TIMEOUT:-60}
|
|
volumes:
|
|
- app-storage:/var/www/html/storage
|
|
- app-logs:/var/www/html/storage/logs
|
|
- /etc/timezone:/etc/timezone:ro
|
|
- /etc/localtime:/etc/localtime:ro
|
|
command: php console.php queue:work --queue=default --timeout=${QUEUE_WORKER_TIMEOUT:-60}
|
|
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
|
|
depends_on:
|
|
app:
|
|
condition: service_started
|
|
redis:
|
|
condition: service_started
|
|
|
|
# Scheduler (Cron Jobs)
|
|
scheduler:
|
|
image: git.michaelschiemer.de:5000/framework:latest
|
|
container_name: scheduler
|
|
restart: unless-stopped
|
|
networks:
|
|
- app-internal
|
|
environment:
|
|
- TZ=Europe/Berlin
|
|
- APP_ENV=${APP_ENV:-production}
|
|
- APP_DEBUG=${APP_DEBUG:-false}
|
|
# Database
|
|
- DB_HOST=${DB_HOST:-postgres}
|
|
- DB_PORT=${DB_PORT:-5432}
|
|
- DB_DATABASE=${DB_DATABASE}
|
|
- DB_USERNAME=${DB_USERNAME}
|
|
- DB_PASSWORD=${DB_PASSWORD}
|
|
# Redis
|
|
- REDIS_HOST=redis
|
|
- REDIS_PORT=6379
|
|
- REDIS_PASSWORD=${REDIS_PASSWORD}
|
|
volumes:
|
|
- app-storage:/var/www/html/storage
|
|
- app-logs:/var/www/html/storage/logs
|
|
- /etc/timezone:/etc/timezone:ro
|
|
- /etc/localtime:/etc/localtime:ro
|
|
command: php console.php scheduler:run
|
|
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
|
|
depends_on:
|
|
app:
|
|
condition: service_started
|
|
redis:
|
|
condition: service_started
|
|
|
|
volumes:
|
|
app-code:
|
|
name: app-code
|
|
external: true
|
|
app-storage:
|
|
name: app-storage
|
|
external: true
|
|
app-logs:
|
|
name: app-logs
|
|
external: true
|
|
redis-data:
|
|
name: redis-data
|
|
external: true
|
|
|
|
networks:
|
|
traefik-public:
|
|
external: true
|
|
app-internal:
|
|
external: true
|
|
name: app-internal
|