Zum Hauptinhalt springen
S-EDV news
← Alle Anleitungen
📘 Anleitung Server & Netzwerk 14.08.2026 · 11 min Lesezeit

Mastodon mit Docker installieren: Dezentrales Fediverse-Mikroblogging auf eigenem Server

Mastodon ist die meistgenutzte Fediverse-Plattform und ermöglicht eine eigene, werbefreie Twitter/X-Alternative. Diese Anleitung zeigt das vollständige Docker-Compose-Setup mit PostgreSQL, Redis, Puma und Sidekiq.

Mastodon mit Docker installieren: Dezentrales Fediverse-Microblogging auf eigenem Server mit Docker Container, Server, Terminal und Mastodon Benutzeroberfläche. KI-generiert

Mastodon ist das meistgenutzte Open-Source-Netzwerk im Fediverse: über 50.000 GitHub-Stars, aktiv gepflegt und bereit für Organisationen, Vereine, Bildungseinrichtungen oder KMUs, die eine datenschutzkonforme Social-Media-Instanz auf eigener Infrastruktur betreiben wollen. Statt Algorithmen, Werbung und Datenweitergabe an Dritte bekommt man volle Kontrolle – und durch das ActivityPub-Protokoll (W3C-Standard) trotzdem Verbindung zu Millionen Nutzerinnen und Nutzern auf anderen Instanzen (Pixelfed, PeerTube, Misskey u.v.m.). Diese Anleitung zeigt den generischen Docker-Compose-Weg auf einem Linux-Host: plattformneutral, ohne GUI-Abhängigkeit, mit Verifikation nach jedem Schritt.

Voraussetzungen

  1. Docker Engine ≥ 24.x mit integriertem Compose-Plugin v2 (docker compose, nicht docker-compose). Falls noch nicht installiert: Docker und Docker Compose auf Linux installieren.
  2. Linux-Host, VM oder leistungsfähiges NAS – mindestens 2 CPU-Kerne, 2 GB RAM, 20 GB SSD-Speicher. Für mehr als 50 gleichzeitige Nutzer werden 4 GB RAM empfohlen.
  3. Eigene Domain (z. B. mastodon.example.com) mit DNS-A-Eintrag auf den Server. Diese Domain ist nach dem ersten Start nicht mehr änderbar.
  4. Öffentliche IPv4-Adresse und eingehende Ports 80/443 erreichbar.
  5. SMTP-Mailserver oder -Dienst (z. B. Mailgun, Postmark, eigener Postfix) – ohne SMTP keine Account-Verifikation.
  6. Reverse Proxy mit TLS (nginx, Caddy oder Traefik). HTTPS ist Pflicht für ActivityPub-Federation. Caddy eignet sich für Einsteiger gut: Caddy als Reverse Proxy mit automatischem HTTPS einrichten.
  7. Optional für große Instanzen (>100 Nutzer): S3-kompatibler Objektspeicher für Medien-Uploads.

Eckdaten auf einen Blick

ParameterWert
Image (Web + Sidekiq)ghcr.io/mastodon/mastodon:v4.5.11
Image (Streaming)ghcr.io/mastodon/mastodon-streaming:v4.5.11
Aktuelle Stable-Versionv4.5.11 (Juni 2025)
Port Web (Puma/Rails)127.0.0.1:3000:3000 – nur lokal, per Reverse Proxy nach außen
Port Streaming (Node.js/WS)127.0.0.1:4000:4000 – nur lokal, per Reverse Proxy nach außen
Volume Medien./public/system/mastodon/public/system (UID/GID 991)
Volume DatenbankNamed Volume postgres14
Volume RedisNamed Volume redis
Architekturlinux/amd64 und linux/arm64
Mindest-RAM2 GB (4 GB empfohlen ab ~100 Nutzern)

Schritt 1: Projektordner anlegen und Verzeichnisstruktur vorbereiten

Lege einen dedizierten Ordner für die Mastodon-Instanz an. Alle Daten, Konfigurationsdateien und Compose-Definitionen landen hier.

