Zum Hauptinhalt springen
S-EDV news
← Alle Anleitungen
📘 Anleitung Docker 18.10.2026 · 10 min Lesezeit

Etherpad mit Docker Compose und PostgreSQL selbst hosten

Diese Anleitung zeigt, wie Sie Etherpad 3.3.8 mit Docker Compose und PostgreSQL betreiben, den Admin-Bereich und die HTTP-API nutzen, den API-Schlüssel über Updates hinweg behalten und Pads per pg_dump sichern sowie nachweislich wiederherstellen.

Mit KI erstellt – redaktionelle Prüfung ausstehend

WerbelinksMit * markierte Links sind Werbelinks: Bei einem Kauf erhalten wir eine Provision, der Preis bleibt gleich. Als Amazon-Partner verdiene ich an qualifizierten Verkäufen. Mehr dazu

Titelbild der Anleitung: Etherpad mit Docker Compose und PostgreSQL selbst hosten, Backup und Restore

Etherpad ist ein Editor, in dem mehrere Personen gleichzeitig am selben Text schreiben. Jede Änderung erscheint sofort bei allen anderen, jede Person bekommt eine eigene Farbe, und über den Zeitregler lässt sich jeder Bearbeitungsstand zurückverfolgen. Für Protokolle und Checklisten im Team ist das oft schneller als ein Office-Dokument. Diese Anleitung zeigt den Betrieb von Etherpad 3.3.8 mit Docker Compose und PostgreSQL, wie ihn das Projekt selbst vorsieht, ergänzt um drei Korrekturen, ohne die der Betrieb im Alltag hakt: einen Datenbank-Healthcheck, einen dauerhaften API-Schlüssel und eigene Secrets für den eingebauten OpenID-Connect-Anbieter.

Voraussetzungen

  • Ein Linux-Server oder eine VM mit Docker Engine und Docker Compose v2, x86_64 oder ARM64.
  • 2 CPU-Kerne und 2 GB RAM reichen für ein kleines Team. Im Test belegte Etherpad im Leerlauf rund 210 MB RAM, PostgreSQL rund 22 MB.
  • Etwa 3 GB Speicherplatz: Das Etherpad-Image ist 1,14 GB groß, PostgreSQL 15 Alpine rund 420 MB, dazu kommen Daten und Backups.
  • Für den Zugriff von außen ein Reverse Proxy mit TLS, zum Beispiel Caddy oder Nginx, und ein DNS-Name wie pad.example.de.
  • Ein Speicherziel außerhalb des Servers für die Sicherungen.

Schritt 1: Projektordner und Geheimnisse anlegen

Etherpad liest seine Konfiguration aus Umgebungsvariablen, die das Image in eine vorbereitete settings.json einsetzt. Alle Geheimnisse gehören deshalb in eine .env neben der Compose-Datei. Legen Sie Ordner, Passwörter und den API-Schlüssel an:

mkdir -p /opt/etherpad && cd /opt/etherpad
cat > .env <<EOF
ETHERPAD_VERSION=3.3.8
ADMIN_PASSWORD=$(openssl rand -hex 16)
DB_PASS=$(openssl rand -hex 16)
ADMIN_SECRET=$(openssl rand -hex 24)
USER_SECRET=$(openssl rand -hex 24)
AUTHENTICATION_METHOD=apikey
EOF
openssl rand -hex 24 | tr -d '\n' > APIKEY.txt
chmod 600 .env && chmod 644 APIKEY.txt

Zur Rolle der Werte: ADMIN_PASSWORD ist das Passwort des Benutzers admin für /admin. ADMIN_SECRET und USER_SECRET ersetzen die Platzhalter-Secrets admin und user des eingebauten OpenID-Connect-Anbieters. Ohne sie schreibt Etherpad bei jedem Start SSO client(s) using a default/placeholder client_secret: admin_client, user_client als Fehler ins Log. AUTHENTICATION_METHOD=apikey schaltet die HTTP-API auf den klassischen statischen Schlüssel um, was für Skripte und Integrationen deutlich einfacher ist als OAuth. Seit Version 3 ist sso der Standard.

