Skip to content
bouine-cachePublic

About

⚡ Bouine is a cloud-native HTTP cache in Go - RFC9111 compliant, zero-alloc hit path, gossip clustering, no external K/V store

Topics

Resources

Code of conduct

Contributing

Security policy

Accessibility

Stars

4 stars

Watchers

1 watching

Forks

Latest commit

 

History

1,310 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

bouine

pronounce: /bwin/

CI codecov Release Latest Release Docker Pulls Docker Docker Image Size Artifact Hub License OpenSSF Best Practices

bouine is a cloud-native HTTP cache in Go — RFC 9111 compliant, zero-alloc hit path, gossip clustering, no external K/V store. It targets the same problem space as a classic HTTP cache but is designed from day one for Kubernetes, multi-instance clustering, and first-class observability.

Status: v1.0-rc — core caching, clustering, negative caching, jittered TTLs, soft-purge, and the Go SDK are shipped. Validated on k3s with 3-node gossip cluster. make conformance scores 349/365 (95.6%) on http-tests/cache-tests. See CHANGELOG.md for what's shipped and what's deferred (prefetching, HTTP/3, VCL shim, and AI insights are not yet implemented).


Highlights

  • Protocols: HTTP/1.1 only on the data plane (fasthttp, ADR-0034); the admin port runs on the same stack. HTTP/2 and HTTP/3 are not supported.
  • Embedded storage: sharded in-RAM hot tier + mmap warm tier. No external KV.
  • Clustering: gossip membership + consistent hash + peer fetch. K8s StatefulSet friendly.
  • Compliance: 95.6 % on http-tests/cache-tests (349/365). Covers RFC 9111 freshness, stale-while-revalidate, stale-if-error, CDN-Cache-Control, heuristic caching, Vary, conditional requests, and must-understand.
  • Performance: zero-alloc hit path, benchmark-gated CI.
  • Observability: Prometheus, OpenTelemetry, slog, pprof.
  • Migration: NGINX and Varnish migration guides included.

Why bouine?

bouine Varnish NGINX
Architecture Reverse-proxy cache Reverse-proxy cache Reverse proxy + cache
Clustering Gossip + consistent hash (built-in) Commercial (Varnish Plus) or external orchestration Upstream hash (no cluster state)
Cache invalidation Purge + ban (predicate) + soft-purge Purge + ban Purge only (no ban)
Observability Prometheus + OTel + slog (built-in) Vmod-based Third-party or NGINX plus
Config YAML (declarative) VCL (imperative) NGINX.conf
Kubernetes StatefulSet + Helm chart, first-class Sidecar / external Ingress controller pattern

Architecture

                    ┌─────────────────────────────────────────┐
  Client ── HTTP──► │                 bouine                  │  ──►  Origin
                    │  ┌───────────┐   ┌───────────┐          │
                    │  │ Hot tier  │   │ Warm tier  │         │
                    │  │ (RAM)     │   │ (mmap disk)│         │
                    │  └───────────┘   └───────────┘          │
                    │  ┌────────────────────────────┐         │
                    │  │ Cache engine (RFC 9111)    │         │
                    │  │ Vary · SWR · SIE · purge   │         │
                    │  └────────────────────────────┘         │
                    └──────────┬──────────┬──────────┬────────┘
                          gossip   peer fetch   metrics / traces
                              │         │          │
                     ┌────────▼───┐     │     ┌────▼─────┐
                     │  peers     │◄────┘     │  Prom    │
                     │ (cluster)  │           │  OTel    │
                     └────────────┘           └──────────┘

Quick Start

Run bouine with Docker and cache a request in under a minute.

Create a minimal config:

cat > bouine.yaml <<EOF
listen:
  http: ":8080"
  admin: ":9000"

upstream_pools:
  - name: origin
    targets: ["httpbin.org:80"]

routes:
  - match: {}
    pool: origin
    cache:
      ttl_default: 60s
EOF

Run bouine:

docker run -d --name bouine -p 8080:8080 -p 9000:9000 \
  -v "$PWD/bouine.yaml:/etc/bouine/config.yaml" \
  bouinecache/bouine:latest serve --config /etc/bouine/config.yaml

Test it — first request is a MISS, second is a HIT:

curl -s -I http://localhost:8080/get | grep x-cache
# X-Cache: MISS

curl -s -I http://localhost:8080/get | grep x-cache
# X-Cache: HIT

