# Payload CMS Multi-Tenant Infrastructure

> Letzte Aktualisierung: 09.12.2025

## Übersicht

Diese Dokumentation beschreibt die Infrastruktur eines Payload CMS 3.x Multi-Tenant-Systems für den Betrieb mehrerer Websites unter einer zentralen CMS-Instanz.

## Gesamtarchitektur

```
┌─────────────────────────────────────────────────────────────────────────────────────┐
│                              GESAMTARCHITEKTUR                                      │
│                                                                                     │
│  ┌───────────────────────────────────────────────────────────────────────────────┐ │
│  │                     LOKALE ENTWICKLUNGSUMGEBUNG                               │ │
│  │                      (Proxmox VE Cluster)                                     │ │
│  │                      LAN: 10.10.181.0/24                                      │ │
│  │                                                                               │ │
│  │   ┌─────────────┐  ┌─────────────┐  ┌─────────────┐  ┌─────────────┐         │ │
│  │   │ sv-payload  │  │ sv-postgres │  │sv-dev-payload│  │sv-analytics │         │ │
│  │   │  LXC 700    │  │  LXC 701    │  │  LXC 702    │  │  LXC 703    │         │ │
│  │   │ Payload CMS │  │ PostgreSQL  │  │  Next.js    │  │   Umami     │         │ │
│  │   │10.10.181.100│  │10.10.181.101│  │10.10.181.102│  │10.10.181.103│         │ │
│  │   │ + Redis     │  │             │  │             │  │             │         │ │
│  │   └─────────────┘  └─────────────┘  └─────────────┘  └─────────────┘         │ │
│  └───────────────────────────────────────────────────────────────────────────────┘ │
│                                        │                                           │
│                    ┌───────────────────┴───────────────────┐                       │
│                    │      LOKALER INTERNETZUGANG           │                       │
│                    │      850 Mbps ↓ / 50 Mbps ↑           │                       │
│                    │                                       │                       │
│                    │  Feste IP-Adressen:                   │                       │
│                    │  37.24.237.178 - Router               │                       │
│                    │  37.24.237.179 - complexcaresolutions │                       │
│                    │  37.24.237.180 - Nginx Proxy Manager  │                       │
│                    │  37.24.237.181 - pl.c2sgmbh.de        │                       │
│                    │  37.24.237.182 - frei                 │                       │
│                    └───────────────────┬───────────────────┘                       │
│                                        │                                           │
│                                   INTERNET                                         │
│                                        │                                           │
│     ┌──────────────────────────────────┼──────────────────────────────────┐        │
│     │                                  │                                  │        │
│     ▼                                  ▼                                  ▼        │
│  ┌─────────────────────┐  ┌─────────────────────┐  ┌─────────────────────┐        │
│  │   HETZNER 1         │  │   HETZNER 2         │  │   HETZNER 3         │        │
│  │   CCS GmbH          │  │   Martin Porwoll    │  │   Backend/Analytics │        │
│  │                     │  │                     │  │                     │        │
│  │   78.46.87.137      │  │   94.130.141.114    │  │   162.55.85.18      │        │
│  │   Debian 12.12      │  │   Ubuntu 24.04      │  │   Debian 13         │        │
│  │   Plesk             │  │   Plesk             │  │   Native            │        │
│  │                     │  │                     │  │                     │        │
│  │   Next.js Frontends │  │   Next.js Frontends │  │   ✅ Payload CMS    │        │
│  │   • complexcare...  │  │   • porwoll.de      │  │   ✅ Umami          │        │
│  │   • gunshin.de      │  │   • caroline-...    │  │   ✅ PostgreSQL 17  │        │
│  └─────────────────────┘  └─────────────────────┘  │   ✅ Redis Cache    │        │
│                                                    │   ✅ Claude Code    │        │
│                                                    └─────────────────────┘        │
│                                                                                     │
└─────────────────────────────────────────────────────────────────────────────────────┘
```

---

## Server-Details

### HETZNER 3 - Backend & Analytics (Produktion)

| Eigenschaft | Wert |
|-------------|------|
| **Hostname** | sv-hz03-backend |
| **IP-Adresse** | 162.55.85.18 |
| **Betriebssystem** | Debian 13 "Trixie" |
| **CPU** | AMD Ryzen 5 3600 (6 Cores / 12 Threads) |
| **RAM** | 64 GB DDR4 ECC |
| **Storage** | 2x 512 GB NVMe SSD (Software RAID 1) |
| **Netzwerk** | 1 Gbit/s (garantiert) |
| **Traffic** | Unbegrenzt |
| **Kosten** | ~€52/Monat |

