feat: CI/CD pipeline setup complete - Ansible playbooks updated, secrets configured, workflow ready
This commit is contained in:
381
docs/deployment/docker-swarm-deployment.md
Normal file
381
docs/deployment/docker-swarm-deployment.md
Normal file
@@ -0,0 +1,381 @@
|
||||
# Docker Swarm + Traefik Deployment Guide
|
||||
|
||||
Production deployment guide for the Custom PHP Framework using Docker Swarm orchestration with Traefik load balancer.
|
||||
|
||||
## Architecture Overview
|
||||
|
||||
```
|
||||
Internet → Traefik (SSL Termination, Load Balancing)
|
||||
↓
|
||||
[Web Service - 3 Replicas]
|
||||
↓ ↓
|
||||
Database Redis Queue Workers
|
||||
(PostgreSQL) (Cache/Sessions) (2 Replicas)
|
||||
```
|
||||
|
||||
**Key Components**:
|
||||
- **Traefik v2.10**: Reverse proxy, SSL termination, automatic service discovery
|
||||
- **Web Service**: 3 replicas of PHP-FPM + Nginx (HTTP only, Traefik handles HTTPS)
|
||||
- **PostgreSQL 16**: Single instance database (manager node)
|
||||
- **Redis 7**: Sessions and cache (manager node)
|
||||
- **Queue Workers**: 2 replicas for background job processing
|
||||
- **Docker Swarm**: Native container orchestration with rolling updates and health checks
|
||||
|
||||
## Prerequisites
|
||||
|
||||
1. **Docker Engine 28.0+** with Swarm mode enabled
|
||||
2. **Production Server** with SSH access
|
||||
3. **SSL Certificates** in `./ssl/` directory (cert.pem, key.pem)
|
||||
4. **Environment Variables** in `.env` file on production server
|
||||
5. **Docker Image** built and available
|
||||
|
||||
## Initial Setup
|
||||
|
||||
### 1. Initialize Docker Swarm
|
||||
|
||||
On production server:
|
||||
```bash
|
||||
docker swarm init
|
||||
```
|
||||
|
||||
Verify:
|
||||
```bash
|
||||
docker node ls
|
||||
# Should show 1 node as Leader
|
||||
```
|
||||
|
||||
### 2. Create Docker Secrets
|
||||
|
||||
Create secrets from .env file values:
|
||||
|
||||
```bash
|
||||
cd /home/deploy/framework
|
||||
|
||||
# Create secrets (one-time setup)
|
||||
echo "$DB_PASSWORD" | docker secret create db_password -
|
||||
echo "$APP_KEY" | docker secret create app_key -
|
||||
echo "$VAULT_ENCRYPTION_KEY" | docker secret create vault_encryption_key -
|
||||
echo "$SHOPIFY_WEBHOOK_SECRET" | docker secret create shopify_webhook_secret -
|
||||
echo "$RAPIDMAIL_PASSWORD" | docker secret create rapidmail_password -
|
||||
```
|
||||
|
||||
Or use the automated script:
|
||||
```bash
|
||||
./scripts/setup-production-secrets.sh
|
||||
```
|
||||
|
||||
Verify secrets:
|
||||
```bash
|
||||
docker secret ls
|
||||
```
|
||||
|
||||
### 3. Build and Transfer Docker Image
|
||||
|
||||
On local machine:
|
||||
|
||||
**Option A: Via Private Registry** (if available):
|
||||
```bash
|
||||
# Build image
|
||||
docker build -f Dockerfile.production -t 94.16.110.151:5000/framework:latest .
|
||||
|
||||
# Push to registry
|
||||
docker push 94.16.110.151:5000/framework:latest
|
||||
```
|
||||
|
||||
**Option B: Direct Transfer via SSH** (recommended for now):
|
||||
```bash
|
||||
# Build image
|
||||
docker build -f Dockerfile.production -t 94.16.110.151:5000/framework:latest .
|
||||
|
||||
# Save and transfer to production
|
||||
docker save 94.16.110.151:5000/framework:latest | \
|
||||
ssh -i ~/.ssh/production deploy@94.16.110.151 'docker load'
|
||||
```
|
||||
|
||||
### 4. Deploy Stack
|
||||
|
||||
On production server:
|
||||
```bash
|
||||
cd /home/deploy/framework
|
||||
|
||||
# Deploy the stack
|
||||
docker stack deploy -c docker-compose.prod.yml framework
|
||||
|
||||
# Monitor deployment
|
||||
watch docker stack ps framework
|
||||
|
||||
# Check service status
|
||||
docker stack services framework
|
||||
```
|
||||
|
||||
## Health Monitoring
|
||||
|
||||
### Check Service Status
|
||||
|
||||
```bash
|
||||
# List all services
|
||||
docker stack services framework
|
||||
|
||||
# Check specific service
|
||||
docker service ps framework_web
|
||||
|
||||
# View service logs
|
||||
docker service logs framework_web -f
|
||||
docker service logs framework_traefik -f
|
||||
docker service logs framework_db -f
|
||||
```
|
||||
|
||||
### Health Check Endpoints
|
||||
|
||||
- **Main Health**: http://localhost/health (via Traefik)
|
||||
- **Traefik Dashboard**: http://traefik.localhost:8080 (manager node only)
|
||||
|
||||
### Expected Service Replicas
|
||||
|
||||
| Service | Replicas | Purpose |
|
||||
|---------|----------|---------|
|
||||
| traefik | 1 | Reverse proxy + SSL |
|
||||
| web | 3 | Application servers |
|
||||
| db | 1 | PostgreSQL database |
|
||||
| redis | 1 | Cache + sessions |
|
||||
| queue-worker | 2 | Background jobs |
|
||||
|
||||
## Rolling Updates
|
||||
|
||||
### Update Application
|
||||
|
||||
1. Build new image with updated code:
|
||||
```bash
|
||||
docker build -f Dockerfile.production -t 94.16.110.151:5000/framework:latest .
|
||||
```
|
||||
|
||||
2. Transfer to production (if no registry):
|
||||
```bash
|
||||
docker save 94.16.110.151:5000/framework:latest | \
|
||||
ssh -i ~/.ssh/production deploy@94.16.110.151 'docker load'
|
||||
```
|
||||
|
||||
3. Update the service:
|
||||
```bash
|
||||
# On production server
|
||||
docker service update --image 94.16.110.151:5000/framework:latest framework_web
|
||||
```
|
||||
|
||||
The update will:
|
||||
- Roll out to 1 container at a time (`parallelism: 1`)
|
||||
- Wait 10 seconds between updates (`delay: 10s`)
|
||||
- Start new container before stopping old one (`order: start-first`)
|
||||
- Automatically rollback on failure (`failure_action: rollback`)
|
||||
|
||||
### Monitor Update Progress
|
||||
|
||||
```bash
|
||||
# Watch update status
|
||||
watch docker service ps framework_web
|
||||
|
||||
# View update logs
|
||||
docker service logs framework_web -f --tail 50
|
||||
```
|
||||
|
||||
### Manual Rollback
|
||||
|
||||
If needed, rollback to previous version:
|
||||
```bash
|
||||
docker service rollback framework_web
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Service Won't Start
|
||||
|
||||
Check service logs:
|
||||
```bash
|
||||
docker service logs framework_web --tail 100
|
||||
```
|
||||
|
||||
Check task failures:
|
||||
```bash
|
||||
docker service ps framework_web --no-trunc
|
||||
```
|
||||
|
||||
### Container Crashing
|
||||
|
||||
Inspect individual container:
|
||||
```bash
|
||||
# Get container ID
|
||||
docker ps -a | grep framework_web
|
||||
|
||||
# View logs
|
||||
docker logs <container_id>
|
||||
|
||||
# Exec into running container
|
||||
docker exec -it <container_id> bash
|
||||
```
|
||||
|
||||
### SSL/TLS Issues
|
||||
|
||||
Traefik handles SSL termination. Check Traefik logs:
|
||||
```bash
|
||||
docker service logs framework_traefik -f
|
||||
```
|
||||
|
||||
Verify SSL certificates are mounted in docker-compose.prod.yml:
|
||||
```yaml
|
||||
volumes:
|
||||
- ./ssl:/ssl:ro
|
||||
```
|
||||
|
||||
### Database Connection Issues
|
||||
|
||||
Check PostgreSQL health:
|
||||
```bash
|
||||
docker service logs framework_db --tail 50
|
||||
|
||||
# Exec into db container
|
||||
docker exec -it $(docker ps -q -f name=framework_db) psql -U postgres -d framework_prod
|
||||
```
|
||||
|
||||
### Redis Connection Issues
|
||||
|
||||
Check Redis availability:
|
||||
```bash
|
||||
docker service logs framework_redis --tail 50
|
||||
|
||||
# Test Redis connection
|
||||
docker exec -it $(docker ps -q -f name=framework_redis) redis-cli ping
|
||||
```
|
||||
|
||||
### Performance Issues
|
||||
|
||||
Check resource usage:
|
||||
```bash
|
||||
# Service resource limits
|
||||
docker service inspect framework_web --format='{{json .Spec.TaskTemplate.Resources}}' | jq
|
||||
|
||||
# Container stats
|
||||
docker stats
|
||||
```
|
||||
|
||||
## Scaling
|
||||
|
||||
### Scale Web Service
|
||||
|
||||
```bash
|
||||
# Scale up to 5 replicas
|
||||
docker service scale framework_web=5
|
||||
|
||||
# Scale down to 2 replicas
|
||||
docker service scale framework_web=2
|
||||
```
|
||||
|
||||
### Scale Queue Workers
|
||||
|
||||
```bash
|
||||
# Scale workers based on queue backlog
|
||||
docker service scale framework_queue-worker=4
|
||||
```
|
||||
|
||||
## Cleanup
|
||||
|
||||
### Remove Stack
|
||||
|
||||
```bash
|
||||
# Remove entire stack
|
||||
docker stack rm framework
|
||||
|
||||
# Verify removal
|
||||
docker stack ls
|
||||
```
|
||||
|
||||
### Remove Secrets
|
||||
|
||||
```bash
|
||||
# List secrets
|
||||
docker secret ls
|
||||
|
||||
# Remove specific secret
|
||||
docker secret rm db_password
|
||||
|
||||
# Remove all framework secrets
|
||||
docker secret ls | grep -E "db_password|app_key|vault_encryption_key" | awk '{print $2}' | xargs docker secret rm
|
||||
```
|
||||
|
||||
### Leave Swarm
|
||||
|
||||
```bash
|
||||
# Force leave Swarm (removes all services and secrets)
|
||||
docker swarm leave --force
|
||||
```
|
||||
|
||||
## Network Architecture
|
||||
|
||||
### Overlay Networks
|
||||
|
||||
- **traefik-public**: External network for Traefik ↔ Web communication
|
||||
- **backend**: Internal network for Web ↔ Database/Redis communication
|
||||
|
||||
### Port Mappings
|
||||
|
||||
| Port | Service | Purpose |
|
||||
|------|---------|---------|
|
||||
| 80 | Traefik | HTTP (redirects to 443) |
|
||||
| 443 | Traefik | HTTPS (production traffic) |
|
||||
| 8080 | Traefik | Dashboard (manager node only) |
|
||||
|
||||
## Volume Management
|
||||
|
||||
### Named Volumes
|
||||
|
||||
| Volume | Purpose | Mounted In |
|
||||
|--------|---------|------------|
|
||||
| traefik-logs | Traefik access logs | traefik |
|
||||
| storage-logs | Application logs | web, queue-worker |
|
||||
| storage-uploads | User uploads | web |
|
||||
| storage-queue | Queue data | queue-worker |
|
||||
| db-data | PostgreSQL data | db |
|
||||
| redis-data | Redis persistence | redis |
|
||||
|
||||
### Backup Volumes
|
||||
|
||||
```bash
|
||||
# Backup database
|
||||
docker exec $(docker ps -q -f name=framework_db) pg_dump -U postgres framework_prod > backup.sql
|
||||
|
||||
# Backup Redis (if persistence enabled)
|
||||
docker exec $(docker ps -q -f name=framework_redis) redis-cli --rdb /data/dump.rdb
|
||||
```
|
||||
|
||||
## Security Best Practices
|
||||
|
||||
1. **Secrets Management**: Never commit secrets to version control, use Docker Secrets
|
||||
2. **Network Isolation**: Backend network is internal-only, no external access
|
||||
3. **SSL/TLS**: Traefik enforces HTTPS, redirects HTTP → HTTPS
|
||||
4. **Health Checks**: All services have health checks with automatic restart
|
||||
5. **Resource Limits**: Production services have memory/CPU limits
|
||||
6. **Least Privilege**: Containers run as www-data (not root) where possible
|
||||
|
||||
## Phase 2 - Monitoring (Coming Soon)
|
||||
|
||||
- Prometheus for metrics collection
|
||||
- Grafana dashboards
|
||||
- Automated PostgreSQL backups
|
||||
- Email/Slack alerting
|
||||
|
||||
## Phase 3 - CI/CD (Coming Soon)
|
||||
|
||||
- Gitea Actions workflow
|
||||
- Loki + Promtail for log aggregation
|
||||
- Performance tuning
|
||||
|
||||
## Phase 4 - High Availability (Future)
|
||||
|
||||
- Multi-node Swarm cluster
|
||||
- Varnish CDN cache layer
|
||||
- PostgreSQL Primary/Replica with pgpool
|
||||
- MinIO object storage
|
||||
|
||||
## References
|
||||
|
||||
- [Docker Swarm Documentation](https://docs.docker.com/engine/swarm/)
|
||||
- [Traefik v2 Documentation](https://doc.traefik.io/traefik/)
|
||||
- [Docker Secrets Management](https://docs.docker.com/engine/swarm/secrets/)
|
||||
@@ -201,12 +201,16 @@ use App\Framework\Logging\Processors\WebInfoProcessor;
|
||||
$webInfoProcessor = new WebInfoProcessor($request);
|
||||
```
|
||||
|
||||
**3. Exception Processor**:
|
||||
**3. Exception Enrichment Processor**:
|
||||
```php
|
||||
use App\Framework\Logging\Processors\ExceptionProcessor;
|
||||
use App\Framework\Logging\Processors\ExceptionEnrichmentProcessor;
|
||||
|
||||
// Adds exception class, file, line, stack trace
|
||||
$exceptionProcessor = new ExceptionProcessor();
|
||||
// Converts Throwables to ExceptionContext with enriched metadata
|
||||
// - Exception hash for pattern recognition
|
||||
// - Severity categorization
|
||||
// - Short stack trace for quick overview
|
||||
// - Exception chain length
|
||||
$exceptionEnrichmentProcessor = new ExceptionEnrichmentProcessor();
|
||||
```
|
||||
|
||||
**4. Interpolation Processor**:
|
||||
|
||||
800
docs/deployment/production-deployment-guide.md
Normal file
800
docs/deployment/production-deployment-guide.md
Normal file
@@ -0,0 +1,800 @@
|
||||
# Production Deployment Guide
|
||||
|
||||
Umfassende Anleitung für das Deployment der Custom PHP Framework Anwendung auf dem Production Server.
|
||||
|
||||
## Inhaltsverzeichnis
|
||||
|
||||
1. [Architektur-Übersicht](#architektur-übersicht)
|
||||
2. [Voraussetzungen](#voraussetzungen)
|
||||
3. [Sicherheits-Setup](#sicherheits-setup)
|
||||
4. [Docker Registry Setup](#docker-registry-setup)
|
||||
5. [Production Image Build](#production-image-build)
|
||||
6. [Deployment Prozess](#deployment-prozess)
|
||||
7. [Troubleshooting](#troubleshooting)
|
||||
8. [Monitoring](#monitoring)
|
||||
|
||||
---
|
||||
|
||||
## Architektur-Übersicht
|
||||
|
||||
### Development vs Production
|
||||
|
||||
**Development** (docker-compose.yml):
|
||||
- Separate Container: Nginx + PHP-FPM
|
||||
- Source Code via Volume Mounts
|
||||
- Hot-Reload für Development
|
||||
- Xdebug aktiviert
|
||||
|
||||
**Production** (docker-compose.prod.yml):
|
||||
- Single Container: Supervisor → Nginx + PHP-FPM
|
||||
- Code im Image eingebacken
|
||||
- Minimale Volume Mounts (nur logs/uploads)
|
||||
- Optimiert für Performance
|
||||
|
||||
### Production Stack
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────┐
|
||||
│ Production Server (94.16.110.151) │
|
||||
│ │
|
||||
│ ┌────────────┐ ┌────────────┐ ┌────────────┐│
|
||||
│ │ Web │ │ PHP │ │ Redis ││
|
||||
│ │ (Supervisor│ │ │ │ Cache ││
|
||||
│ │ Nginx + │ │ │ │ ││
|
||||
│ │ PHP-FPM) │ │ │ │ ││
|
||||
│ └────────────┘ └────────────┘ └────────────┘│
|
||||
│ │
|
||||
│ ┌────────────┐ ┌────────────┐ ┌────────────┐│
|
||||
│ │ PostgreSQL │ │ Queue │ │ Watchtower ││
|
||||
│ │ Database │ │ Worker │ │ Auto-Update││
|
||||
│ └────────────┘ └────────────┘ └────────────┘│
|
||||
│ │
|
||||
│ Monitoring (VPN-only): │
|
||||
│ ┌────────────┐ ┌────────────┐ ┌────────────┐│
|
||||
│ │ Prometheus │ │ Grafana │ │ Portainer ││
|
||||
│ │ :9090 │ │ :3000 │ │ :9443 ││
|
||||
│ └────────────┘ └────────────┘ └────────────┘│
|
||||
└─────────────────────────────────────────────────┘
|
||||
▲
|
||||
│ WireGuard VPN (10.8.0.0/24)
|
||||
│
|
||||
┌───┴────┐
|
||||
│ Client │
|
||||
└────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Voraussetzungen
|
||||
|
||||
### Server Requirements
|
||||
|
||||
- **OS**: Ubuntu 22.04 LTS (oder neuer)
|
||||
- **RAM**: Minimum 4GB (empfohlen: 8GB+)
|
||||
- **CPU**: 2+ Cores
|
||||
- **Disk**: 50GB+ freier Speicherplatz
|
||||
- **Network**: Statische IP oder DNS
|
||||
|
||||
### Installierte Software
|
||||
|
||||
```bash
|
||||
# Docker & Docker Compose
|
||||
docker --version # 24.0+
|
||||
docker-compose --version # 2.20+
|
||||
|
||||
# WireGuard (für sicheren Zugriff)
|
||||
wg --version
|
||||
|
||||
# SSL Tools
|
||||
openssl version
|
||||
```
|
||||
|
||||
### Ports
|
||||
|
||||
**Public (Firewall offen)**:
|
||||
- `8888`: HTTP (optional, für HTTP→HTTPS Redirect)
|
||||
- `8443`: HTTPS (Hauptzugang)
|
||||
- `51820`: WireGuard VPN (UDP)
|
||||
|
||||
**VPN-only (über 10.8.0.1)**:
|
||||
- `9090`: Prometheus
|
||||
- `3000`: Grafana
|
||||
- `9443`: Portainer
|
||||
|
||||
**Internal (nicht extern erreichbar)**:
|
||||
- `5432`: PostgreSQL
|
||||
- `6379`: Redis
|
||||
- `9000`: PHP-FPM
|
||||
|
||||
---
|
||||
|
||||
## Sicherheits-Setup
|
||||
|
||||
### 1. WireGuard VPN
|
||||
|
||||
WireGuard bietet verschlüsselten Zugang zum Production Server für Administration und Monitoring.
|
||||
|
||||
**Server Installation**:
|
||||
```bash
|
||||
# Als root auf Production Server
|
||||
apt update
|
||||
apt install -y wireguard
|
||||
|
||||
# Schlüssel generieren
|
||||
cd /etc/wireguard
|
||||
umask 077
|
||||
wg genkey | tee server_private.key | wg pubkey > server_public.key
|
||||
|
||||
# Server Config
|
||||
cat > /etc/wireguard/wg0.conf <<'EOF'
|
||||
[Interface]
|
||||
Address = 10.8.0.1/24
|
||||
ListenPort = 51820
|
||||
PrivateKey = <server_private_key>
|
||||
|
||||
# Client (Development Machine)
|
||||
[Peer]
|
||||
PublicKey = <client_public_key>
|
||||
AllowedIPs = 10.8.0.2/32
|
||||
EOF
|
||||
|
||||
# Service starten
|
||||
systemctl enable wg-quick@wg0
|
||||
systemctl start wg-quick@wg0
|
||||
systemctl status wg-quick@wg0
|
||||
```
|
||||
|
||||
**Client Configuration** (`/etc/wireguard/wg0-production.conf`):
|
||||
```ini
|
||||
[Interface]
|
||||
Address = 10.8.0.2/32
|
||||
PrivateKey = <client_private_key>
|
||||
DNS = 1.1.1.1
|
||||
|
||||
[Peer]
|
||||
PublicKey = <server_public_key>
|
||||
Endpoint = 94.16.110.151:51820
|
||||
AllowedIPs = 10.8.0.0/24, 94.16.110.151/32
|
||||
PersistentKeepalive = 25
|
||||
```
|
||||
|
||||
**Client Start**:
|
||||
```bash
|
||||
sudo wg-quick up wg0-production
|
||||
sudo wg show # Verify connection
|
||||
ping 10.8.0.1 # Test connectivity
|
||||
```
|
||||
|
||||
### 2. Firewall Configuration
|
||||
|
||||
**UFW Rules** (auf Production Server):
|
||||
```bash
|
||||
# Default policies
|
||||
ufw default deny incoming
|
||||
ufw default allow outgoing
|
||||
|
||||
# SSH (nur von spezifischen IPs)
|
||||
ufw allow from <deine_ip> to any port 22
|
||||
|
||||
# WireGuard
|
||||
ufw allow 51820/udp
|
||||
|
||||
# HTTP/HTTPS
|
||||
ufw allow 8888/tcp # HTTP (optional)
|
||||
ufw allow 8443/tcp # HTTPS
|
||||
|
||||
# Enable firewall
|
||||
ufw enable
|
||||
ufw status verbose
|
||||
```
|
||||
|
||||
### 3. SSL/TLS Zertifikate
|
||||
|
||||
**Development/Testing** (Self-Signed):
|
||||
```bash
|
||||
# Bereits vorhanden in ./ssl/
|
||||
# - cert.pem
|
||||
# - key.pem
|
||||
```
|
||||
|
||||
**Production** (Let's Encrypt empfohlen):
|
||||
```bash
|
||||
# Mit certbot
|
||||
certbot certonly --standalone -d yourdomain.com
|
||||
# Zertifikate nach ./ssl/ kopieren
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Docker Registry Setup
|
||||
|
||||
### Local Registry on Production Server
|
||||
|
||||
Für sichere, private Image-Verwaltung läuft eine lokale Docker Registry auf dem Production Server.
|
||||
|
||||
**Registry starten**:
|
||||
```bash
|
||||
docker run -d \
|
||||
--restart=always \
|
||||
--name registry \
|
||||
-p 127.0.0.1:5000:5000 \
|
||||
registry:2
|
||||
```
|
||||
|
||||
**Verify**:
|
||||
```bash
|
||||
curl http://localhost:5000/v2/_catalog
|
||||
```
|
||||
|
||||
**Registry in Docker konfigurieren**:
|
||||
|
||||
`/etc/docker/daemon.json`:
|
||||
```json
|
||||
{
|
||||
"insecure-registries": ["94.16.110.151:5000"]
|
||||
}
|
||||
```
|
||||
|
||||
```bash
|
||||
sudo systemctl restart docker
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Production Image Build
|
||||
|
||||
### Build-Prozess
|
||||
|
||||
Das Production Image wird lokal gebaut und dann zur Registry gepusht.
|
||||
|
||||
**1. Production Dockerfile** (`Dockerfile.production`):
|
||||
|
||||
```dockerfile
|
||||
# Multi-stage build für optimale Image-Größe
|
||||
FROM php:8.3-fpm-alpine AS base
|
||||
|
||||
# System dependencies
|
||||
RUN apk add --no-cache \
|
||||
nginx \
|
||||
supervisor \
|
||||
postgresql-dev \
|
||||
libpq \
|
||||
&& docker-php-ext-install pdo pdo_pgsql
|
||||
|
||||
# PHP Configuration
|
||||
COPY docker/php/php.production.ini /usr/local/etc/php/conf.d/production.ini
|
||||
COPY docker/php/opcache.ini /usr/local/etc/php/conf.d/opcache.ini
|
||||
COPY docker/php/zz-docker.production.conf /usr/local/etc/php-fpm.d/zz-docker.conf
|
||||
|
||||
# Nginx Configuration
|
||||
COPY docker/nginx/nginx.production.conf /etc/nginx/nginx.conf
|
||||
COPY docker/nginx/default.production.conf /etc/nginx/http.d/default.conf
|
||||
|
||||
# Supervisor Configuration
|
||||
COPY docker/supervisor/supervisord.conf /etc/supervisor/conf.d/supervisord.conf
|
||||
|
||||
# Application Code
|
||||
WORKDIR /var/www/html
|
||||
COPY --chown=www-data:www-data . .
|
||||
|
||||
# Composer dependencies (Production only)
|
||||
RUN composer install --no-dev --optimize-autoloader --no-interaction
|
||||
|
||||
# NPM build
|
||||
RUN npm ci && npm run build
|
||||
|
||||
# Permissions
|
||||
RUN chown -R www-data:www-data /var/www/html/storage \
|
||||
&& chmod -R 775 /var/www/html/storage
|
||||
|
||||
# Start Supervisor (manages nginx + php-fpm)
|
||||
CMD ["/usr/bin/supervisord", "-c", "/etc/supervisor/conf.d/supervisord.conf"]
|
||||
```
|
||||
|
||||
**2. Build Command**:
|
||||
|
||||
```bash
|
||||
# Im Projekt-Root
|
||||
docker build \
|
||||
-f Dockerfile.production \
|
||||
-t 94.16.110.151:5000/framework:latest \
|
||||
.
|
||||
```
|
||||
|
||||
**3. Push to Registry**:
|
||||
|
||||
```bash
|
||||
docker push 94.16.110.151:5000/framework:latest
|
||||
```
|
||||
|
||||
**4. Verify Push**:
|
||||
|
||||
```bash
|
||||
curl http://94.16.110.151:5000/v2/framework/tags/list
|
||||
```
|
||||
|
||||
### Wichtige Konfigurationsdateien
|
||||
|
||||
#### Supervisor Configuration (`docker/supervisor/supervisord.conf`)
|
||||
|
||||
```ini
|
||||
[supervisord]
|
||||
nodaemon=true
|
||||
silent=false
|
||||
logfile=/dev/null
|
||||
logfile_maxbytes=0
|
||||
pidfile=/var/run/supervisord.pid
|
||||
loglevel=info
|
||||
|
||||
[program:php-fpm]
|
||||
command=php-fpm -F
|
||||
stdout_logfile=/dev/stdout
|
||||
stdout_logfile_maxbytes=0
|
||||
stderr_logfile=/dev/stderr
|
||||
stderr_logfile_maxbytes=0
|
||||
autorestart=true
|
||||
startretries=3
|
||||
|
||||
[program:nginx]
|
||||
command=nginx -g 'daemon off;'
|
||||
stdout_logfile=/dev/stdout
|
||||
stdout_logfile_maxbytes=0
|
||||
stderr_logfile=/dev/stderr
|
||||
stderr_logfile_maxbytes=0
|
||||
autorestart=true
|
||||
startretries=3
|
||||
depends_on=php-fpm
|
||||
```
|
||||
|
||||
**Wichtige Änderungen**:
|
||||
- `silent=false` + `logfile=/dev/null`: Supervisor loggt nach stdout/stderr statt Datei
|
||||
- Grund: Python's logging kann `/dev/stdout` oder `/proc/self/fd/1` nicht im append-mode öffnen
|
||||
|
||||
#### PHP-FPM Production Config (`docker/php/zz-docker.production.conf`)
|
||||
|
||||
```ini
|
||||
[www]
|
||||
user = www-data
|
||||
group = www-data
|
||||
listen = 9000
|
||||
listen.owner = www-data
|
||||
listen.group = www-data
|
||||
pm = dynamic
|
||||
pm.max_children = 50
|
||||
pm.start_servers = 10
|
||||
pm.min_spare_servers = 5
|
||||
pm.max_spare_servers = 20
|
||||
pm.max_requests = 500
|
||||
```
|
||||
|
||||
**Wichtig**: User/Group explizit auf `www-data` setzen, da Container als root läuft.
|
||||
|
||||
---
|
||||
|
||||
## Deployment Prozess
|
||||
|
||||
### Docker Compose Setup
|
||||
|
||||
**Base Configuration** (`docker-compose.yml`):
|
||||
- Definiert alle Services für Development
|
||||
- Wird **nicht** auf Production Server deployed
|
||||
|
||||
**Production Overrides** (`docker-compose.prod.yml`):
|
||||
- Merged mit base config
|
||||
- Production-spezifische Einstellungen
|
||||
|
||||
### Production Override Highlights
|
||||
|
||||
**Web Service**:
|
||||
```yaml
|
||||
web:
|
||||
image: 94.16.110.151:5000/framework:latest
|
||||
pull_policy: always # Immer von Registry pullen, nie bauen
|
||||
entrypoint: [] # Entrypoint von Base-Image clearen
|
||||
command: ["/usr/bin/supervisord", "-c", "/etc/supervisor/conf.d/supervisord.conf"]
|
||||
user: root # Container läuft als root, PHP-FPM workers als www-data
|
||||
volumes:
|
||||
- ./storage/logs:/var/www/html/storage/logs:rw
|
||||
- ./storage/uploads:/var/www/html/storage/uploads:rw
|
||||
- ./ssl:/var/www/ssl:ro
|
||||
environment:
|
||||
- APP_ENV=production
|
||||
labels:
|
||||
com.centurylinklabs.watchtower.enable: "true"
|
||||
```
|
||||
|
||||
**Wichtige Overrides**:
|
||||
1. `pull_policy: always`: Verhindert lokales Build, zwingt Registry-Pull
|
||||
2. `entrypoint: []`: Clearen des inherited entrypoint vom Base PHP-Image
|
||||
3. `command: [...]`: Expliziter Start-Command für Supervisor
|
||||
4. `user: root`: Nötig für Supervisor, PHP-FPM läuft intern als www-data
|
||||
|
||||
### Deployment Steps
|
||||
|
||||
**1. Files auf Server kopieren**:
|
||||
|
||||
```bash
|
||||
# Lokale Entwicklungsmaschine (via WireGuard)
|
||||
scp docker-compose.prod.yml deploy@94.16.110.151:/home/deploy/framework/
|
||||
scp .env.production deploy@94.16.110.151:/home/deploy/framework/.env
|
||||
```
|
||||
|
||||
**2. Auf Server: Pull und Deploy**:
|
||||
|
||||
```bash
|
||||
# SSH auf Production Server
|
||||
ssh deploy@94.16.110.151
|
||||
|
||||
# In Projekt-Verzeichnis
|
||||
cd /home/deploy/framework
|
||||
|
||||
# Pull latest image
|
||||
docker pull 94.16.110.151:5000/framework:latest
|
||||
|
||||
# Deploy Stack
|
||||
docker-compose -f docker-compose.yml -f docker-compose.prod.yml up -d
|
||||
|
||||
# Check Status
|
||||
docker-compose -f docker-compose.yml -f docker-compose.prod.yml ps
|
||||
```
|
||||
|
||||
**3. Logs überwachen**:
|
||||
|
||||
```bash
|
||||
# Alle Container
|
||||
docker-compose -f docker-compose.yml -f docker-compose.prod.yml logs -f
|
||||
|
||||
# Spezifischer Container
|
||||
docker logs -f web
|
||||
docker logs -f php
|
||||
```
|
||||
|
||||
### Deployment Verification
|
||||
|
||||
**Container Health Checks**:
|
||||
```bash
|
||||
# Alle Container sollten "healthy" sein
|
||||
docker-compose ps
|
||||
|
||||
# Output sollte zeigen:
|
||||
# web Up (healthy)
|
||||
# php Up (healthy)
|
||||
# db Up (healthy)
|
||||
# redis Up (healthy)
|
||||
```
|
||||
|
||||
**Supervisor Status (im web container)**:
|
||||
```bash
|
||||
docker exec web supervisorctl status
|
||||
|
||||
# Output:
|
||||
# nginx RUNNING pid 7, uptime 0:05:23
|
||||
# php-fpm RUNNING pid 8, uptime 0:05:23
|
||||
```
|
||||
|
||||
**Nginx & PHP-FPM Processes**:
|
||||
```bash
|
||||
docker exec web ps aux | grep -E 'nginx|php-fpm'
|
||||
|
||||
# Sollte zeigen:
|
||||
# root 1 supervisor
|
||||
# root 7 nginx: master
|
||||
# www-data nginx: worker (mehrere)
|
||||
# root 8 php-fpm: master
|
||||
# www-data php-fpm: pool www (mehrere)
|
||||
```
|
||||
|
||||
**Application Test**:
|
||||
```bash
|
||||
# Von lokalem Rechner (via WireGuard)
|
||||
curl -k -I https://94.16.110.151:8443/
|
||||
|
||||
# Erwartete Response:
|
||||
# HTTP/2 200
|
||||
# server: nginx
|
||||
# content-type: text/html
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Problem 1: Supervisor Log File Permission Denied
|
||||
|
||||
**Symptom**:
|
||||
```
|
||||
PermissionError: [Errno 13] Permission denied: '/var/log/supervisor/supervisord.log'
|
||||
```
|
||||
|
||||
**Ursache**: Supervisor kann nicht in `/var/log/supervisor/` schreiben, selbst als root.
|
||||
|
||||
**Lösung**: `supervisord.conf` ändern:
|
||||
```ini
|
||||
silent=false
|
||||
logfile=/dev/null
|
||||
logfile_maxbytes=0
|
||||
```
|
||||
|
||||
**Grund**: Python's logging library kann `/dev/stdout` oder `/proc/self/fd/1` nicht im append-mode öffnen. `/dev/null` + `silent=false` macht Supervisor's logging auf stdout/stderr.
|
||||
|
||||
### Problem 2: EACCES Errors in Web Container
|
||||
|
||||
**Symptom**:
|
||||
```
|
||||
CRIT could not write pidfile /var/run/supervisord.pid
|
||||
spawnerr: unknown error making dispatchers for 'nginx': EACCES
|
||||
```
|
||||
|
||||
**Ursache**: Web container läuft nicht als root, sondern mit inherited user von base config.
|
||||
|
||||
**Lösung**: `docker-compose.prod.yml` - `user: root` setzen:
|
||||
```yaml
|
||||
web:
|
||||
user: root
|
||||
```
|
||||
|
||||
### Problem 3: Docker Entrypoint Override funktioniert nicht
|
||||
|
||||
**Symptom**: Container command zeigt entrypoint prepended:
|
||||
```
|
||||
/usr/local/bin/docker-entrypoint.sh /usr/bin/supervisord -c ...
|
||||
```
|
||||
|
||||
**Ursache**: Base `docker-compose.yml` hat `web` service mit separate build context. Inherited ENTRYPOINT vom Base PHP-Image wird prepended.
|
||||
|
||||
**Lösung**: Explizit entrypoint clearen:
|
||||
```yaml
|
||||
web:
|
||||
image: 94.16.110.151:5000/framework:latest
|
||||
pull_policy: always
|
||||
entrypoint: [] # WICHTIG: Entrypoint clearen
|
||||
command: ["/usr/bin/supervisord", "-c", "/etc/supervisor/conf.d/supervisord.conf"]
|
||||
```
|
||||
|
||||
### Problem 4: Queue Worker restarts kontinuierlich
|
||||
|
||||
**Symptom**:
|
||||
```
|
||||
docker ps # zeigt queue-worker als "Restarting"
|
||||
```
|
||||
|
||||
**Ursache**: Base `docker-compose.yml` command sucht `/var/www/html/worker.php` das nicht existiert.
|
||||
|
||||
**Temporary Fix**: Service deaktivieren in `docker-compose.prod.yml`:
|
||||
```yaml
|
||||
queue-worker:
|
||||
deploy:
|
||||
replicas: 0
|
||||
```
|
||||
|
||||
**Proper Fix**: Richtigen Worker-Command konfigurieren:
|
||||
```yaml
|
||||
queue-worker:
|
||||
command: ["php", "/var/www/html/console.php", "queue:work"]
|
||||
```
|
||||
|
||||
### Problem 5: HTTP Port 80 nicht erreichbar
|
||||
|
||||
**Symptom**: `curl http://94.16.110.151:8888/` → Connection refused
|
||||
|
||||
**Mögliche Ursachen**:
|
||||
1. Nginx nicht auf Port 80 listening (nur 443)
|
||||
2. Firewall blockiert Port 8888
|
||||
3. Intentional (HTTPS-only Configuration)
|
||||
|
||||
**Debug**:
|
||||
```bash
|
||||
# Im Container checken
|
||||
docker exec web netstat -tlnp | grep :80
|
||||
|
||||
# Nginx config testen
|
||||
docker exec web nginx -t
|
||||
|
||||
# Nginx config anschauen
|
||||
docker exec web cat /etc/nginx/http.d/default.conf
|
||||
```
|
||||
|
||||
**Fix (falls HTTP→HTTPS Redirect gewünscht)**:
|
||||
In `docker/nginx/default.production.conf`:
|
||||
```nginx
|
||||
server {
|
||||
listen 80;
|
||||
server_name _;
|
||||
return 301 https://$host$request_uri;
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Monitoring
|
||||
|
||||
### Prometheus
|
||||
|
||||
**Zugang**: http://10.8.0.1:9090 (nur via WireGuard)
|
||||
|
||||
**Konfiguration**: `monitoring/prometheus/prometheus.yml`
|
||||
|
||||
**Scraped Targets**:
|
||||
- Framework Application Metrics
|
||||
- Container Metrics (cAdvisor)
|
||||
- Node Exporter (Server Metrics)
|
||||
|
||||
### Grafana
|
||||
|
||||
**Zugang**: http://10.8.0.1:3000 (nur via WireGuard)
|
||||
|
||||
**Default Login**:
|
||||
- User: `admin`
|
||||
- Password: `${GRAFANA_PASSWORD}` (aus `.env`)
|
||||
|
||||
**Dashboards**: `monitoring/grafana/provisioning/dashboards/`
|
||||
|
||||
### Portainer
|
||||
|
||||
**Zugang**: https://10.8.0.1:9443 (nur via WireGuard)
|
||||
|
||||
**Features**:
|
||||
- Container Management
|
||||
- Stack Deployment
|
||||
- Log Viewing
|
||||
- Resource Usage
|
||||
|
||||
### Watchtower Auto-Update
|
||||
|
||||
Watchtower überwacht Container mit Label `com.centurylinklabs.watchtower.enable: "true"` und updated sie automatisch bei neuen Images.
|
||||
|
||||
**Konfiguration**:
|
||||
```yaml
|
||||
watchtower:
|
||||
environment:
|
||||
WATCHTOWER_CLEANUP: "true"
|
||||
WATCHTOWER_POLL_INTERVAL: 300 # 5 Minuten
|
||||
WATCHTOWER_LABEL_ENABLE: "true"
|
||||
WATCHTOWER_NOTIFICATIONS: "shoutrrr"
|
||||
WATCHTOWER_NOTIFICATION_URL: "${WATCHTOWER_NOTIFICATION_URL}"
|
||||
```
|
||||
|
||||
**Monitoren**:
|
||||
```bash
|
||||
docker logs -f watchtower
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Maintenance
|
||||
|
||||
### Image Updates
|
||||
|
||||
**1. Lokal neues Image bauen**:
|
||||
```bash
|
||||
docker build -f Dockerfile.production -t 94.16.110.151:5000/framework:latest .
|
||||
docker push 94.16.110.151:5000/framework:latest
|
||||
```
|
||||
|
||||
**2. Auf Server**:
|
||||
```bash
|
||||
# Watchtower erkennt Update automatisch innerhalb von 5 Minuten
|
||||
# Oder manuell:
|
||||
docker-compose -f docker-compose.yml -f docker-compose.prod.yml pull
|
||||
docker-compose -f docker-compose.yml -f docker-compose.prod.yml up -d
|
||||
```
|
||||
|
||||
### Database Backups
|
||||
|
||||
```bash
|
||||
# Manual Backup
|
||||
docker exec db pg_dump -U framework_user framework_db > backup_$(date +%Y%m%d_%H%M%S).sql
|
||||
|
||||
# Automated (via cron)
|
||||
0 2 * * * /home/deploy/scripts/backup-database.sh
|
||||
```
|
||||
|
||||
### Log Rotation
|
||||
|
||||
Logs in `./storage/logs/` automatisch rotieren:
|
||||
|
||||
```bash
|
||||
# /etc/logrotate.d/framework
|
||||
/home/deploy/framework/storage/logs/*.log {
|
||||
daily
|
||||
rotate 14
|
||||
compress
|
||||
delaycompress
|
||||
notifempty
|
||||
missingok
|
||||
create 0640 www-data www-data
|
||||
}
|
||||
```
|
||||
|
||||
### SSL Certificate Renewal
|
||||
|
||||
**Let's Encrypt** (automatisch via certbot):
|
||||
```bash
|
||||
certbot renew --deploy-hook "docker exec web nginx -s reload"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Security Checklist
|
||||
|
||||
- [ ] WireGuard VPN konfiguriert und aktiv
|
||||
- [ ] Firewall (UFW) konfiguriert und enabled
|
||||
- [ ] Nur benötigte Ports offen (8443, 51820)
|
||||
- [ ] Monitoring nur via VPN erreichbar (10.8.0.1:*)
|
||||
- [ ] SSL/TLS Zertifikate gültig
|
||||
- [ ] `.env` Secrets nicht in Git committed
|
||||
- [ ] Database Credentials rotiert
|
||||
- [ ] Redis Password gesetzt
|
||||
- [ ] Docker Registry läuft lokal (nicht public)
|
||||
- [ ] Container laufen mit minimal privileges
|
||||
- [ ] Watchtower auto-updates aktiviert
|
||||
- [ ] Backup-Strategie implementiert
|
||||
- [ ] Log monitoring aktiv
|
||||
|
||||
---
|
||||
|
||||
## Performance Tuning
|
||||
|
||||
### PHP-FPM
|
||||
|
||||
`docker/php/zz-docker.production.conf`:
|
||||
```ini
|
||||
pm.max_children = 50 # Max. gleichzeitige Requests
|
||||
pm.start_servers = 10 # Initial workers
|
||||
pm.min_spare_servers = 5 # Min. idle workers
|
||||
pm.max_spare_servers = 20 # Max. idle workers
|
||||
pm.max_requests = 500 # Worker recycling
|
||||
```
|
||||
|
||||
**Tuning basierend auf RAM**:
|
||||
- 4GB RAM: max_children = 30
|
||||
- 8GB RAM: max_children = 50
|
||||
- 16GB RAM: max_children = 100
|
||||
|
||||
### OPcache
|
||||
|
||||
`docker/php/opcache.ini`:
|
||||
```ini
|
||||
opcache.enable=1
|
||||
opcache.memory_consumption=128
|
||||
opcache.interned_strings_buffer=8
|
||||
opcache.max_accelerated_files=10000
|
||||
opcache.validate_timestamps=0 # Production: keine Timestamp-Checks
|
||||
opcache.revalidate_freq=0
|
||||
```
|
||||
|
||||
### Nginx
|
||||
|
||||
```nginx
|
||||
worker_processes auto;
|
||||
worker_connections 1024;
|
||||
keepalive_timeout 65;
|
||||
client_max_body_size 20M;
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Contact & Support
|
||||
|
||||
**Production Server**: 94.16.110.151
|
||||
**VPN Gateway**: 10.8.0.1
|
||||
**Documentation**: `/home/deploy/framework/docs/`
|
||||
**Issue Tracker**: [GitHub/GitLab URL]
|
||||
|
||||
---
|
||||
|
||||
## Change Log
|
||||
|
||||
### 2025-10-28 - Initial Production Deployment
|
||||
|
||||
**Changes**:
|
||||
- Supervisor logging: `/dev/null` + `silent=false`
|
||||
- docker-compose.prod.yml: `user: root` für web, php, queue-worker
|
||||
- docker-compose.prod.yml: `entrypoint: []` für web service
|
||||
- docker-compose.prod.yml: `pull_policy: always` für registry images
|
||||
|
||||
**Deployed**:
|
||||
- Image: `94.16.110.151:5000/framework:latest`
|
||||
- Digest: `sha256:eee1db20b9293cf611f53d01de68e94df1cfb3c748fe967849e080d19b9e4c8b`
|
||||
|
||||
**Status**: ✅ Deployment erfolgreich, Container healthy
|
||||
227
docs/deployment/quick-deploy.md
Normal file
227
docs/deployment/quick-deploy.md
Normal file
@@ -0,0 +1,227 @@
|
||||
# Quick Deploy Guide
|
||||
|
||||
Schnellanleitung für Production Deployments.
|
||||
|
||||
## Voraussetzungen
|
||||
|
||||
- WireGuard VPN aktiv: `sudo wg-quick up wg0-production`
|
||||
- SSH-Zugang konfiguriert
|
||||
- Docker Registry läuft auf Production Server
|
||||
|
||||
## Deployment in 5 Schritten
|
||||
|
||||
### 1. Image bauen und pushen
|
||||
|
||||
```bash
|
||||
# Im Projekt-Root
|
||||
docker build -f Dockerfile.production -t 94.16.110.151:5000/framework:latest .
|
||||
docker push 94.16.110.151:5000/framework:latest
|
||||
```
|
||||
|
||||
**Verify Push**:
|
||||
```bash
|
||||
curl http://94.16.110.151:5000/v2/framework/tags/list
|
||||
```
|
||||
|
||||
### 2. Config-Files auf Server kopieren
|
||||
|
||||
```bash
|
||||
# Falls docker-compose.prod.yml oder .env geändert wurden
|
||||
scp docker-compose.prod.yml deploy@94.16.110.151:/home/deploy/framework/
|
||||
scp .env.production deploy@94.16.110.151:/home/deploy/framework/.env
|
||||
```
|
||||
|
||||
### 3. Auf Server deployen
|
||||
|
||||
```bash
|
||||
ssh deploy@94.16.110.151
|
||||
cd /home/deploy/framework
|
||||
|
||||
# Pull und Deploy
|
||||
docker-compose -f docker-compose.yml -f docker-compose.prod.yml pull
|
||||
docker-compose -f docker-compose.yml -f docker-compose.prod.yml up -d
|
||||
```
|
||||
|
||||
### 4. Status checken
|
||||
|
||||
```bash
|
||||
# Container Status
|
||||
docker-compose -f docker-compose.yml -f docker-compose.prod.yml ps
|
||||
|
||||
# Logs anschauen
|
||||
docker-compose -f docker-compose.yml -f docker-compose.prod.yml logs -f web php
|
||||
|
||||
# Supervisor Status (im web container)
|
||||
docker exec web supervisorctl status
|
||||
```
|
||||
|
||||
### 5. Application testen
|
||||
|
||||
```bash
|
||||
# Von lokaler Maschine (via WireGuard)
|
||||
curl -k -I https://94.16.110.151:8443/
|
||||
|
||||
# Erwartetes Ergebnis:
|
||||
# HTTP/2 200
|
||||
# server: nginx
|
||||
```
|
||||
|
||||
## Rollback
|
||||
|
||||
Falls Probleme auftreten:
|
||||
|
||||
```bash
|
||||
# Auf Server
|
||||
cd /home/deploy/framework
|
||||
|
||||
# Vorheriges Image ID finden
|
||||
docker images 94.16.110.151:5000/framework
|
||||
|
||||
# Zu spezifischem Image wechseln
|
||||
docker-compose -f docker-compose.yml -f docker-compose.prod.yml down
|
||||
docker tag 94.16.110.151:5000/framework@sha256:<old-digest> 94.16.110.151:5000/framework:latest
|
||||
docker-compose -f docker-compose.yml -f docker-compose.prod.yml up -d
|
||||
```
|
||||
|
||||
## Monitoring URLs
|
||||
|
||||
**Zugang nur via WireGuard VPN (10.8.0.1)**:
|
||||
|
||||
- Prometheus: http://10.8.0.1:9090
|
||||
- Grafana: http://10.8.0.1:3000 (admin / $GRAFANA_PASSWORD)
|
||||
- Portainer: https://10.8.0.1:9443
|
||||
|
||||
## Watchtower Auto-Updates
|
||||
|
||||
Watchtower überwacht automatisch und updated Container mit neuem Image (alle 5 Minuten).
|
||||
|
||||
**Status checken**:
|
||||
```bash
|
||||
docker logs watchtower
|
||||
```
|
||||
|
||||
**Manuell triggern**:
|
||||
```bash
|
||||
# Watchtower neu starten (triggert sofortigen Check)
|
||||
docker restart watchtower
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Container nicht healthy
|
||||
|
||||
```bash
|
||||
# Logs anschauen
|
||||
docker logs web
|
||||
docker logs php
|
||||
|
||||
# Im Container debuggen
|
||||
docker exec -it web sh
|
||||
docker exec -it php sh
|
||||
|
||||
# Supervisor Status
|
||||
docker exec web supervisorctl status
|
||||
|
||||
# Nginx/PHP-FPM Prozesse
|
||||
docker exec web ps aux | grep -E 'nginx|php-fpm'
|
||||
```
|
||||
|
||||
### Database Connection Issues
|
||||
|
||||
```bash
|
||||
# PostgreSQL Connection testen
|
||||
docker exec php php -r "new PDO('pgsql:host=db;dbname=framework_db', 'framework_user', 'password');"
|
||||
|
||||
# Database Logs
|
||||
docker logs db
|
||||
|
||||
# In Database connecten
|
||||
docker exec -it db psql -U framework_user -d framework_db
|
||||
```
|
||||
|
||||
### Redis Connection Issues
|
||||
|
||||
```bash
|
||||
# Redis Connection testen
|
||||
docker exec php php -r "var_dump((new Redis())->connect('redis', 6379));"
|
||||
|
||||
# Redis Logs
|
||||
docker logs redis
|
||||
|
||||
# Redis CLI
|
||||
docker exec -it redis redis-cli
|
||||
```
|
||||
|
||||
## Maintenance Commands
|
||||
|
||||
### Database Backup
|
||||
|
||||
```bash
|
||||
# Manual Backup
|
||||
docker exec db pg_dump -U framework_user framework_db > backup_$(date +%Y%m%d_%H%M%S).sql
|
||||
```
|
||||
|
||||
### Logs Cleanup
|
||||
|
||||
```bash
|
||||
# Storage Logs leeren (auf Server)
|
||||
docker exec web sh -c 'rm -rf /var/www/html/storage/logs/*.log'
|
||||
|
||||
# Docker Logs cleanup
|
||||
docker system prune -f
|
||||
docker volume prune -f
|
||||
```
|
||||
|
||||
### Image Cleanup
|
||||
|
||||
```bash
|
||||
# Alte Images entfernen
|
||||
docker image prune -a -f
|
||||
|
||||
# Nur untagged images
|
||||
docker image prune -f
|
||||
```
|
||||
|
||||
## Performance Check
|
||||
|
||||
```bash
|
||||
# Container Resource Usage
|
||||
docker stats
|
||||
|
||||
# PHP-FPM Status
|
||||
docker exec web curl http://localhost/php-fpm-status
|
||||
|
||||
# Nginx Status
|
||||
docker exec web curl http://localhost/nginx-status
|
||||
|
||||
# Database Connections
|
||||
docker exec db psql -U framework_user -d framework_db -c "SELECT count(*) FROM pg_stat_activity;"
|
||||
```
|
||||
|
||||
## SSL Certificate Renewal
|
||||
|
||||
```bash
|
||||
# Let's Encrypt Renewal (auf Server als root)
|
||||
certbot renew
|
||||
docker exec web nginx -s reload
|
||||
```
|
||||
|
||||
## Nützliche Aliases
|
||||
|
||||
Füge zu `~/.bashrc` auf Production Server hinzu:
|
||||
|
||||
```bash
|
||||
alias dc='docker-compose -f docker-compose.yml -f docker-compose.prod.yml'
|
||||
alias dcup='dc up -d'
|
||||
alias dcdown='dc down'
|
||||
alias dcps='dc ps'
|
||||
alias dclogs='dc logs -f'
|
||||
alias dcrestart='dc restart'
|
||||
```
|
||||
|
||||
Dann kannst du einfach verwenden:
|
||||
```bash
|
||||
dcup # Deploy
|
||||
dcps # Status
|
||||
dclogs # Logs anschauen
|
||||
```
|
||||
581
docs/deployment/troubleshooting-checklist.md
Normal file
581
docs/deployment/troubleshooting-checklist.md
Normal file
@@ -0,0 +1,581 @@
|
||||
# Production Deployment Troubleshooting Checklist
|
||||
|
||||
Systematische Problemlösung für häufige Deployment-Issues.
|
||||
|
||||
## Issue 1: Supervisor Log File Permission Denied
|
||||
|
||||
### Symptom
|
||||
```
|
||||
PermissionError: [Errno 13] Permission denied: '/var/log/supervisor/supervisord.log'
|
||||
```
|
||||
|
||||
Container startet nicht, Supervisor kann Logfile nicht schreiben.
|
||||
|
||||
### Diagnose
|
||||
```bash
|
||||
docker logs web # Zeigt Permission Error
|
||||
docker exec web ls -la /var/log/supervisor/ # Directory existiert nicht oder keine Permissions
|
||||
```
|
||||
|
||||
### Root Cause
|
||||
- Supervisor versucht in `/var/log/supervisor/supervisord.log` zu schreiben
|
||||
- Directory existiert nicht oder keine Write-Permissions
|
||||
- Auch als root problematisch in containerisierter Umgebung
|
||||
|
||||
### Lösung 1 (FUNKTIONIERT NICHT)
|
||||
❌ **Versuch**: `/proc/self/fd/1` verwenden
|
||||
|
||||
`docker/supervisor/supervisord.conf`:
|
||||
```ini
|
||||
logfile=/proc/self/fd/1
|
||||
```
|
||||
|
||||
**Fehler**: `PermissionError: [Errno 13] Permission denied: '/proc/self/fd/1'`
|
||||
|
||||
**Grund**: Python's logging library (verwendet von Supervisor) kann `/proc/self/fd/1` oder `/dev/stdout` nicht im append-mode öffnen.
|
||||
|
||||
### Lösung 2 (ERFOLGREICH)
|
||||
✅ **Fix**: `/dev/null` mit `silent=false`
|
||||
|
||||
`docker/supervisor/supervisord.conf`:
|
||||
```ini
|
||||
[supervisord]
|
||||
nodaemon=true
|
||||
silent=false # WICHTIG: Logging trotz /dev/null
|
||||
logfile=/dev/null
|
||||
logfile_maxbytes=0
|
||||
pidfile=/var/run/supervisord.pid
|
||||
loglevel=info
|
||||
```
|
||||
|
||||
**Warum funktioniert das?**
|
||||
- `logfile=/dev/null`: Kein File-Logging
|
||||
- `silent=false`: Supervisor loggt nach stdout/stderr
|
||||
- Logs erscheinen in `docker logs web`
|
||||
|
||||
### Verification
|
||||
```bash
|
||||
docker logs web
|
||||
# Output:
|
||||
# 2025-10-28 16:29:59,976 INFO supervisord started with pid 1
|
||||
# 2025-10-28 16:30:00,980 INFO spawned: 'nginx' with pid 7
|
||||
# 2025-10-28 16:30:00,982 INFO spawned: 'php-fpm' with pid 8
|
||||
# 2025-10-28 16:30:02,077 INFO success: nginx entered RUNNING state
|
||||
# 2025-10-28 16:30:02,077 INFO success: php-fpm entered RUNNING state
|
||||
```
|
||||
|
||||
### Related Files
|
||||
- `docker/supervisor/supervisord.conf`
|
||||
- `Dockerfile.production` (COPY supervisord.conf)
|
||||
|
||||
---
|
||||
|
||||
## Issue 2: Web Container EACCES Errors
|
||||
|
||||
### Symptom
|
||||
```
|
||||
2025-10-28 16:16:52,152 CRIT could not write pidfile /var/run/supervisord.pid
|
||||
2025-10-28 16:16:53,154 INFO spawnerr: unknown error making dispatchers for 'nginx': EACCES
|
||||
2025-10-28 16:16:53,154 INFO spawnerr: unknown error making dispatchers for 'php-fpm': EACCES
|
||||
```
|
||||
|
||||
### Diagnose
|
||||
```bash
|
||||
# Container User checken
|
||||
docker exec web whoami
|
||||
# Falls nicht "root", dann ist das der Issue
|
||||
|
||||
# Docker Compose Config checken
|
||||
docker inspect web | grep -i user
|
||||
# Zeigt inherited user von base config
|
||||
```
|
||||
|
||||
### Root Cause
|
||||
- `web` service in `docker-compose.prod.yml` hat **kein** `user: root` gesetzt
|
||||
- Inherited `user: 1000:1000` oder `user: www-data` von base `docker-compose.yml`
|
||||
- Supervisor benötigt root um nginx/php-fpm master processes zu starten
|
||||
|
||||
### Lösung
|
||||
✅ **Fix**: `user: root` explizit setzen
|
||||
|
||||
`docker-compose.prod.yml`:
|
||||
```yaml
|
||||
web:
|
||||
image: 94.16.110.151:5000/framework:latest
|
||||
user: root # ← HINZUFÜGEN
|
||||
# ... rest der config
|
||||
```
|
||||
|
||||
Auch für `php` und `queue-worker` services hinzufügen:
|
||||
```yaml
|
||||
php:
|
||||
image: 94.16.110.151:5000/framework:latest
|
||||
user: root # ← HINZUFÜGEN
|
||||
|
||||
queue-worker:
|
||||
image: 94.16.110.151:5000/framework:latest
|
||||
user: root # ← HINZUFÜGEN
|
||||
```
|
||||
|
||||
### Warum user: root?
|
||||
- **Container läuft als root**: Supervisor master process
|
||||
- **Nginx master**: root (worker processes als www-data via nginx.conf)
|
||||
- **PHP-FPM master**: root (pool workers als www-data via php-fpm.conf)
|
||||
|
||||
`docker/php/zz-docker.production.conf`:
|
||||
```ini
|
||||
[www]
|
||||
user = www-data # ← Worker processes laufen als www-data
|
||||
group = www-data
|
||||
```
|
||||
|
||||
### Verification
|
||||
```bash
|
||||
docker exec web whoami
|
||||
# root
|
||||
|
||||
docker exec web ps aux | grep -E 'nginx|php-fpm'
|
||||
# root 1 supervisord
|
||||
# root 7 nginx: master process
|
||||
# www-data 10 nginx: worker process
|
||||
# root 8 php-fpm: master process
|
||||
# www-data 11 php-fpm: pool www
|
||||
```
|
||||
|
||||
### Related Files
|
||||
- `docker-compose.prod.yml` (web, php, queue-worker services)
|
||||
- `docker/php/zz-docker.production.conf`
|
||||
- `docker/nginx/nginx.production.conf`
|
||||
|
||||
---
|
||||
|
||||
## Issue 3: Docker Entrypoint Override funktioniert nicht
|
||||
|
||||
### Symptom
|
||||
Container command zeigt Entrypoint prepended:
|
||||
```bash
|
||||
docker ps
|
||||
# COMMAND: "/usr/local/bin/docker-entrypoint.sh /usr/bin/supervisord -c ..."
|
||||
```
|
||||
|
||||
Supervisor wird nicht direkt gestartet, sondern durch einen wrapper script.
|
||||
|
||||
### Diagnose
|
||||
```bash
|
||||
# Container Command checken
|
||||
docker inspect web --format='{{.Config.Entrypoint}}'
|
||||
# [/usr/local/bin/docker-entrypoint.sh]
|
||||
|
||||
docker inspect web --format='{{.Config.Cmd}}'
|
||||
# [/usr/bin/supervisord -c /etc/supervisor/conf.d/supervisord.conf]
|
||||
```
|
||||
|
||||
### Root Cause
|
||||
1. Base `docker-compose.yml` hat `web` service mit separate build:
|
||||
```yaml
|
||||
web:
|
||||
build:
|
||||
context: docker/nginx
|
||||
dockerfile: Dockerfile
|
||||
```
|
||||
|
||||
2. Production override setzt `image:` aber cleared **nicht** den inherited ENTRYPOINT:
|
||||
```yaml
|
||||
web:
|
||||
image: 94.16.110.151:5000/framework:latest
|
||||
command: ["/usr/bin/supervisord", "-c", "/etc/supervisor/conf.d/supervisord.conf"]
|
||||
```
|
||||
|
||||
3. Base PHP image hat ENTRYPOINT der prepended wird
|
||||
4. Docker Compose merge: ENTRYPOINT + CMD = final command
|
||||
|
||||
### Lösung - Iteration 1 (FUNKTIONIERT NICHT)
|
||||
❌ **Versuch**: Nur `command:` setzen
|
||||
|
||||
`docker-compose.prod.yml`:
|
||||
```yaml
|
||||
web:
|
||||
image: 94.16.110.151:5000/framework:latest
|
||||
command: ["/usr/bin/supervisord", "-c", "/etc/supervisor/conf.d/supervisord.conf"]
|
||||
```
|
||||
|
||||
**Result**: Entrypoint wird trotzdem prepended
|
||||
|
||||
### Lösung - Iteration 2 (FUNKTIONIERT NICHT)
|
||||
❌ **Versuch**: `pull_policy: always` hinzufügen
|
||||
|
||||
`docker-compose.prod.yml`:
|
||||
```yaml
|
||||
web:
|
||||
image: 94.16.110.151:5000/framework:latest
|
||||
pull_policy: always # Force registry pull
|
||||
command: ["/usr/bin/supervisord", "-c", "/etc/supervisor/conf.d/supervisord.conf"]
|
||||
```
|
||||
|
||||
**Result**: Image wird von Registry gepullt, aber Entrypoint wird trotzdem prepended
|
||||
|
||||
### Lösung - Iteration 3 (ERFOLGREICH)
|
||||
✅ **Fix**: `entrypoint: []` explizit clearen
|
||||
|
||||
`docker-compose.prod.yml`:
|
||||
```yaml
|
||||
web:
|
||||
image: 94.16.110.151:5000/framework:latest
|
||||
pull_policy: always # Always pull from registry, never build
|
||||
entrypoint: [] # ← WICHTIG: Entrypoint clearen
|
||||
command: ["/usr/bin/supervisord", "-c", "/etc/supervisor/conf.d/supervisord.conf"]
|
||||
user: root
|
||||
```
|
||||
|
||||
**Warum `entrypoint: []`?**
|
||||
- Leeres Array cleared den inherited entrypoint komplett
|
||||
- `command:` wird dann direkt als PID 1 gestartet
|
||||
- Keine wrapper scripts, keine indirection
|
||||
|
||||
### Verification
|
||||
```bash
|
||||
docker inspect web --format='{{.Config.Entrypoint}}'
|
||||
# [] ← Leer!
|
||||
|
||||
docker inspect web --format='{{.Config.Cmd}}'
|
||||
# [/usr/bin/supervisord -c /etc/supervisor/conf.d/supervisord.conf]
|
||||
|
||||
docker exec web ps aux
|
||||
# PID 1: /usr/bin/supervisord -c /etc/supervisor/conf.d/supervisord.conf
|
||||
# Kein entrypoint wrapper!
|
||||
```
|
||||
|
||||
### Related Files
|
||||
- `docker-compose.prod.yml` (web service)
|
||||
|
||||
### Docker Compose Override Rules
|
||||
```
|
||||
Base Config + Override = Final Config
|
||||
|
||||
Base:
|
||||
web:
|
||||
build: docker/nginx
|
||||
→ inherited ENTRYPOINT from base image
|
||||
|
||||
Override (insufficient):
|
||||
web:
|
||||
image: 94.16.110.151:5000/framework:latest
|
||||
command: [...]
|
||||
→ ENTRYPOINT still prepended to command
|
||||
|
||||
Override (correct):
|
||||
web:
|
||||
image: 94.16.110.151:5000/framework:latest
|
||||
entrypoint: [] ← Clears inherited entrypoint
|
||||
command: [...] ← Runs directly as PID 1
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Issue 4: Queue Worker Container Restarts
|
||||
|
||||
### Symptom
|
||||
```bash
|
||||
docker ps
|
||||
# queue-worker Restarting (1) 5 seconds ago
|
||||
```
|
||||
|
||||
Container restart loop, nie healthy.
|
||||
|
||||
### Diagnose
|
||||
```bash
|
||||
docker logs queue-worker
|
||||
# Error: /var/www/html/worker.php not found
|
||||
# oder
|
||||
# php: command not found
|
||||
```
|
||||
|
||||
### Root Cause
|
||||
Base `docker-compose.yml` hat Queue Worker Command für Development:
|
||||
```yaml
|
||||
queue-worker:
|
||||
command: ["php", "/var/www/html/worker.php"]
|
||||
```
|
||||
|
||||
`worker.php` existiert nicht im Production Image.
|
||||
|
||||
### Lösung - Option 1: Service deaktivieren
|
||||
✅ **Quick Fix**: Queue Worker deaktivieren
|
||||
|
||||
`docker-compose.prod.yml`:
|
||||
```yaml
|
||||
queue-worker:
|
||||
deploy:
|
||||
replicas: 0 # Disable service
|
||||
```
|
||||
|
||||
### Lösung - Option 2: Richtigen Command setzen
|
||||
✅ **Proper Fix**: Console Command verwenden
|
||||
|
||||
`docker-compose.prod.yml`:
|
||||
```yaml
|
||||
queue-worker:
|
||||
image: 94.16.110.151:5000/framework:latest
|
||||
user: root
|
||||
command: ["php", "/var/www/html/console.php", "queue:work"]
|
||||
# oder für Supervisor-managed:
|
||||
# entrypoint: []
|
||||
# command: ["/usr/bin/supervisord", "-c", "/etc/supervisor/conf.d/queue-worker-supervisord.conf"]
|
||||
```
|
||||
|
||||
### Verification
|
||||
```bash
|
||||
docker logs queue-worker
|
||||
# [timestamp] INFO Queue worker started
|
||||
# [timestamp] INFO Processing job: ...
|
||||
```
|
||||
|
||||
### Related Files
|
||||
- `docker-compose.yml` (base queue-worker definition)
|
||||
- `docker-compose.prod.yml` (production override)
|
||||
- `console.php` (framework console application)
|
||||
|
||||
---
|
||||
|
||||
## Issue 5: HTTP Port 80 nicht erreichbar
|
||||
|
||||
### Symptom
|
||||
```bash
|
||||
curl http://94.16.110.151:8888/
|
||||
# curl: (7) Failed to connect to 94.16.110.151 port 8888: Connection refused
|
||||
|
||||
docker exec web curl http://localhost/
|
||||
# curl: (7) Failed to connect to localhost port 80: Connection refused
|
||||
```
|
||||
|
||||
### Diagnose
|
||||
```bash
|
||||
# Nginx listening ports checken
|
||||
docker exec web netstat -tlnp | grep nginx
|
||||
# Zeigt nur: 0.0.0.0:443
|
||||
|
||||
# Nginx Config checken
|
||||
docker exec web cat /etc/nginx/http.d/default.conf
|
||||
# Kein "listen 80;" block
|
||||
```
|
||||
|
||||
### Root Cause - Option 1: Intentional HTTPS-only
|
||||
Möglicherweise ist HTTP absichtlich disabled (Security Best Practice).
|
||||
|
||||
### Root Cause - Option 2: Missing HTTP Block
|
||||
Nginx config hat keinen HTTP listener, nur HTTPS.
|
||||
|
||||
### Lösung - HTTP→HTTPS Redirect hinzufügen
|
||||
✅ **Fix**: HTTP Redirect konfigurieren
|
||||
|
||||
`docker/nginx/default.production.conf`:
|
||||
```nginx
|
||||
# HTTP → HTTPS Redirect
|
||||
server {
|
||||
listen 80;
|
||||
server_name _;
|
||||
|
||||
location / {
|
||||
return 301 https://$host$request_uri;
|
||||
}
|
||||
}
|
||||
|
||||
# HTTPS Server
|
||||
server {
|
||||
listen 443 ssl http2;
|
||||
server_name _;
|
||||
|
||||
ssl_certificate /var/www/ssl/cert.pem;
|
||||
ssl_certificate_key /var/www/ssl/key.pem;
|
||||
|
||||
root /var/www/html/public;
|
||||
index index.php;
|
||||
|
||||
location / {
|
||||
try_files $uri $uri/ /index.php?$query_string;
|
||||
}
|
||||
|
||||
location ~ \.php$ {
|
||||
fastcgi_pass php:9000;
|
||||
fastcgi_index index.php;
|
||||
include fastcgi_params;
|
||||
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Verification
|
||||
```bash
|
||||
curl -I http://94.16.110.151:8888/
|
||||
# HTTP/1.1 301 Moved Permanently
|
||||
# Location: https://94.16.110.151:8888/
|
||||
|
||||
curl -k -I https://94.16.110.151:8443/
|
||||
# HTTP/2 200
|
||||
# server: nginx
|
||||
```
|
||||
|
||||
### Related Files
|
||||
- `docker/nginx/default.production.conf`
|
||||
- `Dockerfile.production` (COPY nginx config)
|
||||
|
||||
---
|
||||
|
||||
## General Debugging Commands
|
||||
|
||||
### Container Inspection
|
||||
```bash
|
||||
# Alle Container Status
|
||||
docker-compose -f docker-compose.yml -f docker-compose.prod.yml ps
|
||||
|
||||
# Container Details
|
||||
docker inspect web
|
||||
|
||||
# Container Logs
|
||||
docker logs -f web
|
||||
docker logs --tail 100 web
|
||||
|
||||
# Inside Container
|
||||
docker exec -it web sh
|
||||
docker exec -it php sh
|
||||
```
|
||||
|
||||
### Supervisor Debugging
|
||||
```bash
|
||||
# Supervisor Status
|
||||
docker exec web supervisorctl status
|
||||
|
||||
# Supervisor Logs
|
||||
docker exec web tail -f /dev/null # Logs gehen nach stdout/stderr
|
||||
|
||||
# Supervisor Config testen
|
||||
docker exec web supervisord -c /etc/supervisor/conf.d/supervisord.conf -n
|
||||
```
|
||||
|
||||
### Nginx Debugging
|
||||
```bash
|
||||
# Nginx Config testen
|
||||
docker exec web nginx -t
|
||||
|
||||
# Nginx reload
|
||||
docker exec web nginx -s reload
|
||||
|
||||
# Nginx listening ports
|
||||
docker exec web netstat -tlnp | grep nginx
|
||||
|
||||
# Nginx processes
|
||||
docker exec web ps aux | grep nginx
|
||||
```
|
||||
|
||||
### PHP-FPM Debugging
|
||||
```bash
|
||||
# PHP-FPM Status
|
||||
docker exec web curl http://localhost/php-fpm-status
|
||||
|
||||
# PHP-FPM Config testen
|
||||
docker exec web php-fpm -t
|
||||
|
||||
# PHP-FPM processes
|
||||
docker exec web ps aux | grep php-fpm
|
||||
|
||||
# PHP Version
|
||||
docker exec web php -v
|
||||
|
||||
# PHP Modules
|
||||
docker exec web php -m
|
||||
```
|
||||
|
||||
### Network Debugging
|
||||
```bash
|
||||
# Port listening
|
||||
docker exec web netstat -tlnp
|
||||
|
||||
# DNS resolution
|
||||
docker exec web nslookup db
|
||||
docker exec web nslookup redis
|
||||
|
||||
# Network connectivity
|
||||
docker exec web ping db
|
||||
docker exec web ping redis
|
||||
|
||||
# HTTP request
|
||||
docker exec web curl http://localhost/
|
||||
```
|
||||
|
||||
### Database Debugging
|
||||
```bash
|
||||
# PostgreSQL Connection
|
||||
docker exec php php -r "new PDO('pgsql:host=db;dbname=framework_db', 'framework_user', 'password');"
|
||||
|
||||
# Database Logs
|
||||
docker logs db
|
||||
|
||||
# Connect to DB
|
||||
docker exec -it db psql -U framework_user -d framework_db
|
||||
|
||||
# Check connections
|
||||
docker exec db psql -U framework_user -d framework_db -c "SELECT count(*) FROM pg_stat_activity;"
|
||||
```
|
||||
|
||||
### Performance Monitoring
|
||||
```bash
|
||||
# Container Resource Usage
|
||||
docker stats
|
||||
|
||||
# Disk Usage
|
||||
docker system df
|
||||
|
||||
# Image Sizes
|
||||
docker images
|
||||
|
||||
# Volume Sizes
|
||||
docker system df -v
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Checklist für erfolgreichen Deploy
|
||||
|
||||
### Pre-Deployment
|
||||
- [ ] Image gebaut: `docker build -f Dockerfile.production -t 94.16.110.151:5000/framework:latest .`
|
||||
- [ ] Image gepusht: `docker push 94.16.110.151:5000/framework:latest`
|
||||
- [ ] Registry verfügbar: `curl http://94.16.110.151:5000/v2/_catalog`
|
||||
- [ ] WireGuard VPN aktiv: `wg show`
|
||||
- [ ] `.env.production` auf Server aktuell
|
||||
- [ ] `docker-compose.prod.yml` auf Server aktuell
|
||||
|
||||
### Deployment
|
||||
- [ ] SSH auf Server: `ssh deploy@94.16.110.151`
|
||||
- [ ] Image pullen: `docker-compose -f docker-compose.yml -f docker-compose.prod.yml pull`
|
||||
- [ ] Stack starten: `docker-compose -f docker-compose.yml -f docker-compose.prod.yml up -d`
|
||||
|
||||
### Post-Deployment Verification
|
||||
- [ ] Container laufen: `docker-compose ps` zeigt alle "Up (healthy)"
|
||||
- [ ] Supervisor Status: `docker exec web supervisorctl status` zeigt nginx/php-fpm RUNNING
|
||||
- [ ] Nginx lauscht: `docker exec web netstat -tlnp | grep :443`
|
||||
- [ ] PHP-FPM lauscht: `docker exec web netstat -tlnp | grep :9000`
|
||||
- [ ] Application erreichbar: `curl -k -I https://94.16.110.151:8443/` → HTTP/2 200
|
||||
- [ ] Database erreichbar: `docker exec php php -r "new PDO(...);"`
|
||||
- [ ] Redis erreichbar: `docker exec php php -r "new Redis()->connect('redis', 6379);"`
|
||||
- [ ] Logs sauber: `docker logs web` zeigt keine Errors
|
||||
|
||||
### Monitoring
|
||||
- [ ] Prometheus: http://10.8.0.1:9090 erreichbar
|
||||
- [ ] Grafana: http://10.8.0.1:3000 erreichbar
|
||||
- [ ] Portainer: https://10.8.0.1:9443 erreichbar
|
||||
- [ ] Watchtower aktiv: `docker logs watchtower` zeigt Checks
|
||||
|
||||
---
|
||||
|
||||
## Quick Reference
|
||||
|
||||
### Häufigste Fehlerursachen
|
||||
1. **Supervisor Logging**: Verwende `logfile=/dev/null` + `silent=false`
|
||||
2. **User Permissions**: Setze `user: root` in docker-compose.prod.yml
|
||||
3. **Entrypoint Override**: Setze `entrypoint: []` um inherited entrypoint zu clearen
|
||||
4. **Pull Policy**: Verwende `pull_policy: always` um registry image zu forcen
|
||||
|
||||
### Wichtigste Config-Änderungen
|
||||
- `docker/supervisor/supervisord.conf`: `logfile=/dev/null`, `silent=false`
|
||||
- `docker-compose.prod.yml`: `user: root`, `entrypoint: []`, `pull_policy: always`
|
||||
- `docker/php/zz-docker.production.conf`: `user = www-data`, `group = www-data`
|
||||
Reference in New Issue
Block a user