Installation

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.

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
Pflicht

Die Datenbank. Speichert Nutzer*innen, Beiträge, Entwürfe, Konto-Verknüpfungen, Einstellungen und Verlaufsdaten dauerhaft.

app christinloehner/fedisuite:latest
Pflicht

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
Optional

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
Optional

Zusätzlicher Worker: aktualisiert Beitragszahlen und erzeugt Tipps. Scheduler und Erinnerung sind hier abgeschaltet. Weitere Worker kannst du als Kopie dieses Blocks ergänzen.

Wichtig: FediSuite braucht weder Redis noch einen anderen Queue- oder Cache-Dienst. Die Hintergrundjobs laufen als Timer in den Node.js-Prozessen, die Warteschlange für die Reichweiten-Berechnung liegt in der PostgreSQL-Datenbank.

Service: db (PostgreSQL)

docker-compose.yml
  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

Das offizielle PostgreSQL-Image in Version 15 auf Basis von Alpine Linux. FediSuite nutzt PostgreSQL, MySQL oder andere Datenbanken werden nicht unterstützt. Die Hauptversion solltest du nicht einfach im Tag ändern: Das Datenverzeichnis ist zwischen PostgreSQL-Hauptversionen nicht kompatibel, ein Wechsel läuft über Dump und Restore (siehe Backup & Restore).

env_file: .env

Der Datenbankcontainer liest 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

Stürzt der Container ab oder startet der Server neu, fährt Docker ihn wieder hoch, außer du hast ihn bewusst mit 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.

Merke: Das Verzeichnis ./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

Docker ruft alle 5 Sekunden 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

docker-compose.yml
  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}

Nutzt das Image aus 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

Bei jedem 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

Der 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

Steuern das Plugin-System. 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

Die App wartet, bis der 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"

Die App lauscht im Container auf Port 3000. Das Mapping 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

Alle 5 Sekunden fragt Docker 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

Beim Start laufen zwei Schritte nacheinander: 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.

Was passiert ohne Worker? Läuft kein Worker und übernimmt der 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 Worker

Die Jobs laufen direkt im app-Container (Minimalbetrieb). Das spart Container und Arbeitsspeicher.

Standard

2 Worker

Wie 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 Worker

Zusä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.

docker-compose.yml: worker1 (der Hauptworker)
  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"
docker-compose.yml: worker2 (zusätzlicher Worker)
  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:

docker-compose.yml: app im Minimalbetrieb
  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.

Jederzeit umsteigen möglich. Später kannst du wieder auf Worker wechseln: Worker-Blöcke ergänzen, im 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.

Diesen Ordner niemals löschen. Er enthält alle Datenbankdaten deiner Instanz.
./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.