sudo mkdir -p /opt/mastodon/public/system
sudo chown -R 991:991 /opt/mastodon/public/system
cd /opt/mastodon

Der chown-Befehl ist entscheidend: Alle Mastodon-Prozesse laufen als UID/GID 991. Fehlen die Berechtigungen, schlägt der Web-Container beim Schreiben von Medien-Uploads mit Permission denied fehl.

Verifizieren: Prüfe die Eigentümerschaft des Verzeichnisses:

ls -ld /opt/mastodon/public/system
# Erwartete Ausgabe:
# drwxr-xr-x ... 991 991 ... /opt/mastodon/public/system

Schritt 2: Kryptoschlüssel generieren und .env.production anlegen

Mastodon benötigt vor dem ersten Start sechs Schlüssel, die nach der Inbetriebnahme nicht mehr geändert werden dürfen – Änderungen invalidieren alle Sessions, zerstören 2FA-Tokens und machen verschlüsselte Datenbankfelder unlesbar. Starte dafür temporär den Web-Container ohne laufende Datenbank:

# Im Ordner /opt/mastodon
# SECRET_KEY_BASE generieren
docker run --rm ghcr.io/mastodon/mastodon:v4.5.11 bundle exec rails secret

# OTP_SECRET generieren (zweiter Aufruf, anderer Wert!)
docker run --rm ghcr.io/mastodon/mastodon:v4.5.11 bundle exec rails secret

# ACTIVE_RECORD_ENCRYPTION-Schluessel generieren (alle drei auf einmal)
docker run --rm ghcr.io/mastodon/mastodon:v4.5.11 bundle exec rails db:encryption:init

# VAPID-Schluesselpaar generieren
docker run --rm ghcr.io/mastodon/mastodon:v4.5.11 bundle exec rake mastodon:webpush:generate_vapid_key

Notiere alle Ausgaben. Erstelle dann die Konfigurationsdatei /opt/mastodon/.env.production und ersetze alle Platzhalter durch die generierten Werte:

# ===== Instanz-Domain (NICHT aenderbar nach erstem Start!) =====
LOCAL_DOMAIN=mastodon.example.com

# ===== Rails-Umgebung =====
RAILS_ENV=production

# ===== Kryptoschluessel (generierte Werte eintragen) =====
SECRET_KEY_BASE=HIER_GENERIERTEN_WERT_EINTRAGEN
OTP_SECRET=HIER_GENERIERTEN_WERT_EINTRAGEN

# Active Record Encryption (aus bundle exec rails db:encryption:init)
ACTIVE_RECORD_ENCRYPTION_DETERMINISTIC_KEY=HIER_EINTRAGEN
ACTIVE_RECORD_ENCRYPTION_KEY_DERIVATION_SALT=HIER_EINTRAGEN
ACTIVE_RECORD_ENCRYPTION_PRIMARY_KEY=HIER_EINTRAGEN

# VAPID Web-Push-Schluessel
VAPID_PRIVATE_KEY=HIER_EINTRAGEN
VAPID_PUBLIC_KEY=HIER_EINTRAGEN

# ===== Datenbank =====
DB_HOST=db
DB_PORT=5432
DB_NAME=mastodon_production
DB_USER=mastodon
DB_PASS=sicheres_db_passwort_hier_aendern

# ===== Redis =====
REDIS_HOST=redis
REDIS_PORT=6379

# ===== SMTP (Pflicht fuer Account-Verifikation) =====
SMTP_SERVER=smtp.example.com
SMTP_PORT=587
SMTP_LOGIN=
SMTP_PASSWORD=
SMTP_FROM_ADDRESS=notifications@mastodon.example.com

# ===== Optionale Einstellungen =====
S3_ENABLED=false
ES_ENABLED=false

Setze außerdem das PostgreSQL-Passwort als separate Variable für docker compose:

echo "POSTGRES_PASSWORD=sicheres_db_passwort_hier_aendern" > /opt/mastodon/.env

