You are reading documentation for bouine v0.4.x — not the latest version. View latest →

Configuration

bouine is configured via a YAML file passed with --config. Environment variable interpolation is supported: ${VAR} is replaced with the value of VAR, and ${VAR:-default} provides a fallback. $$ escapes to a literal $.

Pages in this section

  • Cache policy — TTL selection, stale serving, negative caching, jitter, refresh-before-expiry, and cache keys.
  • Static file serving — serve files from a local directory instead of an upstream pool.
  • Storage tiers — hot and warm tiers, eviction, sizing guidelines.
  • TLS — certificates, SNI, and automatic reload.
  • Clustering — consistency modes, gossip, peer fetch, mTLS, invalidation.
  • Experimental features — opt-in features like the H1 fast path.
  • Helm chart reference — all values.yaml keys with defaults.

Minimal working config

listen:
  http: ":8080"
  admin: ":9000"

storage:
  hot_max_bytes: 256MiB

upstream_pools:
  - name: app
    targets: ["app.default.svc:8080"]

routes:
  - match: { path_prefix: / }
    pool: app
    cache:
      ttl_default: 60s

Full example

listen:
  http: ":80"
  https: ":443"
  admin: ":9000"
  cluster: ":8443"

tls:
  certs:
    - cert_file: /etc/bouine/tls/cert.pem
      key_file: /etc/bouine/tls/key.pem
      sni: ["example.com", "*.example.com"]
  min_version: "1.2"

storage:
  hot_max_bytes: 2GiB
  warm_dir: /var/lib/bouine
  warm_max_bytes: 50GiB

cluster:
  join:
    - "bouine-0.bouine-headless.ns.svc.cluster.local:8443"
    - "bouine-1.bouine-headless.ns.svc.cluster.local:8443"
    - "bouine-2.bouine-headless.ns.svc.cluster.local:8443"
  hop_limit: 2

upstream_pools:
  - name: api
    targets: [api.default.svc:8080]
    tls:
      enabled: false
    health:
      active:
        path: /healthz
        interval: 5s
        timeout: 1s
        unhealthy_threshold: 3
      passive:
        consecutive_5xx: 5
    connect:
      timeout: 10s
      keep_alive: 15s

routes:
  - match: { host: "api.example.com", path_prefix: /v1/ }
    pool: api
    cache:
      ttl_default: 60s
      stale_while_revalidate: 30s
      stale_if_error: 300s
      negative_ttl: 5s
      jitter_percent: 10
      refresh_before_expiry: true
      refresh_margin_percent: 20
      refresh_timeout: 5s
      refresh_concurrency: 16
      refresh_min_hits: 3
      refresh_persist_cycles: 2
      refresh_min_score: 1048576
      refresh_max_rps: 100
      refresh_reactive_first: true
      key:
        include_headers:
          - Accept-Language
        exclude_headers:
          - x-request-id
          - x-trace-id
          - x-forwarded-for

Field reference

listen

FieldDefaultDescription
http":80"HTTP/1.1 plaintext listener
https""HTTPS (TLS) listener. See TLS.
admin":9000"Admin API (health, metrics, purge)
cluster""Gossip cluster port
max_connections0Max concurrent data-plane connections (0 = unlimited). Protects against FD exhaustion.
tcp_fast_opentrue (Linux)Enable TCP_FASTOPEN on data-plane listeners. Defaults to true on Linux, no-op elsewhere.
tcp_defer_accepttrue (Linux)Enable TCP_DEFER_ACCEPT on data-plane listeners. Defaults to true on Linux, no-op elsewhere.
reuse_porttrue (Linux)Enable SO_REUSEPORT on data-plane listeners (N parallel accept loops). Defaults to true on Linux, false on other platforms.

storage

