Monitoring
Each release ships an optional Prometheus with alert rules for a stopped service, renders that are not starting, an expiring license and more.
Turn on Prometheus
Section titled “Turn on Prometheus”-
Add this line to
.envin the installation directory:Terminal window COMPOSE_PROFILES=monitoring -
Run
docker compose up -d. -
The Prometheus UI listens only on the server’s own
127.0.0.1, port 9090. From your computer, open an SSH tunnel to the server, then browse tohttp://localhost:9090/alerts:Terminal window ssh -L 9090:127.0.0.1:9090 you@pdf-server
The port and how long metrics are kept are
monitoring settings. Prometheus’s memory limit is
MONITORING_MEMORY_LIMIT.
Get notified
Section titled “Get notified”The bundled Prometheus shows alerts only in its UI. To receive them, add
Alertmanager as below, or
use your own monitoring stack. Put every file below in the
installation directory, next to compose.yaml.
-
Copy the bundled Prometheus configuration out of its container:
Terminal window docker compose cp prometheus:/etc/prometheus/prometheus.yml prometheus.yml -
Add this block to the end of your copy of
prometheus.yml:alerting:alertmanagers:- static_configs:- targets: [alertmanager:9093] -
Create
alertmanager.ymlto say where alerts go (email, chat, webhook). See Alertmanager’s configuration. -
Add both to
compose.override.yaml(create it if you do not have one). Pin the Alertmanager image to its current release, and mount any filealertmanager.ymlreads, such as a password file.services:prometheus:volumes:- ./prometheus.yml:/etc/prometheus/prometheus.yml:roalertmanager:image: prom/alertmanager:v0.34.1profiles: [monitoring]restart: unless-stoppedvolumes:- ./alertmanager.yml:/etc/alertmanager/alertmanager.yml:romem_limit: 256m -
Run
docker compose up -d, then send a test alert and check that it arrives:Terminal window docker compose exec alertmanager amtool alert add TypefoldTest \--alertmanager.url=http://127.0.0.1:9093
Use your own monitoring stack
Section titled “Use your own monitoring stack”If you already run Prometheus elsewhere:
-
Scrape
/metricson port9464of everyapireplica and port8081of everyworkerreplica. Name the scrape jobsapiandworker: the “down” alerts look for those names. -
Copy the alert rules out of the
prometheusimage named incompose.yaml(the bundled Prometheus does not need to run), and load them into your Prometheus:Terminal window docker run --rm --entrypoint cat <the prometheus image in compose.yaml> /etc/prometheus/alerts.yml > alerts.yml
Neither port is published outside the Compose network, so your Prometheus must join that network,
or you publish the ports in compose.override.yaml.
Health checks
Section titled “Health checks”The bundled Compose file already health-checks the api and worker services. You need these endpoints only for
a load balancer or orchestrator of your own. None needs a credential.
| Endpoint | Service | Answers 200 when |
Use it for |
|---|---|---|---|
GET /health |
api |
PostgreSQL and Redis both answer within 2 seconds | Readiness and load balancer checks |
GET /health/live |
api |
The process can answer HTTP | Restart (liveness) checks |
GET /health |
worker |
It is ready to render and Redis is connected | The worker container’s own health check |
Use /health/live, not /health, for restarts, or a database outage restarts every API replica
at once. The public address serves only the API’s /health. The API’s /health/live (port
3000) and the worker’s /health (port 8081) are reachable only inside the Compose network;
publish the ports in compose.override.yaml if your checks run elsewhere.
When the API cannot reach a dependency, /health answers 503 and names it (database or
redis):
{ "status": "unavailable", "unavailable": ["database"] }Alert rules
Section titled “Alert rules”| Alert | Severity | Fires when |
|---|---|---|
TypefoldApiDown |
critical | An api replica cannot be scraped for 2 minutes |
TypefoldNoApi |
critical | No api replica is up for 2 minutes |
TypefoldWorkerDown |
critical | A worker replica cannot be scraped for 2 minutes |
TypefoldNoWorkers |
critical | No worker replica is up for 2 minutes |
TypefoldDependencyUnavailable |
critical | The API cannot read PostgreSQL or Redis for 2 minutes |
TypefoldRenderProcessCold |
critical | A worker has not been ready to render for 5 minutes |
TypefoldWarmUpFailing |
warning | A worker’s render process failed to start 3 or more times in an hour |
TypefoldApiErrorRate |
warning | More than 5% of API requests return 5xx for 10 minutes |
TypefoldApiSlow |
warning | API p95 latency is above 2 seconds for 15 minutes, not counting requests that wait for a document |
TypefoldQueueBacklog |
warning | The next render to start has waited more than 5 minutes |
TypefoldRendersStuckQueued |
critical | A render has been queued for more than 15 minutes |
TypefoldRendersStuckProcessing |
warning | A render has been processing for more than 15 minutes |
TypefoldRenderFailureRate |
warning | More than 10% of render attempts fail for 15 minutes with a retryable error or a timeout |
TypefoldRenderProcessRestarts |
warning | A worker restarted its render process more than 10 times in 15 minutes after failures or timeouts |
TypefoldBackgroundJobStale |
warning | Render repair, file cleanup, workspace cleanup or webhook delivery has not succeeded for 15 minutes, or render expiry for 3 hours |
TypefoldBackgroundJobNeverSucceeded |
warning | A background job keeps failing and has not succeeded since the API started |
TypefoldWorkspaceCleanupStuck |
warning | A deleted workspace’s data has not been purged after an hour |
TypefoldLicenseRefusingRenders |
critical | The license has refused renders for 5 minutes |
TypefoldLicenseCheckInFailing |
warning | The installation has not reached its license server for about 2 days; rendering stops within 5 days |
TypefoldLicenseExpiring |
warning | The license expires within 14 days |
TypefoldLicenseInGrace |
warning | The license has expired and is in its grace period |
What to do when an alert fires
Section titled “What to do when an alert fires”| Alert | First things to check |
|---|---|
TypefoldApiDown, TypefoldWorkerDown, TypefoldNoApi, TypefoldNoWorkers |
docker compose ps, then the logs of the service that is not running |
TypefoldQueueBacklog |
Workers are up; if they are all busy, add workers |
TypefoldRendersStuckQueued |
The api logs for Render queue operation failed. |
TypefoldRenderProcessCold, TypefoldWarmUpFailing |
The worker logs for Render process warm-up failed. |
TypefoldBackgroundJobStale, TypefoldBackgroundJobNeverSucceeded |
The api logs for the failing job’s error |
TypefoldWorkspaceCleanupStuck |
Storage permissions, Object Lock or legal holds that block deleting files |
| License alerts | When rendering stops |
Read a service’s log with docker compose logs, such as docker compose logs api --since 1h.
LOG_MAX_SIZE and LOG_MAX_FILE set how much
Docker keeps. To send logs elsewhere, set logging for each service in compose.override.yaml.
Lines worth knowing:
| Log line | Service | Meaning |
|---|---|---|
Self-hosted setup required. |
api |
No platform administrator yet; the line carries the setupToken |
Made ADMIN_EMAIL the first platform administrator. |
api |
First start with ADMIN_EMAIL; the line carries the address |
DEPLOY_API_KEY created the deploy key. |
api |
The deploy key was created, or replaced after its value changed |
Neither email nor Microsoft sign-in is configured |
api |
Only password accounts can sign in; see sign-in methods |
Self-hosted license loaded. |
api |
The license state at startup |
Self-hosted license state changed. |
api |
The license state changed after a check-in |
License check-in failed |
api |
The installation could not reach its license server |
Webhook secret encryption failed |
api |
Stored webhook secrets could not be encrypted with the current key |
Signing key encryption failed |
api |
Stored signing certificate keys could not be encrypted with the current key |
Render process warm-up failed. |
worker |
A worker could not get ready to render |
Metrics
Section titled “Metrics”The API serves GET /metrics on port 9464 and the worker on port 8081. Neither is reachable
from outside the Compose network. Both also export the standard Node.js process metrics
(process_*, nodejs_*).
API metrics
Section titled “API metrics”| Metric | Labels | Meaning |
|---|---|---|
typefold_http_request_duration_seconds |
method, route, status_code |
Request durations. route is the route pattern, such as /v1/renders/:id, or unmatched. |
typefold_background_job_runs_total |
task, outcome |
Background job runs. outcome is succeeded or failed. |
typefold_background_job_last_success_timestamp_seconds |
task |
When each background job last succeeded on this replica. |
The background jobs (task) are asset_cleanup, render_repair, render_retention,
workspace_cleanup, webhook_dispatch, webhook_retention, webhook_secret_encryption,
signing_key_encryption and license_check_in.
These gauges describe the whole installation, so every API replica reports the same value.
Combine replicas with max(), not sum().
| Metric | Labels | Meaning |
|---|---|---|
typefold_monitoring_source_up |
source |
1 if the scrape could read the database or the queue (Redis) |
typefold_queue_jobs |
state |
Render jobs in Redis by state: waiting, active, delayed, failed |
typefold_queue_oldest_waiting_age_seconds |
How long the next render to start has waited | |
typefold_renders_unfinished |
state |
Renders that are queued or processing |
typefold_render_oldest_queued_age_seconds |
Age of the oldest queued render | |
typefold_render_oldest_processing_age_seconds |
Time since the longest-running processing render last changed | |
typefold_workspace_deletions_pending |
Workspace cleanups not yet finished | |
typefold_workspace_deletion_oldest_age_seconds |
Age of the oldest unfinished workspace cleanup | |
typefold_license_state |
state |
1 for the current license state |
typefold_license_renew_by_timestamp_seconds |
When rendering stops unless a license check-in succeeds (0 if unset) |
|
typefold_license_expires_timestamp_seconds |
When the license expires (0 if unset) |
Worker metrics
Section titled “Worker metrics”| Metric | Labels | Meaning |
|---|---|---|
typefold_worker_job_duration_seconds |
outcome |
Render durations, by outcome (below) |
typefold_worker_child_replacements_total |
reason |
Render process restarts: recycle, failure, timeout |
typefold_worker_child_warm_up_failures_total |
Render processes that failed to start | |
typefold_worker_child_warm |
1 while the worker is ready to render |
outcome is succeeded, ignored (cancelled or already finished), retryable (a failure that
may succeed on retry), final (a failure retrying cannot fix, usually an invalid document or a
limit) or timeout (longer than RENDER_TIMEOUT_MS).
A recycle restart is routine: the worker restarts its render process after a large document to
free memory. failure and timeout restarts point to problems.