Deploying a self-hosted MCP gateway on Kubernetes: A hands-on guide

by Sujay ChoubeySep 18, 202616 min read
MCP Gateway

TL;DR: A self-hosted MCP gateway deployment benefits from three architectural decisions: separating the control plane from the data plane, managing credentials with an external secrets store instead of native Kubernetes Secrets, and enforcing policy-as-code in the request path before the model is involved. This guide provides production-tested manifests for network policies, PodDisruptionBudgets, probes, and OPA policy enforcement, plus readiness requirements you can hand to platform engineering. Composio's Enterprise tier offers patterns for this deployment model, with SOC 2 Type II and ISO/IEC 27001:2022 certifications backing the security review.

Your enterprise prospect just asked where credentials are stored, and "we hash them in the application layer" is not passing the security review. A self-hosted MCP gateway on Kubernetes puts credentials in an AES-256 encrypted vault isolated from application code. Establishing the control plane and data plane separation early is a key design decision.

A recurring failure pattern in self-hosted deployments is treating policy enforcement as a prompt problem instead of an infrastructure problem. This guide is a production reference, not a tutorial. It covers the three architectural decisions that determine whether your deployment survives its first enterprise security review, with manifest templates and operational guidance you can hand directly to platform engineering.

Building for data residency and compliance

The deployment model decision comes before any YAML. For organizations in healthcare, finance, or government where regulated data must remain within specific boundaries, the deployment model is often a legal requirement before any technical comparison begins.

Local data sovereignty requirements

An MCP gateway on-prem keeps credential material, tool invocation payloads, and audit logs inside infrastructure you control. No request body, token, or response transits a vendor's cloud. If your data residency requirement says customer data cannot leave your VPC or your datacenter, a managed gateway is disqualified before the technical evaluation starts.

Meeting industry regulatory requirements

The EU AI Act requires organizations deploying AI in high-risk decisions to implement automatic logging capabilities that support post-hoc auditability (Regulation (EU) 2024/1689, Article 12), and HIPAA adds a hard constraint: you cannot disclose PHI (Protected Health Information) to an external service without a BAA (Business Associate Agreement) in place (45 CFR § 164.308(b)(1)). Self-hosting puts the audit trail, and the obligation, inside your boundary. However, if your compliance requirement includes a signed BAA with your MCP infrastructure vendor, confirm BAA availability directly with the Composio account team before treating self-hosting as your HIPAA solution, as no BAA has been publicly documented for Composio. Composio's MCP gateway governance breakdown documents how that logging maps to EU AI Act and HIPAA audit requirements.

Air-gapped Kubernetes cluster support

Self-hosted deployment is designed to support air-gapped clusters with no external calls required at runtime, but confirm the current maturity of air-gapped patterns with your Composio account team before committing this to a production architecture. Composio's Enterprise tier offers patterns for air-gapped deployments so you can mirror everything to your internal registry. Requirements: mirror all container images, vendor the charts, and pre-stage connector definitions before deploy time.

MCP gateway operational costs and trade-offs

The trade-off is operational. Self-hosting means your platform team owns Kubernetes operations, patching, and scaling, while a managed service shifts infrastructure responsibility to the vendor under a shared responsibility model.

Dimension

Self-hosted gateway

Managed service

Data residency

Full control, on-prem or VPC

Transits vendor infrastructure

Air-gapped

Designed to support (confirm current patterns with account team)

Not applicable

Maintenance burden

Your platform team owns it

Vendor-managed SLA

Initial setup

Weeks of initial setup

Hours to minutes

Technical blueprint for Kubernetes MCP deployments

A common mistake in self-hosted AI agent infrastructure is collapsing the control plane and data plane into one deployment. Retrofitting the separation after go-live means re-architecting request routing, policy enforcement placement, and credential handling across live workloads, work that can span multiple sprints.

Control plane vs data plane roles

An MCP control plane is the governance layer for fleets of MCP servers: it manages server discovery, authenticates and attests each server, enforces which tools an agent is allowed to call, and produces tamper-evident logs of every tool invocation. The data plane is the execution layer where tool calls run: MCP servers, proxies, and credential resolution. This pattern borrows from well-understood control plane separations in distributed systems like clusters and service meshes.

Kubernetes pod responsibility mapping

Pod role