Die Datei APIKEY.txt muss vor dem ersten Start existieren. Fehlt sie, legt Docker beim Bind-Mount an ihrer Stelle ein leeres Verzeichnis an, das Sie anschließend erst wieder entfernen müssen.

Verifizieren: ls -l /opt/etherpad zeigt .env mit Rechten -rw------- und eine APIKEY.txt mit 48 Zeichen (wc -c APIKEY.txt).

Schritt 2: Compose-Datei schreiben

Die Grundlage ist die docker-compose.yml aus dem Etherpad-Repository. Gegenüber dem Original ist das Image auf eine Version festgeschrieben statt latest, PostgreSQL hat einen Healthcheck, und APIKEY.txt wird von außen eingebunden.

services:
  app:
    image: etherpad/etherpad:${ETHERPAD_VERSION:-3.3.8}
    user: "5001:0"
    tty: true
    stdin_open: true
    restart: unless-stopped
    depends_on:
      postgres:
        condition: service_healthy
    ports:
      - "9001:9001"
    volumes:
      - plugins:/opt/etherpad-lite/src/plugin_packages
      - etherpad-var:/opt/etherpad-lite/var
      - ./APIKEY.txt:/opt/etherpad-lite/APIKEY.txt:ro
    environment:
      NODE_ENV: production
      TITLE: "Team-Pad"
      ADMIN_PASSWORD: "${ADMIN_PASSWORD:?ADMIN_PASSWORD in .env setzen}"
      DB_TYPE: postgres
      DB_HOST: postgres
      DB_PORT: 5432
      DB_NAME: etherpad
      DB_USER: etherpad
      DB_PASS: "${DB_PASS:?DB_PASS in .env setzen}"
      DEFAULT_PAD_TEXT: " "
      DISABLE_IP_LOGGING: "true"
      TRUST_PROXY: "${TRUST_PROXY:-false}"
      AUTHENTICATION_METHOD: "${AUTHENTICATION_METHOD:-sso}"
      SOFFICE: "null"
      ADMIN_SECRET: "${ADMIN_SECRET:?ADMIN_SECRET in .env setzen}"
      USER_SECRET: "${USER_SECRET:?USER_SECRET in .env setzen}"

  postgres:
    image: postgres:15-alpine
    restart: unless-stopped
    environment:
      POSTGRES_DB: etherpad
      POSTGRES_USER: etherpad
      POSTGRES_PASSWORD: "${DB_PASS:?DB_PASS in .env setzen}"
      PGDATA: /var/lib/postgresql/data/pgdata
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U etherpad -d etherpad"]
      interval: 5s
      timeout: 5s
      retries: 10
    volumes:
      - postgres_data:/var/lib/postgresql/data

volumes:
  postgres_data:
  plugins:
  etherpad-var:

Warum der Healthcheck? Mit dem schlichten depends_on der Vorlage startet Etherpad, sobald der Datenbank-Container läuft, nicht erst, wenn PostgreSQL Verbindungen annimmt. Beim ersten Start sahen wir deshalb zweimal Error: connect ECONNREFUSED ...:5432 und einen Neustart des Containers, bevor Etherpad lief. Mit condition: service_healthy verschwindet das.

Warum APIKEY.txt als Datei von außen? Etherpad erzeugt den Schlüssel sonst selbst unter /opt/etherpad-lite/APIKEY.txt, also in der beschreibbaren Schicht des Containers und nicht im Volume. Beim Update auf eine neue Image-Version wurde der Container neu erstellt, der Schlüssel war ein anderer und alle Skripte mit dem alten Schlüssel bekamen HTTP 401. Mit dem Bind-Mount bleibt er über jedes Update gleich.

