Homelab & Server

Mastodon von nativ zu Docker umziehen – Schritt für Schritt

Mastodon nativ → Docker mit WireGuard-Tunnel

Wer eine Mastodon-Instanz nativ auf einem VPS betreibt, steht irgendwann vor der Frage: Umzug zu Docker? Dieser Artikel beschreibt den vollständigen Migrationsprozess – von der Vorbereitung über den Datenbankexport bis zum DNS-Cutover – ohne Datenverlust und mit einem klaren Rollback-Plan.

Ich betreibe meine Mastodon-Instanz seit Längerem nativ auf einem VPS. Die Idee, sie auf einen lokalen Rechner zu verlagern und dabei auf Docker zu setzen, lag schon länger auf dem Tisch. Der Vorteil: mehr Kontrolle, günstigere Hardware und die Möglichkeit, alles lokal zu betreiben – ohne dabei auf einen stabilen öffentlichen Zugang verzichten zu müssen.

Der Plan: Der lokale Rechner läuft mit Docker-Mastodon, ist per WireGuard-Tunnel mit einem günstigen VPS verbunden, der lediglich als Traffic-Forwarder dient. TLS-Terminierung und nginx laufen auf dem VPS, der Tunnel transportiert den entschlüsselten Traffic sicher zum lokalen Rechner.

Medien-Dateien lagen bei mir bereits auf Backblaze B2 – das erspart die aufwendigste Aufgabe bei einer solchen Migration komplett.

Voraussetzungen

  • Mastodon läuft nativ auf einem VPS
  • Medien liegen auf Backblaze B2 (kein Dateitransfer nötig)
  • Ein lokaler Rechner mit Linux, auf dem Docker laufen soll
  • Ein VPS (kann der bestehende oder ein separater sein) für WireGuard und nginx
  • Domain bei Cloudflare (für DNS-Challenge-Zertifikate)

Phase 1: Vorbereitung

DNS-TTL senken

Zuerst die TTL für die Mastodon-Domain bei Cloudflare auf 300 Sekunden reduzieren. Das sorgt dafür, dass der spätere DNS-Wechsel schnell propagiert. Mindestens 24 Stunden vor dem Umzug erledigen, damit die alte TTL überall abgelaufen ist.

WireGuard auf dem lokalen Rechner einrichten

sudo apt install wireguard

# Schlüsselpaar erzeugen
wg genkey | tee /etc/wireguard/privatekey | wg pubkey > /etc/wireguard/publickey
cat /etc/wireguard/publickey  # diesen Public Key zum VPS übertragen

Konfiguration in /etc/wireguard/wg0.conf:

[Interface]
PrivateKey = <lokaler-private-key>
Address = 10.0.0.2/24

[Peer]
PublicKey = <vps-public-key>
Endpoint = <vps-öffentliche-ip>:51820
AllowedIPs = 10.0.0.1/32
PersistentKeepalive = 25
sudo systemctl enable wg-quick@wg0
sudo systemctl start wg-quick@wg0

# Tunnel testen
ping 10.0.0.1

Auf dem VPS den lokalen Rechner als neuen Peer in die bestehende WireGuard-Konfiguration eintragen:

sudo wg set wg0 peer <lokaler-public-key> allowed-ips 10.0.0.2/32

Und persistent in /etc/wireguard/wg0.conf ergänzen:

[Peer]
PublicKey = <lokaler-public-key>
AllowedIPs = 10.0.0.2/32

TLS-Zertifikat auf dem VPS

Die Mastodon-Domain zum bestehenden Cloudflare-DNS-Challenge-Setup hinzufügen (certbot oder acme.sh – je nach vorhandener Konfiguration):

sudo certbot certonly \
  --dns-cloudflare \
  --dns-cloudflare-credentials /etc/letsencrypt/cloudflare.ini \
  -d mastodon.meinedomain.de

Der Vorteil der DNS-Challenge: Die Zertifikatserneuerung funktioniert unabhängig davon, ob Port 80 gerade erreichbar ist – ideal für einen Migrationszeitraum.

nginx auf dem VPS konfigurieren

server {
    listen 80;
    server_name mastodon.meinedomain.de;
    return 301 https://$host$request_uri;
}

server {
    listen 443 ssl http2;
    server_name mastodon.meinedomain.de;

    ssl_certificate     /etc/letsencrypt/live/mastodon.meinedomain.de/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/mastodon.meinedomain.de/privkey.pem;

    location / {
        proxy_pass http://10.0.0.2:80;
        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 https;
        proxy_read_timeout 90;
    }

    # WebSocket für Streaming
    location /api/v1/streaming {
        proxy_pass http://10.0.0.2:4000;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
        proxy_set_header Host $host;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto https;
    }
}
sudo nginx -t && sudo systemctl reload nginx

DNS noch nicht umstellen – erst wenn alles getestet ist.

Docker Mastodon auf dem lokalen Rechner vorbereiten

sudo apt install docker.io docker-compose-plugin
sudo usermod -aG docker $USER

cd /opt
sudo git clone https://github.com/mastodon/mastodon.git
cd mastodon
# Gleiche Version wie auf dem Live-Server auschecken
sudo git checkout v4.x.x

.env.production vorbereiten – Achtung: Setup-Wizard überspringen

Hier lauert eine Falle: Wer docker compose run --rm web bundle exec rake mastodon:setup ausführt, bekommt einen interaktiven Assistenten, der die generierten Konfigurationswerte auf dem Bildschirm ausgibt – aber nicht in die .env.production schreibt. Das muss man von Hand übertragen.

