BerichtWerk ist ein schlanker PDF-Report-Generator, den ich für eigene Projekte entwickelt habe: eine JSON-Struktur und ein HTML-Template hinein — und Headless Chromium macht daraus ein sauber gesetztes PDF. Der Quellcode steht öffentlich zur Verfügung: gitlab.com/rn-public/berichtwerk (MIT-Lizenz). Dieser Beitrag stellt Konzept und Aufbau vor.
Die Ausgangsfrage
Die Zielsetzung war bewusst knapp formuliert: Wie kann man ohne großen Overhead einen eigenen PDF-Report-Generator bauen, der aus einfachen Seiten einen anspruchsvollen Bericht erzeugt?
Die klassischen Ansätze haben jeweils einen Haken. PDF-Bibliotheken wie FPDF oder TCPDF setzen jede Zelle von Hand — bei Tabellen, Ampel-Status oder mehrseitigen Berichten wird das schnell mühsam. Fertige Reporting-Suiten bringen im Gegenzug einen erheblichen Lizenz- und Administrationsaufwand mit. Und HTML-zu-PDF-Konverter der ersten Generation (Stichwort wkhtmltopdf) hinken bei modernem CSS hinterher.
Die Erkenntnis war: Der mit Abstand beste Layout-Motor für HTML ist der Browser selbst. Also nicht das Layout in PDF-Koordinaten nachbauen, sondern HTML und CSS das Renderen lassen lassen und die Druckfunktion von Chromium nutzen.
Der erste Schritt war ein Proof of Concept für die PHP-Bibliothek chrome-php/chrome, die Headless Chromium über das DevTools-Protokoll steuert: eine Seite laden, printToPDF aufrufen, PDF speichern. Der PoC überzeugte auf Anhieb — und aus dem Experiment wuchs im Laufe der Arbeiten der komplette Microservice mit HTTP-Endpoint, Template-Verzeichnis und Sicherheitsschaltern.
Der Generator ist dabei kein Laborprojekt: Er läuft seit fünf Jahren produktiv bei verschiedenen Kunden im Einsatz — zum Beispiel im industriellen Bereich für Dokumente der Qualitätssicherung, darüber hinaus für Managementberichte und zur Urkunden-Generierung. In der Praxis entstehen damit Berichte mit Diagrammen und einem Umfang von über 500 Seiten. Die Veröffentlichung als BerichtWerk 1.0 bündelt das gewachsene, im Tagesgeschäft bewährte System und macht es erstmals öffentlich zugänglich.
Architektur: JSON rein, PDF raus
BerichtWerk ist ein PHP-Microservice mit einem einzigen Job:
- Der Endpoint nimmt einen JSON-POST-Request entgegen.
- Darin zeigt
template_urlauf ein HTML- oder PHP-Template. - Ein Headless Chromium (gesteuert über
chrome-php/chromeund das DevTools-Protokoll) lädt das Template und druckt die Seite perprintToPDFin eine PDF-Datei. - Das PDF kommt als Download zurück.
Die Daten kommen auf zwei Wegen ins Template: einfache Parameter über den Query-String, komplexe Inhalte über den JSON-Body, der dem Template als globale Variable JSONData zur Verfügung steht. So kann ein und dasselbe Template erst im Browser begutachtet und dann per API automatisiert befüllt werden.
Der ganze Dienst läuft in einem Docker-Container; die Templates liegen als einziges Volume read-only im Container und werden über Apache ausgeliefert, damit Chromium auch CSS und Bilder des Templates laden kann. Die PDF-Fußzeile mit Seitenzahlen ist über Chromiums footerTemplate konfigurierbar.
Ein Bericht in der Praxis
Ein Aufruf, der den ISMS-Managementbericht mit eigenen Inhalten erzeugt, sieht so aus:
curl -X POST "http://localhost/report/berichtwerk.php" \
-H "Content-Type: application/json" \
--data '{"template_url":"isms-managementbericht/tpl.php?behoerde=Stadtverwaltung%20Musterstadt&zeitraum=Q3/2026","gesamtfortschritt":72,"gesamtstatus":"gruen","kernbotschaften":["Umsetzung im Plan."],"handlungsfelder":[{"kuerzel":"ISR","name":"Informationssicherheitsrahmen","fortschritt":90,"status":"gruen","kommentar":"Policies genehmigt."}]}' \
--output isms-bericht.pdf
Das Template isms-managementbericht rendert einen Managementbericht zum Umsetzungsstatus eines Informationssicherheits-Managementsystems (ISMS) für eine Behörde, Referenzrahmen BSI IT-Grundschutz. Der Bericht enthält eine Management-Summary mit Gesamtfortschritt und Ampel-Status, Handlungsfelder mit Fortschrittsbalken, Schlüsselmaßnahmen mit Verantwortlichkeiten und Zielterminen, Risiken, Meilensteine und Entscheidungsvorlagen.
Ohne JSONData rendert das Template plausible Demodaten — praktisch für Entwurf und Abstimmung des Layouts. Für den echten Einsatz genügt ein JSON-Body mit den Feldern gesamtfortschritt, kernbotschaften, handlungsfelder, massnahmen, risiken und so weiter, und derselbe Bericht erscheint mit den Live-Daten. Genau das war die Zielsetzung: einfache Seiten, anspruchsvolles Ergebnis.
So sieht das Ergebnis aus — die folgenden Seiten entstanden mit dem Aufruf des Templates und seinen Demo-Daten einer fiktiven Behörde:

