# Deployment Guide - Payload CMS Multi-Tenant

*Letzte Aktualisierung: 17. Januar 2026*

> **Wichtig:** Für die vollständige Deployment-Strategie siehe [DEPLOYMENT_STRATEGY.md](./DEPLOYMENT_STRATEGY.md)

## Übersicht

Dieses Dokument beschreibt den Deployment-Prozess für das Payload CMS Multi-Tenant-System.

```
┌─────────────────┐     ┌─────────────────┐     ┌─────────────────┐
│   DEVELOPMENT   │     │     STAGING     │     │   PRODUCTION    │
│                 │     │                 │     │                 │
│  sv-frontend    │────▶│   sv-payload    │────▶│   Hetzner 3     │
│  Local Dev      │     │ pl.porwoll.tech │     │ cms.c2sgmbh.de  │
│                 │     │                 │     │                 │
│  develop branch │     │  develop branch │     │   main branch   │
└─────────────────┘     └─────────────────┘     └─────────────────┘
```

---

## Umgebungen

| Umgebung | Server | URL | Branch | Zweck |
|----------|--------|-----|--------|-------|
| **Development** | sv-payload (LXC 700) | https://pl.porwoll.tech | `develop` | Entwicklung & Testing |
| **Production** | Hetzner 3 | https://cms.c2sgmbh.de | `main` | Live-System |

---

## Git Branching Workflow

**Regel:** Immer auf `develop` entwickeln, nach Freigabe mit `main` mergen.

### Workflow

```bash
# 1. Auf develop entwickeln
git checkout develop
git pull origin develop
# ... Code ändern ...
git add .
git commit -m "feat: neue Funktion"
git push origin develop
# → Automatisches Deployment auf Staging (pl.porwoll.tech)

# 2. Nach Test & Freigabe: Merge in main
git checkout main
git pull origin main
git merge develop
git push origin main
# → Manuelles Deployment auf Production (cms.c2sgmbh.de)

# 3. develop aktuell halten (nach Hotfixes auf main)
git checkout develop
git merge main
git push origin develop
```

### Commit-Konventionen

| Prefix | Verwendung |
|--------|------------|
| `feat:` | Neue Features |
| `fix:` | Bug-Fixes |
| `docs:` | Dokumentation |
| `refactor:` | Code-Refactoring |

---

## Staging Deployment (Development → pl.porwoll.tech)

### Automatisch via GitHub Actions

Bei jedem Push auf `develop` wird automatisch deployed:

```yaml
# .github/workflows/deploy-staging.yml
on:
  push:
    branches: [develop]
```

**Ablauf:**
1. Pre-deployment Checks (Lint, Tests)
2. SSH-Verbindung zu sv-payload
3. `git pull origin develop`
4. `pnpm install`
5. `pnpm payload migrate`
6. `pnpm build`
7. `pm2 restart payload`
8. Health Check

### Manuell auf sv-payload

```bash
# SSH zum Server
ssh payload@10.10.181.100

# Ins Projektverzeichnis
cd /home/payload/payload-cms

# Änderungen holen
git pull origin develop

# Dependencies aktualisieren
pnpm install

# Migrationen ausführen (falls vorhanden)
pnpm payload migrate

# Build erstellen
pnpm build

# PM2 neustarten
pm2 restart payload

# Logs prüfen
pm2 logs payload --lines 20
```

### Mit Deploy-Script

```bash
# Auf sv-payload
./scripts/deploy-staging.sh

# Optionen:
./scripts/deploy-staging.sh --skip-build      # Nur Code-Update
./scripts/deploy-staging.sh --skip-migrations # Ohne Migrationen
```

---

## Production Deployment (main → cms.c2sgmbh.de)

### Option A: Via GitHub Actions (Empfohlen)

1. **develop in main mergen und pushen**
   ```bash
   git checkout main
   git pull origin main
   git merge develop
   git push origin main
   ```

2. **GitHub Actions Workflow starten**
   - Gehe zu: Actions → "Deploy to Production" → "Run workflow"
   - Oder via CLI:
     ```bash
     gh workflow run deploy-production.yml
     ```

3. **Der Workflow führt automatisch aus:**
   - Pre-flight Checks
   - Tests (optional)
   - Datenbank-Backup
   - Deployment
   - Health Check
   - Bei Fehler: Automatischer Rollback

### Option B: Via Deploy-Script auf Server

