A fully conformant Kubernetes gateway, built in Rust for speed. It carries your HTTP, LLM and MCP traffic on one data plane.
helm install portus oci://ghcr.io/portus-gateway/charts/portus-gatewayGateway API v1.6.2, experimental channel. The upstream suite runs in-cluster against a real per-Gateway deployment. Each square below is one passing result.
Measured with the open gateway-api-bench method: three interleaved rounds on one machine, against the same backend pods as the top-ranked gateway in that benchmark. Every number and every round is published.
Method: the howardjohn/gateway-api-bench approach on a 10-vCPU Linux VM on an Apple M4 (k3s, pods at MTU 65485), three interleaved rounds per gateway, fortio in-cluster with one pod per rung, 3 proxy pods each. Medians shown. Numbers from one machine are only comparable with each other. Failover figures come from the gateway-api-bench suite on Docker Desktop with no retry policy; with a RetryPolicy Portus returns 0 errors. Every round, including the tests agentgateway wins →
Kubernetes routes, LLM providers and MCP tool servers sit behind the same Gateway, under the same policies, in the same Rust process.
status.addresses.apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
name: my-gateway
spec:
gatewayClassName: portus-gateway
listeners:
- name: http
protocol: HTTP
port: 80
---
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: my-route
spec:
parentRefs:
- name: my-gateway
hostnames: ["app.example.com"]
rules:
- backendRefs:
- name: my-service
port: 8080
model and stream as bytes arrive, then replays them to the provider unchanged.portus_sk_… keys and stores only hashes. The provider's key never leaves the cluster.x-portus-tokens-remaining.ANTHROPIC_BASE_URL=https://llm.example.com ANTHROPIC_API_KEY=portus_sk_… claudeapiVersion: portus-gateway.dev/v1alpha1
kind: AIProvider
metadata: {name: anthropic}
spec:
kind: anthropic
url: https://api.anthropic.com
credential: {secretRef: {name: anthropic-key}}
---
apiVersion: portus-gateway.dev/v1alpha1
kind: AIRoute
metadata: {name: claude}
spec:
parentRefs: [{name: gateway}]
hostnames: [llm.example.com]
requireApiKey: true
rules:
- matches:
- model: {type: Prefix, value: claude-}
providerRefs: [{name: anthropic}]
---
apiVersion: portus-gateway.dev/v1alpha1
kind: AIUsagePolicy
metadata: {name: daily-cap}
spec:
targetRef: {group: portus-gateway.dev, kind: AIRoute, name: claude}
budget: {tokens: 1000000, window: Daily, per: Key}
method and the tool a tools/call names, so github.* lives on one server and the rest elsewhere.<server>.<tool>. No gateway pod holds session state.claude mcp add --transport http github https://mcp.example.com/mcpapiVersion: portus-gateway.dev/v1alpha1
kind: AIProvider
metadata: {name: github-mcp}
spec:
kind: mcp
url: http://github-mcp.tools.svc.cluster.local:3001
---
apiVersion: portus-gateway.dev/v1alpha1
kind: AIRoute
metadata: {name: mcp}
spec:
parentRefs: [{name: gateway}]
hostnames: [mcp.example.com]
requireApiKey: true
auth:
jwt: {issuer: https://dex.example.com, audience: portus}
rules:
- matches:
- method: {type: Exact, value: tools/call}
tool: {type: Prefix, value: github.}
providerRefs: [{name: github-mcp}]
Keys, budgets and token checks are local lookups against state the ledger pushes in. A request never waits on another service.
Config arrives over mTLS gRPC and swaps atomically. Each data plane acknowledges the fingerprint it applied, and that drives the Gateway's Programmed condition.
Routing, policies, TLS and the AI logic live in a stack-independent core. That let us measure two engines side by side and ship the faster one.
Reconcilers feed a shared store, the store compiles to a CompiledConfig protobuf, and each data plane receives only its own Gateway's slice.
Watches Gateway API and Portus CRDs with kube-rs, provisions a data plane per Gateway, and recompiles on any change. Reconcilers publish events to each other instead of polling.
Builds route maps keyed by port and hostname with full Gateway API precedence. Failing endpoints are ejected passively and readmitted with back-off. SIGTERM drains in-flight requests.
Opt-in with the AI gateway. Issues keys, keeps budgets as a shared counter synced every second, stores usage, and fetches OAuth issuers' signing keys. It never sits on the request path.
Everything Portus decides lives in a core that knows nothing about the proxy framework underneath. So we ran the same gateway on Rama and on Pingora, on the same machine, and measured. Rama was faster on every payload rung and used 2–2.6× less memory, so it became the default in 0.2.4.
Used unpatched, with Portus's own upstream connection pool. 130/130 conformance, and the AI and MCP gateways run on it.
Cloudflare's proxy framework carried Portus through its first releases and the benchmarks above. Rama measured faster than it on every payload rung.
Rama against Pingora: a single round on the same machine, 2026-09-15. A three-round comparison ships with the next release's numbers.
Point the data plane at one YAML file and it runs as a plain reverse proxy, with the same routing, policies and TLS it runs in Kubernetes.
http-01 or tls-alpn-01, renewed at two thirds of their lifetime.acme:
email: ops@example.com
cache_dir: /var/lib/portus/acme
challenge: http-01
listeners:
- port: 80
protocol: HTTP
routes:
- hosts: ["secure.example.com"]
redirect: {scheme: https, status_code: 301}
- port: 443
protocol: HTTPS
tls:
acme:
domains: ["secure.example.com"]
routes:
- hosts: ["secure.example.com"]
backends:
- address: "web:8080" # compose service
weight: 3
- address: "10.0.1.2:8080"
weight: 1
# Gateway API CRDs, experimental channel
kubectl apply --server-side --force-conflicts \
-f https://github.com/kubernetes-sigs/gateway-api/releases/download/v1.6.2/experimental-install.yaml
# Portus: controller, GatewayClass, policy CRDs, mTLS material
helm install portus oci://ghcr.io/portus-gateway/charts/portus-gateway --version 0.2.12 \
--namespace portus --create-namespace