Beide Passwörter (DB_PASS in .env.production und POSTGRES_PASSWORD in .env) müssen identisch sein.

Verifizieren: Stichprobe der gesetzten Werte:

grep -c "HIER_" /opt/mastodon/.env.production
# Muss 0 zurueckgeben – alle Platzhalter muessen ersetzt sein

Schritt 3: compose.yaml erstellen

Lege die Datei /opt/mastodon/compose.yaml an. Das interne Netzwerk internal_network ist mit internal: true isoliert, sodass PostgreSQL und Redis nicht vom Host erreichbar sind. Die Web- und Streaming-Ports werden nur auf 127.0.0.1 gebunden – der Reverse Proxy übernimmt die TLS-Terminierung.

services:
  db:
    image: postgres:14-alpine
    restart: always
    shm_size: 256mb
    networks:
      - internal_network
    healthcheck:
      test: ["CMD", "pg_isready", "-U", "mastodon"]
      interval: 10s
      timeout: 5s
      retries: 5
    volumes:
      - postgres14:/var/lib/postgresql/data
    environment:
      POSTGRES_DB: mastodon_production
      POSTGRES_USER: mastodon
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}

  redis:
    image: redis:7-alpine
    restart: always
    networks:
      - internal_network
    healthcheck:
      test: ["CMD", "redis-cli", "ping"]
      interval: 10s
      timeout: 5s
      retries: 5
    volumes:
      - redis:/data

  web:
    image: ghcr.io/mastodon/mastodon:v4.5.11
    restart: always
    env_file: .env.production
    command: bundle exec puma -C config/puma.rb
    networks:
      - external_network
      - internal_network
    healthcheck:
      test: ["CMD-SHELL", "curl -fs localhost:3000/health || exit 1"]
      interval: 30s
      timeout: 10s
      retries: 3
    ports:
      - "127.0.0.1:3000:3000"
    depends_on:
      db:
        condition: service_healthy
      redis:
        condition: service_healthy
    volumes:
      - system:/mastodon/public/system

  streaming:
    image: ghcr.io/mastodon/mastodon-streaming:v4.5.11
    restart: always
    env_file: .env.production
    command: node ./streaming/index.js
    networks:
      - external_network
      - internal_network
    healthcheck:
      test: ["CMD-SHELL", "curl -fs localhost:4000/api/v1/streaming/health || exit 1"]
      interval: 30s
      timeout: 10s
      retries: 3
    ports:
      - "127.0.0.1:4000:4000"
    depends_on:
      db:
        condition: service_healthy
      redis:
        condition: service_healthy

  sidekiq:
    image: ghcr.io/mastodon/mastodon:v4.5.11
    restart: always
    env_file: .env.production
    command: bundle exec sidekiq
    networks:
      - external_network
      - internal_network
    depends_on:
      db:
        condition: service_healthy
      redis:
        condition: service_healthy
    volumes:
      - system:/mastodon/public/system
    healthcheck:
      test: ["CMD-SHELL", "ps aux | grep '[s]idekiq 6' || exit 1"]
      interval: 30s
      timeout: 10s
      retries: 3

networks:
  external_network:
  internal_network:
    internal: true

volumes:
  postgres14:
  redis:
  system:

Verifizieren: Syntax prüfen, bevor irgendwas gestartet wird:

docker compose -f /opt/mastodon/compose.yaml config --quiet
# Kein Output = keine Syntaxfehler

Schritt 4: Datenbank initialisieren und Instanz einrichten

Starte zunächst nur die Datenbank-Services, damit der Setup-Wizard eine Verbindung aufbauen kann:

cd /opt/mastodon
docker compose up -d db redis

Warte, bis beide Container den Status healthy haben (dauert ca. 15–30 Sekunden), dann führe den interaktiven Setup-Wizard aus:

docker compose run --rm web bundle exec rake mastodon:setup