```bash
# SSH zum Production-Server
ssh payload@162.55.85.18

# Deploy-Script ausführen (mit Backup, Health Check, Rollback-Fähigkeit)
cd ~/payload-cms
./scripts/deploy-production.sh

# Optionen:
./scripts/deploy-production.sh -y              # Ohne Bestätigung
./scripts/deploy-production.sh --skip-backup   # Ohne Backup (nicht empfohlen)
./scripts/deploy-production.sh --skip-build    # Nur Service-Neustart
./scripts/deploy-production.sh --rollback      # Rollback zur vorherigen Version
./scripts/deploy-production.sh --dry-run       # Zeigt was passieren würde
```

### Rollback bei Problemen

```bash
# Automatischer Rollback zur vorherigen Version
./scripts/deploy-production.sh --rollback

# Oder manuell zu spezifischem Commit
git log --oneline -10
git reset --hard <commit-sha>
pnpm install --frozen-lockfile
pnpm build
pm2 restart payload
```

---

## Rollback

### Auf vorherigen Commit zurücksetzen

```bash
# Letzten funktionierenden Commit finden
git log --oneline -10

# Zurücksetzen (VORSICHT: Ändert History nicht)
git checkout <commit-hash>
pnpm install
pnpm build
pm2 restart payload

# Oder: Hard Reset (VORSICHT: Ändert History)
git reset --hard <commit-hash>
git push --force origin main  # Nur im Notfall!
```

### Datenbank-Rollback

```bash
# Backup wiederherstellen (nur bei Datenverlust)
gunzip -c ~/backups/payload_db_YYYY-MM-DD_HH-MM-SS.sql.gz | \
  psql -h localhost -U payload -d payload_db
```

---

## Migrationen

### Migration erstellen

```bash
# Auf Development-Server
pnpm payload migrate:create

# Generiert: src/migrations/YYYYMMDD_HHMMSS_<name>.ts
```

### Migration ausführen

```bash
# Normale Ausführung
pnpm payload migrate

# Bei PgBouncer-Problemen: Direkte Verbindung
./scripts/db-direct.sh migrate
```

### Migration-Status prüfen

```bash
pnpm payload migrate:status
```

---

## Build-Konfiguration

### Memory-Limits

Der Server hat 8GB RAM. Build-Einstellungen:

```json
// package.json
{
  "scripts": {
    "build": "cross-env NODE_OPTIONS='--no-deprecation --max-old-space-size=2048' next build"
  }
}
```

### Bei Memory-Problemen

```bash
# PM2 stoppen um Speicher freizugeben
pm2 stop payload

# Build mit reduziertem Memory
NODE_OPTIONS="--no-deprecation --max-old-space-size=1024" pnpm build

# PM2 wieder starten
pm2 start payload
```

---

## PM2 Konfiguration

### ecosystem.config.cjs

```javascript
module.exports = {
  apps: [
    {
      name: 'payload',
      script: 'node_modules/.bin/next',
      args: 'start',
      cwd: '/home/payload/payload-cms',
      instances: 1,
      max_memory_restart: '2G',
      env: {
        NODE_ENV: 'production',
        PORT: 3000
      }
    },
    {
      name: 'queue-worker',
      script: 'node_modules/tsx/dist/cli.mjs',
      args: 'scripts/run-queue-worker.ts',
      exec_mode: 'fork',
      cwd: '/home/payload/payload-cms',
      instances: 1,
      max_memory_restart: '768M'
    }
  ]
}
```

### PM2 Befehle

```bash
pm2 status              # Status aller Prozesse
pm2 logs payload        # Logs anzeigen
pm2 logs queue-worker   # Queue-Worker Logs
pm2 restart payload     # Neustart
pm2 restart all         # Alle neustarten
pm2 save                # Autostart-Konfiguration speichern
pm2 startup             # Systemd-Integration
```

---

## Health Checks

### Nach Deployment prüfen

```bash
# 1. PM2 Status
pm2 status

# 2. Logs auf Fehler prüfen
pm2 logs payload --lines 50

# 3. API erreichbar?
curl -I https://cms.c2sgmbh.de/api/users

# 4. Admin Panel erreichbar?
curl -I https://cms.c2sgmbh.de/admin

# 5. Redis verbunden?
redis-cli ping
```

### Automatischer Health Check

```bash
#!/bin/bash
# health-check.sh

API_URL="https://cms.c2sgmbh.de/api"
ADMIN_URL="https://cms.c2sgmbh.de/admin"

# API Check
if curl -sf "$API_URL/users" > /dev/null; then
  echo "✅ API OK"
else
  echo "❌ API FAILED"
  exit 1
fi

# Admin Check
if curl -sf "$ADMIN_URL" > /dev/null; then
  echo "✅ Admin OK"
else
  echo "❌ Admin FAILED"
  exit 1
fi

echo "✅ All checks passed"
```