FieldDefaultDescription
hot_max_bytesRAM cache size. See size units. Example: 2GiB.
hot_mmap_slabfalseUse mmap slab allocator for hot body bytes (reduces GC pressure, Linux only)
warm_dir""Path for mmap warm-tier segments. Empty disables. See Storage tiers.
warm_max_bytes""Max warm-tier disk usage
warm_max_entries— (auto)Max warm-tier entry count. Auto-derived from GOMEMLIMIT when unset.
warm_max_disk_bytes""Max total warm-tier disk usage (all segments)
min_free_disk""Minimum free disk space before warm writes are paused
warm_preallocate0Preallocate warm-tier segment files totaling this size at startup. Eliminates disk amplification from append-only segments. Zero = create on demand.
compact_interval30mInterval between warm-tier compaction sweeps. Set to -1 to disable periodic compaction (not recommended).
body_threshold64KiBBody size threshold for warm-tier admission. Objects larger than this are written to warm on every Put; smaller objects only by the background sync loop.
warm_sync_interval60sInterval between hot-to-warm sync batches
warm_sync_batch_size5000Max objects per warm sync batch
wal_sync_interval100msWAL fsync interval (async batching)
compact_startup_delay5mDelay before first compaction on startup. Prevents I/O contention with WAL replay and cluster join. Set to -1 to start immediately.
checkpoint_interval5mWarm-tier checkpoint interval
checkpoint_wal_threshold100000WAL entry count that triggers a checkpoint, regardless of interval. Bounds WAL replay time on unclean restart.
segment_cache_size0 (auto)Number of warm-tier segment files to keep mmap-ed. 0 = auto (min(segCount, 256)). -1 = unlimited (no eviction).
tombstone_queue_size65536Tombstone queue depth for warm-tier deletions. Increasing this reduces drops under bursty eviction pressure.
tombstone_drain_interval1sInterval between tombstone drain sweeps. Set to -1 to disable the dedicated drain goroutine.

cluster

FieldDefaultDescription
modestrongConsistency mode: strong or eventual. The cluster is enabled when listen.cluster is set. See Clustering.
join[]Seed addresses (StatefulSet pod DNS)
hop_limit2Max peer-fetch hops before origin fallback (strong mode only)
join_timeout120sMax time to wait for cluster join. In strong mode, the pod stays not-ready if join fails. In eventual mode, the pod becomes ready and retries in the background.
handoff_queue_depth4096Memberlist per-peer message buffer. Absorbs bursts of cache invalidations. Negative values are rejected.
tls.ca_bundle""CA certificate path for peer-to-peer mTLS. Empty = plain HTTP.
tls.cert_file""Client certificate for mTLS
tls.key_file""Client private key for mTLS

routes[]

Route matching uses host, path_prefix, and optionally methods. Routes are matched in declaration order; the first match wins. Regex-based path matching is not supported in routes — use path_regex in ban predicates for invalidation.

A route must specify exactly one of pool or static.root. The former proxies to an upstream pool; the latter serves files from a local directory. See Static file serving.

FieldDefaultDescription
name""Human-readable label used in Prometheus route label and the dashboard. Defaults to host:path_prefix when empty.
match.host""Match on Host header (empty = any)
match.path_prefix""Match on URL path prefix (empty = any)
match.methods[]Restrict to listed HTTP methods, e.g. [GET, HEAD]. Empty = all methods. Normalised to upper-case. Lets you give GET and POST on the same path independent cache policies.
poolUpstream pool name. Required unless static.root is set.
static.root""Absolute path to a directory to serve files from. Required unless pool is set. See Static file serving.
static.index[]Index files to try (in order) when the request path maps to a directory, e.g. [index.html].
static.max_file_size10MiBPer-file size cap. Files larger than this are rejected with 413.

routes[].request

FieldDefaultDescription
header_set{}Headers to set on the upstream request
header_remove[]Headers to remove from the upstream request
strip_prefix""Strip this path prefix before forwarding to the upstream (e.g. /api/v1/users/users). Must start with /. The cache key still uses the original path.

routes[].cache