#### Services auf Hetzner 3

| Service | User | Port | URL | Status |
|---------|------|------|-----|--------|
| PostgreSQL 17 | postgres | 5432 | localhost | ✅ Läuft |
| Payload CMS | payload | 3001 | https://cms.c2sgmbh.de | ✅ Läuft |
| Umami Analytics | umami | 3000 | https://analytics.c2sgmbh.de | ✅ Läuft |
| Redis Cache | redis | 6379 | localhost | ✅ Läuft |
| Nginx | root | 80/443 | Reverse Proxy | ✅ Läuft |
| Claude Code | claude | - | CLI Tool | ✅ Installiert |

#### System-User

| User | Zweck | Home-Verzeichnis |
|------|-------|------------------|
| root | System-Administration | /root |
| payload | Payload CMS | /home/payload |
| umami | Umami Analytics | /home/umami |
| claude | Claude Code / Server-Admin | /home/claude |

---

### HETZNER 1 - Complex Care Solutions GmbH

| Eigenschaft | Wert |
|-------------|------|
| **Eigentümer** | Complex Care Solutions GmbH |
| **IP-Adresse** | 78.46.87.137 |
| **Betriebssystem** | Debian 12.12 |
| **Control Panel** | Plesk Web Pro Edition 18.0.73 |
| **CPU** | AMD Ryzen 7 Pro 8700GE |
| **RAM** | 64 GB |
| **Storage** | 2x 512 GB NVMe SSD (Software RAID 1) |

#### Domains auf Hetzner 1

| Domain | Zweck |
|--------|-------|
| **complexcaresolutions.de** | Hauptdomain |
| **gunshin.de** | Portfolio/Holding |
| c2sgmbh.de | Kurzform → Redirect |
| zweitmeinung-*.de | Fachgebiete → Redirect |

---

### HETZNER 2 - Martin Porwoll (privat)

| Eigenschaft | Wert |
|-------------|------|
| **Eigentümer** | Martin Porwoll (privat) |
| **IP-Adresse** | 94.130.141.114 |
| **Betriebssystem** | Ubuntu 24.04 LTS |
| **Control Panel** | Plesk Web Pro Edition 18.0.73 |
| **CPU** | Intel Xeon E3-1275v6 |
| **RAM** | 64 GB |
| **Storage** | 2x 512 GB NVMe SSD (Software RAID 1) |

#### Domains auf Hetzner 2

| Domain | Zweck |
|--------|-------|
| **porwoll.de** | Hauptdomain |
| **caroline-porwoll.de** | Dr. Caroline Porwoll |

---

### Lokale Infrastruktur (Proxmox) - Entwicklung

| Server | IP | Port | Funktion | OS |
|--------|-----|------|----------|-----|
| sv-payload | 10.10.181.100 | 3000 | Payload CMS (Dev) + Redis | Debian 13 |
| sv-postgres | 10.10.181.101 | 5432 | PostgreSQL (Dev) | Debian 13 |
| sv-dev-payload | 10.10.181.102 | 3001 | Next.js Frontend | Debian 13 |
| sv-analytics | 10.10.181.103 | 3000 | Umami (Dev) | Debian 13 |

#### Feste IP-Adressen (Lokal → Internet)

| IP | Verwendung |
|----|------------|
| 37.24.237.178 | Router / Gateway |
| 37.24.237.179 | complexcaresolutions.cloud |
| 37.24.237.180 | Nginx Proxy Manager |
| 37.24.237.181 | pl.c2sgmbh.de (Payload Dev) |
| 37.24.237.182 | **Frei** |

---

## Credentials

### Produktion (sv-hz03-backend)

#### PostgreSQL

| Datenbank | User | Passwort |
|-----------|------|----------|
| payload_db | payload | Suchen55 |
| umami_db | umami | Suchen55 |

#### Redis

```bash
redis-cli -h localhost -p 6379
# Kein Passwort (nur localhost)
```

#### Environment Variables - Payload (.env)