Seite 1: Berichtskopf mit Metadaten (Behörde, Zeitraum, Version, Einstufung), Management-Summary mit Gesamtfortschritt und Ampel-Status sowie die Handlungsfelder mit Fortschrittsbalken.

Seite 2: Schlüsselmaßnahmen mit Verantwortlichkeiten, Status und Zielterminen — eine einfache HTML-Tabelle, vom Browser sauber über die Seite gesetzt.

Seite 3: Risiken, Meilensteine und Entscheidungsvorlagen für die Behördenleitung.
Den vollständigen Beispielbericht gibt es als Download: ISMS-Managementbericht (Demo, PDF, 3 Seiten) — erzeugt vom BerichtWerk-Endpoint mit den Demo-Daten der fiktiven Stadtverwaltung Musterstadt.
Technischer Anhang: Architektur im Detail
Datenfluss: JSON-POST an den Endpoint, Rendern des Templates im Headless Chromium, PDF als Download zurück.
Komponenten im Überblick
| Komponente | Aufgabe |
|---|---|
berichtwerk.php |
HTTP-Endpoint: liest den JSON-Body, instanziiert die Kernklasse, liefert das fertige PDF als Download |
Kernklasse BerichtWerk |
steuert Chromium über chrome-php/chrome (DevTools-Protokoll), prüft die Template-URL gegen den SSRF-Schutz, schreibt Logs, setzt die PDF-Fußzeile |
| Headless Chromium | lädt das Template über Apache, rendert es und erzeugt per printToPDF das PDF |
templates/ (Volume) |
HTML-/PHP-Templates, read-only in den Container gemountet; Apache liefert sie aus, damit Chromium auch CSS und Bilder des Templates laden kann |
Ablauf eines Requests
- Der Client sendet
template_urlund optionalJSONDatasowie Timeouts als JSON-POST. - Der Endpoint validiert die Template-URL: bei aktiviertem
BERICHTWERK_BLOCK_PRIVATEwerden private und reservierte IP-Adressen abgelehnt,BERICHTWERK_ALLOWED_HOSTSbegrenzt zulässige Hosts. - Chromium lädt das Template; einfache Parameter kommen aus dem Query-String, der JSON-Body steht dem Template als globale Variable
JSONDatabereit und überschreibt die Demo-Inhalte. - Nach einer konfigurierbaren Wartezeit (
sleep_pdf) ruftprintToPDFdie Seite ab — inklusive Fußzeile mit Seitenzahlen. - Das PDF geht als Download an den Client zurück; Logs landen in
berichtwerk.logundberichtwerk-request.log.
Konfiguration (Auszug)
| Variable | Wirkung |
|---|---|
BERICHTWERK_TPL_BASE |
Basis-URL, gegen die relative template_url-Pfade aufgelöst werden |
BERICHTWERK_BLOCK_PRIVATE |
1 blockiert Template-URLs mit privaten/reservierten IPs (SSRF-Schutz) |
BERICHTWERK_ALLOWED_HOSTS |
Regex für erlaubte Template-Hosts |
BERICHTWERK_PDF_FOOTER |
HTML für die rechte Seite der PDF-Fußzeile; Seitenzahlen bleiben fix |
browser_timeout / sleep_pdf |
Request-Felder: Timeout für das Rendering (max. 120 s) und Wartezeit vor der PDF-Erzeugung (max. 30 s) |
Beispiel-Umgebung per KI-Agent aufsetzen
Wer BerichtWerk ausprobieren möchte, braucht nur Docker, den Quellcode und ein Template. Der häufigste Stolperstein beim ersten Start: templates/ ist als Volume für individuelle Inhalte gedacht und wird im Repository bewusst ausgeblendet (gitignored) — nach dem Klonen existiert das Verzeichnis nicht, und der Container startet ohne ein Template ins Leere. Der folgende Prompt übergibt einem lokalen KI-Agent die komplette Einrichtung: Voraussetzungen prüfen, Repository klonen, ein einfaches IT-Managementbericht-Template anlegen, den Dienst bauen und mit einem echten PDF-Aufruf verifizieren.
Richte auf diesem Rechner eine lokale Beispiel-Umgebung für BerichtWerk ein.
Projekt: https://gitlab.com/rn-public/berichtwerk
Ziel: Der PDF-Generator läuft im Docker und erzeugt einen Beispielbericht als PDF.
Vorgehen:
1. Prüfe die Voraussetzungen: docker und docker compose müssen verfügbar sein,
Port 80 muss frei sein. Fehlt etwas, melde das klar und breche ab —
installiere nichts systemweit ohne Rückfrage.
2. Klone das Repository in ein Verzeichnis deiner Wahl
(z. B. ~/Projekte/berichtwerk), sofern es dort noch nicht liegt.
3. templates/ ist im Repository ausgeblendet (gitignored) und existiert nach
dem Klon nicht. Lege das Verzeichnis an und erzeuge ein einfaches
Beispiel-Template templates/it-managementbericht/tpl.php: ein
IT-Managementbericht als HTML/PHP-Seite mit Druck-Styling (A4, @page).
Query-Parameter mit Standardwerten: organisation, zeitraum, datum.
Inhalt mit festen Demodaten: 1. Management-Summary mit Gesamtstatus
(Ampel), 2. IT-Kennzahlen (Systemverfügbarkeit, offene Vorfälle,
Backup-Erfolgsquote — Tabelle mit Ampelfarben), 3. Projekte (Tabelle mit
Status), 4. Risiken und Handlungsempfehlungen (Liste). Alle Ausgaben mit
htmlspecialchars maskieren. Dateirechte: Verzeichnisse 755, Dateien 644 —
Apache läuft als www-data.
4. Kopiere .env.example nach .env, sofern .env nicht existiert. Die Defaults
passen für die lokale Entwicklung.
5. Baue und starte den Dienst: docker compose up --build -d
6. Prüfe: http://localhost/ antwortet, und
http://localhost/report/erep-tpl/it-managementbericht/tpl.php liefert die
Beispielseite (HTTP 200).
7. Erzeuge einen Beispielbericht über die API:
curl -X POST "http://localhost/report/berichtwerk.php" \
-H "Content-Type: application/json" \
--data '{"template_url":"it-managementbericht/tpl.php?organisation=Beispielfirma&zeitraum=Q3/2026"}' \
--output it-managementbericht.pdf
Bestätige mit file it-managementbericht.pdf und
pdfinfo it-managementbericht.pdf, dass ein PDF mit mindestens einer Seite
entstanden ist.
8. Melde zum Abschluss: die erreichbaren URLs, den Pfad des erzeugten PDFs
und das Kommando zum Herunterfahren (docker compose down).
Randbedingungen:
- Keine Commits, Pushes oder Änderungen an entfernten Systemen.
- Bei einem Portkonflikt auf 80: stoppe fremde Dienste nicht eigenmächtig,
sondern melde den Konflikt.
- Weichen Schritte von der README des Repos ab, folge der README.
Am Ende sollte der Agent mit zwei erreichbaren URLs und einem erzeugten PDF melden — mehr braucht eine Beispiel-Umgebung nicht. Wer anschließend eigene Berichte gestalten will, ersetzt das Beispiel-Template durch ein eigenes Layout unter templates/; das Schema ist immer gleich: HTML/PHP als Vorlage, Daten per Query-String oder JSONData, der Generator macht daraus das PDF.
Sicherheit und Betrieb
Ein bewusst schlanker Dienst verzichtet auf alles, was ein Reverse Proxy besser kann. Konkret:
- Der Webservice selbst hat keine Authentifizierung — Absicherung gehört vor die Tür, an den Webserver bzw. Reverse Proxy.
- Da
template_urlURLs entgegennimmt, blockiert ein SSRF-Schutz private und reservierte IP-Adressen auf Wunsch ab (BERICHTWERK_BLOCK_PRIVATE); erlaubte Hosts lassen sich per Regex einschränken (BERICHTWERK_ALLOWED_HOSTS). - Timeouts für Browser-Rendering und Wartezeit vor der PDF-Erzeugung sind begrenzt und konfigurierbar.
Damit bleibt der Kern klein, aber die Angriffsfläche kontrollierbar — ein Kompromiss, der für einen internen Microservice passt.
Fazit
BerichtWerk zeigt, dass man für anspruchsvolle PDF-Berichte keine schwere Reporting-Plattform braucht: ein PHP-Endpoint, Headless Chromium und ein HTML-Template reichen aus. Das Layout lebt in gewöhnlichem HTML und CSS, die Daten kommen als JSON, und der Browser kümmert sich um alles, was sonst mühsam ist — Seitenumbruch, Tabellen, Typografie.
Der Generator ist als Version 1.0 abgeschlossen und unter MIT-Lizenz veröffentlicht — nach fünf Jahren Produktiveinsatz bei verschiedenen Kunden ist das Kernkonzept ausgereift: Quellcode, Docker-Setup und Templates gibt es im öffentlichen Projekt unter gitlab.com/rn-public/berichtwerk. Ausbau-Ideen für später: Template-Pakete für weitere Berichtstypen und ein Command-Line-Interface für Batch-Generierung.