FieldDefaultDescription
enabledtrueSet to false to bypass caching for this route
ttl_default0Default TTL when origin has no Cache-Control
ttl_override0Force bouine’s internal TTL regardless of upstream Cache-Control/Expires; upstream headers are forwarded unaltered. See TTL override.
stale_while_revalidate0Serve stale while refreshing in background
stale_if_error0Serve stale on origin 5xx
negative_ttl0Cache 404/405/410/501 responses for this duration
jitter_percent0Random ±N% on TTLs to prevent stampedes (0–50)
stayin_alivefalseServe stale indefinitely when upstream is down (see Stayin Alive)
allow_set_cookiefalseAllow caching responses that carry Set-Cookie. Default blocks caching such responses (nginx-style). When true, the response is cached but Set-Cookie is stripped from the stored copy. See Set-Cookie caching.
max_object_size0Skip caching responses whose body exceeds this size (e.g. 1MiB). The response is still proxied. 0 = no limit.
max_response_bytes64MiBHard cap on origin response body size. Aborts the fetch if exceeded. Different from max_object_size which controls caching eligibility.
max_fetch_concurrency64Max concurrent origin fetches per route (collapsed via singleflight).
fetch_timeout60sMax duration for a single origin fetch.
refresh_before_expiryfalseEnable proactive background conditional revalidation before TTL expiry. See Refresh before expiry.
refresh_margin_percent10Percentage of TTL before expiry at which the background refresh fires (1–50). E.g. 20 fires at 80% of TTL.
refresh_timeout10sMaximum duration for a single background refresh fetch (5s–120s)
refresh_concurrency8Maximum concurrent background refresh fetches per route (1–64)
refresh_min_hits0Minimum cache hits during a TTL window for an object to qualify for re-scheduling after a refresh. 0 disables the gate. See Popularity gates.
refresh_persist_cycles0Additional TTL cycles to keep refreshing after the popularity gate would block. Requires refresh_min_hits > 0. See Persist cycles.
refresh_min_score0Minimum refresh priority score (staleHits × bodySize) for re-scheduling. Requires refresh_min_hits > 0. See Popularity gates.
refresh_max_rps0Caps background refresh fetches per second per route (0 or 1–10000). 0 = no limit. See Rate limiting.
refresh_reactive_firstfalseSWR-first mode: new objects rely on stale-while-revalidate instead of proactive refresh. Requires stale_while_revalidate > 0 and refresh_min_hits > 0. See Reactive-first mode.

routes[].cache.key

FieldDefaultDescription
include_headers[]Headers to include in cache key (replaces Vary)
exclude_headers[]Request header names to strip from the Vary-based variant key, preventing cache fragmentation from per-request headers like X-Request-Id. Matched case-insensitively. See Excluding headers.
strip_query_params[]Query parameter names to exclude from the cache key, e.g. [utm_source, fbclid]. The params are still forwarded to the upstream. See Stripping query parameters.
keep_query_params[]When non-empty, restricts the cache key to only these query parameters; all others are excluded. Mutually exclusive with strip_query_params and strip_query_prefix. Equivalent to Varnish qs.keep().
strip_query_prefix[]Strip query params whose names start with any of these prefixes (e.g. [utm_, fb_, _ga]). Covers wildcard stripping without enumerating every variant. Capped at 16 entries.
strip_empty_paramsfalseRemove query params with empty values (?foo=&bar=1?bar=1). Does not apply to params in keep_query_params.
dedup_query_paramsfalseKeep only the first value for duplicate query params (?a=2&a=1?a=2). Values are not sorted.
canonicalize_pathfalseNormalize the path component: percent-decode unreserved chars, uppercase remaining hex, resolve dot-segments. Applies at the listener level if any route on that listener enables it.

upstream_pools[].connect

FieldDefaultDescription
timeout10sTCP dial timeout
keep_alive15sTCP keep-alive interval
max_connections0Max concurrent connections per pool (0 = unlimited)
response_header_timeout30sMax time to wait for response headers from upstream. Zero applies a 30s built-in default. Primary defence against slow-origin resource exhaustion.
hedge_timeout""Fire a duplicate request after this duration; first response wins ( hedged fetch). Empty disables.

