Kubernetes Deployment
This guide deploys the Gateway on Kubernetes as an ordinary stateless Deployment. It works on managed Kubernetes (EKS, GKE, AKS) and on self-managed clusters.
There is nothing unusual to model: no StatefulSet, no persistent volume, no leader election. All state lives in Postgres, so the Gateway is a plain horizontally-scaled web workload.
Overview
You’ll create these in a dedicated britive-mcp-gateway namespace:
- A Secret holding the tenant, pool token, and database URL. That is the whole of the pod’s configuration; every other setting comes from the gateway pool on your Britive tenant.
- A Deployment of two Gateway replicas, with startup, readiness, and liveness probes.
- A Service exposing them inside the cluster.
- An Ingress as one way to get traffic in.
Architecture
flowchart LR
Clients["MCP clients and agents"]
Britive["Britive tenant"]
Backends["Backend MCP servers"]
subgraph ns["Namespace: britive-mcp-gateway"]
Ing["Ingress<br/>TLS termination"]
Svc["Service<br/>ClusterIP"]
P1["Gateway pod"]
P2["Gateway pod"]
Ing --> Svc
Svc --> P1
Svc --> P2
end
DB[("Postgres<br/>(yours)")]
Clients -->|"HTTPS"| Ing
P1 --> DB
P2 --> DB
P1 --> Britive
P1 --> Backends
Before You Begin
You need:
- A Kubernetes cluster and
kubectlconfigured for it. - A Postgres 14+ database the pods can reach. Not created by this manifest - a managed service is the expected choice.
- An ingress controller or another way to expose a Service, plus a TLS certificate.
- A Britive tenant and pool token.
Deploy
Download the manifest
Download mcp-gateway-k8s.yaml - Namespace, Secret, Deployment, Service, and Ingress.
Fill in the Secret
Replace the three REPLACE_ME values in the manifest’s Secret: TENANT,
GATEWAY_POOL_TOKEN, and DATABASE_URL.
That is the whole of the pod’s configuration. There is no master secret to generate and no ConfigMap of settings: the key protecting stored tokens and sessions is minted by the platform for the gateway pool and arrives with the pool’s settings, so every pod agrees on it without anything being distributed.
For a real cluster, prefer the External Secrets Operator, Sealed Secrets, or your cloud’s secret store over committing a manifest with values in it.
Set the public URL on the pool
In the Britive tenant portal, open the gateway pool your token belongs to and set Public base URL to the hostname clients will use - the same hostname as the Ingress:
https://mcp-gateway.example.internalOAuth redirect and resource URLs are built from it, so it must match what the browser actually reaches - not the pod or the Service.
Apply it
kubectl apply -f mcp-gateway-k8s.yaml
kubectl -n britive-mcp-gateway rollout status deploy/mcp-gatewayConfirm the pods are healthy
kubectl -n britive-mcp-gateway get pods
kubectl -n britive-mcp-gateway logs deploy/mcp-gateway --tail=50Both pods should be Running and ready. The Gateway names configuration problems
explicitly in its startup logs.
Verify
kubectl -n britive-mcp-gateway port-forward svc/mcp-gateway 8080:80
curl -s http://localhost:8080/healthzThen through the Ingress:
GET /healthzreturns 200 at your hostname.- Signing in at the root URL reaches the admin console.
- Deleting one pod leaves you signed in, and the replacement serves traffic - proof the pods share state through Postgres rather than holding it locally.
kubectl -n britive-mcp-gateway delete pod <one-pod-name>Ingress Settings That Matter
The manifest sets three annotations because the defaults break real usage. They are written for nginx-ingress; use your controller’s equivalents.
nginx.ingress.kubernetes.io/proxy-read-timeout: "300"
nginx.ingress.kubernetes.io/proxy-send-timeout: "300"
nginx.ingress.kubernetes.io/proxy-buffering: "off"The timeouts. A Britive checkout that needs human approval can take longer than a default 60 seconds. An ingress that cuts the connection at 60s fails those tool calls, and it looks like a Gateway problem.
Buffering off. MCP holds a stream open for server-to-client messages. Buffering it makes a working call look like a hang.
No session affinity is needed anywhere. Every pod shares one database, so any pod can serve any request - do not add sticky sessions to work around a problem, because it will not be the cause.
Scaling and Upgrades
kubectl -n britive-mcp-gateway scale deploy/mcp-gateway --replicas=4The Deployment uses maxUnavailable: 0, so a rolling upgrade brings up a new pod
before removing an old one and the service stays up:
kubectl -n britive-mcp-gateway set image deploy/mcp-gateway \
gateway=britive/mcp-gateway:<tag>
kubectl -n britive-mcp-gateway rollout status deploy/mcp-gatewaySchema migrations run on startup, so there is no migration Job to run. The
startupProbe gives that first run room to finish before the liveness probe starts
counting failures.
Pin a specific image tag rather than latest, so a pod rescheduling does not
silently change versions.
Changing Settings
Nothing beyond those three Secret keys is configured in the cluster. Audit sinks, rate limits, inspection, backend policy - all of it lives on the gateway pool in the Britive tenant portal, and every pod picks a change up on its next sync, within five minutes by default. No ConfigMap, no rollout, no restart.
That is deliberate: the person who changes a setting is a platform administrator, and
the person who can kubectl rollout restart is frequently someone else. See
Configuration.
Troubleshoot
| Symptom | Cause | Fix |
|---|---|---|
Pods in CrashLoopBackOff | Missing DATABASE_URL, unreachable database, or rejected pool token | kubectl logs - the message names it. |
| Pods never become ready | Readiness probe failing, or the database is slow to accept connections | Check /readyz and database connectivity from the pod. |
CreateContainerConfigError | Secret key missing | Confirm all three keys exist in the Secret. |
| Pods exit naming the tenant | The tenant is unreachable from the pod, or the pool token was rejected | Fetching settings is fatal at startup by design. The log names both causes; check egress and NetworkPolicy first. |
| OAuth fails at the callback | The pool’s Public base URL doesn’t match the Ingress host | Make them agree. |
| Calls appear to hang | Ingress buffering the MCP stream | Set proxy-buffering: "off". |
| Approval-gated calls time out | Ingress read timeout too low | Raise proxy-read-timeout. |
| Users signed out at random | Pods reading different Secrets, or pool tokens from different pools | Every pod must read the same Secret, and its token must belong to one pool. |
| A settings change hasn’t taken effect | The next sync hasn’t run, or the last one failed | The console’s Settings tab shows what each pod is running and flags stale settings. |