Gateway

Deployment

Binary

Download a tagged release tarball (Linux/macOS, x86_64/arm64) and extract the gw binary:

# substitute the release tag and target platform
VERSION=vX.Y.Z  # a tag from the Releases page
OS=linux      # or darwin
ARCH=amd64    # or arm64
curl --proto '=https' --tlsv1.2 -LsSf -o gw.tar.gz \
  "https://github.com/cocoonstack/gateway/releases/download/${VERSION}/gw_${VERSION#v}_${OS}_${ARCH}.tar.gz"
tar -xzf gw.tar.gz gw

Or build from source:

make release            # target/release/gw, built --locked
GW_CONFIG=/etc/gateway.yaml ./target/release/gw

The release profile is fat LTO with one codegen unit, functions aligned to 64 bytes and branch targets to 32 bytes (.cargo/config.toml), and gw runs on jemalloc as its global allocator.

Environment

Variable Effect
GW_CONFIG config file path; unset uses the embedded demo config (with storage.postgres_url the config store’s newest version replaces it; the file only seeds an empty store)
GW_HOST override listen.host (containers set 0.0.0.0)
GW_PORT override listen.port
GW_TRANSPORT mock (zero egress) / http (no mock) / unset (auto-route)
GW_CONTENT_KEY 64 hex chars (32 bytes); seals retained content at rest. Without it, full retention stores redacted text instead of raw
GW_ADMIN_TOKEN global admin bearer named by the default config’s admin.token_env; unset with no tenant admin_token_env leaves /admin/* and /internal/* answering 404; a tenant token alone opens /admin/* for its scope
RUST_LOG log level, e.g. info, gw_views=debug
OTEL_EXPORTER_OTLP_ENDPOINT / OTEL_EXPORTER_OTLP_TRACES_ENDPOINT either enables the per-request OTLP span export (Observability)
OTEL_SERVICE_NAME, OTEL_TRACES_SAMPLER, OTEL_TRACES_SAMPLER_ARG service name (default gw) and sampler of the exported spans (SDK defaults: parent-based, always on)
provider key vars named by each account’s api_key_env
MCP credential vars named by mcp_servers[].api_key_env, oauth.client_secret_env and oauth.refresh_token_env; read at each call or token fetch, never stored

The process drains on SIGINT/SIGTERM (graceful shutdown of in-flight requests).

Docker

docker build -t gateway .
docker run -p 8080:8080 gateway            # embedded demo config
docker run -p 8080:8080 \
  -v $PWD/conf/gateway.yaml:/etc/gateway.yaml \
  -e GW_CONFIG=/etc/gateway.yaml \
  -e OPENAI_API_KEY=sk-... \
  gateway

The image is a slim non-root runtime, binds 0.0.0.0, and has a /health HEALTHCHECK. Tagged v* pushes publish a multi-arch image to ghcr.io/cocoonstack/gateway.

Multi-replica

State that must be shared across replicas has a backend:

storage:
  postgres_url: "postgres://gw:secret@db:5432/gw"  # fleet config + keys + ledger/files/batches
  redis_url: "redis://redis:6379"      # shared rate limits + quotas + account health
  ledger_max_rows: 1000000             # prune oldest billing rows past the cap
  postgres_max_connections: 10         # store and key-store connection-pool size
  # sqlite_path: /var/lib/gw/store.db  # single-node alternative to postgres_url

Control plane

The browser console in control-plane/ deploys beside the gateway fleet: a single Go binary serving the built web assets, plus its own Postgres (identities) and Redis (sessions) — never the gateway’s tables. Releases ship ghcr.io/cocoonstack/gateway-control-plane (multi-arch), binary tarballs for linux/darwin × amd64/arm64, and the built web assets as a separate control-plane-web-dist tarball.

Point it at the fleet with CP_GATEWAY_TARGETS, give it the global admin token via CP_GATEWAY_ADMIN_TOKEN, and hand each tenant’s scoped token to CP_GATEWAY_TENANT_TOKENS (tenant=token,... matching each tenant’s admin_token_env) so tenant-admin key mutations run under the gateway’s own tenant scope and fail closed without one. Full reference: control-plane/README.md.