---

## Umgebungsvariablen

### Production (.env)

```env
# Datenbank
DATABASE_URI=postgresql://payload:***@localhost:5432/payload_db
PAYLOAD_SECRET=***

# URLs
PAYLOAD_PUBLIC_SERVER_URL=https://cms.c2sgmbh.de
NEXT_PUBLIC_SERVER_URL=https://cms.c2sgmbh.de

# Environment
NODE_ENV=production
PORT=3001

# Redis
REDIS_URL=redis://localhost:6379
REDIS_PASSWORD=***

# Security
CSRF_SECRET=***
TRUST_PROXY=true
```

### Staging (.env)

```env
# Datenbank (via PgBouncer)
DATABASE_URI=postgresql://payload:***@127.0.0.1:6432/payload_db
PAYLOAD_SECRET=***

# URLs
PAYLOAD_PUBLIC_SERVER_URL=https://pl.porwoll.tech
NEXT_PUBLIC_SERVER_URL=https://pl.porwoll.tech

# Environment
NODE_ENV=production
PORT=3000

# Redis
REDIS_URL=redis://localhost:6379
REDIS_PASSWORD=***

# Security
CSRF_SECRET=***
TRUST_PROXY=true
```

---

## CI/CD Pipeline

### GitHub Actions Workflows

| Workflow | Trigger | Aktion |
|----------|---------|--------|
| `ci.yml` | Push/PR auf main, develop | Lint, Test, Build, E2E |
| `security.yml` | Push/PR, Schedule | Security Scanning |
| `deploy-staging.yml` | Push auf develop | Auto-Deploy zu Staging |
| `deploy-production.yml` | Manuell (workflow_dispatch) | Production Deployment |

### Secrets (GitHub)

| Secret | Beschreibung |
|--------|--------------|
| `STAGING_SSH_KEY` | SSH Private Key für sv-payload |
| `PRODUCTION_SSH_KEY` | SSH Private Key für Hetzner 3 |

### Manuelles Deployment triggern

```bash
# Via GitHub CLI
gh workflow run deploy-staging.yml

# Mit skip_tests Option
gh workflow run deploy-staging.yml -f skip_tests=true
```

---

## Backup vor Deployment

### Automatisches Backup

Backups laufen täglich um 03:00 Uhr. Vor großen Deployments manuell:

```bash
# Auf Production
~/backup.sh

# Auf Staging
/home/payload/backups/postgres/backup-db.sh --verbose
```

### Backup-Speicherorte

| Server | Lokal | Offsite |
|--------|-------|---------|
| Production | ~/backups/ | - |
| Staging | /home/payload/backups/postgres/ | s3://c2s/backups/postgres/ |

---

## Troubleshooting

### Build schlägt fehl

```bash
# Cache löschen
rm -rf .next
pnpm build

# Mit mehr Speicher
NODE_OPTIONS="--max-old-space-size=4096" pnpm build
```

### PM2 startet nicht

```bash
# Logs prüfen
pm2 logs payload --lines 100

# Prozess komplett entfernen und neu starten
pm2 delete payload
pm2 start ecosystem.config.cjs
```

### Datenbank-Verbindung fehlgeschlagen

```bash
# PgBouncer Status (Staging)
sudo systemctl status pgbouncer

# PostgreSQL Status (Production)
sudo systemctl status postgresql

# Verbindung testen
psql -h localhost -U payload -d payload_db -c "SELECT 1"
```

### Migration fehlgeschlagen

```bash
# Status prüfen
pnpm payload migrate:status

# Bei PgBouncer-Problemen
./scripts/db-direct.sh migrate

# Einzelne Migration manuell
pnpm payload migrate --name 20251216_073000_add_video_collections
```

---

## Checkliste

### Vor dem Deployment

- [ ] Alle Tests grün (`pnpm test`)
- [ ] Build erfolgreich (`pnpm build`)
- [ ] Migrationen getestet auf Staging
- [ ] Backup erstellt (bei Datenbank-Änderungen)
- [ ] CLAUDE.md/Docs aktualisiert (bei neuen Features)

### Nach dem Deployment

- [ ] `pm2 status` - Prozesse online
- [ ] Admin Panel erreichbar
- [ ] API funktioniert
- [ ] Logs auf Fehler geprüft
- [ ] Neue Features manuell getestet

---

## Kontakte

| Rolle | Kontakt |
|-------|---------|
| Server Admin | Martin Porwoll |
| Repository | https://github.com/complexcaresolutions/cms.c2sgmbh |

---

*Dokumentation: Complex Care Solutions GmbH | 29.12.2025*
