Ghost + Caddy: Dedizierter Single-App-Server Setup (Production Ready)
- ✅ Konfliktfrei
- ✅ Wartungsarm
- ✅ Schnell
- ✅ Skalierbar (ohne Komplexität)
Hardware-Anforderungen (minimal)
- CPU: 1-2 vCPU reicht vollkommen
- RAM: 2-4GB (Ghost ~100MB, Caddy ~14MB, MySQL ~150-200MB)
- Disk: 20-50GB je nach Traffic und Media
- Bandbreite: Standard (bei Blog selten ein Problem)
Empfohlener VPS: Hetzner CX11, Netcup 512MB+, Contabo Basic
Architecture: Single Server, Caddy Full Power
┌─────────────────────────────────────────────────────────────┐
│ INTERNET (Port 80/443) │
└────────────────────┬────────────────────────────────────────┘
│
▼
┌────────────────────────┐
│ CADDY REVERSE PROXY │ ← Automatisches SSL/TLS
│ (Port 80/443) │ ← Certificate Management
│ Single Caddyfile │ ← Traffic Routing
└────────────┬───────────┘
│
┌────────────┴──────────────┐
│ │
▼ ▼
┌──────────────┐ ┌──────────────┐
│ Ghost │ │ MySQL │
│ (Port 2368) │ │ (localhost) │
│ Container │ │ Container │
└──────────────┘ └──────────────┘
│ │
└───────────────────────────┘
(Docker Compose Network)
Optional (für ActivityPub self-hosted):
┌────────────────────────┐
│ CADDY (cont.) │
└────────────┬───────────┘
│
┌────────────┴──────────────────────┐
│ │
▼ ▼
┌──────────────┐ ┌──────────────┐
│ Ghost │ │ ActivityPub │
│ (2368) │ │ (Port 8080) │
└──────────────┘ └──────────────┘
Step-by-Step Setup
1. Server vorbereiten (Ubuntu 24.04)
# SSH-Keys einrichten (am besten)
# Domain DNS-Record auf Server-IP zeigen
# System updaten
sudo apt update && sudo apt upgrade -y
# Docker installieren (offizielle Methode)
curl -fsSL https://get.docker.com -o get-docker.sh
sudo sh get-docker.sh
rm get-docker.sh
# Docker Compose als Plugin (nicht docker-compose!)
sudo apt install -y docker-compose-plugin
# Verifikation
docker --version
docker compose version
# User zur docker-Gruppe hinzufügen (optional, weniger sudo)
sudo usermod -aG docker $USER
2. Ghost Docker Setup
# Verzeichnis erstellen
sudo mkdir -p /opt/ghost && cd /opt/ghost
# Repo klonen
sudo git clone https://github.com/TryGhost/ghost-docker.git .
# (Punkt = in dieses Verzeichnis)
# Konfiguration vorbereiten
sudo cp .env.example .env
sudo nano .env
In .env eintragen:
DOMAIN=deine-domain.de # Keine www, keine https!
ADMIN_DOMAIN= # Leer lassen (=DOMAIN)
# Sichere Passwörter generieren:
# openssl rand -hex 32
DATABASE_ROOT_PASSWORD=<256bit-hex-passwort>
DATABASE_PASSWORD=<256bit-hex-passwort>
# SMTP für E-Mails (optional aber empfohlen)
MAIL_FROM="Dein Blog <[email protected]>"
MAIL_TRANSPORT=SMTP
MAIL_HOST=smtp.dein-provider.de
MAIL_PORT=587
MAIL_USER=dein-smtp-user
MAIL_PASSWORD=dein-smtp-passwort
# ActivityPub (optional, self-hosted)
COMPOSE_PROFILES=activitypub
3. Caddy Konfiguration
# Caddy config folder
sudo nano caddy/Caddyfile
Minimal Caddyfile (du brauchst fast nichts zu ändern):
deine-domain.de {
reverse_proxy ghost:2368
# Security Headers (optional, aber empfohlen)
header {
Strict-Transport-Security "max-age=31536000; includeSubDomains"
X-Content-Type-Options "nosniff"
X-Frame-Options "DENY"
Referrer-Policy "no-referrer-when-downgrade"
}
# Kompression
encode zstd gzip
}
# Redirect www zu non-www (optional)
www.deine-domain.de {
redir https://deine-domain.de{uri} permanent
}
# ActivityPub Pfade (nur wenn self-hosted)
deine-domain.de {
# Ghost main
reverse_proxy /ghost/admin* ghost:2368
reverse_proxy /* ghost:2368
# ActivityPub
reverse_proxy /.ghost/activitypub/* activitypub:8080
reverse_proxy /.well-known/webfinger activitypub:8080
reverse_proxy /.well-known/nodeinfo activitypub:8080
}
Das war's. Caddy macht den Rest automatisch:
- SSL-Zertifikat von Let's Encrypt
- HTTP → HTTPS Redirect
- Certificate Renewal
- All ohne dich zu fragen
4. Starten
cd /opt/ghost
# Bilder pullen
sudo docker compose pull
# Starten
sudo docker compose up -d
# Logs checken (sollte nach ~30sec laufen)
sudo docker compose logs -f ghost
Nach ~30 Sekunden: https://deine-domain.de sollte funktionieren.
Admin Panel: https://deine-domain.de/ghost
5. Backups (kritisch!)
# Täglich um 2 Uhr nachts
# In crontab: crontab -e
0 2 * * * cd /opt/ghost && \
docker compose exec -T db mysqldump -u root -p$DATABASE_ROOT_PASSWORD --all-databases | \
gzip > /backups/ghost-db-$(date +\%Y\%m\%d).sql.gz && \
tar czf /backups/ghost-content-$(date +\%Y\%m\%d).tar.gz ghost-content/
Updates
cd /opt/ghost
# Images updaten
docker compose pull
# Restart
docker compose up -d
# Fertig. Caddy merkt das gar nicht.
Logs & Monitoring
# Ghost Logs
docker compose logs -f ghost
# Caddy Logs
docker compose logs -f caddy
# MySQL
docker compose logs -f db
# Ressourcen
docker stats
ActivityPub (wenn gewünscht)
Nach der Installation:
- Admin Panel:
/ghost - Settings → Growth → Network
- Toggle "Network" an
- Handle ist automatisch
@[email protected]oder dein Owner-Slug
Das war's. Self-hosted ActivityPub läuft komplett im Container, Caddy routet automatisch.
Warum das die beste Lösung ist
✅ Keine Konflikte: Caddy hat volle Kontrolle, keine Konkurrenz
✅ Automatische Zertifikate: Let's Encrypt wird vollautomatisch verwaltet
✅ Simpel: Ein docker compose.yml, ein Caddyfile
✅ Wartungsarm: Updates sind pull + up -d
✅ Skalierbar: Wenn du später mehr brauchst, ist das Fundament stabil
✅ Zukunftssicher: Ghost 7.0 ist nur noch ein Update
✅ ActivityPub ready: Alles vorkonfiguriert
Performance
Auf einem 1GB VPS:
- Caddy: ~14MB RAM
- Ghost: ~100MB RAM
- MySQL: ~150-200MB RAM
- Gesamt: ~300-400MB RAM
- Reste: ~600-700MB für OS/Buffer
Result: Viel Luft, sehr schnell, kein Swapping.
Troubleshooting
Caddy findet SSL-Zertifikat nicht:
→ DNS-Record ist kaputt, warte 5 Min oder check docker compose logs caddy
Ghost zeigt "Wrong URL":
→ In .env: DOMAIN=deine-domain.de (KEINE https://)
ActivityPub funktioniert nicht:
→ Check Caddyfile: Alle 3 Pfade zu Port 8080?
Backup/Restore:
→ docker compose stop → Backups ersetzen → docker compose up -d
Fazit
Das ist der saubere, "DevOps-richtige" Weg für einen Self-Hoster, der keine Systeme nebeneinander jonglieren will. Ein Server, eine App, volle Kontrolle.