```env
DATABASE_URI=postgresql://payload:Suchen55@localhost:5432/payload_db
PAYLOAD_SECRET=hxPARlMkmv+ZdCOAMw+N4o2x4mNbERB237iDQTYXALY=
PAYLOAD_PUBLIC_SERVER_URL=https://cms.c2sgmbh.de
NEXT_PUBLIC_SERVER_URL=https://cms.c2sgmbh.de
NODE_ENV=production
PORT=3001
REDIS_HOST=localhost
REDIS_PORT=6379
```

### Entwicklung (pl.c2sgmbh.de)

#### PostgreSQL (sv-postgres)

| Datenbank | User | Passwort |
|-----------|------|----------|
| payload_db | payload | Finden55 |

#### Environment Variables (.env)

```env
DATABASE_URI=postgresql://payload:Finden55@10.10.181.101:5432/payload_db
PAYLOAD_SECRET=a53b254070d3fffd2b5cfcc3
PAYLOAD_PUBLIC_SERVER_URL=https://pl.c2sgmbh.de
NEXT_PUBLIC_SERVER_URL=https://pl.c2sgmbh.de
NODE_ENV=production
PORT=3000
REDIS_HOST=localhost
REDIS_PORT=6379
```

---

## Multi-Tenant Konzept

Das System verwendet `@payloadcms/plugin-multi-tenant` für die Mandantenfähigkeit.

### Tenants (Mandanten)

Jeder Tenant repräsentiert eine separate Website:

| Tenant | Slug | Domains |
|--------|------|---------|
| porwoll.de | porwoll | porwoll.de, www.porwoll.de |
| Complex Care Solutions GmbH | c2s | complexcaresolutions.de |
| Gunshin | gunshin | gunshin.de |

### Datenisolation

- Jeder Content (Media, Pages, Posts etc.) gehört zu genau einem Tenant
- User werden Tenants zugewiesen und sehen nur deren Inhalte
- Die Domain-Erkennung erfolgt automatisch durch das Plugin

### Datenbank-Tabellen

```
tenants           - Mandanten-Stammdaten
tenants_domains   - Domain-Zuordnungen
users_tenants     - User-Mandanten-Beziehung (N:M)
```

---

## Redis Caching

### Architektur

```
┌─────────────────────────────────────────────────────────────────────────────────────┐
│  REDIS CACHING STRATEGIE                                                           │
│                                                                                     │
│  Request → Payload CMS → Redis Cache?                                              │
│                              │                                                      │
│                         ┌────┴────┐                                                 │
│                        HIT       MISS                                               │
│                         │         │                                                 │
│                         ▼         ▼                                                 │
│                      Return    PostgreSQL → Cache in Redis → Return                │
│                                                                                     │
│  Cache-Typen:                                                                       │
│  • API Response Cache (GET /api/pages, /api/posts)                                 │
│  • Automatische Invalidierung bei Content-Änderungen                               │
│                                                                                     │
│  Konfiguration:                                                                     │
│  • Max Memory: 2GB (Prod) / 512MB (Dev)                                            │
│  • Eviction: allkeys-lru                                                           │
│  • TTL: 5 Minuten (Standard)                                                       │
│                                                                                     │
└─────────────────────────────────────────────────────────────────────────────────────┘
```

### Redis Befehle

```bash
# Status prüfen
redis-cli ping

# Statistiken
redis-cli info stats

# Cache-Keys anzeigen
redis-cli keys "*"

# Cache leeren
redis-cli flushdb

# Live-Monitoring
redis-cli monitor
```

### Background Jobs (geplant)

**Evaluation (09.12.2025): BullMQ vs Agenda.js**

| Kriterium | BullMQ | Agenda.js |
|-----------|--------|-----------|
| Database | Redis ✅ | MongoDB ❌ |
| TypeScript | Native ✅ | Begrenzt ⚠️ |
| Priority Jobs | Ja ✅ | Nein ❌ |
| Rate Limiting | Ja ✅ | Nein ❌ |
| UI Dashboard | @bull-board ✅ | Keine ❌ |

**Entscheidung: BullMQ** (Redis bereits vorhanden, TypeScript-native, @bull-board UI)

**Geplante Konfiguration:**
```bash
# Environment Variables (wenn implementiert)
QUEUE_REDIS_URL=redis://localhost:6379/1   # Separate DB für Jobs
QUEUE_CONCURRENCY=5                         # Parallele Worker
QUEUE_DEFAULT_RETRY=3                       # Wiederholungsversuche
```

