Documentation
Self-hosting
Run Katagami with PostgreSQL and storage that you control.
Choose your storage
Production Compose pulls the exact Katagami release image and starts only PostgreSQL beside it. It does not run MinIO. Point Katagami at an S3-compatible bucket that you operate.
cp .env.prod.example .env
chmod 600 .env
docker compose --env-file .env -f docker-compose.prod.yml up -d
KATAGAMI_IMAGE selects the release and is required; .env.prod.example pins it to
the exact immutable tag ghcr.io/bro3886/katagami:0.0.2. The production file fails
docker compose config if KATAGAMI_IMAGE is unset, so a deployment can never
silently fall back to a moving or wrong tag. Upgrade by changing that value to a
newer exact tag, pulling, and recreating the renderer; committed examples never
consume a moving tag.
The example uses an external MinIO service:
S3_ENDPOINT=https://minio.example.internal
S3_FORCE_PATH_STYLE=true
Use S3_FORCE_PATH_STYLE=false for AWS S3. The storage identity needs HeadBucket,
CreateBucket, GetObject, and PutObject permissions. Katagami uses conditional
object creation, so the provider must support If-None-Match: *.
Protect the service
The container binds the renderer to 127.0.0.1. Put a TLS reverse proxy and your own
access policy in front of it when remote clients need access. Keep PostgreSQL private.
The dedicated metrics listener defaults to 0.0.0.0:9090 in the container and has no
host port mapping; allow an internal monitoring network to scrape it, never a public
proxy or ingress. Treat .env as a secret.
Metrics do not require a collector. If you export traces, set
OTEL_EXPORTER_OTLP_ENDPOINT to an internal OTLP HTTP/protobuf base URL. The
renderer appends /v1/traces. The collector/operator owns sampling, storage, and
retention. A disabled or unreachable exporter does not affect renders, liveness,
or readiness.
For a collector that uses a private CA, mount a trusted PEM CA bundle into the
renderer and set SSL_CERT_FILE to its container path. Keep certificate
verification enabled.
Upgrade safely
- Back up PostgreSQL and the object-storage bucket.
- Point
KATAGAMI_IMAGEat the new exact tag, then pull and start one updated renderer. - Call
GET /v1/templatesand render a known template version. - Start more renderer replicas only after the first renderer responds successfully.
Schema migrations move forward only. A manifest and the objects it names are one durable unit, so restore both PostgreSQL and object storage together.
Capacity and lifecycle policy
Set these positive-integer environment variables for each renderer deployment:
| Variable | Default | Purpose |
|---|---|---|
REGISTRY_MAX_VERSIONS |
128 |
Per-renderer LRU entry cap |
REGISTRY_MAX_BYTES |
67108864 |
Per-renderer LRU byte cap |
MAX_ACTIVE_VERSIONS |
10000 |
Deployment-wide active-version cap |
DEFAULT_TEMPLATE_ACTIVE_VERSIONS |
100 |
Default cap for a template |
MAX_STORAGE_BYTES |
1073741824 |
Catalog-accounted retained-version byte cap |
METRICS_ADDR |
0.0.0.0:9090 |
Dedicated Prometheus listener; private only when you do not publish or route it publicly |
OTEL_EXPORTER_OTLP_ENDPOINT |
unset | Optional operator-managed OTLP HTTP/protobuf base URL |
WORKER_POOL_SIZE |
2 |
Target supervised Typst child workers |
RENDER_DEADLINE_MS |
10000 |
Per-worker render deadline |
WORKER_MEMORY_BYTES |
536870912 |
Per-worker address-space limit on Unix except macOS; unsupported native hosts fail startup |
WORKER_MAX_REQUEST_BYTES |
16777216 |
Maximum worker IPC request |
WORKER_MAX_RESPONSE_BYTES |
33554432 |
Maximum worker IPC response |
WORKER_RESTART_BUDGET |
5 |
Deaths allowed in the restart window |
WORKER_RESTART_WINDOW_MS |
60000 |
Restart-budget window |
WORKER_RESTART_BACKOFF_MS |
250 |
Replacement-spawn delay |
WORKER_RECOVERY_COOLDOWN_MS |
30000 |
Degraded-pool recovery delay |
WORKER_LIVENESS_INTERVAL_MS |
5000 |
Liveness-probe interval |
WORKER_LIVENESS_TIMEOUT_MS |
1000 |
Liveness deadline, no greater than interval |
WORKER_BINARY defaults to the sibling katagami-worker binary. Production Compose
sets it explicitly and applies RENDERER_CPUS (default 2.0) and
RENDERER_MEMORY_LIMIT (default 2g) to the server and its entire worker pool
together.
Native startup fails closed on macOS and non-Unix hosts because
WORKER_MEMORY_BYTES cannot be enforced. Linux containers enforce that per-worker
limit in addition to RENDERER_MEMORY_LIMIT for the complete container.
Startup does not preload the catalog. A cold render loads and SHA-256-verifies its stored pack through PostgreSQL and S3. Cache eviction never blocks publication.
Per-template limits are SQL-resident operator policy, never publish input:
UPDATE templates SET active_version_limit = 3 WHERE id = 'invoice';
UPDATE templates SET active_version_limit = NULL WHERE id = 'invoice';
Retirement frees an active slot while preserving objects. Unretirement verifies the stored pack and rechecks active capacity before it becomes renderable.
Retirement does not free MAX_STORAGE_BYTES. A legacy object_bytes = NULL row is
repaired by one cold SHA-verified render that records exact bytes; startup does not
reload the catalog. New publications require exact bytes. Until all retained rows are
accounted, publication fails closed with 503 catalog retained-version byte capacity exceeded or incomplete.