Der Wizard fragt schrittweise nach Domain, Datenbankverbindung, Redis, SMTP und ob ein Admin-Konto erstellt werden soll. Da alle Werte bereits in .env.production stehen, kannst du die meisten Abfragen mit Enter bestätigen. Am Ende führt der Wizard db:migrate sowie assets:precompile durch. Das dauert je nach Hardware 3–10 Minuten – besonders die Asset-Kompilierung ist RAM-intensiv (mindestens 2 GB RAM notwendig).

Falls du den Admin-Account lieber manuell anlegen möchtest:

docker compose exec web tootctl accounts create meinadmin \
  --email admin@mastodon.example.com \
  --confirmed \
  --role Owner

Verifizieren:

docker compose ps db redis
# BEIDE Container muessen Status "healthy" zeigen:
# NAME       IMAGE               STATUS
# ...db      postgres:14-alpine  Up X minutes (healthy)
# ...redis   redis:7-alpine      Up X minutes (healthy)

docker compose logs db --tail=10
# Letzte Zeile sollte enthalten: "database system is ready to accept connections"

Schritt 5: Alle Container starten

Starte jetzt den vollständigen Stack:

cd /opt/mastodon
docker compose up -d

Alle fünf Container – db, redis, web, streaming und sidekiq – sollten hochfahren. Web und Streaming starten erst, wenn db und redis healthy melden (dank depends_on: condition: service_healthy).

Verifizieren:

docker compose ps
# Alle 5 Container: Status "Up" oder "Up (healthy)"
# NAME                  STATUS
# mastodon-db-1         Up X minutes (healthy)
# mastodon-redis-1      Up X minutes (healthy)
# mastodon-web-1        Up X minutes (healthy)
# mastodon-streaming-1  Up X minutes (healthy)
# mastodon-sidekiq-1    Up X minutes (healthy)

# Web-Endpunkt lokal testen
curl -I http://localhost:3000/health
# HTTP/1.1 200 OK

# Streaming-Endpunkt testen
curl -I http://localhost:4000/api/v1/streaming/health
# HTTP/1.1 200 OK

Zeigen einzelne Container Fehler, schau in die Logs:

docker compose logs web --tail=30
docker compose logs sidekiq --tail=30

Schritt 6: Reverse Proxy und HTTPS einrichten

Mastodon bindet Web (3000) und Streaming (4000) ausschließlich an 127.0.0.1 – ein Reverse Proxy mit TLS-Terminierung ist Pflicht. Ohne HTTPS funktioniert ActivityPub-Federation grundsätzlich nicht. Für eine ausführliche Einrichtung von Caddy mit automatischem Let's-Encrypt-Zertifikat: Caddy als Reverse Proxy mit automatischem HTTPS einrichten. Alternativ eignet sich Traefik als Docker-Reverse-Proxy für komplexere Setups mit mehreren Diensten.

Minimale nginx-Konfiguration für Mastodon (TLS-Block ergänzen):

server {
    listen 443 ssl http2;
    server_name mastodon.example.com;

    location / {
        proxy_pass http://127.0.0.1:3000;
        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;
    }

    location /api/v1/streaming {
        proxy_pass http://127.0.0.1:4000;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
    }
}

Verifizieren: Rufe https://mastodon.example.com im Browser auf. Die Mastodon-Startseite muss erscheinen. Im Browser-Entwicklertools-Tab „Netzwerk" sollte beim Laden der Timeline eine WS-Verbindung zu /api/v1/streaming aufgebaut werden.

curl -I https://mastodon.example.com
# HTTP/2 200

Schritt 7: Updates durchführen

Vor jedem Update die Datenbankmigrationen nicht vergessen – fehlende Migrationen führen zu Missing columns or tables-Fehlern. Für ein automatisches Monitoring von neuen Image-Versionen eignet sich Diun oder WUD als Watchtower-Nachfolger.

cd /opt/mastodon

# 1. Neue Image-Tags in compose.yaml setzen (z. B. v4.5.11 -> v4.6.x)
# 2. Images ziehen
docker compose pull