Da wir eine bestehende Instanz migrieren, überspringen wir den Wizard vollständig und kopieren die vorhandene Datei direkt vom Live-Server:

scp mastodon@vps-hostname:/home/mastodon/live/.env.production /opt/mastodon/.env.production

Folgende Werte für Docker anpassen:

DB_HOST=db
REDIS_HOST=redis
RAILS_ENV=production

Die S3/Backblaze-Konfiguration bleibt unverändert. Wichtig: SECRET_KEY_BASE, OTP_SECRET und die VAPID_*-Schlüssel dürfen nicht verändert werden – sonst werden alle bestehenden Sessions ungültig und Push-Benachrichtigungen funktionieren nicht mehr.


Phase 2: Migrationstag

Wartungsmodus aktivieren

# Auf dem Live-VPS
sudo systemctl stop mastodon-sidekiq
sudo systemctl stop mastodon-streaming
sudo systemctl stop mastodon-web

Sidekiq-Warteschlangen prüfen

Vor dem Stopp von Sidekiq sicherstellen, dass keine Jobs mehr in der Warteschlange sind:

cd /home/mastodon/live
sudo -u mastodon RAILS_ENV=production bundle exec rails runner \
  "puts Sidekiq::Queue.all.map{|q| \"#{q.name}: #{q.size}\"}.join(\"\n\")"

Datenbank-Dump erstellen

sudo -u postgres pg_dump -Fc mastodon_production > /tmp/mastodon_$(date +%Y%m%d).dump
ls -lh /tmp/mastodon_*.dump

Bei einer 10-GB-Datenbank dauert das etwa 5 Minuten.

Dump übertragen

Vom lokalen Rechner aus:

scp user@vps-hostname:/tmp/mastodon_$(date +%Y%m%d).dump /opt/mastodon/

Nur den Datenbankcontainer starten

Hier liegt eine zweite Falle: Startet man alle Container auf einmal mit docker compose up -d, versucht der Web-Container beim ersten Start automatisch, die Datenbank anzulegen – die aber bereits durch den Dump-Import existiert. Das führt zu Fehlern oder schlimmstenfalls zu einem leeren Schema über den wiederhergestellten Daten.

Deshalb zuerst nur den db-Container starten, den Dump einspielen und erst danach die restlichen Services hochfahren:

cd /opt/mastodon
docker compose up -d db

# Kurz warten, dann Datenbankuser und Datenbank anlegen
docker compose exec db psql -U postgres -c "CREATE USER mastodon WITH PASSWORD 'passwort';"
docker compose exec db psql -U postgres -c "CREATE DATABASE mastodon_production OWNER mastodon;"
docker compose exec db psql -U postgres -c "GRANT ALL PRIVILEGES ON DATABASE mastodon_production TO mastodon;"

Das Passwort muss mit DB_PASS in der .env.production übereinstimmen.

Datenbank wiederherstellen

docker cp /opt/mastodon/mastodon_*.dump mastodon-db-1:/tmp/mastodon.dump

docker compose exec db pg_restore \
  -U mastodon \
  -d mastodon_production \
  --no-owner \
  --role=mastodon \
  /tmp/mastodon.dump

Warnungen zu fehlenden Rollen sind normal. Bei 10 GB Datenbankgröße dauert der Vorgang etwa 10–15 Minuten.

Kurze Plausibilitätsprüfung:

docker compose exec db psql -U mastodon -d mastodon_production \
  -c "SELECT COUNT(*) FROM accounts;"

Alle Services starten

docker compose up -d

docker compose ps
docker compose logs --tail=50 web
docker compose logs --tail=50 sidekiq

Lokal testen vor dem DNS-Cutover

Temporären Hosts-Eintrag setzen:

127.0.0.1 mastodon.meinedomain.de

Im Browser prüfen: Login, Timeline, Medien aus Backblaze, Einstellungen. Danach den Eintrag wieder entfernen.

DNS umstellen

Bei Cloudflare den A-Record auf die öffentliche IP des VPS zeigen lassen (oder bestätigen, dass er bereits korrekt ist). Nach spätestens 5 Minuten (TTL = 300 s) propagiert:

watch dig +short mastodon.meinedomain.de

Betrieb prüfen

  • Anmeldung funktioniert
  • Eingehende Toots von anderen Instanzen erscheinen
  • Eigene Posts federierten korrekt
  • Sidekiq-Logs im Auge behalten:
docker compose logs -f sidekiq

Phase 3: Aufräumen

Nach einigen Tagen stabilem Betrieb:

  • Dump-Dateien von beiden Rechnern löschen
  • Native Mastodon-Dienste auf dem alten VPS deaktivieren:
sudo systemctl disable mastodon-web mastodon-sidekiq mastodon-streaming
  • DNS-TTL wieder auf einen normalen Wert erhöhen (3600 oder höher)
  • Alten nginx-Server-Block vom VPS entfernen, falls Mastodon vorher direkt dort lief

Rollback-Plan

Falls nach dem DNS-Cutover etwas schiefläuft:

  1. DNS zurück auf die alte VPS-IP zeigen
  2. Dienste auf dem alten Server wieder starten:
sudo systemctl start mastodon-web mastodon-sidekiq mastodon-streaming

Da der alte Server nur in den Wartungsmodus versetzt und nicht gelöscht wurde, ist ein Rollback innerhalb von Minuten möglich. Deshalb: Den alten Server erst abschalten, wenn die neue Instanz mehrere Tage stabil läuft.


Share