Example configurations
Static site / blog
listen:
http: ":8080"
admin: ":9000"
storage:
hot_max_bytes: 64Mo
upstream_pools:
- name: site
targets: ["site.default.svc.cluster.local:80"]
routes:
- match: { path_prefix: /assets/ }
pool: site
cache:
ttl_default: 86400s
stale_if_error: 604800s
jitter_percent: 10
- match: { path_prefix: / }
pool: site
cache:
ttl_default: 300s
stale_while_revalidate: 60s
stale_if_error: 86400s
jitter_percent: 15Self-served static site (no origin server)
bouine can serve files directly from disk — no separate nginx or Caddy needed. See Static file serving for the full reference.
listen:
http: ":8080"
admin: ":9000"
routes:
- name: assets
match: { path_prefix: /assets/ }
static:
root: /var/www/assets
max_file_size: 50MiB
request:
strip_prefix: /assets/
response:
header_set:
X-Content-Type-Options: nosniff
Cache-Control: public, max-age=86400
- name: root
match: {}
static:
root: /var/www/html
index: [index.html]
response:
header_set:
X-Content-Type-Options: nosniffNo upstream_pools section is needed. The OS page cache handles hot
caching. Enable cache.enabled: true on static routes when you need
cluster replication or TTL-based eviction.
API gateway
upstream_pools:
- name: api
targets: ["api.default.svc.cluster.local:8080"]
health:
active:
path: /healthz
interval: 5s
timeout: 1s
unhealthy_threshold: 3
routes:
- match: { path_prefix: /v1/ }
pool: api
cache:
ttl_default: 30s
stale_while_revalidate: 10s
stale_if_error: 300s
negative_ttl: 5s
key:
include_headers: [Accept-Language]E-commerce
routes:
- match: { path_prefix: /static/ }
pool: storefront
cache:
ttl_default: 604800s
stale_if_error: 3600s
- match: { path_prefix: /products/ }
pool: storefront
cache:
ttl_default: 60s
stale_while_revalidate: 30s
stale_if_error: 300s
- match: { path_prefix: /cart/ }
pool: cart-api
cache:
enabled: false
- match: { path_prefix: /checkout/ }
pool: cart-api
cache:
enabled: falsePrivate routes should bypass cache entirely. Do not cache cart, checkout, account, or authenticated responses unless the origin explicitly marks them public.
bouine in front of Cloudflare
The origin emits Cache-Control: max-age=60 intended for the browser and
the Cloudflare edge. bouine’s ttl_override lets you hold responses for
much longer internally while forwarding the original headers unchanged, so
Cloudflare and the browser still see max-age=60.
listen:
http: ":8080"
admin: ":9000"
storage:
hot_max_bytes: 512Mo
warm_dir: /var/cache/bouine
warm_max_bytes: 10Go
upstream_pools:
- name: api
targets: ["api.default.svc.cluster.local:8080"]
health:
active:
path: /healthz
interval: 5s
unhealthy_threshold: 3
routes:
# Public API responses: origin emits max-age=60, but bouine holds for 1 h.
# Cloudflare (and browsers) still see Cache-Control: max-age=60.
- name: public-api
match: { path_prefix: /api/v1/ }
pool: api
cache:
ttl_override: 1h # bouine's internal TTL
ttl_default: 30s # fallback if origin omits Cache-Control
stale_while_revalidate: 5m # serve stale during background refresh
stale_if_error: 24h # keep serving if origin is down
jitter_percent: 5 # spread expiry across ±5 %
# Static assets: long TTL on all layers.
- name: assets
match: { path_prefix: /static/ }
pool: api
cache:
ttl_override: 24h
stale_if_error: 168h # 1 week
jitter_percent: 10
# Authenticated or user-specific routes: must not be cached.
- name: auth
match: { path_prefix: /account/ }
pool: api
cache:
enabled: false
cloudflare:
zone_id: "your-zone-id"
async: true
propagate:
purge: true
ban: true
refresh: trueWhat each layer caches:
| Layer | /api/v1/* | /static/* |
|---|---|---|
| bouine (internal) | 1 h (ttl_override) | 24 h (ttl_override) |
| Cloudflare edge | Origin’s max-age (e.g. 60 s) | Origin’s Cache-Control |
| Browser | Origin’s max-age (e.g. 60 s) | Origin’s Cache-Control |
See Cache policy → TTL override and Cloudflare CDN propagation for the full reference.
Method-split routes and path rewriting
Cache GET/HEAD on an API path while passing writes straight through, and
strip the routing prefix so the upstream sees the path it expects.
upstream_pools:
- name: api
targets: ["api.default.svc.cluster.local:8080"]
routes:
# Cached reads. strip_prefix rewrites /api/v1/users → /users for the upstream.
- name: api-reads
match:
path_prefix: /api/v1
methods: [GET, HEAD]
pool: api
request:
strip_prefix: /api/v1
cache:
ttl_default: 30s
stale_while_revalidate: 10s
max_object_size: 512KiB
key:
strip_query_params: [utm_source, fbclid]
# Writes: same path + prefix rewrite, but never cached.
- name: api-writes
match:
path_prefix: /api/v1
methods: [POST, PUT, PATCH, DELETE]
pool: api
request:
strip_prefix: /api/v1
cache:
enabled: falsematch.methodslets the samepath_prefixcarry two route entries with independent cache policies (first match wins, so order reads before writes).request.strip_prefixrewrites the upstream path but leaves the cache key on the original path, so/api/v1/usersand/api/v2/usersnever collide.- Empty/omitted
methodsmatches all methods (the default).
See the routes field reference for all options.