Build your image

PDF Server is distributed as a base Docker image. You build your service by layering your templates on top of it.

With Docker

# Dockerfile
ARG PDF_SERVER_IMAGE=registry.gitlab.com/c0va23/pdf-server:latest-chromium
FROM ${PDF_SERVER_IMAGE}
ADD templates templates
ADD compositions compositions
docker build -t my-pdf-service .
docker run --rm -it -p 9999:9999 my-pdf-service

The base image’s entrypoint runs the server, loading templates/ and compositions/ from the working directory. Configure everything else via environment variables (see the CLI & config reference).

With Earthly

# Earthfile
my-image:
  FROM registry.gitlab.com/c0va23/pdf-server:latest-chromium
  ENV TEMPLATES_RECURSIVE=true
  COPY --dir templates compositions .
  SAVE IMAGE --push registry.example.com/my-pdf-service:latest

Chromium vs Firefox

Two base image families are published:

ImageBrowserNotes
…:latest-chromiumChromiumSupports both runner and page factory modes
…:latest-firefoxFirefoxUse --factory-type=runner; ships different BROWSER_ARGS

Most deployments use Chromium. See Deployment for factory modes and pool tuning.

Nested (recursive) templates

If you organise templates into nested folders, enable recursive loading so names like invoices/eu are discovered:

-e TEMPLATES_RECURSIVE=true

Verify in CI

Fail the build if any template or example cannot render:

pdf-server validate --templates-dir ./templates --compositions-dir ./compositions

Run it

Your service now serves the HTTP API:

curl -X POST -H 'Content-Type: application/json' \
  -d '{"number":"A-1"}' \
  http://localhost:9999/templates/invoice/render > invoice.pdf

Put an authenticating reverse proxy in front of it and apply the hardening checklist before exposing it.