Plane

Responsibility

Policy engine (OPA)

Control

Allow/deny evaluation in request path

Registry/discovery

Control

MCP server catalog and attestation

Audit logger

Control

Tamper-evident tool invocation logs

MCP gateway proxy

Data

Request routing, credential injection

MCP servers

Data

Tool execution against external APIs

The 2026-07-28 MCP specification's stateless HTTP transport means your gateway proxy tier can scale horizontally without session affinity, provided clients are using that transport mode — the earlier 2025-11-25 Streamable HTTP transport required session affinity due to the initialize handshake and Mcp-Session-Id header. That simplifies autoscaling, but it also means every request must carry enough context for the policy engine to evaluate it independently.

Environment and cluster readiness steps

Before any manifest ships, verify the cluster against these steps. Each item maps to a documented failure mode that has stalled production launches.

  1. Cluster version: Recommended v1.24 or later for modern Helm-based deployments. If you are deploying the CEL ValidatingAdmissionPolicy block in the policy-as-code section, v1.30 or later is required; clusters on v1.24–1.29 must use the OPA/Gatekeeper-based equivalent instead.

  2. Resource headroom: Plan capacity for multiple gateway replicas plus control plane pods.

  3. Storage class: Verify a CSI (Container Storage Interface) driver with appropriate access modes for stateful components.

  4. Ingress controller: Confirm an ingress controller is installed and configured.

  5. Secrets backend: Consider an external secrets store reachable from the cluster.

  6. Network policy support: Verify the CNI (Container Network Interface) enforces NetworkPolicy (Calico, Cilium).

Cluster resource headroom for gateway replicas

Size for the steady state plus a node failure. With pod anti-affinity spreading gateway replicas across nodes, losing one node must leave enough headroom for the survivors to absorb shifted traffic while the HPA's (Horizontal Pod Autoscaler) stabilization window settles, or autoscaling will chase its own scaling decisions during failover.

Storage setup for MCP gateways

Stateful components (registries, credential caches) need persistent volumes. Validate dynamic provisioning works before deployment day, because a StatefulSet stuck in Pending on a missing StorageClass is a launch-day incident, not a planning detail.

External access patterns for MCP gateways

Decide early whether agents reach the gateway over internal cluster DNS, a private load balancer, or a public ingress with TLS and OIDC (OpenID Connect). This choice drives your ingress manifests, certificate management, and the network policies in the next sections.

Handling sensitive data in MCP deployments

Kubernetes Secrets have a fundamental problem: they are base64 encoded in manifests and, by default, stored unencrypted in etcd unless you configure encryption at rest. Anyone with RBAC access to read secrets can decode them instantly. Native secrets alone do not meet production audit requirements.

External Secrets Operator for credential management

External Secrets Operator (ESO) solves this by syncing secrets from external providers like HashiCorp Vault, AWS Secrets Manager, or Azure Key Vault into your cluster automatically. Your applications consume standard Kubernetes Secrets while benefiting from Vault's dynamic secrets, detailed audit logs, and centralized policy management.

apiVersion: external-secrets.io/v1
kind: ClusterSecretStore
metadata:
  name: vault-backend
spec:
  provider:
    vault:
      server: "https://vault.example.com"
      path: "secret"
      version: "v2"
      auth:
        kubernetes:
          mountPath: "kubernetes"
          role: "external-secrets"
          serviceAccountRef:
            name: external-secrets
            namespace: external-secrets-system
---
apiVersion: external-secrets.io/v1
kind: ExternalSecret
metadata:
  name: app-secret
  namespace: production
spec:
  refreshInterval: 1h
  secretStoreRef:
    name: vault-backend
    kind: ClusterSecretStore
  target:
    name: app-secret
  data:
    - secretKey: config
      remoteRef:
        key: production/app/config

Vault and Kubernetes ServiceAccount authentication

Never use static AppRole credentials or root tokens in production, because they violate least-privilege principles and create audit findings. Instead, use Kubernetes ServiceAccount authentication, which binds Vault access directly to pod identity and namespace boundaries. This same pattern applies to connector credential injection: credentials land in pod environments through environment variable injection, without ever touching application code or the LLM context.

K8s native secrets management options

