Skip to content

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

MethodComponentsNotes
Environment variablesmroki-api, mroki-proxy, mroki-hubPrefixed with MROKI_APP_ (api/proxy) or VITE_ (hub)
.env filesmroki-api, mroki-proxy, mroki-hubLoaded automatically when present in working directory
Caddyfile directivescaddy-mrokiUses 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:

ComponentVariablePoints at
mroki-proxy (API mode)MROKI_APP_API_URLmroki-api server URL — see mroki-proxy → API Mode
mroki-hub (production, Docker)MROKI_APP_API_BASE_URLmroki-api base URL — see mroki-hub
mroki-hub (development, Vite)VITE_API_BASE_URLmroki-api base URL — see mroki-hub

Not interchangeable: the proxy's MROKI_APP_API_URL and the hub's MROKI_APP_API_BASE_URL are distinct variables for distinct components. Setting MROKI_APP_API_BASE_URL on the proxy — or MROKI_APP_API_URL on the hub — has no effect.


mroki-api

VariableRequiredDefaultDescription
MROKI_APP_DATABASE_URLYesPostgreSQL connection string
MROKI_APP_API_KEYYesAPI key for authentication
MROKI_APP_PORTNo8090Server port
MROKI_APP_RATE_LIMITNo1000Requests per minute per IP
MROKI_APP_MAX_BODY_SIZENo10485760Request body size limit in bytes (10 MB)
MROKI_APP_CORS_ORIGINSNo(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_PROXIESNo(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_RETENTIONNo720hGlobal 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_INTERVALNo1hCleanup job interval (Go duration format)
MROKI_APP_READ_TIMEOUTNo15sServer read timeout
MROKI_APP_WRITE_TIMEOUTNo30sServer write timeout (must be ≥ read timeout)
MROKI_APP_IDLE_TIMEOUTNo60sServer idle timeout (must be ≥ write timeout)
MROKI_APP_METRICS_ENABLEDNotrueExpose Prometheus metrics at GET /metrics on the API port (unauthenticated, like the health endpoints)
MROKI_APP_HSTS_ENABLEDNofalseEmit the Strict-Transport-Security header. Off by default — only enable behind a TLS-terminating reverse proxy. See Security → Security Headers
MROKI_APP_HSTS_MAX_AGENo8760hmax-age advertised in the HSTS header (Go duration, default 365d). Must be positive when MROKI_APP_HSTS_ENABLED=true
MROKI_APP_APP_ENVNodevelopmentApplication 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_LEVELNo(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_FORMATNo(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_CONNSNo25Connection pool max connections
MROKI_APP_DATABASE_MIN_CONNSNo5Connection pool min connections
MROKI_APP_DATABASE_MAX_CONN_IDLENo5mMax idle time for a pooled connection
MROKI_APP_DATABASE_MAX_CONN_LIFENo1hMax lifetime of a pooled connection

Schema migrations are not configured via environment variables. They are applied by the mroki-db-migrator image (Atlas) — a Helm pre-install/pre-upgrade Job on Kubernetes (api.migration.*, including baseline for 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_RETENTION previously defaulted to 0 (keep forever); it now defaults to 720h and must be positive. Setting MROKI_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 on 0 to 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-For is 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_PROXIES to 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

VariableRequiredDefaultDescription
MROKI_APP_PORTNo8080Proxy server port
MROKI_APP_ADMIN_PORTNo8081Admin server port for health endpoints — must differ from MROKI_APP_PORT
MROKI_APP_LIVE_TIMEOUTNo5sLive request timeout — blocks client response, keep tight
MROKI_APP_SHADOW_TIMEOUTNo10sShadow request timeout — does not block client
MROKI_APP_MAX_BODY_SIZENo10485760Skip shadow for requests above this size in bytes (0 = unlimited)
MROKI_APP_SAMPLING_RATENo1.0Shadow traffic sampling rate (0.01.0, 1.0 = 100%)
MROKI_APP_SHADOW_RULESNo(deny non-idempotent)Shadow matching rules — see Shadow Matching Rules
MROKI_APP_MAX_CONCURRENT_CALLBACKSNo200Max concurrent background shadow-comparison callbacks. When full, comparisons are dropped with a warning (live traffic unaffected); 0 = unbounded
MROKI_APP_READ_TIMEOUTNo30sServer read timeout
MROKI_APP_WRITE_TIMEOUTNo60sServer write timeout (must be ≥ live timeout)
MROKI_APP_IDLE_TIMEOUTNo120sServer idle timeout
MROKI_APP_METRICS_ENABLEDNotrueExpose Prometheus metrics at GET /metrics on the admin port (MROKI_APP_ADMIN_PORT), isolated from proxied traffic
MROKI_APP_APP_ENVNodevelopmentApplication 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_LEVELNo(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_FORMATNo(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_CONNSNo100Outbound idle connection pool size across all hosts (0 = unlimited)
MROKI_APP_HTTP_CLIENT_MAX_IDLE_CONNS_PER_HOSTNo10Outbound idle connections kept per host (0 = Go default of 2)
MROKI_APP_HTTP_CLIENT_MAX_CONNS_PER_HOSTNo100Limit on total outbound connections per host (0 = unlimited)
MROKI_APP_HTTP_CLIENT_IDLE_CONN_TIMEOUTNo90sHow 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.

EndpointPurposeResponses
GET /health/liveLiveness — process is running200 OK
GET /health/readyReadiness — accepting traffic200 OK when ready, 503 Service Unavailable during startup or shutdown
GET /metricsPrometheus metrics (when MROKI_APP_METRICS_ENABLED=true)200 OK with the Prometheus text exposition format

Fetches gate configuration (live/shadow URLs) from mroki-api on startup.

VariableRequiredDefaultDescription
MROKI_APP_API_URLYesmroki-api server URL
MROKI_APP_GATE_IDYesGate ID from mroki-api (UUID)
MROKI_APP_API_KEYYesAPI key for authentication
MROKI_APP_API_TIMEOUTNo30sOverall deadline for API calls including all retries
MROKI_APP_MAX_RETRIESNo3Maximum retry attempts for API requests
MROKI_APP_RETRY_DELAYNo1sInitial delay between retries, doubles each attempt
MROKI_APP_CB_FAILURE_THRESHOLDNo5Circuit breaker: consecutive failures before opening
MROKI_APP_CB_DELAYNo1mCircuit breaker: delay before transitioning from open to half-open
MROKI_APP_CB_SUCCESS_THRESHOLDNo2Circuit breaker: successes in half-open state before closing

Standalone Mode

Uses hardcoded URLs — no communication with mroki-api.

VariableRequiredDefaultDescription
MROKI_APP_LIVE_URLYesLive/production service URL
MROKI_APP_SHADOW_URLYesShadow/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: shadow header 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.

VariableRequiredDefaultDescription
MROKI_APP_REDACTED_FIELDSNo(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:

  • ACTIONallow (shadow it) or deny (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/users and /admin/users/42.
  • A leading * (the only wildcard) is a suffix match that crosses /. For example, *.json matches /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's path.Match). For example, /gates/*/requests/*/details matches /gates/abc/requests/def/details but 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.

bash
# 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"
VariableRequiredDefaultDescription
MROKI_APP_SHADOW_RULESNo(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-mroki module also accepts a shadow_rules directive 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.

VariableRequiredDefaultDescription
MROKI_APP_DIFF_IGNORED_FIELDSNo(none)Comma-separated field paths to ignore during comparison
MROKI_APP_DIFF_INCLUDED_FIELDSNo(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_TOLERANCENo0Tolerance for floating-point comparisons (0 = exact)
MROKI_APP_DIFF_SORT_ARRAYSNofalseSort 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.

VariableRequiredDefaultDescription
MROKI_APP_API_BASE_URLYesmroki-api base URL
MROKI_APP_API_KEYYesAPI 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/:

VariableRequiredDefaultDescription
VITE_API_BASE_URLYesmroki-api base URL
VITE_API_KEYYesAPI 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

caddyfile
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

ParameterRequiredDefaultDescription
liveYesURL of live/production service
shadowYesURL of shadow/experimental service
sampling_rateNo1.0Sample rate (0.01.0, 1.0 = 100%)
live_timeoutNo5sLive request timeout
shadow_timeoutNo10sShadow request timeout
max_body_sizeNo(unlimited)Skip shadow for requests above this size in bytes (0 = unlimited)
shadow_rulesNo(none — base rules deny POST/PUT/DELETE/PATCH)Comma-separated allow/deny rules controlling which requests are shadowed (see Shadow Matching Rules)
max_concurrent_callbacksNo200Max concurrent background shadow-comparison callbacks (0 = unbounded). When full, comparisons are dropped with a warning
http_clientNoNested block for outbound connection-pool tuning (see http_client block)
diff_ignored_fieldsNo(none)Comma-separated fields to ignore in diff (gjson syntax)
diff_included_fieldsNo(none)Comma-separated fields to include in diff (whitelist)
diff_float_toleranceNo(exact)Float comparison tolerance
diff_sort_arraysNofalseSort arrays before comparison so element order is ignored (false = positional, reorders show as remove/add pairs)
redacted_fieldsNo(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.

ParameterDefaultDescription
max_idle_conns100Outbound idle connection pool size across all hosts (0 = unlimited)
max_idle_conns_per_host10Outbound idle connections kept per host (0 = Go default of 2)
max_conns_per_host100Limit on total outbound connections per host (0 = unlimited)
idle_conn_timeout90sHow long an idle outbound connection is kept before closing (0 = no timeout)

Examples

Development (Standalone Mode)

bash
# 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-chars

Production (API Mode)

bash
# 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-key

Standalone with Diff Tuning

bash
# 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