Template author workflow
You use PDF Server as a base image: you write templates and schemas, test them, and ship your own image. Here is the development cycle that supports that.
1. Scaffold a template
Create a directory under your templates/ folder. The minimum is a body
template; add schema, params, assets, partials, and examples as needed.
templates/
invoice/
template.handlebars # body (required)
schema.json # validate request data
params.json # PDF options (margins, format, …)
examples/
sample.json # example dataset for the tester + CI
See Your first template and Template structure.
2. Iterate with hot reload + the tester UI
Run the server against your templates with reload mode always so edits take
effect without a restart:
docker run --rm -it -p 9999:9999 \
-e TEMPLATES_RELOAD_MODE=always \
-v "$PWD/templates:/templates" \
registry.gitlab.com/c0va23/pdf-server:latest-chromium
Then open http://localhost:9999/ and use the tester UI: pick your template, paste or load example data, and preview the rendered PDF. Edit the template file, re-render — the change is live. See Tester UI.
The examples/*.json datasets you commit show up in the tester and are what CI
renders, so they double as fixtures and documentation.
3. Validate before you ship
The validate subcommand parses every template and renders every example,
exiting non-zero on any failure. Wire it into CI so a broken template fails the
build:
pdf-server validate \
--templates-dir ./templates \
--compositions-dir ./compositions
Filter to a subset while iterating:
pdf-server validate invoice/sample
4. Build your image
Layer your templates onto the base image:
ARG PDF_SERVER_IMAGE=registry.gitlab.com/c0va23/pdf-server:latest-chromium
FROM ${PDF_SERVER_IMAGE}
ADD templates templates
ADD compositions compositions
See Build your image for the full deployment story (Chromium vs Firefox, recursive templates, compositions).
5. Deploy and operate
Run the image on your infrastructure behind your own auth/proxy. Tune the browser pool, timeouts, and factory mode for your load, and turn on tracing if you use OpenTelemetry. See Operations and Tuning.
The loop, in short
scaffold → run with reload + tester UI → edit/preview → validate → build image → deploy
Everything except the final deploy runs locally with a single container and no rebuilds.