Native secrets are acceptable for non-production clusters or low-sensitivity configuration when you enable etcd encryption at rest and tight RBAC. They fall short when you need automated rotation, centralized audit of secret access, or multi-cluster sync, which is why regulated deployments standardize on an external store.

Secret lifecycle and rotation in Kubernetes

Set refreshInterval on every ExternalSecret and test that rotation triggers no service disruption. Implement NetworkPolicies restricting ESO pod egress to only Vault endpoints and Kubernetes API servers, blocking lateral movement if the controller is compromised.

Kubernetes ingress and policy best practices

The moment one egress network policy applies to a pod, the pod is isolated for egress. Start from default deny and open only what the gateway needs.

MCP gateway namespace default-deny policy

Implementing default deny enforces the principle of least privilege:

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: default-deny-all
  namespace: mcp-gateway
spec:
  podSelector: {}
  policyTypes:
    - Ingress
    - Egress

Then allow DNS:

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: allow-dns
  namespace: mcp-gateway
spec:
  podSelector: {}
  policyTypes:
    - Egress
  egress:
    - to:
        - namespaceSelector:
            matchLabels:
              kubernetes.io/metadata.name: kube-system
          podSelector:
            matchLabels:
              k8s-app: kube-dns
      ports:
        - protocol: UDP
          port: 53
        - protocol: TCP
          port: 53

Kubernetes ingress for MCP gateways

Restrict inbound traffic to the ingress controller namespace only:

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: mcp-gateway-ingress
  namespace: mcp-gateway
spec:
  podSelector:
    matchLabels:
      app: mcp-gateway
  policyTypes:
    - Ingress
  ingress:
    - from:
        - namespaceSelector:
            matchLabels:
              kubernetes.io/metadata.name: ingress-nginx
      ports:
        - protocol: TCP
          port: 8080

Once you integrate network policy to control pod connectivity, you need to configure the allow and deny settings in both directions.

TLS termination and mTLS for gateway traffic

Terminate TLS at the ingress with certificates from cert-manager, and require mTLS or a signed internal certificate for control plane to data plane traffic. A production-grade MCP gateway Kubernetes deployment should minimize plaintext hops in the request path.

MCP gateway egress restrictions

The data plane typically needs egress to the external APIs your connectors call and your identity provider. Enumerate both as explicit egress rules. A default-deny posture here is what turns "the agent cannot reach arbitrary endpoints" from a prompt instruction into an infrastructure fact.

Strategies for reliable self-hosted MCP nodes

Without PodDisruptionBudgets, a node drain could take down your entire application. PDBs tell Kubernetes how many pods can be unavailable during voluntary disruptions.

Pod disruption budget best practices

apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
  name: mcp-gateway-pdb
  namespace: mcp-gateway
spec:
  minAvailable: 2
  selector:
    matchLabels:
      app: mcp-gateway

Pair the PDB with anti-affinity so replicas never share a node:

spec:
  replicas: 3
  template:
    spec:
      affinity:
        podAntiAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:
            - labelSelector:
                matchLabels:
                  app: mcp-gateway
              topologyKey: kubernetes.io/hostname

Voluntary disruptions are planned actions you control (node drains, cluster upgrades), while involuntary disruptions are unplanned (hardware failures, OOM kills), and your PDB only governs the voluntary kind.

K8s autoscaling for MCP gateways

apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: mcp-gateway-hpa
  namespace: mcp-gateway
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: mcp-gateway
  minReplicas: 3
  maxReplicas: 10
  metrics:
    - type: Resource
      resource:
        name: cpu
        target:
          type: Utilization
          averageUtilization: 70

Set minReplicas above your PDB's minAvailable with enough headroom to absorb rolling update surge, so autoscaling never fights your disruption budget during a scale-down.

Cross-region gateway deployment patterns

For multi-region deployments, run an independent gateway stack per region and route with geo-aware DNS. Consider replicating the control plane's registry across regions so a region loss can degrade gracefully rather than taking down policy enforcement globally.

When to use StatefulSets for gateways

Use StatefulSets for stateful MCP components like credential stores and registries, where stable identity and persistent volumes matter:

apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: mcp-registry
  namespace: mcp-gateway