# 3. Migrationen ausfuehren (VOR dem Restart!)
docker compose run --rm web bundle exec rails db:migrate

# 4. Container neu starten
docker compose up -d

Bei Major-Updates Mastodon-Changelogs auf SKIP_POST_DEPLOYMENT_MIGRATIONS-Hinweise prüfen – bei großen Migrationen wird ein zweistufiges Rollout empfohlen.

Verifizieren:

docker compose ps
# Alle Container: "Up (healthy)"
docker compose logs web --tail=20
# Keine "Missing columns"-Fehler

Schritt 8: Backup einrichten

Eine vollständige Mastodon-Sicherung besteht aus drei Teilen:

# PostgreSQL-Dump
docker compose exec db pg_dump -U mastodon mastodon_production \
  > /backup/mastodon-db-$(date +%F).sql

# Medien-Uploads
rsync -av /opt/mastodon/public/system/ /backup/mastodon-media/

# Konfigurationsdatei (enthaelt alle Secrets!)
cp /opt/mastodon/.env.production /backup/mastodon-env.production.bak

Für automatisierte Backups mit Rotation und Cloud-Sync: MySQL & PostgreSQL Backup automatisieren mit cron. Die .env.production sicher und verschlüsselt aufbewahren – sie enthält alle Schlüssel.

Verifizieren:

ls -lh /backup/mastodon-db-*.sql
# Datei vorhanden, Groesse > 0 Bytes

Troubleshooting / Typische Fehler

  1. could not connect to server: Connection refused (DB): DB_HOST in .env.production muss db sein (Docker-Compose-Service-Name), nicht localhost oder /var/run/postgresql. Prüfen: grep DB_HOST .env.production.
  2. Permission denied @ rb_sysopen oder Read-only file system: Das Verzeichnis ./public/system gehört nicht UID 991. Fix: sudo chown -R 991:991 /opt/mastodon/public/system.
  3. Asset-Kompilierung schlägt fehl (Out-of-Memory): Weniger als 2 GB RAM. Swap-Space einrichten: fallocate -l 2G /swapfile && chmod 600 /swapfile && mkswap /swapfile && swapon /swapfile.
  4. Keine Echtzeit-Updates im Browser (Timeline friert ein): Streaming-Container abgestürzt oder Reverse Proxy leitet /api/v1/streaming nicht als WebSocket weiter. Prüfen: docker compose logs streaming --tail=30 und Proxy-Konfiguration auf Upgrade-Header.
  5. Keine Federation mit anderen Instanzen: Häufigste Ursachen: (1) Kein gültiges HTTPS-Zertifikat, (2) LOCAL_DOMAIN stimmt nicht mit der tatsächlichen erreichbaren Domain überein, (3) Port 443 nicht von außen erreichbar, (4) Sidekiq-Container läuft nicht (Federation läuft asynchron über Sidekiq). Prüfen: docker compose ps sidekiq.
  6. Failed to release advisory lock bei Migrationen: PgBouncer-Konflikt. DB_PORT=5432 explizit in .env.production setzen und sicherstellen, dass Migrationen direkt gegen PostgreSQL laufen.
  7. Missing columns or tables nach Update: Datenbankmigrationen wurden nicht ausgeführt. Vor dem nächsten docker compose up -d immer: docker compose run --rm web bundle exec rails db:migrate.
  8. Instanz nicht erreichbar nach LOCAL_DOMAIN-Änderung: Alle Nutzer-IDs, Medien-URLs und Federationsbeziehungen sind ungültig. LOCAL_DOMAIN muss vor dem allerersten Start korrekt gesetzt und danach nie geändert werden.

Häufige Fragen

Kann ich Mastodon auf ARM64 (Raspberry Pi, Apple Silicon) betreiben?

Ja. Die offiziellen Images auf ghcr.io unterstützen sowohl linux/amd64 als auch linux/arm64. Auf einem Raspberry Pi 4 mit 4 GB RAM und SSD läuft eine kleine Instanz mit weniger als 20 Nutzern problemlos.

