# Spec 378 PDF Renderer Decision Matrix Date: 2026-06-14 Scope: Management Report PDF v1 renderer/runtime governance only. Outcome: approved with controls for Gotenberg 8 Chromium as an internal Docker service. ## Repo Safety Baseline - Branch: `378-management-report-pdf-v1` - HEAD: `f1eadadf docs: add spec 377 post-productization browser reaudit closeout gate (#448)` - `git status --short --branch`: branch `378-management-report-pdf-v1`; untracked `specs/378-management-report-pdf-v1/` - `git diff --name-only`: no tracked-file diff before this governance update - Staged files before this governance update: none observed - Dirty/untracked files before this governance update: `specs/378-management-report-pdf-v1/` - Spec 378 files already changed before this governance update: present as an untracked active spec directory - Other specs affected before this governance update: none observed - Existing runtime PDF renderer in `apps/platform/composer.json`: none - Existing PDF runtime in `apps/platform/package.json`: none; `playwright` is listed only under `devDependencies` - Docker infrastructure: root `docker-compose.yml` exists with `laravel.test`, `queue`, `pgsql`, and `redis`; `apps/platform/docker-compose.yml`, `apps/platform/Dockerfile`, and `apps/platform/docker/` are not present ## Gate Verification Result: gate can be updated with approved renderer. - Spec 378 was correctly blocked at the renderer/package gate. - The missing approved production-safe PDF renderer was documented in `spec.md`, `plan.md`, `tasks.md`, and `checklists/requirements.md`. - Downstream implementation tasks remain open. - No runtime implementation was started in tracked app/runtime files. - Playwright is dev/browser tooling only in `apps/platform/package.json`. - No Composer PDF renderer is present in `apps/platform/composer.json`. - Root Docker Compose already has an internal service network, so an internal renderer service can fit the deployment model, but no Gotenberg service exists yet. ## Decision Matrix | Candidate | Runtime model | Deployment impact | Security posture | Maintenance posture | Layout quality | Laravel integration complexity | Docker/Dokploy fit | License/commercial concern | Enterprise suitability | Decision | Rationale | |---|---|---|---|---|---|---|---|---|---|---|---| | Gotenberg 8 Chromium internal service | Separate Docker HTTP service using Chromium HTML-to-PDF route; Laravel calls it through a narrow `PdfRenderingGateway` / `PdfRendererClient` | Requires adding a pinned internal service, health check, env config, timeouts, request/output limits, and Dokploy service wiring | Strongest option when kept internal-only with no public port, no user-provided URL rendering, outbound restrictions, server-generated HTML, and structured failure mapping | Operationally patchable as a service image; avoids embedding Chrome/Node in the app container; image must be pinned to an explicit 8.x Chromium tag or digest | High for modern HTML/CSS report layouts, page breaks, headers/footers, and branded management reports | Moderate; HTTP client and gateway contract are needed, but no Composer PDF package or browser binary in Laravel | Strong; repo is Sail/Docker/Dokploy-oriented and already uses a compose network | Open-source service; no per-document commercial licensing identified for report rendering; production image/version governance still required | Best fit for enterprise SaaS report PDFs under controls | approved with controls | Matches Docker-first deployment, isolates browser runtime from Laravel, supports Chromium-quality rendering, and can be governed with network, timeout, egress, and artifact controls. | | Spatie Browsershot / Spatie Laravel PDF with Browsershot | Laravel package invokes Puppeteer/Chrome through Node | Adds Composer package plus Node/Puppeteer/Chrome runtime or custom image changes near the app container | Larger app-container attack/ops surface; browser process ownership, temp files, sandboxing, and binary path management move into Laravel runtime | Actively used but coupled to Node/Puppeteer/Chrome versions and host dependencies | High Chromium quality | Low at code level but high at runtime; needs Node 22+, Puppeteer, Chrome, binary paths | Weaker for this repo because it pulls browser runtime into app/queue containers | MIT-style package ecosystem, but operational dependency surface is larger | Good for smaller apps; not preferred here | rejected | It solves layout quality but violates the preferred isolation boundary: no direct Chromium/Node/browser runtime in Laravel app or queue containers for v1. | | dompdf / barryvdh/laravel-dompdf | Pure PHP package inside Laravel | Simple Composer install and no external service | Smaller infrastructure surface, but renderer runs in app process and remote resource/file options require careful hardening | Mature PHP package, but HTML/CSS engine is intentionally limited | Not sufficient for modern enterprise report layouts; limited CSS, no JavaScript, weak repeating header/footer support | Low | Good deployment simplicity | LGPL/transitive license review required; no commercial blocker | Suitable for simple PDFs only | rejected | Simplicity is outweighed by CSS/layout limitations for branded management reports with reliable page breaks, tables, and headers/footers. | | wkhtmltopdf / Snappy | Native binary or PHP wrapper around wkhtmltopdf using Qt WebKit | Requires binary installation, container image changes, and runtime process management | Higher maintenance and security risk due to old browser engine lineage | Poor; upstream wkhtmltopdf repository is archived/read-only and organization is marked unmaintained | Historically useful but outdated rendering engine | Moderate; wrappers exist but depend on external binary | Weak for new platform work | LGPL-style tooling; main issue is maintenance, not license | Not acceptable for new enterprise SaaS renderer | rejected | The upstream status and Qt WebKit legacy make it a poor default for a new security-sensitive SaaS runtime. | | PrinceXML | Commercial print/Paged Media engine | Requires licensed binary/service integration and procurement | Strong if licensed and isolated correctly | Strong commercial vendor posture | Highest print/Paged Media quality | Moderate; custom client/integration required | Good if packaged as service or sidecar | Commercial/OEM license and cost review required | Excellent for premium publishing/compliance documents | premium future option | Overkill for v1 management reports, but worth revisiting for premium print workflows or legally constrained document classes if licensing is approved. | ## Selected Renderer Decision: approved with controls. Approved renderer family: Gotenberg 8 Chromium internal PDF rendering service. Production pinning rule: use an explicit Gotenberg 8 Chromium image tag or immutable digest during the runtime config task. Do not use `latest`. Do not treat the unpinned major tag as sufficient for production promotion unless the deployment governance explicitly accepts that risk. Approved scope: - Internal PDF rendering infrastructure for TenantPilot report-style documents. - Server-generated HTML-to-PDF using the Chromium conversion route. - Use by Spec 378 Management Report PDF v1 through a Laravel gateway/client abstraction. Not approved: - Legal invoice generation. - German B2B e-invoicing. - XRechnung, ZUGFeRD, or Factur-X compliance. - GoBD archival. - Tax calculation. - Invoice numbering. - Billing compliance. - Public renderer exposure. - User-supplied URL-to-PDF rendering in v1. - Direct Node/Chromium/browser runtime inside the Laravel app or queue containers. ## Consulted Primary Sources - Gotenberg installation and image variants: https://gotenberg.dev/docs/getting-started/installation - Gotenberg Chromium HTML-to-PDF route: https://gotenberg.dev/docs/convert-with-chromium/convert-html-to-pdf - Gotenberg configuration flags: https://gotenberg.dev/docs/configuration - Gotenberg outbound URL filtering: https://gotenberg.dev/docs/outbound-url-filtering - Gotenberg health check: https://gotenberg.dev/docs/system/get-health-check - Spatie Browsershot requirements: https://spatie.be/docs/browsershot/v4/requirements - Dompdf README: https://github.com/dompdf/dompdf - Spatie Laravel PDF DOMPDF driver limitations: https://spatie.be/docs/laravel-pdf/v2/drivers/using-the-dompdf-driver - wkhtmltopdf status: https://wkhtmltopdf.org/status.html - wkhtmltopdf archived repository evidence: https://github.com/wkhtmltopdf/wkhtmltopdf/issues/5160 - Snappy wrapper dependency: https://github.com/KnpLabs/snappy/ - PrinceXML product and licensing: https://www.princexml.com/ and https://www.princexml.com/purchase/