**PM2 Worker-Konfiguration (geplant):**
```javascript
// ecosystem.config.cjs - Erweiterung
{
  name: 'queue-worker',
  script: './scripts/run-queue-worker.ts',
  instances: 1,
  max_memory_restart: '500M'
}
```

---

## Deployment Workflow

```
┌─────────────────────────────────────────────────────────────────────────────────────┐
│  DEPLOYMENT WORKFLOW                                                                │
│                                                                                     │
│  ┌──────────────────────────────────┐        ┌──────────────────────────────────┐  │
│  │     ENTWICKLUNG (DEV)            │        │     PRODUKTION (PROD)            │  │
│  │     pl.c2sgmbh.de                │        │     cms.c2sgmbh.de               │  │
│  │     37.24.237.181                │        │     162.55.85.18                 │  │
│  └──────────────────────────────────┘        └──────────────────────────────────┘  │
│                                                                                     │
│  Step 1: CODE ENTWICKELN                                                           │
│  ┌────────────────────────────────────────────────────────────────────────┐        │
│  │  cd /home/payload/payload-cms                                          │        │
│  │  pnpm dev                     # Lokal testen                           │        │
│  │  pnpm build                   # Build-Test                             │        │
│  │  pm2 restart payload          # Auf Dev-Server deployen               │        │
│  └────────────────────────────────────────────────────────────────────────┘        │
│                                  ↓                                                  │
│                                                                                     │
│  Step 2: ZU GITHUB PUSHEN                                                          │
│  ┌────────────────────────────────────────────────────────────────────────┐        │
│  │  git add .                    # Alle Änderungen stagen                 │        │
│  │  git commit -m "feat: XYZ"    # Commit erstellen                       │        │
│  │  git push origin main         # Zu GitHub pushen                       │        │
│  └────────────────────────────────────────────────────────────────────────┘        │
│                                  ↓                                                  │
│                                                                                     │
│           ┌────────────────────────────────────────────────────┐                   │
│           │          GITHUB REPOSITORY (PRIVAT)               │                   │
│           │     https://github.com/c2s-admin/cms.c2sgmbh      │                   │
│           └────────────────────────────────────────────────────┘                   │
│                                  ↓                                                  │
│                                                                                     │
│  Step 3: AUF PRODUKTION DEPLOYEN                                                   │
│  ┌────────────────────────────────────────────────────────────────────────┐        │
│  │  ssh payload@162.55.85.18                                              │        │
│  │  ~/deploy.sh                  # Automatisches Deployment               │        │
│  │                                                                         │        │
│  │  Das deploy.sh Script macht:                                           │        │
│  │  ├─ git pull origin main       # Code von GitHub holen                 │        │
│  │  ├─ pnpm install               # Dependencies aktualisieren            │        │
│  │  ├─ pnpm build                 # Produktions-Build                     │        │
│  │  └─ pm2 restart payload        # Service neustarten                    │        │
│  └────────────────────────────────────────────────────────────────────────┘        │
│                                                                                     │
└─────────────────────────────────────────────────────────────────────────────────────┘
```

### Git-Setup auf Servern

| Server | User | Remote | Auth-Methode | Status |
|--------|------|--------|--------------|--------|
| pl.c2sgmbh.de (Dev) | payload | HTTPS | GitHub CLI (`gh auth`) | ✅ Konfiguriert |
| cms.c2sgmbh.de (Prod) | payload | SSH | SSH-Key | ✅ Eingerichtet |

### Deployment-Befehle

**Entwicklungsserver → GitHub:**

```bash
cd /home/payload/payload-cms
git status
pnpm build
pm2 restart payload
git add .
git commit -m "feat: Beschreibung der Änderung"
git push origin main
```

**GitHub → Produktionsserver:**

```bash
# Option A: SSH + Deploy-Script (empfohlen)
ssh payload@162.55.85.18 '~/deploy.sh'

# Option B: Manuelles SSH-Login
ssh payload@162.55.85.18
cd ~/payload-cms
git pull origin main
pnpm install
pnpm build
pm2 restart payload
```

---

## Backup

### Backup-Script (~/backup.sh)

```bash
#!/bin/bash
set -e

BACKUP_DIR=~/backups
DATE=$(date +%Y-%m-%d_%H-%M-%S)
RETENTION_DAYS=7

mkdir -p $BACKUP_DIR

# PostgreSQL Backup
PGPASSWORD=Suchen55 pg_dump -h localhost -U payload payload_db > $BACKUP_DIR/payload_db_$DATE.sql
gzip $BACKUP_DIR/payload_db_$DATE.sql

# Alte Backups löschen
find $BACKUP_DIR -name "*.sql.gz" -mtime +$RETENTION_DAYS -delete
```

