Configuration Reference
This is the single source of truth for all mroki configuration. Each component is configured independently — refer to the relevant section for your deployment.
Configuration Methods
| Method | Components | Notes |
|---|---|---|
| Environment variables | mroki-api, mroki-proxy, mroki-hub | Prefixed with MROKI_APP_ (api/proxy) or VITE_ (hub) |
.env files | mroki-api, mroki-proxy, mroki-hub | Loaded automatically when present in working directory |
| Caddyfile directives | caddy-mroki | Uses mroki_gate block syntax inside Caddyfile |
API URL variables
Three similarly named variables tell the proxy and hub where to reach mroki-api. They belong to different components and are not interchangeable — set the one that matches the component you are configuring:
| Component | Variable | Points at |
|---|---|---|
| mroki-proxy (API mode) | MROKI_APP_API_URL | mroki-api server URL — see mroki-proxy → API Mode |
| mroki-hub (production, Docker) | MROKI_APP_API_BASE_URL | mroki-api base URL — see mroki-hub |
| mroki-hub (development, Vite) | VITE_API_BASE_URL | mroki-api base URL — see mroki-hub |
Not interchangeable: the proxy's
MROKI_APP_API_URLand the hub'sMROKI_APP_API_BASE_URLare distinct variables for distinct components. SettingMROKI_APP_API_BASE_URLon the proxy — orMROKI_APP_API_URLon the hub — has no effect.
mroki-api
| Variable | Required | Default | Description |
|---|---|---|---|
MROKI_APP_DATABASE_URL | Yes | — | PostgreSQL connection string |
MROKI_APP_API_KEY | Yes | — | API key for authentication |
MROKI_APP_PORT | No | 8090 | Server port |
MROKI_APP_RATE_LIMIT | No | 1000 | Requests per minute per IP |
MROKI_APP_MAX_BODY_SIZE | No | 10485760 | Request body size limit in bytes (10 MB) |
MROKI_APP_CORS_ORIGINS | No | (disabled) | Comma-separated allowed origins. The wildcard * is rejected at startup because the API allows the Authorization header — set explicit origins instead |
MROKI_APP_TRUSTED_PROXIES | No | (none) | Comma-separated CIDRs/IPs (e.g. 10.0.0.0/8, 192.168.1.1) allowed to set X-Forwarded-For for rate-limit keying. See Trusted proxies |
MROKI_APP_RETENTION | No | 720h | Global request retention floor (Go duration format). Must be positive — keep-forever (0) is no longer supported. Acts as the minimum for any per-gate retention override |
MROKI_APP_CLEANUP_INTERVAL | No | 1h | Cleanup job interval (Go duration format) |
MROKI_APP_READ_TIMEOUT | No | 15s | Server read timeout |
MROKI_APP_WRITE_TIMEOUT | No | 30s | Server write timeout (must be ≥ read timeout) |
MROKI_APP_IDLE_TIMEOUT | No | 60s | Server idle timeout (must be ≥ write timeout) |
MROKI_APP_METRICS_ENABLED | No | true | Expose Prometheus metrics at GET /metrics on the API port (unauthenticated, like the health endpoints) |
MROKI_APP_HSTS_ENABLED | No | false | Emit the Strict-Transport-Security header. Off by default — only enable behind a TLS-terminating reverse proxy. See Security → Security Headers |
MROKI_APP_HSTS_MAX_AGE | No | 8760h | max-age advertised in the HSTS header (Go duration, default 365d). Must be positive when MROKI_APP_HSTS_ENABLED=true |
MROKI_APP_APP_ENV | No | development | Application environment: development or production. Drives the MROKI_APP_LOG_LEVEL/MROKI_APP_LOG_FORMAT defaults below. Any other non-empty value (e.g. a typo like prod) is rejected at startup |
MROKI_APP_LOG_LEVEL | No | (derived from APP_ENV) | Minimum log level: debug, info, warn, or error. Defaults to debug when APP_ENV=development and info when APP_ENV=production |
MROKI_APP_LOG_FORMAT | No | (derived from APP_ENV) | Log output format: text or json. Defaults to text when APP_ENV=development and json when APP_ENV=production |
MROKI_APP_DATABASE_MAX_CONNS | No | 25 | Connection pool max connections |
MROKI_APP_DATABASE_MIN_CONNS | No | 5 | Connection pool min connections |
MROKI_APP_DATABASE_MAX_CONN_IDLE | No | 5m | Max idle time for a pooled connection |
MROKI_APP_DATABASE_MAX_CONN_LIFE | No | 1h | Max lifetime of a pooled connection |
Schema migrations are not configured via environment variables. They are applied by the
mroki-db-migratorimage (Atlas) — a Helmpre-install/pre-upgradeJob on Kubernetes (api.migration.*, includingbaselinefor pre-existing databases) and a one-shot service on Docker Compose. See Kubernetes → Database migrations.
Upgrade note — retention is now a positive floor.
MROKI_APP_RETENTIONpreviously defaulted to0(keep forever); it now defaults to720hand must be positive. SettingMROKI_APP_RETENTION=0(or any non-positive value) now fails startup — keep-forever is no longer supported. The global value acts as the minimum for any per-gate retention override: a gate may extend retention beyond the floor but never below it. If you relied on0to disable cleanup, set an explicit large duration instead.
Trusted proxies
Per-IP rate limiting keys off the client IP. The X-Forwarded-For header is client-controlled, so it is honored only when a request's immediate peer (RemoteAddr) is listed in MROKI_APP_TRUSTED_PROXIES. Otherwise the header is ignored and the limiter keys off RemoteAddr, preventing clients from spoofing or rotating X-Forwarded-For to evade limits.
- Empty (default):
X-Forwarded-Foris never trusted; rate limiting always uses the direct peer. Use this when the API is exposed directly. - Behind a reverse proxy / load balancer: set
MROKI_APP_TRUSTED_PROXIESto the CIDR(s) or IP(s) of your proxies (e.g.10.0.0.0/8,192.168.1.1). Invalid entries are rejected at startup.
When the peer is trusted, the client IP is the right-most X-Forwarded-For entry that is not itself a trusted proxy — the real client behind the (possibly chained) proxies — rather than the spoofable left-most entry. If no untrusted hop is found, the API falls back to X-Real-IP and finally to RemoteAddr.
mroki-proxy
The proxy supports two mutually exclusive operating modes: API mode and Standalone mode. You must configure exactly one.
Common
| Variable | Required | Default | Description |
|---|---|---|---|
MROKI_APP_PORT | No | 8080 | Proxy server port |
MROKI_APP_ADMIN_PORT | No | 8081 | Admin server port for health endpoints — must differ from MROKI_APP_PORT |
MROKI_APP_LIVE_TIMEOUT | No | 5s | Live request timeout — blocks client response, keep tight |
MROKI_APP_SHADOW_TIMEOUT | No | 10s | Shadow request timeout — does not block client |
MROKI_APP_MAX_BODY_SIZE | No | 10485760 | Skip shadow for requests above this size in bytes (0 = unlimited) |
MROKI_APP_SAMPLING_RATE | No | 1.0 | Shadow traffic sampling rate (0.0–1.0, 1.0 = 100%) |
MROKI_APP_SHADOW_RULES | No | (deny non-idempotent) | Shadow matching rules — see Shadow Matching Rules |
MROKI_APP_MAX_CONCURRENT_CALLBACKS | No | 200 | Max concurrent background shadow-comparison callbacks. When full, comparisons are dropped with a warning (live traffic unaffected); 0 = unbounded |
MROKI_APP_READ_TIMEOUT | No | 30s | Server read timeout |
MROKI_APP_WRITE_TIMEOUT | No | 60s | Server write timeout (must be ≥ live timeout) |
MROKI_APP_IDLE_TIMEOUT | No | 120s | Server idle timeout |
MROKI_APP_METRICS_ENABLED | No | true | Expose Prometheus metrics at GET /metrics on the admin port (MROKI_APP_ADMIN_PORT), isolated from proxied traffic |
MROKI_APP_APP_ENV | No | development | Application environment: development or production. Drives the MROKI_APP_LOG_LEVEL/MROKI_APP_LOG_FORMAT defaults below. Any other non-empty value (e.g. a typo like prod) is rejected at startup |
MROKI_APP_LOG_LEVEL | No | (derived from APP_ENV) | Minimum log level: debug, info, warn, or error. Defaults to debug when APP_ENV=development and info when APP_ENV=production |
MROKI_APP_LOG_FORMAT | No | (derived from APP_ENV) | Log output format: text or json. Defaults to text when APP_ENV=development and json when APP_ENV=production |
MROKI_APP_HTTP_CLIENT_MAX_IDLE_CONNS | No | 100 | Outbound idle connection pool size across all hosts (0 = unlimited) |
MROKI_APP_HTTP_CLIENT_MAX_IDLE_CONNS_PER_HOST | No | 10 | Outbound idle connections kept per host (0 = Go default of 2) |
MROKI_APP_HTTP_CLIENT_MAX_CONNS_PER_HOST | No | 100 | Limit on total outbound connections per host (0 = unlimited) |
MROKI_APP_HTTP_CLIENT_IDLE_CONN_TIMEOUT | No | 90s | How long an idle outbound connection is kept before closing (0 = no timeout) |
Health endpoints
The proxy exposes health endpoints on the admin port (MROKI_APP_ADMIN_PORT, default 8081), kept separate from the main proxy port so they never collide with proxied traffic forwarded to the upstream service.
| Endpoint | Purpose | Responses |
|---|---|---|
GET /health/live | Liveness — process is running | 200 OK |
GET /health/ready | Readiness — accepting traffic | 200 OK when ready, 503 Service Unavailable during startup or shutdown |
GET /metrics | Prometheus metrics (when MROKI_APP_METRICS_ENABLED=true) | 200 OK with the Prometheus text exposition format |
API Mode (Recommended)
Fetches gate configuration (live/shadow URLs) from mroki-api on startup.
| Variable | Required | Default | Description |
|---|---|---|---|
MROKI_APP_API_URL | Yes | — | mroki-api server URL |
MROKI_APP_GATE_ID | Yes | — | Gate ID from mroki-api (UUID) |
MROKI_APP_API_KEY | Yes | — | API key for authentication |
MROKI_APP_API_TIMEOUT | No | 30s | Overall deadline for API calls including all retries |
MROKI_APP_MAX_RETRIES | No | 3 | Maximum retry attempts for API requests |
MROKI_APP_RETRY_DELAY | No | 1s | Initial delay between retries, doubles each attempt |
MROKI_APP_CB_FAILURE_THRESHOLD | No | 5 | Circuit breaker: consecutive failures before opening |
MROKI_APP_CB_DELAY | No | 1m | Circuit breaker: delay before transitioning from open to half-open |
MROKI_APP_CB_SUCCESS_THRESHOLD | No | 2 | Circuit breaker: successes in half-open state before closing |
Standalone Mode
Uses hardcoded URLs — no communication with mroki-api.
| Variable | Required | Default | Description |
|---|---|---|---|
MROKI_APP_LIVE_URL | Yes | — | Live/production service URL |
MROKI_APP_SHADOW_URL | Yes | — | Shadow/experimental service URL |
Field Redaction
Sensitive field values (headers and JSON body) are replaced with [REDACTED] before storage or diff computation. A default set (Authorization, Cookie, Set-Cookie, X-Api-Key headers) is always redacted. In API mode, extra redacted fields come from the gate configuration in mroki-api.
Note: Requests forwarded to the shadow service include a fixed
X-Mroki-Mode: shadowheader so downstream systems can identify shadow traffic. It is added to shadow requests only — live requests are never modified — and is intentionally not redacted so its value stays visible for reference in stored request data. The header name is not configurable.
| Variable | Required | Default | Description |
|---|---|---|---|
MROKI_APP_REDACTED_FIELDS | No | (none) | Comma-separated additional fields to redact (gjson path notation). Redacted fields are also excluded from diff computation. |
Shadow Matching Rules
Selectively shadow requests based on HTTP method and path. By default the proxy mirrors every request to the shadow service, including infrastructure routes (/metrics, /health) and non-idempotent requests that may cause side effects.
MROKI_APP_SHADOW_RULES is a comma-separated list of ACTION METHOD:path entries:
- ACTION —
allow(shadow it) ordeny(skip shadow, live-only) - METHOD — an HTTP method (e.g.
POST) or*for any method - path — a path pattern (e.g.
/health/*,*.json,*)
Path patterns use the same semantics as Caddy's path matcher, so the standalone proxy and the caddy-mroki module behave identically. The meaning of the * wildcard depends on where it sits:
- A bare
*matches any path. - A trailing
*(the only wildcard) is a recursive prefix match that crosses/. For example,/admin/*matches/admin/usersand/admin/users/42. - A leading
*(the only wildcard) is a suffix match that crosses/. For example,*.jsonmatches/api/data.json. - A leading and trailing
*(exactly two wildcards) is a substring match. For example,*/admin/*matches any path containing/admin/. - Any other
*(mid-pattern or multiple) matches a single path segment and does not cross/(via Go'spath.Match). For example,/gates/*/requests/*/detailsmatches/gates/abc/requests/def/detailsbut not/gates/a/b/requests/c/details.
Matching is case-insensitive, and doubled slashes in the request path are merged before matching (unless the pattern itself contains //).
Rules are evaluated in definition order; the first match wins. Requests that match no rule are shadowed.
A set of base rules is always appended as the final, catch-all entries: deny POST, PUT, DELETE, and PATCH (so only GET, HEAD, and OPTIONS are shadowed by default). Your MROKI_APP_SHADOW_RULES are evaluated before the base rules — so you can override them per pattern (e.g. allow POST:/api/v1/search), but you cannot accidentally drop the write-protection by configuring custom rules. To shadow all writes, add explicit allow rules for those methods.
# Deny everything under /health and /admin, allow one search endpoint;
# base rules still deny all other writes
MROKI_APP_SHADOW_RULES="deny *:/health/*,deny *:/admin/*,allow POST:/api/v1/search"| Variable | Required | Default | Description |
|---|---|---|---|
MROKI_APP_SHADOW_RULES | No | (none — base rules deny POST/PUT/DELETE/PATCH) | Comma-separated ACTION METHOD:path rules. First match wins; unmatched requests are shadowed. Evaluated before the always-present base rules. |
Note: The
caddy-mrokimodule also accepts ashadow_rulesdirective with identical semantics (including the always-appended write-protection base rules). Caddy's native route matchers are an alternative way to scope shadowing per route.
Diff Options
Configure how responses are compared. These options only apply in Standalone mode — in API mode, diff computation is handled server-side by mroki-api. All field paths use gjson syntax.
| Variable | Required | Default | Description |
|---|---|---|---|
MROKI_APP_DIFF_IGNORED_FIELDS | No | (none) | Comma-separated field paths to ignore during comparison |
MROKI_APP_DIFF_INCLUDED_FIELDS | No | (none) | Comma-separated field paths to include (whitelist mode). When set, only these fields are compared, then ignored fields are applied. |
MROKI_APP_DIFF_FLOAT_TOLERANCE | No | 0 | Tolerance for floating-point comparisons (0 = exact) |
MROKI_APP_DIFF_SORT_ARRAYS | No | false | Sort arrays before comparison so element order is ignored. When false, arrays are compared positionally and a reorder surfaces as remove/add pairs. |
mroki-hub
The hub is a static SPA. It doesn't read environment variables at runtime — configuration is injected differently depending on the environment:
Production (Docker): The container entrypoint script reads MROKI_APP_* env vars and generates a config.js file that injects them into window.__MROKI__ before the app loads.
| Variable | Required | Default | Description |
|---|---|---|---|
MROKI_APP_API_BASE_URL | Yes | — | mroki-api base URL |
MROKI_APP_API_KEY | Yes | — | API key for authentication |
Development (Vite dev server): Vite compiles VITE_* env vars into the bundle at build time. Set these in a .env file inside web/mroki-hub/:
| Variable | Required | Default | Description |
|---|---|---|---|
VITE_API_BASE_URL | Yes | — | mroki-api base URL |
VITE_API_KEY | Yes | — | API key for authentication |
Note: CORS must be configured on mroki-api (
MROKI_APP_CORS_ORIGINS) to allow requests from the hub.
caddy-mroki
The Caddy module uses mroki_gate directive blocks inside a Caddyfile. It operates in standalone mode only.
Syntax
mroki_gate {
live <live_url>
shadow <shadow_url>
[sampling_rate <rate>]
[live_timeout <duration>]
[shadow_timeout <duration>]
[max_body_size <bytes>]
[shadow_rules <comma-separated "ACTION METHOD:path">]
[max_concurrent_callbacks <int>]
[http_client {
[max_idle_conns <int>]
[max_idle_conns_per_host <int>]
[max_conns_per_host <int>]
[idle_conn_timeout <duration>]
}]
[diff_ignored_fields <comma-separated>]
[diff_included_fields <comma-separated>]
[diff_float_tolerance <float>]
[diff_sort_arrays <bool>]
[redacted_fields <comma-separated>]
}Parameters
| Parameter | Required | Default | Description |
|---|---|---|---|
live | Yes | — | URL of live/production service |
shadow | Yes | — | URL of shadow/experimental service |
sampling_rate | No | 1.0 | Sample rate (0.0–1.0, 1.0 = 100%) |
live_timeout | No | 5s | Live request timeout |
shadow_timeout | No | 10s | Shadow request timeout |
max_body_size | No | (unlimited) | Skip shadow for requests above this size in bytes (0 = unlimited) |
shadow_rules | No | (none — base rules deny POST/PUT/DELETE/PATCH) | Comma-separated allow/deny rules controlling which requests are shadowed (see Shadow Matching Rules) |
max_concurrent_callbacks | No | 200 | Max concurrent background shadow-comparison callbacks (0 = unbounded). When full, comparisons are dropped with a warning |
http_client | No | — | Nested block for outbound connection-pool tuning (see http_client block) |
diff_ignored_fields | No | (none) | Comma-separated fields to ignore in diff (gjson syntax) |
diff_included_fields | No | (none) | Comma-separated fields to include in diff (whitelist) |
diff_float_tolerance | No | (exact) | Float comparison tolerance |
diff_sort_arrays | No | false | Sort arrays before comparison so element order is ignored (false = positional, reorders show as remove/add pairs) |
redacted_fields | No | (none) | Comma-separated fields to redact from diff output |
http_client block
Outbound connection-pool tuning for the shared HTTP client. Grouped under an http_client block to mirror the proxy binary's MROKI_APP_HTTP_CLIENT_* environment variables. Omit the block to use the defaults.
| Parameter | Default | Description |
|---|---|---|
max_idle_conns | 100 | Outbound idle connection pool size across all hosts (0 = unlimited) |
max_idle_conns_per_host | 10 | Outbound idle connections kept per host (0 = Go default of 2) |
max_conns_per_host | 100 | Limit on total outbound connections per host (0 = unlimited) |
idle_conn_timeout | 90s | How long an idle outbound connection is kept before closing (0 = no timeout) |
Examples
Development (Standalone Mode)
# mroki-proxy
MROKI_APP_PORT=8080
MROKI_APP_LIVE_URL=http://localhost:3000
MROKI_APP_SHADOW_URL=http://localhost:3001
MROKI_APP_LIVE_TIMEOUT=5s
MROKI_APP_SHADOW_TIMEOUT=10s
# mroki-api
MROKI_APP_DATABASE_URL=postgres://postgres:postgres@localhost:5432/mroki
MROKI_APP_API_KEY=dev-test-key-min-16-chars
MROKI_APP_CORS_ORIGINS=http://localhost:5173
# mroki-hub (dev uses VITE_ prefix, see note above)
VITE_API_BASE_URL=http://localhost:8090
VITE_API_KEY=dev-test-key-min-16-charsProduction (API Mode)
# mroki-proxy
MROKI_APP_PORT=8080
MROKI_APP_API_URL=http://mroki-api:8090
MROKI_APP_GATE_ID=550e8400-e29b-41d4-a716-446655440000
MROKI_APP_API_KEY=your-production-api-key
MROKI_APP_API_TIMEOUT=30s
MROKI_APP_MAX_RETRIES=3
MROKI_APP_RETRY_DELAY=1s
MROKI_APP_LIVE_TIMEOUT=3s
MROKI_APP_SHADOW_TIMEOUT=15s
MROKI_APP_MAX_BODY_SIZE=10485760
MROKI_APP_SAMPLING_RATE=1.0
MROKI_APP_REDACTED_FIELDS=headers.X-Internal-Token,body.user.password
# mroki-api
MROKI_APP_DATABASE_URL=postgres://user:pass@db-host:5432/mroki
MROKI_APP_API_KEY=your-production-api-key
MROKI_APP_RATE_LIMIT=1000
MROKI_APP_RETENTION=720h
MROKI_APP_CLEANUP_INTERVAL=1h
MROKI_APP_CORS_ORIGINS=https://hub.example.com
# mroki-hub
MROKI_APP_API_BASE_URL=https://api.example.com
MROKI_APP_API_KEY=your-production-api-keyStandalone with Diff Tuning
# mroki-proxy — standalone with selective diff comparison
MROKI_APP_PORT=8080
MROKI_APP_LIVE_URL=https://api.production.example.com
MROKI_APP_SHADOW_URL=https://api.shadow.example.com
MROKI_APP_SAMPLING_RATE=0.5
MROKI_APP_REDACTED_FIELDS=headers.X-Internal-Token
MROKI_APP_DIFF_IGNORED_FIELDS=timestamp,created_at,updated_at
MROKI_APP_DIFF_INCLUDED_FIELDS=user,order
MROKI_APP_DIFF_FLOAT_TOLERANCE=0.001