Health checks

Active health probes the upstream periodically. Passive ejects after consecutive failures.

health:
  active:
    path: /healthz
    method: GET          # default
    interval: 5s
    timeout: 1s
    healthy_threshold: 1
    unhealthy_threshold: 3
    expected_status_codes: [200]
  passive:
    consecutive_5xx: 5
    eject_for: 30s

admin

FieldDefaultDescription
token"" (auto-generated)Admin bearer token. See Authentication.
max_batch_size1000Max URLs per /v1/purge/batch request
rate_limit_per_second0Rate limit on admin write endpoints (0 = no limit)
pprof_enabledfalseEnable /debug/pprof/* profiling endpoints
drain_duration10sDuration the /drain endpoint blocks during shutdown (K8s preStop hook)

tracing

Configure OpenTelemetry span export. Leave endpoint empty (default) to disable tracing.

FieldDefaultDescription
endpoint""OTLP/HTTP collector URL, e.g. http://otel-collector:4318. Empty disables.
service_name"bouine"service.name OTel resource attribute
sampling_rate1.0Fraction of requests to sample (0.0–1.0)

cloudflare

Optional Cloudflare Cache API propagation. See Cloudflare CDN propagation for full details and Kubernetes secret wiring.

FieldDefaultDescription
zone_id""Cloudflare zone identifier (non-secret)
api_token""Cache Purge API token. Prefer CF_API_TOKEN env var.
asynctrueReturn immediately; CF call runs in background goroutine
timeout10sPer-call timeout for CF API requests
propagate.purgetrueForward POST /v1/purge to CF PurgeSingleFile
propagate.bantrueForward POST /v1/ban to CF (tags / prefixes / hostnames)
propagate.refreshtrueForward POST /v1/refresh to CF PurgeSingleFile

experimental

Opt-in features that are not yet stable. All fields default to off. See Experimental features.

FieldDefaultDescription
h1_fast_pathfalseEnable custom HTTP/1.1 parser for zero-allocation cache hits. Eliminates *http.Request and http.ResponseWriter construction on the hit path (~40% CPU reduction, 0 allocations). Misses and non-GET/HEAD requests fall through to fasthttp.

Top-level fields

FieldDefaultDescription
gogc100Go GC percentage. Set to -1 to disable percentage-based GC, relying solely on GOMEMLIMIT.
url_ring_sample_rate01-in-N sampling for the dashboard URL ring buffer. 0 = record every non-HIT request. 100 = 1 in 100 (reduces sync.Map overhead under high miss rates). 1 = record every call (debug mode).

Hedged fetch

When hedge_timeout is set on an upstream pool, bouine fires a duplicate request to the origin after the specified duration if the first request hasn’t responded. The first response to arrive wins; the other is discarded.

upstream_pools:
  - name: api
    targets: [api.default.svc:8080]
    connect:
      hedge_timeout: 50ms

Use hedging when your origin has occasional high-latency outliers (p99 » p50). It trades a small amount of extra origin load for significantly better tail latency. Do not use hedging for non-idempotent requests or when origin load is already near capacity.


Size units

All byte-size fields (hot_max_bytes, warm_max_bytes) accept any of these suffixes (case-insensitive):

SuffixMultiplierFamily
B1exact
K, KB10³SI decimal
KiB, KI1 024IEC binary
Ko10³French SI
M, MB10⁶SI decimal
MiB, MI1 048 576IEC binary
Mo10⁶French SI
G, GB10⁹SI decimal
GiB, GI1 073 741 824IEC binary
Go10⁹French SI
T, TB10¹²SI decimal
TiB, TI2⁴⁰IEC binary
To10¹²French SI

Recommendation: Use IEC binary units (MiB, GiB) for clarity. The Helm chart defaults use GiB.


Config updates

bouine does not support live config reload. All config changes require a process restart. On Kubernetes, use a rolling restart:

kubectl rollout restart statefulset/bouine

For TLS certificate rotation, restart the process or use Kubernetes rolling restarts with cert-manager.