EckdatenWert
Imageetherpad/etherpad:3.3.8 (Spiegel: ghcr.io/ether/etherpad)
Datenbankpostgres:15-alpine, eine Tabelle store als Schlüssel-Wert-Speicher
Port9001 (HTTP und WebSocket)
Volumespostgres_data, etherpad-var, plugins, Datei APIKEY.txt
Wichtige VariablenADMIN_PASSWORD, DB_*, AUTHENTICATION_METHOD, ADMIN_SECRET, USER_SECRET, TRUST_PROXY
LizenzApache-2.0

Verifizieren: docker compose config --quiet endet ohne Ausgabe. Fehlt eine Pflichtvariable, bricht Compose mit einer klaren Meldung ab, etwa required variable ADMIN_PASSWORD is missing a value: ADMIN_PASSWORD in .env setzen.

Schritt 3: Etherpad starten

docker compose up -d
docker compose ps
curl -s localhost:9001/health

Im Test war die Instanz etwa 15 Sekunden nach dem Start bereit. Der Endpunkt /health antwortet mit der laufenden Version, das ist auch der schnellste Weg, nach einem Update die Version zu prüfen.

Startseite von Etherpad mit Feld für den Padnamen und Liste zuletzt bearbeiteter Pads
Startseite der eigenen Instanz: Padname eingeben oder ein neues Pad erzeugen.

Verifizieren: docker compose ps zeigt beide Container mit (healthy), curl -s localhost:9001/health liefert {"status":"pass","releaseId":"3.3.8"}, und docker compose logs app | grep -E "Api key file read|Etherpad is running" zeigt Api key file read from: "/opt/etherpad-lite/APIKEY.txt" sowie Etherpad is running.

Schritt 4: Hinter den Reverse Proxy stellen

Port 9001 spricht nur HTTP. Für den Betrieb außerhalb des Testnetzes gehört ein TLS-Proxy davor, der auch WebSockets durchreicht, denn darüber laufen die Live-Änderungen. Bei Caddy genügt ein Block mit reverse_proxy 127.0.0.1:9001. Binden Sie den Port dann nur lokal, also "127.0.0.1:9001:9001", und setzen Sie TRUST_PROXY=true in der .env, damit Etherpad die X-Forwarded-*-Header des Proxys auswertet. Ohne Proxy sollte die Variable auf false bleiben, sonst könnte jeder Client diese Header fälschen.

Unsere Testinstanz lief über einen vorgeschalteten HTTPS-Proxy; Editor und Admin-Bereich funktionierten darüber ohne weitere Einstellungen.

Verifizieren: curl -sI https://pad.example.de/ liefert HTTP/2 200, und beim Tippen in einem Pad erscheinen die Änderungen ohne Neuladen in einem zweiten Browserfenster.

Schritt 5: Pads anlegen und den Admin-Bereich nutzen

Ein Pad entsteht, sobald jemand https://pad.example.de/p/NAME aufruft oder auf der Startseite einen Namen eingibt. Jede Person, die die Adresse kennt, kann mitschreiben. Für interne Notizen ist das bequem, für vertrauliche Inhalte nicht: Wählen Sie dann schwer erratbare Padnamen oder aktivieren Sie mit REQUIRE_AUTHENTICATION=true eine Anmeldung für alle Pads.

Etherpad-Editor mit einem Besprechungsprotokoll, Zeilennummern und Werkzeugleiste
Ein Pad im Editor. Alle Teilnehmer sehen Änderungen sofort.

Der Admin-Bereich unter /admin verlangt den Benutzernamen admin und das ADMIN_PASSWORD. Ein falsches Passwort beantwortet der Server mit HTTP 401. Unter „Pads verwalten“ sehen Sie alle Pads mit Bearbeitungen und aktiven Nutzern, können leere Pads finden und Pads löschen. „Etherpad updates“ zeigt die laufende und die neueste Version samt Installationsart docker.

Admin-Bereich von Etherpad 3.3.8 mit der Übersicht Pads verwalten
Pads verwalten im Admin-Bereich mit Bearbeitungen und aktiven Nutzern.

Für Skripte gibt es die HTTP-API, aktuell in Version 1.3.1. Der Schlüssel aus APIKEY.txt wird als Parameter apikey übergeben:

