No description
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
2026-09-02 19:47:24 +00:00
volumes feat(mastodon): add initial Mastodon docker stack 2026-08-11 19:49:16 +02:00
.env feat(mastodon): add initial Mastodon docker stack 2026-08-11 19:49:16 +02:00
.gitignore feat(mastodon): add initial Mastodon docker stack 2026-08-11 19:49:16 +02:00
AGENTS.md feat(mastodon): add initial Mastodon docker stack 2026-08-11 19:49:16 +02:00
CLAUDE.md feat(mastodon): add initial Mastodon docker stack 2026-08-11 19:49:16 +02:00
docker-compose.yml feat(mastodon): add initial Mastodon docker stack 2026-08-11 19:49:16 +02:00
LICENSE chore(license): add AGPL-3.0-or-later license and copyright notice 2026-09-02 21:32:21 +02:00
Projekt.md feat(mastodon): add initial Mastodon docker stack 2026-08-11 19:49:16 +02:00
README.md chore(license): add AGPL-3.0-or-later license and copyright notice 2026-09-02 21:32:21 +02:00

Mastodon – WSC Standard Stack

Selbst gehosteter Mastodon-Server (Fediverse/ActivityPub) auf Basis der offiziellen ghcr.io/mastodon/mastodon- und ghcr.io/mastodon/mastodon-streaming-Images, mit PostgreSQL 17, Elasticsearch (Volltextsuche) und Redis. Für den persönlichen Gebrauch schlank dimensioniert – Details siehe Projekt.md.

Template-Hinweis: .env enthält nur Platzhalter (CHANGE_ME, example.*). Vor dem Einsatz anpassen, Secrets gemäß Anleitung unten erzeugen. Werte, die je in Git lagen, gelten als kompromittiert → rotieren.


1. Voraussetzungen

  • Docker + Docker Compose v2
  • Externes Netz traefik_proxy_network (Traefik-Stack)
  • Zentrale Stacks für die Integrationen: docker-autoheal-neustart, docker-ofelia-cronjobs, docker-docker-backup-backups
  • Backup-Verzeichnis ${BACKUP_PATH} (Default /Daten/Backups) muss auf dem Host existieren
  • Die Volume-Ordner unter ./volumes/ existieren bereits nach git clone/pull (leer bis auf .gitkeep) – der local-Volume-Treiber (o: bind, type: none) legt fehlende Zielverzeichnisse NICHT selbst an, ein frischer Deploy würde sonst beim ersten docker compose up mit „no such file or directory" abbrechen
  • Ein SMTP-Zugang für den Mail-Versand (Registrierungsbestätigung, Freigabe-Benachrichtigung, Passwort-Reset)

2. .env vorbereiten

2.1 Domain

HOSTRULE und PRIMARY_DOMAIN auf die eigene Domain setzen. Wichtig: PRIMARY_DOMAIN wird als LOCAL_DOMAIN an Mastodon durchgereicht und kann nach dem ersten Produktivstart nicht mehr sicher geändert werden (betrifft Federation und alle Account-Handles wie @user@social.example.com) – vorher final festlegen.

2.2 Passwörter (Postgres, Redis, Elasticsearch)

openssl rand -hex 32

Für DB_PASSWORD, REDIS_PASSWORD, ES_PASSWORD je einmal ausführen und in die .env eintragen.

2.3 Mastodon-Secrets

Diese drei Secret-Gruppen nicht selbst erfinden, sondern mit den offiziellen Mastodon-Bordmitteln erzeugen. MASTODON_VERSION aus der .env übernehmen (Platzhalter unten: v4.6.5 – ggf. an den aktuell in der .env gesetzten Wert anpassen):

# SECRET_KEY_BASE
docker run --rm ghcr.io/mastodon/mastodon:v4.6.5 bundle exec rails secret

# VAPID_PRIVATE_KEY / VAPID_PUBLIC_KEY (Web Push)
docker run --rm ghcr.io/mastodon/mastodon:v4.6.5 \
  bundle exec rails mastodon:webpush:generate_vapid_key

# ACTIVE_RECORD_ENCRYPTION_DETERMINISTIC_KEY / _KEY_DERIVATION_SALT / _PRIMARY_KEY
docker run --rm ghcr.io/mastodon/mastodon:v4.6.5 bin/rails db:encryption:init

Jeweiligen Output in die passenden .env-Variablen eintragen.

⚠️ ACTIVE_RECORD_ENCRYPTION_* nach dem ersten Produktivstart nie mehr ändern – sonst sind verschlüsselte Bestandsdaten (u. a. 2FA-Secrets) nicht mehr lesbar.

2.4 SMTP

SMTP_SERVER, SMTP_PORT, SMTP_LOGIN, SMTP_PASSWORD, SMTP_FROM_ADDRESS eintragen. Ohne funktionierendes SMTP können sich neue Nutzer nicht bestätigen und du bekommst keine Freigabe-Benachrichtigungen (siehe Abschnitt 5).