Check the admin endpoint:

curl -s http://localhost:9000/healthz
# ok
Demo: first request is a MISS, second is a HIT
$ curl -sI http://localhost:8080/get
HTTP/1.1 200 OK
Content-Type: application/json
X-Cache: MISS
Age: 0

$ curl -sI http://localhost:8080/get
HTTP/1.1 200 OK
Content-Type: application/json
X-Cache: HIT
Age: 1

Install

# Homebrew (macOS)
brew install bouine-cache/bouine/bouine

# Prebuilt binary (from releases)
curl -L https://github.com/bouine-cache/bouine/releases/latest/download/bouine-v0.4.1-linux-amd64 -o bouine
chmod +x bouine

# or via Go
go install github.com/bouine-cache/bouine/cmd/bouine@latest

# or Docker
docker pull bouinecache/bouine:latest

Building from source

git clone https://github.com/bouine-cache/bouine.git
cd bouine
make build   # -> ./bin/bouine
./bin/bouine serve --config bouine.yaml

See CONTRIBUTING.md for the full development setup.

Administering a cache with the CLI

Every admin-plane subcommand (status, purge, ban, refresh, cluster peers, config get/set/diff, keys find/top/recent) accepts the global flags --server, --token, and --format text|json. Values resolve flag > environment > default, so scripts usually export the connection once:

export BOUINE_SERVER=127.0.0.1:9000        # admin listener
export BOUINE_TOKEN=<admin bearer token>     # prefer env: never lands in shell history
bouine purge http://example.com/img.png
bouine cluster peers --format json