spec:
  serviceName: mcp-registry
  replicas: 2
  selector:
    matchLabels:
      app: mcp-registry
  template:
    metadata:
      labels:
        app: mcp-registry
    spec:
      containers:
        - name: registry
          image: composio/mcp-registry:latest
          volumeMounts:
            - name: data
              mountPath: /data
  volumeClaimTemplates:
    - metadata:
        name: data
      spec:
        accessModes: ["ReadWriteOnce"]
        resources:
          requests:
            storage: 10Gi

Stateless gateway proxies stay as Deployments. Mixing the two concerns into one workload type is how teams end up with proxies that cannot scale and registries that lose data on reschedule.

Key validation steps for production launch

The validation phase is where you prove the architecture claims before the security reviewer asks.

Distributed tracing for MCP gateway tool calls

Add the OpenTelemetry Operator to enable automatic instrumentation, annotate deployments to inject instrumentation without code changes, and deploy the Collector to gather traces from all nodes:

apiVersion: opentelemetry.io/v1alpha1
kind: OpenTelemetryCollector
metadata:
  name: mcp-collector
  namespace: mcp-gateway
spec:
  config: |
    receivers:
      otlp:
        protocols:
          grpc:
            endpoint: 0.0.0.0:4317
    processors:
      batch:
        send_batch_size: 1024
    exporters:
      jaeger:
        endpoint: jaeger-collector:14250
    service:
      pipelines:
        traces:
          receivers: [otlp]
          processors: [batch]
          exporters: [jaeger]

A trace for a single tool call should show the ingress hop, the policy evaluation span, credential resolution, and the outbound API call. If any span is missing, your audit story has a gap.

Kubernetes health checks for MCP gateways

Kubernetes supports three probe types: startup probes check if the container has started, liveness probes check if it is still running, and readiness probes check if it can accept traffic:

startupProbe:
  httpGet:
    path: /healthz/startup
    port: 8080
  periodSeconds: 5
  failureThreshold: 60
livenessProbe:
  httpGet:
    path: /healthz/live
    port: 8080
  periodSeconds: 15
  failureThreshold: 3
readinessProbe:
  httpGet:
    path: /healthz/ready
    port: 8080
  periodSeconds: 5
  failureThreshold: 3

The readiness endpoint must reflect downstream dependency health (secrets backend reachable, policy engine loaded), not just process liveness, or Kubernetes will route traffic to a pod that cannot serve it.

MCP gateway backup and restore

Back up the registry's persistent volumes and your ExternalSecrets definitions, and rehearse a restore into a fresh namespace on a regular cadence. A restored gateway with stale policy definitions is a governance incident, so test that policy state restores exactly, not just that pods come back.

Policy-as-code enforcement for gateway access

This is where policy-as-code separates a governance control from a prompt suggestion. The API gateway is a natural enforcement point because it already sits on the request path and already knows the caller's identity, and what CEL (Common Expression Language) and OPA bring is the ability to express and evaluate policies, not just check role lists.

package mcp.authz

import rego.v1

default allow := false

# Allow tool invocation if user has required role and tool is not restricted
allow if {
    input.user.role in ["admin", "agent_operator"]
    not restricted_tools[input.tool.name]
    input.method == "POST"
}

# Allow read-only tool calls for auditors
allow if {
    input.user.role == "auditor"
    input.method == "GET"
    input.tool.name in ["describe_tools", "list_executions"]
}

# Restrict sensitive financial tools to finance team only
restricted_tools[tool] if {
    tool in ["stripe_charge", "paypal_transfer", "accounting_ledger_write"]
}

When policies are declared in a versioned policy repo, they become auditable, and requests can be evaluated against explicit policies in the request path. A prompt instruction and a policy-as-code control look identical until a user tries to override one. The prompt bends, and the policy does not.

For Kubernetes-level guardrails, CEL admission policies enforce pod security at the API level. Note: ValidatingAdmissionPolicy with CEL reached beta in v1.28 (feature-gated) and GA in v1.30. Clusters running v1.24–1.27 cannot use this manifest as shown and will need to use an OPA/Gatekeeper-based equivalent instead:

apiVersion: admissionregistration.k8s.io/v1beta1
kind: ValidatingAdmissionPolicy
metadata:
  name: mcp-gateway-policy
spec:
  failurePolicy: fail
  validations:
    - expression: "object.spec.containers[0].securityContext.runAsNonRoot == true"
      message: "MCP gateway pods must run as non-root"
    - expression: "object.spec.containers[0].securityContext.allowPrivilegeEscalation == false"
      message: "MCP gateway pods must not allow privilege escalation"

