Released docs. You are viewing the documentation published with v0.34.0. Development docs are available at Latest.
You have a sealed template bundle and callers that need PDFs, and you want registry-render serve
answering them. At the end of this page one registry-render process serves your bundle on a private
address behind your TLS proxy, answers GET /health and GET /ready, renders under API-key
authentication with a per-render audit trail, and shuts down within a bounded window.
Registry Render is pre-release software for institutional pilots. There is no published installer yet, so this page builds the binary from source; release packaging follows the standard roster checklist.
Rendering your first document covers the bundle side: scaffolding, authoring, and sealing. This page starts from a sealed bundle directory.
What you provide
Section titled “What you provide”- One sealed bundle directory, mounted read-only.
registry-render sealwrites per-file sha256 hashes into the bundle’smanifest.yaml, and serve verifies the seal at startup and again in the worker on every request, so a bundle that drifts while the service runs is refused, not rendered. Treat the directory as content the deployment does not write. - Two secrets in owner-only files: a caller API key and an audit chain key. The API key is at least 32 bytes of ASCII material; serve trims exactly one trailing line ending from the file and refuses to start if the key carries any other whitespace, because a silently mis-armed key would reject every caller while health stays green.
- One directory for the audit ledger. The process appends a keyed hash-chained JSONL ledger and refuses a directory it cannot make owner-only. One process writes one chain; plan the directory accordingly.
- A TLS proxy or ingress in front of the listener. The process serves plain HTTP, defaults to
127.0.0.1:8080, and refuses to start on a public or all-interfaces address. TLS termination, HSTS, and client addresses stay with your proxy, and the listener reads no forwarded header. - A host for one process. Renders run in supervised worker processes that are killed at the configured timeout and capped in address space on Linux, so one pathological template costs its timeout, not the service.
Build the binary
Section titled “Build the binary”From a Registry Stack checkout, build with the workspace lockfile, because the pinned compression stack is part of Render’s byte contract:
cargo build --release --locked -p registry-rendertarget/release/registry-render --versionThe version line names the release and the Typst pin, for example registry-render <version> (typst 0.15.1). Record it: every audit event carries the same string, and a stored PDF’s provenance is
this binary plus the bundle hash.
Write the runtime configuration
Section titled “Write the runtime configuration”Serve mode reads one YAML file. Every field is a contract, and unknown fields are refused:
apiVersion: render.registrystack.org/v1alpha1kind: RenderRuntimeserver: bind: 127.0.0.1:8080 shutdownGraceSeconds: 30bundle: path: /var/lib/registry-render/bundleauth: apiKeyRef: secret:file/render-api-keylimits: renderTimeoutSeconds: 20 maxOutputBytes: 8388608 maxRequestBodyBytes: 8388608 maxConcurrency: 4audit: directory: /var/lib/registry-render/audit integrityKeyRef: secret:file/render-audit-key maxSegmentBytes: 67108864Secret references resolve against files beside the runtime file or named environment variables.
Relative bundle.path and audit.directory values anchor to the runtime file’s directory too, so
the same file behaves identically wherever the process is launched from.
The limits carry hard ceilings the runtime refuses to exceed: the output cap cannot exceed 8 MiB
and the request body cap 64 MiB, so a configuration mistake cannot unbound the process. On
shutdown the process drains in-flight renders for one grace period, gives open connections a
second one, and then abandons the rest; shutdownGraceSeconds is therefore the number to align
with your stop timeout, not a decorative field. It must be between 1 and 3600 seconds: zero would
drop the renders it exists to protect, and startup refuses both ends.
Start and verify
Section titled “Start and verify”Start the process with the runtime file, and check both endpoints before sending traffic:
target/release/registry-render serve --runtime /etc/registry-render/runtime.yaml &curl -s http://127.0.0.1:8080/healthcurl -s http://127.0.0.1:8080/readyGET /health answers with the bundle version and hash and the renderer version, so you can
reconcile the running service against what you deployed. GET /ready includes the audit ledger:
if the sink cannot answer, readiness fails. Both are value-free.
registry-render healthcheck --runtime <file> performs the same probe for a supervisor script.
The rendering call
Section titled “The rendering call”Callers POST one JSON body to /v1/render/{type} with the API key and the issuance time:
curl -s -X POST http://127.0.0.1:8080/v1/render/<document-type> \ -H "Authorization: Bearer <api-key>" \ -H "Content-Type: application/json" \ -H "Accept: application/pdf" \ -o receipt.pdf \ -D headers.txt \ -d '{"issuedAt":"<issuance-time>","data":<document-data>}'grep -i x-registry headers.txtThe body’s data object must satisfy the document’s JSON Schema; violations come back as
problems with JSON pointers into the data, and registry-render validate dry-runs that check without
rendering.
The default response is the PDF bytes with X-Registry-Pdf-Sha256,
X-Registry-Data-Sha256, and X-Registry-Document-Version headers; Accept: application/json
returns the same facts plus the PDF as base64 for callers that cannot take binaries. Requests
must declare Content-Length: chunked transfer is refused, because a chunked body that exceeds
the limit mid-stream cannot be answered at all. Idempotency-Key is echoed and audited as an
opaque correlation id; rendering is deterministic, so no deduplication exists or is needed.
Failures are RFC 9457 problem documents with a closed vocabulary, on HTTP and in the CLI’s exit codes, and every render response, success or refusal, is preceded by its audit event: if the ledger cannot be written, the render fails closed. Refusals that precede authentication, a 401 or an oversized body, are audited on a best-effort basis and still answer.
Verify the ledger
Section titled “Verify the ledger”The ledger is a keyed hash chain in sealed segments. Stop the process first: the writer holds the active segment; once no writer holds it, the newest segment is verified too. Then:
target/release/registry-render audit-verify --runtime /etc/registry-render/runtime.yamlThe command prints the record and segment counts. If it reports zero records against a non-empty ledger, it says so: either the process is still running, or the chain is not what you expect, and both are worth knowing before an audit depends on it. One event per render, value-free: hashes, versions, the caller’s key fingerprint, and the trace and correlation ids, never data values or image bytes.
Upgrade discipline
Section titled “Upgrade discipline”A Render binary is exact source plus the Typst pin plus the lockfile, and any change to the Typst pin, the compression stack, or a template changes output bytes by design. Treat every upgrade as a golden-hash review: the two-OS job in CI fails on any byte difference and the diff is the review. Never overwrite a deployment’s bundle content without resealing, because the seal, not the directory listing, is what serve trusts.