Self-hosting
Navii ships a production-ready Docker image. Single-process Hono app, no database, in-memory PNG cache. Tested on Hetzner; portable to any Node runtime, Bun, Deno, or Cloudflare Workers (with the wasm raster).
Docker
docker build -t navii-api packages/api
docker run -p 8787:8787 navii-api
The Dockerfile is multi-stage: pnpm install + build in stage 1, slim runtime in stage 2. Fonts (fonts-dejavu-core) are installed in the runtime image so resvg-js renders text properly in PNG/OG output.
Environment variables
| Var | Default | Description |
|---|---|---|
PORT | 8787 | HTTP listen port. |
HOST | 0.0.0.0 | HTTP bind address. |
RATE_LIMIT_PER_MIN | 120 (engine) · 600 (hosted) | Per-IP rate limit on /avatar/*. Engine default is 120 if unset; the hosted deployment at navii-api.uxderrick.com runs 600. See Rate limits for full details. |
PNG_CACHE_SIZE | 500 | LRU capacity for rasterized PNG responses. |
TRUST_PROXY | 0 | Set to 1 behind a reverse proxy you control (Caddy/Nginx). Enables X-Forwarded-For reading for rate-limit IP attribution. Never enable behind raw CDN — clients could spoof IPs. |
NAVII_API_BASE | https://navii-api.uxderrick.com | Used in landing + docs HTML for absolute API URLs (e.g. cast images, OG image). |
NAVII_SITE_BASE | https://navii.uxderrick.com | Public site URL. Used in canonical + OpenGraph meta. |
Reverse proxy
A sample Caddyfile snippet lives at deploy/Caddyfile.snippet. It does the usual: TLS, gzip, forward to :8787, set X-Forwarded-For.
Per-domain routing (landing on navii.uxderrick.com, API on navii-api.uxderrick.com) is purely DNS + proxy concern — the Hono app handles both transparently.
Health check
GET /healthz returns { "ok": true, "pngCacheSize": N }. The Docker image declares a built-in HEALTHCHECK that hits this endpoint every 30 s.
Resource notes
- SVG generation is essentially free (~microseconds per avatar).
- PNG raster is the expensive op — depends on size. ~10–40 ms for 256 px on a small VPS, dominated by resvg's text + filter pipeline.
- In-process PNG cache (LRU) absorbs repeated hits. Default 500 entries; tune via
PNG_CACHE_SIZE. - For multi-replica horizontal scale, swap the rate-limit Map for Redis (current implementation is single-process).