Warum darf ich nicht das alte tootsuite/mastodon-Image von Docker Hub verwenden?

Das tootsuite/mastodon-Image auf Docker Hub ist seit v4.x nicht mehr offiziell gepflegt und erhält keine Security-Updates. Die aktuellen Images sind ausschließlich auf der GitHub Container Registry verfügbar: ghcr.io/mastodon/mastodon und ghcr.io/mastodon/mastodon-streaming.

Wie erstelle ich nach dem ersten Start einen weiteren Admin-Account?

Mit dem integrierten Verwaltungswerkzeug tootctl, das im Web-Container enthalten ist:

docker compose exec web tootctl accounts create BENUTZERNAME \
  --email admin@mastodon.example.com \
  --confirmed \
  --role Owner

Ab wann sollte ich auf S3-Objektspeicher umstellen?

Ab etwa 100 aktiven Nutzern wächst das Medien-Volume (public/system) schnell auf viele Gigabyte an. S3-kompatibler Objektspeicher (AWS S3, Hetzner Object Storage, MinIO, Wasabi) entlastet den Server erheblich. Für ein MinIO-Setup auf eigenem Server: MinIO S3 selbst hosten mit Docker. In .env.production genügt dann S3_ENABLED=true plus Zugangsdaten.

Kann Mastodon mit anderen Fediverse-Plattformen kommunizieren?

Ja – das ist der Kerngedanke von ActivityPub. Mastodon-Nutzer können Accounts auf Pixelfed (Bilder), PeerTube (Videos), Misskey, Pleroma/Akkoma oder Friendica folgen und mit ihnen interagieren, sofern die eigene Instanz über HTTPS erreichbar ist und die Remote-Instanz nicht blockiert wurde.

Wie aktiviere ich die Volltextsuche?

Mastodon unterstützt optional Elasticsearch (oder den Fork OpenSearch) für die Suche über eigene Posts und favorisierte Beiträge. Einen separaten Elasticsearch-Container in compose.yaml ergänzen und ES_ENABLED=true sowie ES_HOST=elasticsearch in .env.production setzen. Danach Index aufbauen: docker compose run --rm web tootctl search deploy.

Fazit

Mastodon ist technisch anspruchsvoller als typische Single-Container-Anwendungen – fünf Services, strenge Secret-Generierung, ein unveränderlicher Domain-Name und ein zwingend notwendiger Reverse Proxy mit TLS. Der Aufwand lohnt sich für Organisationen, Vereine und Communities, die eine vollständig selbst kontrollierte, datenschutzkonforme Social-Media-Plattform betreiben wollen. Wer die initiale Konfiguration sauber aufgesetzt hat, profitiert von aktiver Weiterentwicklung, regelmäßigen Security-Releases und der Verbindung zu Millionen Fediverse-Nutzern weltweit. Für eine robuste Produktivumgebung sollte man neben dem Stack selbst auch den Backup-Rhythmus (täglicher DB-Dump + Medien-Sync) und ein Monitoring einplanen, das alle fünf Container überwacht.

Weiterführende Anleitungen und Quellen

  1. Docker und Docker Compose auf Linux installieren – Grundlage für alle Docker-Setups
  2. Caddy als Reverse Proxy mit automatischem HTTPS einrichten – einfachster Weg zu TLS
  3. MinIO S3 selbst hosten mit Docker – für Medien-Uploads bei wachsenden Instanzen
  4. MySQL & PostgreSQL Backup automatisieren mit cron – Datenbankbackup mit Rotation
  5. Traefik als Docker-Reverse-Proxy mit automatischem HTTPS
  6. NetBird mit Docker: Zero-Trust-Netzwerk – sicherer Zugriff auf die Admin-Oberfläche

Quellen: Mastodon Offizielle Dokumentation – Admin-Konfiguration | mastodon/mastodon – Offizielles GitHub-Repository (docker-compose.yml) | Mastodon Troubleshooting – Offizielle Dokumentation