Kubernetes install with Helm
Run an HA MCPG fleet on Kubernetes with the operator + Helm charts. NATS or Redis for shared state, plugin sets via CRD.
This guide installs MCPG on Kubernetes for production: the operator, an HA gateway deployment, and shared state via NATS (or Redis). Target audience: SREs and platform teams.
Prerequisites
- Kubernetes 1.26 or newer
- Helm 3.x
- A namespace to deploy into (we'll use
mcpg) - Either NATS or Redis available in-cluster (the gateway chart can deploy NATS as a subchart)
1. Install the operator
The operator reconciles eight CRDs (API group mcpg.dev, version v1alpha1):
MCPGGateway, MCPGPlugin, MCPGPluginSet, MCPGRevocationList, MCPGCluster,
MCPGRoute, MCPGTenant, and MCPGPluginMirror.
kubectl create namespace mcpg
helm install mcpg-operator oci://ghcr.io/mcpg-dev/source-code/charts/mcpg-operator --version 0.1.0 -n mcpg \
--set rbac.create=true \
--set webhook.enabled=true
The operator serves admission webhooks on :9443 and its metrics / health endpoint on
:8443. Verify:
kubectl -n mcpg get pods
# mcpg-operator-7d8c4b9b8-xj2pq 1/1 Running
2. (Optional) Install the control plane
The control plane is optional for single-instance and operator-managed deployments; install it when you want the fleet dashboard + agent registration.
helm install mcpg-cp oci://ghcr.io/mcpg-dev/source-code/charts/mcpg-control-plane --version 0.1.0 -n mcpg \
--set postgresql.enabled=true \
--set auth.mode=oidc \
--set auth.oidc.issuer=https://idp.example.com \
--set ingress.enabled=true \
--set ingress.hosts[0].host=mcpg-cp.example.com
The CP serves its HTTP API + dashboard on 7843 and the gRPC agent endpoint on 7844.
With postgresql.enabled=true it deploys a Postgres subchart for state; auth.mode is one
of none / oidc / mock.
3. Install a gateway fleet
Use the mcpg chart with autoscaling, a PDB, and the bundled NATS subchart for shared
state:
helm install gw oci://ghcr.io/mcpg-dev/source-code/charts/mcpg --version 0.1.0 -n mcpg \
--set replicaCount=3 \
--set autoscaling.enabled=true \
--set autoscaling.minReplicas=3 \
--set autoscaling.maxReplicas=10 \
--set podDisruptionBudget.enabled=true \
--set nats.enabled=true
With nats.enabled=true the chart auto-wires cluster.kind: nats so any replica can answer
any session. The gateway's own config block is rendered into config.yaml; override it via
the chart's config: / extraConfig: values, or manage it declaratively through the
MCPGGateway CRD (next section).
4. Manage the gateway via the MCPGGateway CRD
For GitOps-managed deployments, drive the gateway through the operator. The spec.config
field is the gateway's own AppConfig schema verbatim (snake_case, deny_unknown_fields):
apiVersion: mcpg.dev/v1alpha1
kind: MCPGGateway
metadata:
name: gw
namespace: mcpg
spec:
replicas: 3
image:
repository: ghcr.io/mcpg-dev/source-code/gateway
tag: v1.2.3
pluginSetRef:
name: production
config:
gateway:
server:
bind_address: "0.0.0.0:8787"
allowed_origins:
- "https://gateway.example.com"
governance:
access:
jwks:
url: "https://idp.example.com/.well-known/jwks.json"
audience: "mcpg-prod"
mcp:
capabilities:
tools:
- name: api.account.lookup
description: Look up an account by id.
backend:
kind: http
url: "https://accounts.internal.example.com/v1/accounts/${arguments.account_id}"
method: get
expected_status_codes: [200]
autoscaling:
enabled: true
minReplicas: 3
maxReplicas: 10
Tools live under spec.config.mcp.capabilities.tools[] — the same AppConfig shape the
standalone gateway boots from. Validate any inline config with mcpg config check before
committing it.
5. Bind a plugin set
Plugins are distributed and bound declaratively. A plugin set lists entries that reference
cluster-scoped MCPGPlugin artifacts:
apiVersion: mcpg.dev/v1alpha1
kind: MCPGPluginSet
metadata:
name: production
namespace: mcpg
spec:
entries:
- id: dev.mcpg.identity.oidc
pluginRef:
name: identity-oidc-1.0.0-linux-amd64
enabled: true
- id: dev.mcpg.policy.cedar
pluginRef:
name: policy-cedar-1.0.0-linux-amd64
enabled: true
config:
policy_dir: "/etc/mcpg/policies"
- id: dev.mcpg.rate-limit
pluginRef:
name: rate-limit-1.0.0-linux-amd64
enabled: true
config:
requests_per_minute: 100
capabilityGrants:
dev.mcpg.identity.oidc:
- network_outbound
Apply, then bind it to the gateway:
kubectl apply -f plugin-set.yaml
kubectl patch mcpggateway gw -n mcpg --type=merge \
-p '{"spec":{"pluginSetRef":{"name":"production"}}}'
The operator reconciles, materialises each plugin's projected Secret, and the gateway pods
reload with the rendered plugins[] config.
Cluster state options
| Backend | Pick when |
|---|---|
NATS JetStream (nats.enabled=true / cluster.kind: nats) | You don't already run Redis. JetStream gives KV + pub/sub + leases in one component. |
Redis (redis.enabled=true / cluster.kind: redis) | You already run Redis. Lower operational overhead. |
Built-in single-node (cluster.kind: single_node) | Single-instance only. Don't use for HA. |
For multi-tenant clusters, add the MCPGTenant and MCPGRoute CRDs — see the
multi-tenant guide.
What's next
- Observability — Prometheus, OTLP, audit
- Identity setup — wire OIDC / JWKS / SPIFFE
- Plugin authoring — write your own plugin