Gotenberg mit Docker installieren: Entwicklerfreundliche Docker-API für PDF-Konvertierung
Gotenberg ist eine zustandslose REST-API im Docker-Container, die HTML, DOCX, XLSX, PPTX und 100+ weitere Formate per einfachem multipart-POST in PDFs verwandelt – ohne LibreOffice oder Chromium auf dem Host. Diese Anleitung zeigt den kompletten Weg von compose.yaml bis zum ersten curl-Test.

Wer in einer Anwendung PDF-Dateien erzeugen muss, kennt das Problem: LibreOffice auf dem Server installieren, Headless-Chromium konfigurieren, Abhängigkeiten verwalten – und das auf jedem Deployment-Ziel erneut. Gotenberg löst genau dieses Problem. Die in Go geschriebene REST-API verpackt Headless Chromium und LibreOffice in einen einzigen Docker-Container. Entwickler schicken Dateien per multipart/form-data an den Container und erhalten ein fertiges PDF zurück. Mit über 12.000 GitHub-Stars und 1,4 Millionen Docker-Pulls pro Woche ist Gotenberg das meistgenutzte Werkzeug dieser Art – und der Betrieb erfordert weder Datenbank noch zusätzliche Services.
Voraussetzungen
- Docker Engine 24.x oder neuer mit Docker Compose Plugin v2 (
docker composeohne Bindestrich) – wie du das auf einem Linux-Host einrichtest, erklärt die Anleitung Docker und Docker Compose auf Linux installieren. - Beliebiger Linux-Host, VM oder NAS mit Docker-Unterstützung (amd64, arm64, armv7, Raspberry Pi, Apple Silicon werden alle unterstützt).
- Mindestens 512 MB freier RAM; bei regelmäßigen LibreOffice-Konvertierungen unter Last lieber 1–2 GB reservieren.
curlzum Testen der API – auf praktisch allen Linux-Systemen vorinstalliert.- Optional: Reverse Proxy (Caddy, Nginx, Traefik) für TLS-Terminierung, wenn der Dienst von außen erreichbar sein soll. Die Anleitung Caddy als Reverse Proxy einrichten zeigt den Weg.
- Eine Testdatei (
.docxoder.html) für die Verifikation nach dem Start.
Schritt 1: Projektordner anlegen
Lege einen dedizierten Ordner für den Gotenberg-Stack an. Alle Konfigurationsdateien liegen hier – mehr braucht Gotenberg nicht, da es vollständig zustandslos ist und keine Datenbank oder persistenten Volumes benötigt.
mkdir -p /opt/gotenberg
cd /opt/gotenbergWer den Stack im Home-Verzeichnis bevorzugt, nimmt entsprechend ~/gotenberg.
Verifizieren: ls /opt/gotenberg – der Ordner ist vorhanden. Der Befehl gibt keine Fehlermeldung zurück.
Schritt 2: .env-Datei anlegen
Die .env-Datei enthält alle anpassbaren Werte. Sensible Zugangsdaten für Basic Auth gehören hier rein – sie werden nie direkt in die compose.yaml geschrieben.
# /opt/gotenberg/.env
# API-Konfiguration
API_TIMEOUT=60s
API_BODY_LIMIT=20MB
# Basic Auth (in Produktion auf true setzen)
API_ENABLE_BASIC_AUTH=false
GOTENBERG_API_BASIC_AUTH_USERNAME=admin
GOTENBERG_API_BASIC_AUTH_PASSWORD=sicheres_passwort_hier_aendern
# Logging
LOG_LEVEL=info
LOG_STD_FORMAT=auto
# Chromium-Modul
CHROMIUM_MAX_CONCURRENCY=6
CHROMIUM_RESTART_AFTER=100
CHROMIUM_AUTO_START=false
# LibreOffice-Modul
LIBREOFFICE_RESTART_AFTER=10
LIBREOFFICE_AUTO_START=falseFür Produktionsumgebungen: API_ENABLE_BASIC_AUTH=false auf true ändern und ein starkes Passwort setzen, dann docker compose up -d erneut ausführen. Wer sehr große DOCX- oder XLSX-Dateien konvertiert, sollte API_TIMEOUT auf 120s oder mehr erhöhen – der Standard von 30 Sekunden (hier bereits auf 60s erweitert) reicht für große Dokumente manchmal nicht aus.
Verifizieren: cat /opt/gotenberg/.env – alle Variablen sind korrekt eingetragen, keine Syntaxfehler.
Schritt 3: compose.yaml erstellen
Die folgende compose.yaml startet Gotenberg mit Ressourcenbegrenzung, Health-Check und sicherem Port-Binding. Das Image-Tag :8 folgt immer dem neuesten stabilen 8.x-Patch (aktuell v8.34.0) – wer exakt pinnen will, verwendet gotenberg/gotenberg:8.34.0.
# /opt/gotenberg/compose.yaml
services:
gotenberg:
image: gotenberg/gotenberg:8
container_name: gotenberg
restart: unless-stopped
ports:
- "127.0.0.1:3000:3000"
environment:
API_TIMEOUT: ${API_TIMEOUT:-60s}
API_BODY_LIMIT: ${API_BODY_LIMIT:-20MB}
API_ENABLE_BASIC_AUTH: ${API_ENABLE_BASIC_AUTH:-false}
GOTENBERG_API_BASIC_AUTH_USERNAME: ${GOTENBERG_API_BASIC_AUTH_USERNAME:-}
GOTENBERG_API_BASIC_AUTH_PASSWORD: ${GOTENBERG_API_BASIC_AUTH_PASSWORD:-}
LOG_LEVEL: ${LOG_LEVEL:-info}
LOG_STD_FORMAT: ${LOG_STD_FORMAT:-auto}
CHROMIUM_MAX_CONCURRENCY: ${CHROMIUM_MAX_CONCURRENCY:-6}
CHROMIUM_RESTART_AFTER: ${CHROMIUM_RESTART_AFTER:-100}
CHROMIUM_AUTO_START: ${CHROMIUM_AUTO_START:-false}
LIBREOFFICE_RESTART_AFTER: ${LIBREOFFICE_RESTART_AFTER:-10}
LIBREOFFICE_AUTO_START: ${LIBREOFFICE_AUTO_START:-false}
deploy:
resources:
limits:
memory: 1G
cpus: "1.0"
reservations:
memory: 512M
cpus: "0.2"
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:3000/health"]
interval: 30s
timeout: 10s
retries: 3
start_period: 10sWichtige Designentscheidung: Der Port ist bewusst auf 127.0.0.1:3000:3000 beschränkt. Gotenberg hat im Standardmodus keine Authentifizierung – ein versehentlich freigegebener Port würde jedem erlauben, beliebige Konvertierungen anzustoßen. Sollen andere Docker-Services im gleichen Stack auf Gotenberg zugreifen, brauchen sie gar keinen exportierten Port: Sie erreichen den Container intern über http://gotenberg:3000.
Eine ausführliche Erklärung zu Docker-Netzwerken und der internen Service-Kommunikation findest du in der Anleitung Docker-Netzwerke und Volumes richtig nutzen.
Verifizieren: docker compose config im Projektordner zeigt die aufgelöste Konfiguration inklusive der .env-Werte – keine Fehlermeldung, alle Variablen befüllt.
Schritt 4: Container starten
Vom Projektordner aus den Stack starten:
cd /opt/gotenberg
docker compose up -dDocker lädt das Image herunter (ca. 1,5 GB für das Vollimage mit Chromium und LibreOffice) und startet den Container im Hintergrund. Der erste Start kann je nach Internetverbindung einige Minuten dauern.
Status und Logs prüfen:
# Container-Status anzeigen
docker compose ps
# Logs verfolgen (Ctrl+C zum Beenden)
docker compose logs -f gotenbergErwartete Ausgabe von docker compose ps:
NAME IMAGE STATUS
gotenberg gotenberg/gotenberg:8 Up 30 seconds (healthy)In den Logs sollte nach wenigen Sekunden eine Zeile ähnlich dieser erscheinen:
{"level":"INFO","msg":"server listening","port":3000}Verifizieren: docker compose ps zeigt STATUS = Up ... (healthy). Sollte der Status (health: starting) zeigen, kurz warten und erneut prüfen – der Health-Check hat 10 Sekunden Anlaufzeit.
Schritt 5: API testen – Health-Check und erste Konvertierung
Zuerst den Health-Endpunkt aufrufen:
curl -I http://localhost:3000/healthErwartete Antwort:
HTTP/1.1 200 OK
Content-Type: application/jsonAlternativ im Browser: http://localhost:3000/health – dort erscheint ein JSON-Objekt mit dem Status beider Module (Chromium, LibreOffice).
Eine Webseite per URL in PDF konvertieren
curl --request POST \
http://localhost:3000/forms/chromium/convert/url \
--form url=https://example.com \
-o /tmp/example-com.pdfEine DOCX-Datei in PDF konvertieren
curl --request POST \
http://localhost:3000/forms/libreoffice/convert \
--form files=@/pfad/zu/dokument.docx \
-o /tmp/ausgabe.pdfEine lokale HTML-Datei in PDF konvertieren
curl --request POST \
http://localhost:3000/forms/chromium/convert/html \
--form files=@/pfad/zu/seite.html \
-o /tmp/seite.pdfWenn Basic Auth aktiv ist (API_ENABLE_BASIC_AUTH=true), muss bei jedem curl-Aufruf --user admin:passwort ergänzt werden.
Verifizieren: Die erzeugten PDF-Dateien unter /tmp/ sind vorhanden (ls -lh /tmp/*.pdf) und lassen sich öffnen. Eine leere oder 0-Byte-Datei deutet auf einen Konvertierungsfehler hin – dann docker compose logs gotenberg auf Fehlermeldungen prüfen.
Schritt 6: Eckdaten im Überblick
| Parameter | Wert | Hinweis |
|---|---|---|
| Image | gotenberg/gotenberg:8 | Vollimage (Chromium + LibreOffice); aktuell v8.34.0 |
| Image-Varianten | :8-chromium, :8-libreoffice | ~30–40 % kleiner, jeweils nur ein Modul |
| Port | 3000/tcp | Nur auf localhost binden: 127.0.0.1:3000:3000 |
| Volumes | keine | Zustandslos, keine persistenten Daten nötig |
| Datenbank | keine | Einziger Container genügt |
| Architektur | amd64, arm64, armv7, i386, ppc64le | Raspberry Pi und Apple Silicon unterstützt |
| RAM (min.) | 512 MB | Unter LibreOffice-Last eher 1–2 GB |
| Chromium-Parallelität | bis 6 (einstellbar) | CHROMIUM_MAX_CONCURRENCY |
| LibreOffice-Parallelität | 1 (sequenziell) | Bei hoher Last: mehrere Container-Instanzen |
| API-Endpunkt | Modul | Unterstützte Formate (Auswahl) |
|---|---|---|
POST /forms/chromium/convert/url | Chromium | Beliebige URL → PDF |
POST /forms/chromium/convert/html | Chromium | HTML-Datei(en) → PDF |
POST /forms/chromium/convert/markdown | Chromium | Markdown + HTML-Template → PDF |
POST /forms/libreoffice/convert | LibreOffice | DOCX, XLSX, PPTX, ODT, ODS, RTF, CSV und 100+ weitere |
GET /health | – | Status beider Module, 200 OK wenn bereit |
Schritt 7: Updates einspielen
Da Gotenberg zustandslos ist und keine Volumes hat, ist ein Update denkbar einfach:
cd /opt/gotenberg
docker compose pull
docker compose up -dDocker zieht das neue Image, stoppt den laufenden Container und startet ihn mit dem aktualisierten Image neu. Downtime: wenige Sekunden. Wer Container-Updates automatisieren möchte, findet in der Anleitung Watchtower mit Docker: Container automatisch aktualisieren eine praktische Lösung.
Verifizieren: Nach dem Update docker compose ps und curl -I http://localhost:3000/health – Status muss wieder 200 OK und healthy sein.
Schritt 8: Optional – Basic Auth und Reverse Proxy aktivieren
Soll Gotenberg über das Internet erreichbar sein, sind zwei Schritte Pflicht:
Basic Auth aktivieren: In der .env die Zeile API_ENABLE_BASIC_AUTH=false auf true ändern und ein starkes Passwort setzen, dann docker compose up -d erneut ausführen. Alle curl-Anfragen müssen dann --user admin:passwort enthalten.
Reverse Proxy mit TLS vorschalten: Gotenberg selbst terminiert kein TLS. Für HTTPS einen Reverse Proxy (Caddy, Nginx Proxy Manager oder Traefik) vorschalten. Der Proxy leitet Anfragen an http://gotenberg:3000 weiter – bei gemeinsamen Docker-Netzwerken ohne exportierten Port.
Für Docker Compose Stacks, die Secrets aus einer .env sicher handhaben und Healthchecks korrekt einsetzen, gibt es weitere Best Practices in der Anleitung Docker Compose absichern: Secrets, Healthchecks, Non-Root und Read-Only.
Verifizieren: Mit aktivierter Basic Auth einen curl-Test ohne Credentials durchführen – der Server antwortet mit 401 Unauthorized. Mit --user admin:passwort erneut testen – jetzt kommt 200 OK zurück.
Troubleshooting / Typische Fehler
503 Supervisor run task: context deadline exceeded: Das Timeout wurde überschritten. Ursache: großes DOCX/XLSX oder langsamer Host. Lösung:API_TIMEOUT=120sin der.envsetzen unddocker compose up -dneu starten.- Layout-Verschiebungen bei DOCX-zu-PDF: Microsoft Core Fonts (Arial, Calibri, Times New Roman) fehlen aus Lizenzgründen. Lösung: eigenes
Dockerfileauf BasisFROM gotenberg/gotenberg:8erstellen und die gewünschten Fonts perapt-getoderCOPYhinzufügen. Ein Volume-Mount funktioniert hier nicht. - Port versehentlich öffentlich freigegeben: Wenn
0.0.0.0:3000:3000statt127.0.0.1:3000:3000gesetzt ist, ist Gotenberg ohne Auth von außen erreichbar. Sofort korrigieren unddocker compose up -dneu starten. - HTML referenziert
http://localhost:8080– kein Zugriff möglich: Im Container zeigtlocalhostauf Gotenberg selbst, nicht auf den Docker-Host. Lösung: Docker-interne DNS-Namen verwenden (http://mein-app-service:8080) oder unter Docker Desktophost.docker.internalnutzen. - Bilder und CSS fehlen im generierten PDF: Externe Ressourcen müssen per erreichbarer URL eingebunden oder als zusätzliche Dateien im multipart-Request mitgeschickt werden (
--form files=@style.css --form files=@logo.png). - Chromium
Print Error -32000/ Speichermangel: Sehr große HTML-Seiten überfordern Chromium. Lösung:deploy.resources.limits.memoryauf2Gerhöhen. - LibreOffice-Timeouts unter Last: LibreOffice läuft als einzelne Instanz mit Lock – Anfragen stellen sich sequenziell in die Warteschlange. Lösung: mehrere Gotenberg-Container hinter einem Load Balancer betreiben.
- Fehler nicht lesbar:
LOG_LEVEL=debugsetzen. Bei HTTP-400-Fehlern immer den Response-Body lesen – Gotenberg gibt dort das fehlerhafte Formularfeld im Klartext an.
Häufige Fragen
Braucht Gotenberg eine Datenbank oder Redis?
Nein. Gotenberg ist vollständig zustandslos – kein Redis, keine SQL-Datenbank, keine externe Abhängigkeit. Temporäre Dateien werden intern verwaltet und beim Container-Neustart gelöscht. Ein einzelner Container genügt.
Welches Image-Tag sollte ich verwenden?
Für Reproduzierbarkeit empfiehlt sich gotenberg/gotenberg:8 – dieses Tag folgt automatisch dem neuesten stabilen 8.x-Patch (aktuell v8.34.0). Das :latest-Tag kann auf eine neue Hauptversion springen und ist für den Produktionsbetrieb nicht empfohlen. Wer exakt pinnen will, nimmt gotenberg/gotenberg:8.34.0.
Kann ich nur HTML oder nur Office-Dokumente konvertieren – ohne das große Vollimage?
Ja. Gotenberg bietet zwei schlankere Varianten: gotenberg/gotenberg:8-chromium (nur Chromium, ca. 30 % kleiner – für HTML, URL, Markdown) und gotenberg/gotenberg:8-libreoffice (nur LibreOffice, ca. 40 % kleiner – für Office-Formate). Für den Einstieg und gemischte Workloads ist das Vollimage :8 die sichere Wahl.
Kann Gotenberg mehrere Dokumente gleichzeitig konvertieren?
Das hängt vom Modul ab. Chromium unterstützt bis zu 6 parallele Konvertierungen (einstellbar über CHROMIUM_MAX_CONCURRENCY). LibreOffice läuft als einzelne Instanz mit Lock – Anfragen werden sequenziell verarbeitet. Bei hoher LibreOffice-Last hilft nur horizontales Skalieren: mehrere Gotenberg-Container hinter einem Load Balancer.
Wie aktiviere ich Authentifizierung?
In der .env API_ENABLE_BASIC_AUTH=true setzen sowie GOTENBERG_API_BASIC_AUTH_USERNAME und GOTENBERG_API_BASIC_AUTH_PASSWORD mit echten Werten belegen, dann docker compose up -d. Bei curl-Anfragen --user benutzername:passwort ergänzen. Für den Produktionseinsatz ist zusätzlich ein Reverse Proxy mit TLS empfohlen.
Läuft Gotenberg auf ARM-Hardware (Raspberry Pi, Apple Silicon)?
Ja. Das offizielle Image unterstützt amd64, arm64v8, arm32v7, i386 und ppc64le. Docker wählt beim Pull automatisch das passende Manifest aus – kein manueller Eingriff nötig.
Kann ich Webhooks nutzen, um auf Konvertierungsergebnisse zu warten?
Ja. Gotenberg unterstützt asynchrone Verarbeitung via Webhook: Den HTTP-Header Gotenberg-Webhook-Url mit der Rückruf-URL mitgeben. Gotenberg sendet das fertige PDF per POST an diese URL, sobald die Konvertierung abgeschlossen ist – nützlich bei großen Dokumenten, die länger brauchen als ein synchrones Request-Timeout erlaubt.
Fazit
Gotenberg ist der schnellste Weg, eine skalierbare PDF-Konvertierung in einen bestehenden Workflow zu integrieren. Ein einzelner Container, keine Datenbank, keine Host-Abhängigkeiten – das macht Deployments auf unterschiedlichen Plattformen unkompliziert. Die REST-API ist bewusst einfach gehalten: multipart-POST rein, PDF raus. Wer HTML-zu-PDF und Office-Konvertierung in einer Anwendung braucht, findet in Gotenberg eine deutlich wartungsärmere Lösung als selbst installierte LibreOffice- oder wkhtmltopdf-Setups. Einen Punkt sollte man im Blick behalten: die sequenzielle Natur von LibreOffice unter Last – bei skalierenden Workloads früh über mehrere Container-Instanzen nachdenken.
Weiterführende Anleitungen und Quellen
- Paperless-ngx mit Docker einrichten: papierlose Dokumentenverwaltung mit OCR – ergänzt Gotenberg ideal für automatisierte Dokumenten-Workflows.
- Stirling-PDF mit Docker: der lokale PDF-Werkzeugkasten – für manuelle PDF-Operationen im Browser statt per API.
- Docker und Docker Compose auf Linux installieren: die Self-Hosting-Grundlage
- Caddy als Reverse Proxy einrichten: Anfänger-Anleitung mit automatischem HTTPS
Offizielle Quellen: Gotenberg Dokumentation und Gotenberg auf GitHub.