K=$(cat /opt/etherpad/APIKEY.txt)
curl -s "localhost:9001/api/1.3.1/createPad?apikey=$K&padID=protokoll&text=Erster%20Eintrag"
curl -s "localhost:9001/api/1.3.1/getText?apikey=$K&padID=protokoll"
curl -s "localhost:9001/api/1.3.1/listAllPads?apikey=$K"

Verifizieren: getText liefert {"code":0,"message":"ok","data":{"text":"Erster Eintrag\n"}}. Derselbe Aufruf mit falschem oder ohne Schlüssel endet mit HTTP 401 und {"code":4,"message":"no or wrong API Key","data":null}.

Schritt 6: Backup anlegen

Alle Pads, Revisionen, Autoren und Chats liegen in PostgreSQL, in einer einzigen Tabelle store. Das Volume etherpad-var enthält nur Laufzeitdaten wie den Plugin-Stand. Sichern Sie trotzdem beides, dazu .env und APIKEY.txt, sonst passen nach einem Neuaufbau Passwörter und Schlüssel nicht mehr.

cd /opt/etherpad
mkdir -p backup
docker compose exec -T postgres pg_dump -U etherpad -d etherpad -Fc \
  > backup/etherpad-$(date +%F).dump
docker run --rm -v etherpad_etherpad-var:/data:ro -v "$PWD/backup":/backup \
  alpine tar czf /backup/etherpad-var-$(date +%F).tgz -C /data .
cp .env APIKEY.txt compose.yaml backup/

Der Volume-Name setzt sich aus Projektordner und Volume zusammen, bei /opt/etherpad also etherpad_etherpad-var. docker volume ls zeigt den genauen Namen. Den Dump im Custom-Format (-Fc) können Sie im laufenden Betrieb ziehen; er ist komprimiert und lässt sich gezielt mit pg_restore zurückspielen. Kopieren Sie den Ordner danach auf ein anderes System.

Verifizieren: ls -l backup/ zeigt Dump und Archiv mit einer Größe über 0 Byte, und docker compose exec -T postgres pg_restore -l < backup/etherpad-$(date +%F).dump | grep store listet die Tabelle store.

Schritt 7: Wiederherstellen

Ein Backup ist erst etwas wert, wenn die Rücksicherung klappt. Im Test haben wir ein Pad mit einem Markertext angelegt, gesichert, das Pad per API gelöscht und danach die Datenbank komplett ersetzt. Gehen Sie im Ernstfall genauso vor:

docker compose stop app
docker compose exec -T postgres dropdb -U etherpad etherpad
docker compose exec -T postgres createdb -U etherpad -O etherpad etherpad
docker compose exec -T postgres pg_restore -U etherpad -d etherpad \
  --exit-on-error < backup/etherpad-2026-10-18.dump
docker compose start app

Stoppen Sie Etherpad vorher, sonst blockieren offene Verbindungen das dropdb, und die Anwendung puffert Daten im Arbeitsspeicher, die nicht zum zurückgespielten Stand passen. Nach dem Löschen lieferte getText für das Testpad padID does not exist, die neu angelegte Datenbank enthielt null Tabellen. Nach pg_restore und dem Start war der Markertext unverändert wieder da.

Verifizieren: pg_restore endet mit Exit-Code 0, curl -s localhost:9001/health meldet pass, und getText beziehungsweise der Admin-Bereich zeigt Ihre Pads wieder an.

Schritt 8: Updates einspielen

Etherpad veröffentlicht häufig Patch-Versionen; die Tags 3.3.8, 3.3, 3 und latest zeigten am 18.10.2026 auf denselben Stand. Bleiben Sie beim exakten Versions-Tag und ändern Sie ihn bewusst. Der Ablauf, den wir von 3.3.7 auf 3.3.8 durchgespielt haben:

cd /opt/etherpad
# vorher Schritt 6 ausführen
sed -i 's/^ETHERPAD_VERSION=.*/ETHERPAD_VERSION=3.3.8/' .env
docker compose pull app
docker compose up -d
curl -s localhost:9001/health

Die bestehenden Pads blieben erhalten, das Log meldete check installed plugins for migration und kurz danach Etherpad is running. Lesen Sie vor größeren Sprüngen die Release Notes auf GitHub. Beachten Sie außerdem: Änderungen an settings.json über den Admin-Bereich landen ohne eigenen Mount in der Container-Schicht und sind nach einem Update weg. Steuern Sie die Konfiguration deshalb über Variablen in der Compose-Datei.

Verifizieren: /health liefert die neue releaseId, docker compose ps zeigt das neue Image, und ein API-Aufruf mit dem bisherigen Schlüssel aus APIKEY.txt funktioniert weiterhin.

Troubleshooting

  • Error: connect ECONNREFUSED ...:5432 beim Start: Etherpad war schneller als PostgreSQL. Der Container startet zwar neu und kommt meist hoch, sauber ist der Healthcheck mit condition: service_healthy aus Schritt 2.
  • password authentication failed for user "etherpad": DB_PASS passt nicht mehr zum Passwort, mit dem das Volume angelegt wurde. PostgreSQL übernimmt POSTGRES_PASSWORD nur beim allerersten Start; ein späteres Ändern in der .env wirkt nicht auf die Datenbank. Altes Passwort zurücksetzen oder in der Datenbank per ALTER USER ändern.
  • API meldet nach einem Update no or wrong API Key: Der Schlüssel lag im Container und wurde neu erzeugt. Den Bind-Mount für APIKEY.txt ergänzen und Skripte auf den neuen Schlüssel umstellen.
  • Bind for 0.0.0.0:9001 failed: port is already allocated: Ein anderer Dienst nutzt den Port. ss -ltnp | grep 9001 zeigt welcher; den linken Teil der Portzuordnung ändern.

Häufige Fragen

Brauche ich PostgreSQL oder reicht die eingebaute Datenbank?

Ohne DB_TYPE nutzt Etherpad DirtyDB, eine einfache Datei, die das Projekt selbst nur für Tests empfiehlt. Für den Dauerbetrieb ist PostgreSQL die bessere Wahl, auch weil Sie dann mit den üblichen Werkzeugen sichern können.

Wie schütze ich Pads vor Fremden?

Standardmäßig ist jedes Pad für jeden lesbar, der die Adresse kennt. Abhilfe schaffen ein Zugriff nur aus dem internen Netz oder VPN, REQUIRE_AUTHENTICATION=true mit Benutzern aus der settings.json oder ein Authentifizierungs-Plugin wie LDAP oder OpenID Connect.

Kann ich Plugins installieren?

Ja, die Pluginverwaltung im Admin-Bereich listet über 160 Plugins aus dem npm-Katalog. Installierte Plugins landen im Volume plugins. Jedes Plugin muss zur Etherpad-Version passen.

Lässt sich Etherpad ganz ohne Verbindungen ins Internet betreiben?

Ja. Etherpad prüft regelmäßig auf neue Versionen und lädt den Plugin-Katalog. Mit UPDATES_TIER=off, PRIVACY_UPDATE_CHECK=false und PRIVACY_PLUGIN_CATALOG=false entfallen diese Abrufe.

Fazit

Etherpad ist schnell aufgesetzt und braucht wenig Ressourcen. Die offizielle Compose-Datei ist ein guter Ausgangspunkt, im Betrieb lohnen sich aber drei Ergänzungen: Healthcheck für PostgreSQL, ein außerhalb des Containers liegender API-Schlüssel und eigene OIDC-Secrets. Planen Sie vor dem Einsatz für vertrauliche Inhalte die Zugriffsfrage, denn ohne Anmeldung ist jedes Pad nur so geheim wie sein Name.

Weiterführende Anleitungen und Quellen

EtherpadDocker ComposePostgreSQLSelfhostingBackupKollaborationEchtzeit-Editor