One-glance overview. bouine status renders five sections — node (version, uptime, readiness), cache (hit ratio, tier fill), origins (per-target health and probe streaks), cluster (peers, peer-fetch stats), and insights (the dashboard's evaluated rules) — in one round-trip to GET /v1/status:

bouine status                # node-local; no peer traffic
bouine status --cluster      # adds per-peer request counters (one peer fan-out)
bouine status --watch        # fullscreen TUI on a terminal; Ctrl-C aborts the in-flight fetch

On a terminal, one-shot status prints the styled frame inline (colors, tier bars) and exits; --watch enters a fullscreen alternate-screen TUI with q/Esc/Ctrl-C (quit), r (refresh), and c (toggle cluster) keys. Piped output and --format json never take the TUI path, so scripts never see escape sequences.

Tuning a running daemon. bouine config reads and changes hot-config knobs without a restart. Runtime changes are ephemeral — lost at the next restart — so every set prints a warning and config diff shows what is at risk:

bouine config get                          # every knob: value + source (file/default/runtime)
bouine config set cluster.ban_ttl 90m     # validated point mutation, applied live
bouine config set cluster.ban_ttl 90m --dry-run   # validate and report, change nothing
bouine config set pool:api.health.passive.consecutive_5xx 10   # per-pool scoped knob
bouine config diff                         # knobs a restart would lose
bouine config diff prod.yaml staging.yaml  # deploy preview: effective-config diff

On a strong-mode cluster an applied set is broadcast to every live peer (the CLI reports the acknowledged count), so runtime overrides stay identical across the fleet.

Reloading a whole file (experimental). With experimental.config_reload: true, bouine config reload [--file] applies a config file's hot-appliable changes at once and reports what needs a restart — parse failures change nothing:

bouine config reload
# reloaded /etc/bouine/config.yaml
# applied   cluster.ban_ttl: 24h0m0s -> 45m0s
# NEEDS RESTART (restart required to activate):
#   listen.http: 127.0.0.1:9000 -> 127.0.0.1:9001

Cache-key queries. bouine keys answers cache questions without storage enumeration: find computes the key for a URL exactly the way the data plane does and reports the object's state on this node, and top/recent replay the URL ring's sampled non-HIT traffic — what the node is missing on lately:

bouine keys find http://example.com/pages/p?x=1    # key + tier/size/TTL/ban state
bouine keys find http://example.com/pages/p --peer # cluster: also ask the ring owner
bouine keys top --limit 20                          # most-requested prefixes, result breakdown
bouine keys top --class miss                        # keep only prefixes with misses
bouine keys recent                                  # latest-active prefixes

The listing is a request-traffic view, not a storage inventory: the ring samples non-HIT requests (url_ring_sample_rate), keeps the first three path segments per prefix, and holds at most 512 prefixes. URL→key is deterministic; key→URL is impossible (the hash is irreversible), which is why there is no object listing. find and the listings are node-local — target named pods for per-node answers.

Targeting a pod in Kubernetes. The admin port is deliberately never exposed by a LoadBalancer (the admin Service is ClusterIP, and the image is distroless so there is no shell to kubectl exec into). Use port-forward against a named pod:

kubectl -n cache port-forward pod/bouine-0 9000:9000 &
export BOUINE_SERVER=127.0.0.1:9000
export BOUINE_TOKEN="$(kubectl -n cache get secret bouine-admin-token \
  -o jsonpath='{.data.token}' | base64 -d)"
bouine cluster peers

Caveats:

  • Port-forward works because listen.admin binds :9000 on all interfaces; RBAC on pods/portforward gates access.
  • The ClusterIP admin Service picks a random pod per connection — fine for broadcast operations (purge, ban, refresh propagate cluster-wide) but wrong for node-local output. Target named pods via port-forward or the StatefulSet headless DNS (bouine-0.bouine-headless.<namespace>.svc:9000).
  • Do not expose the admin port via Ingress/LoadBalancer: token-only auth is too thin for a public listener.

Verifying release signatures

All releases are cryptographically signed using cosign with keyless signing (Sigstore, via GitHub Actions OIDC). The private key is never stored on any distribution server — signatures are generated in CI using ephemeral credentials tied to the GitHub workflow.

Verify a binary release:

# Download the release assets
curl -L -O https://github.com/bouine-cache/bouine/releases/latest/download/SHA256SUMS
curl -L -O https://github.com/bouine-cache/bouine/releases/latest/download/SHA256SUMS.sig
curl -L -O https://github.com/bouine-cache/bouine/releases/latest/download/SHA256SUMS.pem

# Verify the checksums signature
cosign verify-blob \
  --certificate SHA256SUMS.pem \
  --signature SHA256SUMS.sig \
  --certificate-identity https://github.com/bouine-cache/bouine/.github/workflows/release.yml@refs/tags/v0.4.1 \
  --certificate-oidc-issuer https://token.actions.githubusercontent.com \
  SHA256SUMS

# Verify the binary matches the checksums
sha256sum -c SHA256SUMS

Verify the Docker image:

cosign verify --certificate-identity-regexp "https://github.com/bouine-cache/bouine/.github/workflows/release.yml@refs/tags/" \
  --certificate-oidc-issuer https://token.actions.githubusercontent.com \
  bouinecache/bouine:latest

The signing certificate's identity is tied to the release.yml workflow in the bouine-cache/bouine repository, so only releases built by the official CI pipeline can produce valid signatures.


Kubernetes

helm repo add bouine https://charts.bouine.org
helm repo update

helm install bouine bouine/bouine \
  --namespace bouine --create-namespace \
  --set "config.upstream_pools[0].name=app" \
  --set "config.upstream_pools[0].targets[0]=app.default.svc:8080" \
  --set "config.routes[0].pool=app" \
  --set config.cluster.enabled=true

The chart deploys a 3-replica StatefulSet with gossip clustering, a headless Service for peer discovery, and a PodDisruptionBudget. See deploy/helm/bouine/values.yaml for all options.


Documentation

📖 Full documentation: bouine.org (getting started, configuration, operations, guides). The in-repo docs below are the canonical source the site is built from.

Topic Where
Getting started & install bouine.org/docs/getting-started
Architecture reference docs/architecture.md
Roadmap ROADMAP.md
Configuration reference bouine.org/docs/configuration
Migration from NGINX docs/migration/nginx.md
Migration from Varnish docs/migration/varnish.md
Contributing CONTRIBUTING.md
Governance GOVERNANCE.md
Changelog CHANGELOG.md
Security policy & disclosure SECURITY.md
Discussions & community GitHub Discussions

License

Apache License 2.0.

Contributing

All contributors are bound by AGENTS.md. Humans should start at CONTRIBUTING.md; AI agents start at AGENTS.md. Security issues go through SECURITY.md, never public issues.

Community

About

⚡ Bouine is a cloud-native HTTP cache in Go - RFC9111 compliant, zero-alloc hit path, gossip clustering, no external K/V store

Topics

Resources

Code of conduct

Contributing

Security policy

Accessibility

Stars

4 stars

Watchers

1 watching

Forks

Releases

Packages

Used by

Contributors

Languages