Ghost Blog – Umzug auf neuen Server (Docker Compose + NGINX)
Ziel: Komplette Ghost-Instanz (Inhalte, Datenbank, Medien, Theme) von Server A auf Server B migrieren – ohne Datenverlust, mit minimaler Downtime. Die Domain bleibt gleich. NGINX läuft auf beiden Servern nativ (nicht im Container).
Inhaltsverzeichnis
- Voraussetzungen
- Überblick über die Verzeichnisstruktur
- Phase 1 – Alte Instanz sichern
- Phase 2 – Neue Instanz vorbereiten
- Phase 3 – Daten übertragen
- Phase 4 – Neue Instanz starten & prüfen
- Phase 5 – NGINX auf dem neuen Server konfigurieren
- Phase 6 – DNS umstellen & Go-Live
- Phase 7 – Alten Server abschalten
- Rollback-Plan
- Checkliste
1. Voraussetzungen
Auf dem alten Server (Server A):
- Ghost läuft als
docker-compose-Stack - NGINX ist nativ installiert und als Reverse Proxy konfiguriert
- SSH-Zugang vorhanden
rsyncoderscpverfügbar
Auf dem neuen Server (Server B):
- Ubuntu 22.04 LTS empfohlen
- Docker + Docker Compose installiert
- NGINX installiert (
apt install nginx) - SSH-Zugang vorhanden
- Gleiche oder höhere Docker-Version wie auf Server A
Lokal:
- SSH-Keys für beide Server hinterlegt
⚠️ Wichtig: Notiere dir vor dem Start die Ghost-Version auf Server A (docker inspect <ghost-container> | grep -i image). Die neue Instanz muss dieselbe Version verwenden – erst nach erfolgreichem Umzug updaten.2. Überblick über die Verzeichnisstruktur
Eine typische Ghost-Docker-Compose-Instanz sieht so aus:
/opt/ghost/
├── docker-compose.yml
├── .env # Umgebungsvariablen (DB-Passwörter etc.)
├── ghost-content/ # Ghost Content-Verzeichnis (Volumes)
│ ├── data/ # SQLite-Datenbank (falls nicht MySQL)
│ ├── images/
│ ├── themes/
│ ├── files/
│ └── logs/
└── mysql-data/ # MySQL-Daten (falls MySQL genutzt)
Die genauen Pfade hängen von deiner docker-compose.yml ab. Passe alle Pfade in dieser Anleitung entsprechend an.3. Phase 1 – Alte Instanz sichern
3.1 Aktuellen Zustand dokumentieren
# Auf Server A
cd /opt/ghost
# Ghost-Version notieren
docker compose ps
docker compose images
# Laufende Container anzeigen
docker ps -a
# Verwendete Ports anzeigen
docker compose port ghost 2368
3.2 Ghost in den Wartungsmodus versetzen (optional, empfohlen)
Damit während des Backups keine neuen Inhalte verloren gehen, Ghost kurz stoppen:
# Auf Server A
cd /opt/ghost
docker compose stop ghost
Der MySQL-Container kann weiterlaufen – wir greifen gleich direkt darauf zu.
3.3 MySQL-Datenbank exportieren
# Auf Server A – Datenbankname, User und Passwort aus .env entnehmen
source /opt/ghost/.env
docker compose exec mysql mysqldump \
-u"${MYSQL_USER}" \
-p"${MYSQL_PASSWORD}" \
"${MYSQL_DATABASE}" \
> /opt/ghost/backup_ghost_db_$(date +%Y%m%d_%H%M%S).sql
# Dump prüfen (sollte mehrere MB groß sein)
ls -lh /opt/ghost/backup_ghost_db_*.sql
Falls du SQLite nutzt (kein MySQL-Container), liegt die Datenbank unter ghost-content/data/ghost.db – diese Datei wird im nächsten Schritt mit dem Content-Verzeichnis gesichert.3.4 Ghost Content-Verzeichnis sichern
# Auf Server A
tar -czf /opt/ghost/backup_ghost_content_$(date +%Y%m%d_%H%M%S).tar.gz \
-C /opt/ghost ghost-content/
# Integrität prüfen
tar -tzf /opt/ghost/backup_ghost_content_*.tar.gz | head -20
3.5 Docker Compose-Konfiguration sichern
# Auf Server A
cp /opt/ghost/docker-compose.yml /opt/ghost/docker-compose.yml.bak
cp /opt/ghost/.env /opt/ghost/.env.bak
# Alles in ein Archiv
tar -czf /opt/ghost/backup_config_$(date +%Y%m%d_%H%M%S).tar.gz \
/opt/ghost/docker-compose.yml \
/opt/ghost/.env
3.6 NGINX-Konfiguration sichern
# Auf Server A
sudo cp -r /etc/nginx /tmp/nginx_backup_$(date +%Y%m%d)
sudo tar -czf /opt/ghost/backup_nginx_$(date +%Y%m%d_%H%M%S).tar.gz /tmp/nginx_backup_*
3.7 Backups auf lokalen Rechner herunterladen (Sicherheitskopie)
# Lokal ausführen
scp user@server-a:/opt/ghost/backup_*.* ./ghost-migration-backup/
4. Phase 2 – Neue Instanz vorbereiten
4.1 Docker und Docker Compose installieren (falls noch nicht vorhanden)
# Auf Server B
sudo apt update && sudo apt upgrade -y
# Docker installieren
curl -fsSL https://get.docker.com | sudo sh
sudo usermod -aG docker $USER
newgrp docker
# Docker Compose Plugin prüfen
docker compose version
4.2 Verzeichnisstruktur anlegen
# Auf Server B
sudo mkdir -p /opt/ghost/ghost-content/{data,images,themes,files,logs}
sudo mkdir -p /opt/ghost/mysql-data
sudo chown -R $USER:$USER /opt/ghost
4.3 NGINX installieren und vorbereiten
# Auf Server B
sudo apt install -y nginx certbot python3-certbot-nginx
sudo systemctl enable nginx
sudo systemctl start nginx
4.4 Docker Compose-Konfiguration übertragen und anpassen
Kopiere die docker-compose.yml von Server A:
# Lokal oder direkt von Server A auf Server B
scp user@server-a:/opt/ghost/docker-compose.yml user@server-b:/opt/ghost/
scp user@server-a:/opt/ghost/.env user@server-b:/opt/ghost/
Wichtige Anpassungen in der docker-compose.yml auf Server B:
version: '3.8'
services:
ghost:
image: ghost:5.x.x # Gleiche Version wie auf Server A!
restart: always
ports:
- "127.0.0.1:2368:2368" # Nur lokal binden – NGINX proxyt nach außen
environment:
database__client: mysql
database__connection__host: mysql
database__connection__user: ${MYSQL_USER}
database__connection__password: ${MYSQL_PASSWORD}
database__connection__database: ${MYSQL_DATABASE}
url: https://deine-domain.de # Domain anpassen
mail__transport: SMTP # Mail-Einstellungen ggf. anpassen
volumes:
- ./ghost-content:/var/lib/ghost/content
depends_on:
mysql:
condition: service_healthy
mysql:
image: mysql:8.0
restart: always
environment:
MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD}
MYSQL_DATABASE: ${MYSQL_DATABASE}
MYSQL_USER: ${MYSQL_USER}
MYSQL_PASSWORD: ${MYSQL_PASSWORD}
volumes:
- ./mysql-data:/var/lib/mysql
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
interval: 10s
timeout: 5s
retries: 5
Port-Hinweis: Ghost intern läuft auf Port2368. Binde ihn an127.0.0.1, damit er nicht direkt öffentlich erreichbar ist. NGINX auf dem Host proxyt dann auf diesen Port.
5. Phase 3 – Daten übertragen
5.1 Ghost Content-Verzeichnis übertragen
# Auf Server A – Content direkt auf Server B übertragen
rsync -avz --progress \
/opt/ghost/ghost-content/ \
user@server-b:/opt/ghost/ghost-content/
# Alternativ: Archiv übertragen und entpacken
scp /opt/ghost/backup_ghost_content_*.tar.gz user@server-b:/opt/ghost/
ssh user@server-b "cd /opt/ghost && tar -xzf backup_ghost_content_*.tar.gz"
5.2 Datenbank-Dump übertragen
# Auf Server A
scp /opt/ghost/backup_ghost_db_*.sql user@server-b:/opt/ghost/
5.3 MySQL auf Server B starten (nur den DB-Container)
# Auf Server B – nur MySQL starten, Ghost noch nicht
cd /opt/ghost
docker compose up -d mysql
# Warten bis MySQL bereit ist (ca. 15-30 Sekunden)
docker compose logs -f mysql
# Warten auf: "ready for connections"
5.4 Datenbank-Dump einspielen
# Auf Server B
source /opt/ghost/.env
# SQL-Dump in den Container kopieren
docker compose cp /opt/ghost/backup_ghost_db_*.sql mysql:/tmp/ghost_backup.sql
# Dump einspielen
docker compose exec mysql mysql \
-u"${MYSQL_USER}" \
-p"${MYSQL_PASSWORD}" \
"${MYSQL_DATABASE}" \
< /tmp/ghost_backup.sql
# Erfolg prüfen: Tabellen anzeigen
docker compose exec mysql mysql \
-u"${MYSQL_USER}" \
-p"${MYSQL_PASSWORD}" \
-e "USE ${MYSQL_DATABASE}; SHOW TABLES;"
5.5 Berechtigungen im Content-Verzeichnis setzen
# Auf Server B – Ghost läuft im Container als UID 1000
sudo chown -R 1000:1000 /opt/ghost/ghost-content/
6. Phase 4 – Neue Instanz starten & prüfen
6.1 Ghost-Container starten
# Auf Server B
cd /opt/ghost
docker compose up -d
# Logs beobachten
docker compose logs -f ghost
# Warten auf: "Ghost boot" oder "Listening on: http://..."
6.2 Interne Erreichbarkeit testen
# Auf Server B – direkt gegen Ghost testen (ohne NGINX)
curl -I http://127.0.0.1:2368
# Erwartete Antwort: HTTP/1.1 200 OK oder 301 Redirect
6.3 Admin-Panel über direkten Zugriff prüfen (temporär)
Trage kurzzeitig eine /etc/hosts-Zeile auf deinem lokalen Rechner ein:
# Lokal: /etc/hosts
<IP-Server-B> deine-domain.de
Dann https://deine-domain.de/ghost aufrufen und prüfen:
- Alle Beiträge vorhanden
- Bilder laden korrekt
- Theme aktiv
Danach den Eintrag aus /etc/hosts wieder entfernen.
7. Phase 5 – NGINX auf dem neuen Server konfigurieren
7.1 NGINX-Konfiguration erstellen
# Auf Server B
sudo nano /etc/nginx/sites-available/ghost
Inhalt:
server {
listen 80;
server_name deine-domain.de www.deine-domain.de;
# Weiterleitung zu HTTPS
return 301 https://$host$request_uri;
}
server {
listen 443 ssl http2;
server_name deine-domain.de www.deine-domain.de;
# SSL-Zertifikat (Let's Encrypt oder eigenes)
ssl_certificate /etc/letsencrypt/live/deine-domain.de/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/deine-domain.de/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers HIGH:!aNULL:!MD5;
ssl_prefer_server_ciphers on;
# Sicherheits-Header
add_header X-Frame-Options "SAMEORIGIN";
add_header X-Content-Type-Options "nosniff";
add_header X-XSS-Protection "1; mode=block";
# Client Upload-Größe (für Medien-Uploads)
client_max_body_size 50m;
location / {
proxy_pass http://127.0.0.1:2368;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_buffering off;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
}
7.2 Konfiguration aktivieren und SSL einrichten
# Auf Server B
sudo ln -s /etc/nginx/sites-available/ghost /etc/nginx/sites-enabled/
# Konfiguration testen
sudo nginx -t
# SSL-Zertifikat ausstellen (funktioniert erst nach DNS-Umstellung)
# Alternativ: Zertifikat vom alten Server übertragen (siehe 7.3)
sudo certbot --nginx -d deine-domain.de -d www.deine-domain.de
7.3 Zertifikate vom alten Server übertragen (Alternative)
Falls du das SSL-Zertifikat direkt übertragen möchtest (vor DNS-Umstellung):
# Auf Server A – Zertifikate kopieren
sudo tar -czf /tmp/letsencrypt_backup.tar.gz /etc/letsencrypt/
scp /tmp/letsencrypt_backup.tar.gz user@server-b:/tmp/
# Auf Server B – entpacken
sudo tar -xzf /tmp/letsencrypt_backup.tar.gz -C /
sudo systemctl reload nginx
⚠️ Let's Encrypt-Zertifikate sind an die Domain gebunden, nicht an den Server. Das Übertragen ist legitim und funktioniert problemlos.
7.4 NGINX neu laden
# Auf Server B
sudo systemctl reload nginx
8. Phase 6 – DNS umstellen & Go-Live
8.1 Vorbereitungen
# Auf Server A – Ghost wieder starten (falls noch gestoppt)
cd /opt/ghost
docker compose start ghost
# Auf Server A – aktuellen DNS-TTL prüfen
dig deine-domain.de +short
8.2 DNS-Eintrag umstellen
Ändere den A-Record deiner Domain in deinem DNS-Provider auf die IP von Server B:
deine-domain.de A <IP-Server-B> TTL: 300
www.deine-domain.de A <IP-Server-B> TTL: 300
Setze den TTL vor der Umstellung auf 300 Sekunden (5 Minuten), damit die Propagation schneller erfolgt.
8.3 Propagation beobachten
# Lokal – DNS-Propagation prüfen
watch -n 10 "dig deine-domain.de +short"
# Alternativ: https://dnschecker.org
8.4 Letzten Inhalt sichern und übertragen
Sobald der DNS propagiert ist, aber bevor du Server A abschaltest, synchronisiere ein letztes Mal alle Inhalte, die während der Umstellung entstanden sind:
# Auf Server A – finalen DB-Dump erstellen
source /opt/ghost/.env
docker compose exec mysql mysqldump \
-u"${MYSQL_USER}" \
-p"${MYSQL_PASSWORD}" \
"${MYSQL_DATABASE}" > /opt/ghost/backup_ghost_db_final.sql
# Auf Server A – Ghost stoppen
docker compose stop ghost
# Finalen Dump auf Server B übertragen und einspielen
scp /opt/ghost/backup_ghost_db_final.sql user@server-b:/opt/ghost/
# Auf Server B – finalen Dump einspielen
source /opt/ghost/.env
docker compose stop ghost
docker compose cp /opt/ghost/backup_ghost_db_final.sql mysql:/tmp/ghost_final.sql
docker compose exec mysql mysql \
-u"${MYSQL_USER}" \
-p"${MYSQL_PASSWORD}" \
"${MYSQL_DATABASE}" < /tmp/ghost_final.sql
# Neue Medien-Dateien synchronisieren
rsync -avz user@server-a:/opt/ghost/ghost-content/images/ \
/opt/ghost/ghost-content/images/
# Ghost auf Server B wieder starten
docker compose start ghost
9. Phase 7 – Alten Server abschalten
9.1 Finale Überprüfung
- [ ] Alle Beiträge auf
https://deine-domain.deerreichbar - [ ] Bilder und Medien laden korrekt
- [ ] Admin-Panel (
/ghost) funktioniert - [ ] Neue Beiträge können erstellt werden
- [ ] E-Mail-Versand funktioniert
- [ ] SSL-Zertifikat gültig (
https://ohne Warnung)
9.2 NGINX auf Server A deaktivieren
# Auf Server A
sudo systemctl stop nginx
docker compose down
9.3 Monitoring einrichten (empfohlen)
# Auf Server B – einfaches Monitoring mit cron
(crontab -l 2>/dev/null; echo "*/5 * * * * curl -sf https://deine-domain.de > /dev/null || echo 'Ghost down!' | mail -s 'Alert' dein@email.de") | crontab -
9.4 Alten Server archivieren oder löschen
Behalte Server A noch mindestens 7 Tage aktiv (aber ohne Traffic), bevor du ihn endgültig abschaltest oder löschst.
10. Rollback-Plan
Falls etwas schiefläuft, kannst du schnell zurück:
- DNS zurück auf Server A zeigen (A-Record auf alte IP setzen)
- NGINX auf Server A starten:
sudo systemctl start nginx - Ghost auf Server A starten:
cd /opt/ghost && docker compose start ghost - Propagation abwarten (5–15 Minuten bei TTL 300)
11. Checkliste
Server A (alt)
- [ ] Ghost-Version notiert
- [ ] MySQL-Dump erstellt und geprüft
- [ ] Content-Verzeichnis gesichert
- [ ]
docker-compose.ymlund.envgesichert - [ ] NGINX-Konfiguration gesichert
- [ ] SSL-Zertifikate gesichert
- [ ] Backups lokal heruntergeladen
Server B (neu)
- [ ] Docker + Docker Compose installiert
- [ ] NGINX installiert
- [ ] Verzeichnisstruktur angelegt
- [ ]
docker-compose.ymlangepasst (Version, Port 127.0.0.1:2368) - [ ]
.envübertragen - [ ] MySQL-Dump eingespielt
- [ ] Content-Verzeichnis übertragen
- [ ] Berechtigungen gesetzt (UID 1000)
- [ ] Ghost intern erreichbar (
curl http://127.0.0.1:2368) - [ ] NGINX-Konfiguration erstellt und aktiviert
- [ ] SSL-Zertifikat vorhanden
Go-Live
- [ ] DNS-TTL auf 300 gesetzt
- [ ] A-Record auf Server B umgestellt
- [ ] DNS-Propagation abgewartet
- [ ] Finaler DB-Dump eingespielt
- [ ] Finale Medien-Synchronisation durchgeführt
- [ ] Ghost auf Server A gestoppt
- [ ] Alle Funktionen auf Server B getestet
Erstellt für Ghost 5.x mit Docker Compose und nativem NGINX als Reverse Proxy.