### Cronjob (täglich 3:00 Uhr)

```
0 3 * * * /home/payload/backup.sh >> /home/payload/backups/backup.log 2>&1
```

---

## Service-Management

### PM2 Befehle

```bash
pm2 status              # Status
pm2 logs payload        # Logs
pm2 restart payload     # Neustart
pm2 save                # Autostart speichern
```

### Systemd Services

```bash
# PostgreSQL
systemctl status postgresql
systemctl restart postgresql

# Nginx
systemctl status nginx
systemctl restart nginx
nginx -t  # Config testen

# Redis
systemctl status redis-server
systemctl restart redis-server
```

---

## URLs Übersicht

| Service | Entwicklung | Produktion |
|---------|-------------|------------|
| Payload Admin | https://pl.c2sgmbh.de/admin | https://cms.c2sgmbh.de/admin |
| Payload API | https://pl.c2sgmbh.de/api | https://cms.c2sgmbh.de/api |
| Umami | - | https://analytics.c2sgmbh.de |

---

## SSH Schnellzugriff

```bash
# Produktion (Hetzner 3)
ssh root@162.55.85.18          # Root
ssh payload@162.55.85.18       # Payload User
ssh umami@162.55.85.18         # Umami User
ssh claude@162.55.85.18        # Claude Code

# Hetzner Server
ssh root@78.46.87.137          # Hetzner 1 (CCS)
ssh root@94.130.141.114        # Hetzner 2 (Porwoll)

# Entwicklung (Proxmox)
ssh payload@10.10.181.100      # sv-payload
ssh root@10.10.181.101         # sv-postgres
```

---

## Netzwerk & Firewall

### UFW Regeln auf sv-payload (Dev)

```bash
22/tcp    ALLOW  10.10.181.0/24   # SSH aus VLAN
80/tcp    ALLOW  Anywhere         # HTTP (ACME)
443/tcp   ALLOW  Anywhere         # HTTPS
```

### UFW Regeln auf sv-postgres (Dev)

```bash
22/tcp    ALLOW  10.10.181.0/24   # SSH aus VLAN
5432/tcp  ALLOW  10.10.181.100    # PostgreSQL nur von Payload
```

### UFW Regeln auf sv-hz03-backend (Prod)

```bash
22/tcp    ALLOW  Anywhere         # SSH
80/tcp    ALLOW  Anywhere         # HTTP
443/tcp   ALLOW  Anywhere         # HTTPS
```

---

## SSL Zertifikate

| Domain | Anbieter | Status |
|--------|----------|--------|
| pl.c2sgmbh.de | Let's Encrypt (Caddy) | Auto-Renewal |
| cms.c2sgmbh.de | Let's Encrypt (Certbot) | Auto-Renewal |
| analytics.c2sgmbh.de | Let's Encrypt (Certbot) | Auto-Renewal |

---

## Tech Stack

| Komponente | Technologie | Version |
|------------|-------------|---------|
| CMS | Payload CMS | 3.x |
| Framework | Next.js | 15.4.7 |
| Runtime | Node.js | 22.x |
| Datenbank | PostgreSQL | 17 |
| Cache | Redis | 7.x |
| Analytics | Umami | 3.x |
| Process Manager | PM2 | Latest |
| Package Manager | pnpm | Latest |
| Reverse Proxy (Dev) | Caddy | 2.10.2 |
| Reverse Proxy (Prod) | Nginx | Latest |
| SSL | Let's Encrypt | - |

---

## DSGVO-Konformität

Die Architektur wurde bewusst ohne Cloudflare designed:

- Keine US-Dienste im Datenpfad für Admin-Zugriffe
- Direkte öffentliche IP statt Proxy
- Keine Auftragsverarbeiter-Verträge für CDN nötig
- Redakteur-IPs und Sessions bleiben in DE

---

## Checkliste nach Deployment

- [ ] `pm2 status` - Alle Prozesse online?
- [ ] `redis-cli ping` - Redis antwortet?
- [ ] Admin Panel erreichbar?
- [ ] `pm2 logs payload --lines 10` - Keine Fehler?

---

*Stand: 09. Dezember 2025*
