Monlet Server
FastAPI server: heartbeat/event ingestion, current check state, incident lifecycle, stale/dead detector, notification outbox, Prometheus metrics.
Stack
- Python 3.14+
uv- FastAPI, Pydantic v2
- SQLAlchemy 2.x async +
asyncpg - Alembic (sync
psycopg) structlog,prometheus-client- PostgreSQL 16+
Local development
cd server
UV_PROJECT_ENVIRONMENT=.venv UV_CACHE_DIR=../.cache/uv uv sync --extra dev
Run a Postgres instance (Docker or local), set MONLET_DATABASE_URL, then:
UV_PROJECT_ENVIRONMENT=.venv UV_CACHE_DIR=../.cache/uv uv run alembic upgrade head
UV_PROJECT_ENVIRONMENT=.venv UV_CACHE_DIR=../.cache/uv uv run fastapi dev monlet_server/main.py
Environment
| Var | Default | Purpose |
|---|---|---|
MONLET_AUTH_TOKEN |
required | Shared Bearer token (ADR-0007); changeme is rejected |
MONLET_DATABASE_URL |
postgresql+asyncpg://monlet:monlet@127.0.0.1:5432/monlet |
Async DSN |
MONLET_LOG_LEVEL |
INFO |
Log level |
MONLET_BODY_LIMIT_BYTES |
1048576 |
Request body limit (ADR-0005) |
MONLET_OUTPUT_MAX_BYTES |
8192 |
Per-event output limit |
MONLET_STALE_AFTER_SEC |
90 |
Stale threshold |
MONLET_DEAD_AFTER_SEC |
300 |
Dead threshold |
MONLET_DETECTOR_TICK_SEC |
15 |
Detector tick |
MONLET_ENABLE_DETECTOR |
true |
Toggle detector task |
MONLET_EVENTS_RETENTION_MAX_ROWS |
100000 |
Maximum retained check-result events; 0 disables pruning |
MONLET_OUTBOX_RETENTION_MAX_ROWS |
50000 |
Maximum retained terminal outbox rows; 0 disables pruning |
MONLET_ENABLE_NOTIFIER_WORKER |
true |
Toggle outbox worker |
MONLET_NOTIFIER_TICK_SEC |
5 |
Worker poll interval |
MONLET_NOTIFIER_BATCH_SIZE |
20 |
Rows claimed per tick |
MONLET_NOTIFIER_MAX_ATTEMPTS |
8 |
Terminal-failure threshold (ADR-0005) |
MONLET_NOTIFIER_LEASE_SEC |
300 |
Recovery lease for stuck sending rows |
MONLET_NOTIFIER_HTTP_TIMEOUT_SEC |
10 |
Outbound HTTP timeout |
MONLET_NOTIFICATION_TIME_ZONE |
UTC |
IANA timezone for human-readable notification timestamps |
MONLET_NOTIFIER_DEBUG_ENABLED |
true |
Log-only notifier |
MONLET_NOTIFIER_TELEGRAM_ENABLED |
false |
Telegram notifier on/off |
MONLET_NOTIFIER_TELEGRAM_TOKEN |
`` | Telegram bot token |
MONLET_NOTIFIER_TELEGRAM_CHAT_ID |
`` | Telegram chat id |
MONLET_NOTIFIER_WEBHOOK_ENABLED |
false |
Generic webhook notifier on/off |
MONLET_NOTIFIER_WEBHOOK_URL |
`` | Webhook URL |
MONLET_NOTIFIER_WEBHOOK_TOKEN |
`` | Optional Bearer token for webhook |
MONLET_NOTIFIER_ALERTMANAGER_ENABLED |
false |
Alertmanager notifier on/off |
MONLET_NOTIFIER_ALERTMANAGER_URL |
`` | Alertmanager base URL (e.g. http://am:9093) |
Tests
Tests use testcontainers-python to spin up a real PostgreSQL container; Docker must be available.
UV_PROJECT_ENVIRONMENT=.venv UV_CACHE_DIR=../.cache/uv uv run pytest -q
Docker
Image installs dependencies inside the container only; host venv is never mounted.
docker build -t monlet-server .
Endpoints
All routes under /api/v1 (per api/openapi.yaml). /metrics and /api/v1/health, /api/v1/ready are open; everything else requires Authorization: Bearer <token>.