Konfiguration
Die Datei docker-compose.yml steuert, welche Container FediSuite startet, wie sie miteinander reden, wo Daten gespeichert werden und was passiert, wenn ein Container abstürzt. Diese Seite erklärt die mitgelieferte Datei Service für Service.
Auf dieser Seite
Was ist die docker-compose.yml?
Die docker-compose.yml beschreibt, welche Container gestartet werden, wie sie sich gegenseitig erreichen, wo sie ihre Daten ablegen und in welcher Reihenfolge sie starten. Docker Compose liest die Datei und setzt den Stack um, ein Container ist dabei ein Programm in einer isolierten Umgebung mit eigenem Dateisystem und eigenem Netzwerk.
Für einen einfachen Betrieb musst du die docker-compose.yml nicht anfassen. Domain, Passwörter und E-Mail-Konfiguration gehören in die .env-Datei. Anpassen musst du die Compose-Datei nur, wenn du die Worker verändern oder einen Reverse Proxy einbinden willst.
Eigene Änderungen am besten in eine docker-compose.override.yml auslagern. Docker Compose lädt sie automatisch zusätzlich, und das Self-Hosting-Repository führt sie in der .gitignore. So bleibt die mitgelieferte Datei unverändert und lässt sich mit git pull aktualisieren.
Überblick: die vier Services
Die mitgelieferte Compose-Datei startet vier Container. Jeder hat eine klar abgegrenzte Aufgabe:
db
postgres:15-alpine
Die Datenbank. Speichert Nutzer*innen, Beiträge, Entwürfe, Konto-Verknüpfungen, Einstellungen und Verlaufsdaten dauerhaft.
app
christinloehner/fedisuite:latest
Stellt die Web-Oberfläche und die API bereit. Führt bei jedem Start zuerst node server/init-db.js aus (Schema, Migrationen, Admin-Account) und startet danach den Server.
worker1
christinloehner/fedisuite:latest
Hintergrundjobs: veröffentlicht geplante Beiträge (Scheduler), aktualisiert Beitragszahlen und Reichweiten, versendet die Erinnerung an Nutzer*innen ohne verbundenes Konto und erzeugt Tipps. Als einziger Container mit aktivem Scheduler.
worker2
christinloehner/fedisuite:latest
Zusätzlicher Worker: aktualisiert Beitragszahlen und erzeugt Tipps. Scheduler und Erinnerung sind hier abgeschaltet. Weitere Worker kannst du als Kopie dieses Blocks ergänzen.
Service: db (PostgreSQL)
db:
image: postgres:15-alpine
env_file:
- .env
restart: unless-stopped
volumes:
- ./postgres:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U $${POSTGRES_USER} -d $${POSTGRES_DB}"]
interval: 5s
timeout: 5s
retries: 5
image: postgres:15-alpine
env_file: .env
POSTGRES_DB, POSTGRES_USER und POSTGRES_PASSWORD aus deiner .env. Der Benutzer ist zugleich Superuser der Datenbank. Die Werte werden nur beim ersten Anlegen des Datenverzeichnisses ausgewertet: Ein späteres Ändern von POSTGRES_PASSWORD in der .env ändert das Passwort in einer bestehenden Datenbank nicht.restart: unless-stopped
docker compose stop angehalten.volumes: ./postgres:/var/lib/postgresql/data
Der Ordner ./postgres auf deinem Server wird mit dem Datenverzeichnis innerhalb des Containers verknüpft (Bind Mount). Ohne dieses Volume wären alle Daten weg, sobald der Container neu erstellt wird, zum Beispiel bei einem Update.
./postgres/ entsteht beim ersten Start neben der docker-compose.yml. Lösche es niemals, es enthält alle deine Daten. Die .gitignore des Repositories führt es nicht auf (dort steht postgres_data/), committe es also nicht versehentlich.
healthcheck
pg_isready auf. Schlägt der Check fünfmal hintereinander fehl, gilt der Container als unhealthy. Die App und die Worker starten erst, wenn die Datenbank healthy meldet, damit sie nicht auf eine Datenbank zugreifen, die noch hochfährt.Service: app
app:
image: ${FEDISUITE_IMAGE:-christinloehner/fedisuite:latest}
pull_policy: always
env_file:
- .env
environment:
- ENABLE_SCHEDULER=false
- ENABLE_POSTS_REFRESH=false
- ENABLE_IDLE_REMINDER=false
- ENABLE_TIPS_ENGINE=false
- PLUGINS_ENABLED=true
- PLUGIN_SCAN_ON_START=true
- PLUGIN_API_VERSION=1
depends_on:
db:
condition: service_healthy
volumes:
- ./uploads:/app/uploads
- ./plugins:/app/plugins
- ./logs:/app/logs
ports:
- "3000:3000"
restart: unless-stopped
command: sh -c "node server/init-db.js && node server/index.js"
healthcheck:
test: ["CMD", "node", "--input-type=module", "-e", "try { const response = await fetch('http://127.0.0.1:3000/api/health', { signal: AbortSignal.timeout(3000) }); if (!response.ok) process.exitCode = 1; } catch { process.exitCode = 1; }"]
interval: 5s
timeout: 5s
retries: 5
start_period: 10m
image: ${FEDISUITE_IMAGE:-christinloehner/fedisuite:latest}
FEDISUITE_IMAGE in deiner .env. Der Teil nach :- ist der Standardwert, falls die Variable fehlt oder leer ist. Die Tags stehen auf der Seite Umgebungsvariablen.pull_policy: always
docker compose up prüft Docker, ob auf Docker Hub ein neueres Image für den Tag vorliegt, und lädt es bei Bedarf. Mit latest kann deshalb schon ein einfaches docker compose up -d (zum Beispiel nach einer Konfigurationsänderung) auf eine neue Version aktualisieren. Wer das nicht will, trägt in der .env einen festen Tag ein.environment: ENABLE_*=false
app-Container hat in der Standardkonfiguration Scheduler, Beitrags-Refresh, Erinnerung und Tipps abgeschaltet, das übernehmen die Worker. Was in environment steht, überschreibt gleichnamige Werte aus der .env (die per env_file geladen wird). Die Variablen in der .env gelten deshalb für den app-Container nur, wenn sie nicht in environment stehen. Der Reichweiten-Worker (ENABLE_REACH_REFRESH) und der Wiederholungs-Worker für historische Importe laufen in der Standarddatei in jedem Container, auch in app.PLUGINS_ENABLED, PLUGIN_SCAN_ON_START, PLUGIN_API_VERSION
PLUGINS_ENABLED=true schaltet es ein, PLUGIN_SCAN_ON_START=true durchsucht beim Start den Ordner /app/plugins nach Plugins (Ordner mit einer plugin.json), PLUGIN_API_VERSION ist die Plugin-API-Version der Instanz (aktuell 1). Details auf den Seiten zu Plugins.depends_on: db: condition: service_healthy
db-Container seinen Healthcheck besteht. Ohne diese Abhängigkeit könnte sie beim ersten Start versuchen, die Datenbank zu initialisieren, bevor PostgreSQL bereit ist.ports: "3000:3000"
3000:3000 veröffentlicht ihn auf dem Host, und zwar auf allen Schnittstellen. Docker umgeht dabei Firewalls wie ufw. Mit Reverse Proxy solltest du das Mapping entfernen oder auf 127.0.0.1:3000:3000 beschränken (siehe Reverse Proxy).volumes: ./uploads, ./plugins & ./logs
Drei Bind Mounts, die App und Worker gemeinsam nutzen:
- •
./uploads:/app/uploads: Anhänge zu Beiträgen und Entwürfen. Seit FediSuite 2.0 bleiben die Dateien eines Beitrags auch nach der Veröffentlichung erhalten, damit er als Entwurf wiederverwendet werden kann. Sie werden gelöscht, wenn der Beitrag gelöscht wird. - •
./plugins:/app/plugins: der Plugins-Ordner. Plugins werden auf dem Host abgelegt und sind für App und Worker sichtbar. - •
./logs:/app/logs: Audit-Logs. FediSuite schreibt sicherheitsrelevante Ereignisse (Login, Registrierung, Zwei-Faktor-Authentifizierung u. a.) als JSON-Zeilen in monatlich wechselnde Dateien (audit-YYYY-MM.log, Monat in UTC). E-Mail-Adressen und Fediverse-Handles werden darin teilweise maskiert. Die Logs erscheinen nicht in der Container-Ausgabe.
healthcheck
http://127.0.0.1:3000/api/health ab. Die Antwort enthält den Zustand der Datenbankverbindung. start_period: 10m bedeutet: In den ersten zehn Minuten nach dem Start zählen Fehlversuche nicht, damit lange Datenbankmigrationen den Container nicht als defekt markieren. Sobald ein Check besteht, ist der Container healthy. Danach führen fünf aufeinanderfolgende Fehlschläge zu unhealthy. Die Worker warten auf diesen Zustand (siehe unten).command: node server/init-db.js && node server/index.js
init-db.js legt das Datenbankschema an oder bringt es auf den aktuellen Stand und legt den Admin-Account aus ADMIN_EMAIL und ADMIN_PASSWORD an, falls es ihn noch nicht gibt. Schlägt ein Schritt fehl, startet der Server nicht. Danach startet index.js die eigentliche App. Du musst nichts manuell anstoßen.Worker: Hintergrundaufgaben
Der app-Container nimmt Anfragen von Nutzer*innen entgegen und beantwortet sie sofort. Die Worker erledigen alles, was nicht sofort passieren muss, aber regelmäßig laufen muss. Sie nutzen dasselbe Image wie die App und starten node server/index.js, veröffentlichen aber keinen Port und haben andere Umgebungsvariablen.
Diese Aufgaben laufen automatisch im Hintergrund:
Geplante Beiträge veröffentlichen
Der Scheduler prüft jede Minute, ob Beiträge fällig sind, und veröffentlicht sie auf der jeweiligen Plattform. Er gibt außerdem Beiträge frei, die mitten in der Veröffentlichung hängen geblieben sind (nach 30 Minuten ohne Änderung).
Beitragszahlen aktualisieren
Jede Minute werden fällige Konten abgearbeitet: neue Beiträge, Likes, Boosts, Antworten und Benachrichtigungen. Pro Stunde liest eine Rotation außerdem einige ältere Beiträge neu ein, damit ihre Zähler aktuell bleiben.
Reichweite berechnen
Eine Warteschlange in der Datenbank sammelt Aufträge zur Reichweiten-Berechnung. Jeder Prozess arbeitet sie in kleinen Stapeln ab und begrenzt die gleichzeitigen Anfragen pro fremder Instanz. Dieser Worker läuft in der Standarddatei in allen drei Containern.
Erinnerung senden
Einmal pro Nutzer*in: Wer sich vor mehr als 48 Stunden registriert und bestätigt, aber noch kein Fediverse-Konto verbunden hat, bekommt eine E-Mail. Das Admin-Konto aus der .env ist ausgenommen.
Tipps erzeugen
Alle sechs Stunden werden die gespeicherten Tipps aller verbundenen Konten neu berechnet, etwa zu Posting-Zeiten und Formaten.
Instanzliste abgleichen
Ist die Instanz im Verzeichnis unter fedisuite.com/instances veröffentlicht, gleicht der Container mit aktivem Scheduler die Angaben (Name, Version, Nutzerzahl) alle zwei Stunden ab.
app-Container die Jobs nicht selbst, werden geplante Beiträge nicht veröffentlicht, Beitragszahlen bleiben eingefroren und Erinnerungen und Tipps werden nicht erzeugt. Entweder betreibst du mindestens einen Worker, oder du schaltest die Jobs im app-Container ein (Minimalbetrieb, siehe unten).
Wie viele Worker?
Eine feste Formel gibt es nicht. Die Compose-Datei startet zwei Worker, das ist der Ausgangspunkt:
Nur du selbst oder eine sehr kleine Gruppe
Kein WorkerDie Jobs laufen direkt im app-Container (Minimalbetrieb). Das spart Container und Arbeitsspeicher.
Standard
2 WorkerWie mitgeliefert. worker1 hat den Scheduler und die Erinnerung, worker2 hilft bei Beitragszahlen und Tipps. Der app-Container bleibt für die Web-Oberfläche frei.
Viele Konten oder lange Warteschlange
weitere WorkerZusätzliche Worker verteilen die Aktualisierung von Konten und die Reichweiten-Warteschlange. Die Arbeit wird über die Datenbank aufgeteilt, die Worker laufen deshalb gefahrlos parallel. Ob mehr Worker helfen oder eher REFRESH_BATCH_SIZE und die REACH_*-Werte, siehst du in den Logs.
Konfiguration der Worker
Alle Worker nutzen dasselbe Image wie app, aber ohne veröffentlichten Port. Die Aufgabenverteilung steuern Umgebungsvariablen. Der Scheduler gehört in genau einen Container (ENABLE_SCHEDULER=true nur in worker1). Jeder Beitrag wird vor der Veröffentlichung atomar beansprucht, ein zweiter Scheduler würde ihn daher nicht doppelt posten, wäre aber überflüssig.
worker1:
image: ${FEDISUITE_IMAGE:-christinloehner/fedisuite:latest}
pull_policy: always
env_file:
- .env
environment:
- ENABLE_SCHEDULER=true # nur hier true: veröffentlicht geplante Beiträge
- ENABLE_POSTS_REFRESH=true # Konten aktualisieren
- ENABLE_IDLE_REMINDER=true # Erinnerungs-E-Mails
- ENABLE_TIPS_ENGINE=true # Tipps erzeugen
- WORKER_ID=worker-1
- REFRESH_BATCH_SIZE=5
- PLUGINS_ENABLED=true
- PLUGIN_SCAN_ON_START=true
- PLUGIN_API_VERSION=1
depends_on:
db:
condition: service_healthy
app:
condition: service_healthy
restart: unless-stopped
volumes:
- ./uploads:/app/uploads
- ./plugins:/app/plugins
- ./logs:/app/logs
command: sh -c "node server/index.js"
worker2:
image: ${FEDISUITE_IMAGE:-christinloehner/fedisuite:latest}
pull_policy: always
env_file:
- .env
environment:
- ENABLE_SCHEDULER=false # false: der Scheduler läuft nur in worker1
- ENABLE_POSTS_REFRESH=true # hilft bei der Aktualisierung
- ENABLE_IDLE_REMINDER=false # ein Container reicht
- ENABLE_TIPS_ENGINE=true # Tipps parallel verarbeiten
- WORKER_ID=worker-2
- REFRESH_BATCH_SIZE=5
- PLUGINS_ENABLED=true
- PLUGIN_SCAN_ON_START=true
- PLUGIN_API_VERSION=1
depends_on:
db:
condition: service_healthy
app:
condition: service_healthy
restart: unless-stopped
volumes:
- ./uploads:/app/uploads
- ./plugins:/app/plugins
- ./logs:/app/logs
command: sh -c "node server/index.js"
Weitere Worker hinzufügen (worker3, worker4 …)
Kopiere den worker2-Block und erhöhe die Nummer im Service-Namen und in WORKER_ID. Alle weiteren Worker haben ENABLE_SCHEDULER=false und ENABLE_IDLE_REMINDER=false. Die WORKER_ID muss pro Worker eindeutig sein.
worker3:
image: ${FEDISUITE_IMAGE:-christinloehner/fedisuite:latest}
pull_policy: always
env_file:
- .env
environment:
- ENABLE_SCHEDULER=false
- ENABLE_POSTS_REFRESH=true
- ENABLE_IDLE_REMINDER=false
- ENABLE_TIPS_ENGINE=true
- WORKER_ID=worker-3
- REFRESH_BATCH_SIZE=5
- PLUGINS_ENABLED=true
- PLUGIN_SCAN_ON_START=true
- PLUGIN_API_VERSION=1
depends_on:
db:
condition: service_healthy
app:
condition: service_healthy
restart: unless-stopped
volumes:
- ./uploads:/app/uploads
- ./plugins:/app/plugins
- ./logs:/app/logs
command: sh -c "node server/index.js"
Was bedeuten die Variablen im Detail?
ENABLE_SCHEDULER
Steuert, ob der Container fällige geplante Beiträge veröffentlicht (jede Minute) und, falls die Instanz im Verzeichnis veröffentlicht ist, die Instanzliste abgleicht. Standard im Code: true, die Compose-Datei setzt es in worker1 auf true und sonst auf false.
ENABLE_POSTS_REFRESH
Aktualisiert regelmäßig Konten und Beitragszahlen (jede Minute ein Stapel). Darf in mehreren Containern laufen, die Konten werden per Datenbanksperre aufgeteilt. Standard: true.
ENABLE_REACH_REFRESH
Schaltet die Reichweiten-Warteschlange ein. In der Compose-Datei nicht gesetzt, also in jedem Container aktiv. Standard: true.
ENABLE_IDLE_REMINDER
Versendet die einmalige Erinnerung an Nutzer*innen ohne verbundenes Fediverse-Konto (stündliche Prüfung, höchstens 50 pro Durchlauf). Ein Container genügt. Standard: true.
ENABLE_TIPS_ENGINE
Erzeugt alle sechs Stunden die Tipps. Darf in mehreren Containern laufen. Standard: true.
WORKER_ID
Frei wählbarer, eindeutiger Name des Containers, zum Beispiel worker-1. Er erscheint in den Logs und in Sperren und Warteschlangen. Ohne Angabe nutzt FediSuite den Hostnamen des Containers.
REFRESH_BATCH_SIZE
Wie viele Konten ein Worker pro Durchlauf (alle 60 Sekunden) aktualisiert. Wertebereich 1 bis 50, Standard im Code 10, die Compose-Datei setzt 5. Kleinere Werte schonen die Server der verbundenen Instanzen, größere arbeiten viele Konten schneller ab.
Weitere Stellschrauben (Reichweite, Zeitlimits, Protokollierung) stehen auf der Seite Umgebungsvariablen.
Minimalbetrieb: nur db + app
Worker sind optional. Betreibst du FediSuite für dich allein oder für eine sehr kleine Gruppe, kannst du auf die Worker-Container verzichten. Das spart Arbeitsspeicher, weil weniger Container laufen.
In der Standarddatei hat der app-Container die Hintergrundjobs abgeschaltet (ENABLE_*=false). Im Minimalbetrieb schaltest du sie dort wieder ein:
app:
image: ${FEDISUITE_IMAGE:-christinloehner/fedisuite:latest}
...
environment:
- ENABLE_SCHEDULER=true # vorher false
- ENABLE_POSTS_REFRESH=true # vorher false
- ENABLE_IDLE_REMINDER=true # vorher false
- ENABLE_TIPS_ENGINE=true # vorher false
- PLUGINS_ENABLED=true
- PLUGIN_SCAN_ON_START=true
- PLUGIN_API_VERSION=1
Danach kannst du die worker1- und worker2-Blöcke löschen oder auskommentieren. docker compose up -d startet dann nur noch db und app.
app-Container die ENABLE_*-Variablen wieder auf false setzen und docker compose up -d ausführen. Die Daten bleiben dabei erhalten.
Volumes: wo Daten gespeichert werden
Container sind flüchtig: Wird ein Container entfernt, sind seine Daten weg. Volumes verknüpfen einen Ordner auf dem Host mit einem Ordner im Container, damit Daten erhalten bleiben.
FediSuite nutzt ausschließlich Bind Mounts. Die Daten liegen als normale Ordner neben der docker-compose.yml, ein Backup sichert also diese Ordner (siehe Backup & Restore).
./postgres/
/var/lib/postgresql/data
Genutzt von: db
Alle Datenbankdaten: Nutzer*innen, Beiträge, Entwürfe, Einstellungen, Verknüpfungen zu Fediverse-Konten. Das Wichtigste im gesamten Setup.
./uploads/
/app/uploads
Genutzt von: app, worker1, worker2
Anhänge zu Beiträgen und Entwürfen. Muss zwischen App und Workern geteilt werden, weil die Worker beim Veröffentlichen darauf zugreifen.
./plugins/
/app/plugins
Genutzt von: app, worker1, worker2
Installierte Plugins (je ein Unterordner mit einer plugin.json).
./logs/
/app/logs
Genutzt von: app, worker1, worker2
Audit-Logs als JSON-Zeilen in monatlichen Dateien (audit-YYYY-MM.log). Teile der E-Mail-Adressen und Handles sind maskiert. Die Logs erscheinen nicht in der Container-Ausgabe.
Weitere Konzepte erklärt
Warum laufen worker1 und worker2 mit demselben Image wie app?
FediSuite ist eine einzelne Node.js-Anwendung, die je nach Umgebungsvariablen unterschiedliche Rollen übernimmt. Welche Rolle ein Container hat, bestimmen die ENABLE_*-Variablen. Dass nur app von außen erreichbar ist, liegt am veröffentlichten Port in der Compose-Datei, nicht am Image.
Was bedeutet depends_on mit service_healthy?
Ohne diese Bedingung startet Docker alle Container gleichzeitig. Die App würde dann auf die Datenbank zugreifen, bevor PostgreSQL bereit ist. Mit condition: service_healthy wartet jeder abhängige Container auf den bestandenen Healthcheck. Die Worker hängen von db und app ab: Sie starten erst, wenn die App die Migrationen abgeschlossen hat und antwortet, damit nicht Worker und App gleichzeitig am Datenbankschema arbeiten.
Netzwerk: Wie reden die Container miteinander?
Docker Compose legt ein internes Netzwerk an, in dem sich alle Container über ihren Service-Namen erreichen. Die App spricht die Datenbank unter dem Hostnamen db an, deshalb lautet die DATABASE_URL postgresql://…@db:5432/…. Die Datenbank veröffentlicht keinen Port und ist von außen nicht erreichbar.
Traefik-Labels und Netzwerk in der docker-compose.yml
Im app-Service sind Traefik-Labels und die Netzwerk-Einträge auskommentiert vorbereitet. Die Datei docker-compose.traefik.example.yml zeigt dieselben Einträge als Beispiel. Wie du sie anpasst, steht auf der Seite Reverse Proxy.