Your pre-launch validation steps:

  • Verify probe endpoints return HTTP 200 when healthy and 503 when unhealthy.

  • Confirm credential rotation triggers no service disruption.

  • Test network policy rules with connectivity tests from inside and outside the namespace.

  • Validate audit logging captures every tool invocation, including denied calls.

  • Confirm traces appear in your backend (Jaeger, Datadog).

  • Drain a node and verify the PDB prevents simultaneous pod evictions.

  • Run a denied-action test against the policy engine and confirm the deny is logged.

Build vs. buy ROI template

Once the architecture is validated and your platform team has signed off on the manifests, the next step is making the case internally. Use this template to document the decision for your CTO and CFO:

  • In-house build cost: Engineering hours per integration multiplied by your loaded hourly rate, multiplied by planned integration count.

  • Ongoing maintenance (annual): Plan on dedicated platform ownership for Kubernetes patching and secrets backend upkeep, not a fraction of one engineer's time.

  • Self-hosted Enterprise tier: Setup measured in weeks, with connector updates handled by Composio (confirm this scope applies to self-hosted deployments with your account team), plus upstream API drift handled by Composio.

  • Customer case studies:

    • 11x saved approximately 380 engineering hours across Outlook, Salesforce, and Cal.com integrations while enabling $4.2M in enterprise deals.

    • Assista AI shipped Gmail, Calendar, GitHub, and Drive integrations live in production within days. Zams shipped Salesforce, HubSpot, Notion, and Slack through the same pattern, per the Zams case study.

    • Opennote evaluated multiple options and chose Composio on simplicity, per the Opennote case study.

Compliance documentation for the security review

Compliance documentation is the other half of the security review. Composio holds SOC 2 Type II and ISO/IEC 27001:2022 certifications, and Composio's trust center provides pre-filled compliance packs so most enterprise security questionnaires come back within a day rather than a sprint.

Book a call to walk through your self-hosted deployment requirements before the next enterprise security review: talk to the Composio team.

FAQs

What happens when the upstream API changes?

With an in-house build, your team owns the fix and the regression testing. With Composio's managed connector layer, schema updates and OAuth changes are handled centrally, which is the maintenance burden difference between a configuration update and a sprint of engineering work.

How do I handle credential rotation without downtime?

Set a refreshInterval on every ExternalSecret so rotated secrets sync automatically, and verify in staging that pods pick up new values without dropping in-flight requests. Composio's managed OAuth layer handles the full token lifecycle, including refresh, inside its isolated runtime.

Can I run this in an air-gapped cluster?

Yes, with the account-team confirmation noted above. Mirror all container images to an internal registry, vendor your Helm charts, and pre-stage connector definitions, because the cluster cannot pull anything at runtime.

What maintenance burden should I expect after go-live?

Plan for Kubernetes patching, secrets backend upkeep, and policy repo reviews as standing platform work with dedicated ownership. A vendor's self-hosted tier with managed connector updates removes a significant recurring item: upstream API drift.

How does this compare to using a managed service?

A managed service gets you to production in hours with zero infrastructure responsibility, but data transits vendor infrastructure and air-gapped deployment is off the table. Self-hosting costs weeks of setup and ongoing ops in exchange for full data residency and egress control.

Key terms glossary

MCP server: A service that exposes tools to AI agents via the Model Context Protocol. Each server implements a specific set of capabilities (GitHub API access, Gmail operations) and runs in the data plane. Composio owns the tool implementations directly across 1,000+ apps rather than proxying third-party servers, which means action schema changes and authentication updates are handled inside Composio's layer rather than passed through to your team.

Control plane: The governance layer that manages MCP server discovery, authentication, policy enforcement, and audit logging. It never touches tool payloads.

Data plane: The execution layer where tool calls run: gateway proxies, MCP servers, and credential resolution. It never makes authorization decisions.

Policy-as-code: Access rules expressed in a versioned, declarative language (Rego or CEL) and evaluated in the request path before the model is involved.

Get started

Your agents can
do more

Connect your agents to 1,500+ apps. Start for free, no credit card needed.

Are you an AI agent? See setup options

Share