3. Starten

Wichtig: docker compose immer aus diesem Stack-Verzeichnis starten (${PWD}-Mounts).

3.1 Volume-Berechtigungen setzen (einmalig, VOR dem ersten Start!)

Docker legt ./volumes/<name> beim allerersten docker compose up automatisch an – und zwar als root:root. postgres und redis fixen das selbst (ihr Entrypoint startet als root, chownt sein Datenverzeichnis und wechselt danach per gosu zum eigentlichen Prozess-User) – elasticsearch und das offizielle Mastodon-Image (web/sidekiq) tun das NICHT, sie laufen von Anfang an fest als nicht-root-User und brechen sonst beim Start ab (siehe Abschnitt 7, „Volume-Berechtigungen"). Deshalb diese beiden vorher chownen:

mkdir -p volumes/postgres volumes/redis volumes/elasticsearch volumes/public-system

sudo chown -R 1000:0  volumes/elasticsearch    # docker.elastic.co/elasticsearch/elasticsearch
sudo chown -R 991:991 volumes/public-system    # ghcr.io/mastodon/mastodon (web/sidekiq)
# volumes/postgres und volumes/redis NICHT manuell chownen - die offiziellen
# Postgres-/Redis-Images korrigieren die Rechte beim Start selbst.

3.2 Stack starten

# Basis-Dienste zuerst hochfahren (Datenbank muss für den Setup-Schritt bereitstehen)
docker compose up -d db redis elasticsearch

# Datenbank-Schema einmalig anlegen (nur beim allerersten Start, NICHT bei Updates!)
docker compose run --rm web bundle exec rails db:setup

# Restlichen Stack starten
docker compose up -d

4. Ersten Admin-Account anlegen

docker compose exec web bin/tootctl accounts create \
  DEIN_BENUTZERNAME \
  --email deine@email.de \
  --confirmed \
  --role Owner

Gibt ein zufälliges Passwort in der Konsolenausgabe aus (danach im Profil ändern).


5. Registrierung auf „Freigabe erforderlich" stellen

Der Registrierungsmodus ist eine Laufzeit-Einstellung in der Datenbank, keine .env-Variable – deshalb einmalig nach dem Erststart per tootctl setzen:

docker compose exec web bin/tootctl settings registrations approved

Damit sind öffentliche Anmeldungen sichtbar, neue Konten werden aber erst nach deiner manuellen Freigabe im Admin-Bereich (/admin/pending_accounts) aktiv. Mastodon schaltet offene Registrierungen zusätzlich automatisch auf „Freigabe erforderlich" zurück, sobald eine Woche lang keine moderierende Person aktiv war (DISABLE_AUTOMATIC_SWITCHING_TO_ APPROVED_REGISTRATIONS=true in der .env setzen, falls unerwünscht).

Um die Registrierung ganz zu schließen (nur du selbst als Nutzer):

docker compose exec web bin/tootctl settings registrations none

6. Integrationen

Autoheal (Self-Healing)

autoheal=true auf db, redis, elasticsearch, web, streaming, sidekiq (alle mit Healthcheck).

⚠️ Der web-Healthcheck prüft http://localhost:3000/health. Liefert die App dauerhaft Fehler (z. B. db:setup vergessen), kann Autoheal den Container in eine Restart-Schleife bringen → beim ersten Setup beobachten.

CronJobs (Ofelia) – Schedule ist 6-Feld (mit Sekunden!)

Auf web (als ${CRONJOB_USER}, Standard mastodon): zwischengespeicherte Remote-Medien entfernen (media remove --days=${MEDIA_RETENTION_DAYS}), verwaiste Medien-Anhänge (media remove-orphans), alte Link-Vorschaubilder (preview_cards remove), wöchentlich verwaiste Remote-Account-Daten (accounts cull).

DB-Backup (shyim/docker-backup) – Schedule 5-Feld

Label auf db: type=postgres, BACKUP_SCHEDULE (Default alle 6 h), Retention BACKUP_RETENTION. Landet unter /Daten/Backups/<db-container>/db/….

Datei-Backup (Ofelia + backup-helper)

Da shyim nur named volumes sichert (Medien sind bind-backed), läuft das Datei-Backup über den idle backup-helper: Ofelia job-exec packt täglich 05:00 ein tar.gz von /mastodon/public/system nach ${BACKUP_PATH}/<projekt>/files/ (Retention 5 Tage).


7. Hinweise / Stolpersteine

  • Volume-Berechtigungen (UID/GID) beim allerersten Start – live gegentestet: Docker legt ./volumes/<name> beim ersten docker compose up als root:root an.
    • db (postgres:17-alpine) und redis (redis:7-alpine) fixen das selbst: ihr Entrypoint startet als root, chownt sein Datenverzeichnis und wechselt danach per gosu zum eigentlichen Prozess-User – kein manuelles chown nötig.
    • elasticsearch (docker.elastic.co/elasticsearch/elasticsearch) läuft von Anfang an fest als 1000:0 ohne Selbstkorrektur. Ohne vorherigen chown bricht der Start mit failed to obtain node locks ... maybe these locations are not writable ab (java.io.IOException, siehe docker compose logs elasticsearch).
    • web/sidekiq (ghcr.io/mastodon/mastodon) laufen ebenfalls fest als 991:991 ohne Selbstkorrektur (nur USER mastodon im Image, kein chown-Entrypoint) – betrifft den public-system-Mount (Medien). Live aufgetretenes Symptom: Login und normale Nutzung funktionieren, aber ein Bild-Upload schlägt mit HTTP 500 fehl, weil web/sidekiq nicht in /mastodon/public/system schreiben können. Check: docker compose exec web id (erwartet uid=991) gegen ls -ln volumes/public-system (Owner-UID) vergleichen; bei Mismatch sudo chown -R 991:991 volumes/public-system und Stack neu starten.
    • Reihenfolge/Befehle siehe Abschnitt 3.1. Falls trotzdem vergessen: erst docker compose down, betroffenes volumes/<name> komplett neu anlegen (nicht nur leeren – ein abgebrochener Init hinterlässt oft Reste) und chownen, dann neu starten.
    • Postgres-Sonderfall, live aufgetreten: Nach mehreren abgebrochenen Start­ versuchen blieb im volumes/postgres-Verzeichnis eine versteckte (Punkt-)Datei zurück → initdb: error: directory ... exists but is not empty / contains a dot-prefixed/invisible file. rm -rf volumes/postgres/* reicht dafür nicht, weil die Shell-Glob * keine Dot-Dateien erfasst – das Verzeichnis muss komplett gelöscht und neu angelegt werden (rm -rf volumes/postgres && mkdir -p volumes/postgres).
  • Ofelia vs. shyim Cron-Format: Ofelia = 6-Feld (0 5 0 * * *), shyim = 5-Feld (5 0 * * *). Nicht verwechseln.
  • Redis: nur eine Instanz für Cache + Sidekiq-Queue + Streaming – maxmemory-policy noeviction ist Pflicht, sonst gehen unter Speicherdruck stillschweigend Hintergrund-Jobs verloren.
  • Elasticsearch: xpack.security ist deaktiviert (wie in der offiziellen Mastodon-Vorlage), der Service ist rein intern (kein Traefik-Label). ES_PASSWORD wird aktuell nicht erzwungen, ist aber für eine spätere Härtung schon vorbereitet.
  • Streaming-Routing: Traefik-Router für streaming hat priority=100 (fest gesetzt), der web-Router priority=1 – so geht /api/v1/streaming immer an streaming, alles andere an web. Bei eigenen Anpassungen an den Traefik-Labels diese Priorität beibehalten.
  • LOCAL_DOMAIN/PRIMARY_DOMAIN vor dem ersten Produktivstart final festlegen (siehe Abschnitt 2.1).
  • Updates: bei einem neuen MASTODON_VERSION immer zuerst den offiziellen Changelog auf Breaking Changes/Migrationen prüfen, dann docker compose pull && docker compose run --rm web bundle exec rails db:migrate && docker compose up -d.

8. Nützliche Befehle

docker compose config -q          # Konfiguration validieren
docker compose up -d
docker compose logs -f web
docker compose exec web bin/tootctl <command>
docker compose down

9. Mastodon-Befehle (Auswahl)

# Ausstehende Registrierungen anzeigen / freigeben
docker compose exec web bin/tootctl accounts approve --number 1
docker compose exec web bin/tootctl accounts approve --all

# Speicherplatz-Nutzung prüfen
docker compose exec web bin/tootctl media usage

# Passwort eines Accounts zurücksetzen
docker compose exec web bin/tootctl accounts modify BENUTZERNAME --reset-password

10. Weiterführende Doku

https://docs.joinmastodon.org/admin/config/ https://docs.joinmastodon.org/admin/tootctl/ https://docs.joinmastodon.org/admin/upgrading/

Lizenz

Copyright (C) 2026 Christian Säum – web-seo-consulting.eu

Dieses Projekt steht unter der GNU Affero General Public License, Version 3 oder (nach deiner Wahl) einer späteren Version (AGPL-3.0-or-later). Der vollständige Lizenztext steht in der Datei LICENSE.

Du darfst die Software nutzen, weitergeben und – auch kommerziell – verkaufen. Gibst du eine veränderte Fassung weiter oder betreibst du sie über ein Netzwerk (z. B. als Dienst), muss deren vollständiger Quellcode ebenfalls unter der AGPL-3.0-or-later verfügbar sein. Der Copyright-Hinweis und die Nennung des ursprünglichen Autors dürfen nicht entfernt werden.

Diese Angabe betrifft nur die in diesem Repository enthaltenen eigenen Dateien (Konfiguration, Skripte, Anpassungen). Eingebundene Fremdsoftware und Container-Images unterliegen weiterhin ihren eigenen Lizenzen.