Deployment12 min read

Deploying MCP Servers to Kubernetes — Production-Grade Container Orchestration

Step-by-step guide to deploying MCP servers on Kubernetes. Covers Deployments, Services, ConfigMaps, health checks, horizontal pod autoscaling, and zero-downtime rollouts for production MCP infrastructure.

By MyMCPTools Team·

Kubernetes is the standard for running containerized workloads at scale. If your team already runs services on Kubernetes, deploying MCP servers as first-class workloads gives you the same operational benefits — autoscaling, rolling updates, health-based restarts, and centralized observability — that you get for every other service in your cluster.

This guide covers the complete path from a Dockerized MCP server to a production Kubernetes deployment: manifests, configuration management, health probes, autoscaling, and ingress for HTTP-transport servers.

Prerequisites

  • A containerized MCP server (see Deploying MCP to Docker for the base image)
  • A Kubernetes cluster (EKS, GKE, AKS, or local via kind/minikube)
  • kubectl configured to talk to your cluster
  • A container registry (ECR, GCR, Docker Hub, or GHCR)

Step 1: Build and Push Your MCP Server Image

Start with a minimal production Dockerfile. MCP servers are typically lightweight Node.js or Python processes:

# Dockerfile
FROM node:22-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci --production

FROM node:22-alpine
WORKDIR /app
COPY --from=builder /app/node_modules ./node_modules
COPY . .
EXPOSE 8080
ENV PORT=8080
CMD ["node", "dist/server.js"]

Build and push to your registry:

docker build -t your-registry/mcp-server:v1.0.0 .
docker push your-registry/mcp-server:v1.0.0

Step 2: Create the Deployment Manifest

A Kubernetes Deployment manages your MCP server pods, handles restarts on failure, and coordinates rolling updates:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: mcp-server
  namespace: mcp
  labels:
    app: mcp-server
    version: v1.0.0
spec:
  replicas: 2
  selector:
    matchLabels:
      app: mcp-server
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxSurge: 1
      maxUnavailable: 0   # Zero-downtime rollouts
  template:
    metadata:
      labels:
        app: mcp-server
    spec:
      containers:
      - name: mcp-server
        image: your-registry/mcp-server:v1.0.0
        ports:
        - containerPort: 8080
        env:
        - name: NODE_ENV
          value: production
        - name: DATABASE_URL
          valueFrom:
            secretKeyRef:
              name: mcp-server-secrets
              key: database-url
        - name: API_KEY
          valueFrom:
            secretKeyRef:
              name: mcp-server-secrets
              key: api-key
        resources:
          requests:
            cpu: 100m
            memory: 128Mi
          limits:
            cpu: 500m
            memory: 512Mi
        livenessProbe:
          httpGet:
            path: /health
            port: 8080
          initialDelaySeconds: 10
          periodSeconds: 30
          failureThreshold: 3
        readinessProbe:
          httpGet:
            path: /ready
            port: 8080
          initialDelaySeconds: 5
          periodSeconds: 10
          failureThreshold: 3

Step 3: Expose with a Service

A ClusterIP Service makes your MCP server reachable within the cluster. Use a LoadBalancer or Ingress for external access:

apiVersion: v1
kind: Service
metadata:
  name: mcp-server
  namespace: mcp
spec:
  selector:
    app: mcp-server
  ports:
  - name: http
    protocol: TCP
    port: 80
    targetPort: 8080
  type: ClusterIP

Step 4: Manage Configuration with ConfigMaps and Secrets

Never bake credentials into your container image. Use Kubernetes-native secrets management:

# ConfigMap for non-sensitive config
apiVersion: v1
kind: ConfigMap
metadata:
  name: mcp-server-config
  namespace: mcp
data:
  LOG_LEVEL: "info"
  MAX_CONNECTIONS: "100"
  RATE_LIMIT_RPM: "60"
---
# Secret for credentials (base64-encoded values)
apiVersion: v1
kind: Secret
metadata:
  name: mcp-server-secrets
  namespace: mcp
type: Opaque
stringData:
  database-url: "postgresql://user:pass@postgres:5432/mcpdb"
  api-key: "sk-your-api-key-here"

Reference the ConfigMap in your Deployment:

envFrom:
- configMapRef:
    name: mcp-server-config
- secretRef:
    name: mcp-server-secrets

Step 5: Add Health Check Endpoints

Kubernetes relies on your health probes to route traffic and restart unhealthy pods. Add both liveness and readiness endpoints to your MCP server:

import express from 'express'

const app = express()

// Liveness: is the process running?
app.get('/health', (req, res) => {
  res.json({ status: 'ok', uptime: process.uptime() })
})

// Readiness: is the server ready to handle MCP connections?
app.get('/ready', async (req, res) => {
  try {
    // Check dependencies (DB connection, external APIs)
    await db.query('SELECT 1')
    res.json({ status: 'ready' })
  } catch (err) {
    res.status(503).json({ status: 'not ready', error: err.message })
  }
})

Step 6: Horizontal Pod Autoscaling

HPA automatically scales your MCP server pods based on CPU or memory utilization:

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

Step 7: Ingress for HTTP-Transport MCP Servers

For MCP servers using SSE or HTTP transport (vs. stdio), expose them through an Ingress controller:

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: mcp-server-ingress
  namespace: mcp
  annotations:
    nginx.ingress.kubernetes.io/proxy-read-timeout: "3600"
    nginx.ingress.kubernetes.io/proxy-send-timeout: "3600"
    nginx.ingress.kubernetes.io/proxy-buffering: "off"   # Required for SSE
    cert-manager.io/cluster-issuer: "letsencrypt-prod"
spec:
  ingressClassName: nginx
  tls:
  - hosts:
    - mcp.yourdomain.com
    secretName: mcp-server-tls
  rules:
  - host: mcp.yourdomain.com
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: mcp-server
            port:
              number: 80

The proxy-buffering: off annotation is critical for SSE transport — nginx must not buffer the event stream or SSE clients will hang.

Apply Everything

kubectl create namespace mcp
kubectl apply -f configmap.yaml
kubectl apply -f secret.yaml
kubectl apply -f deployment.yaml
kubectl apply -f service.yaml
kubectl apply -f hpa.yaml
kubectl apply -f ingress.yaml

# Verify pods are running
kubectl get pods -n mcp

# Check rollout status
kubectl rollout status deployment/mcp-server -n mcp

Rolling Updates

Update your MCP server with zero downtime by bumping the image tag:

kubectl set image deployment/mcp-server   mcp-server=your-registry/mcp-server:v1.1.0   -n mcp

# Watch the rollout
kubectl rollout status deployment/mcp-server -n mcp

# Roll back if needed
kubectl rollout undo deployment/mcp-server -n mcp

Production Tips

Pod Disruption Budgets: Ensure at least one pod stays available during node maintenance:

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

Resource tuning: MCP servers handling many concurrent tool calls benefit from higher memory limits. Profile your server under load before setting production limits.

Namespace isolation: Run MCP servers in a dedicated namespace with NetworkPolicies restricting egress to only the external APIs they actually need.

Browse the MCP server directory to find production-ready MCP servers to deploy on your Kubernetes cluster, and check our guides for other platforms: AWS Lambda, Cloudflare Workers, and Google Cloud Run.

Recommended Tools

Better Stack

Free Plan

Get alerted when your APIs, browser tests, payment pipelines, or MCP server dependencies go down. Used by 100K+ developers.

Start monitoring free →

1Password

14-day Free Trial

Store and inject API keys, payment credentials, tokens, and file access secrets into your MCP server configs. Trusted by 150K+ developers.

Try 1Password free →

🔧 MCP Servers Mentioned in This Article

🔧

Docker MCP Server

The Docker MCP server (ckreiling/mcp-server-docker) gives an AI assistant direct control of a Docker daemon over the Model Context Protocol: containers, images, networks and volumes, as tools rather than shell commands. It is the community server most people mean by "Docker MCP" — distinct from Docker’s own Docker MCP Gateway, which does not manage your containers at all but runs *other* MCP servers inside containers. If you want to ask Claude why the postgres container keeps restarting, you want this one; if you want a single secure endpoint in front of twenty catalog servers, you want the gateway. The tool surface is explicit and small enough to reason about: list_containers, create_container, run_container, recreate_container, start_container, fetch_container_logs, stop_container and remove_container for containers; list_images, pull_image, push_image, build_image and remove_image for images; list_networks / create_network / remove_network and list_volumes / create_volume / remove_volume for the rest. Two resource templates, docker://containers/{id}/logs and docker://containers/{id}/stats, let a client read logs and live stats by container ID or name without a tool call. It also ships a docker_compose prompt that puts the model into a plan-then-apply loop — you describe the containers you want under a project name, the model proposes a concise plan, and nothing runs until you approve it; reopening the prompt with the same project name re-reads the state of everything created under it, which is how you clean up after a lost chat. It runs on the Python Docker SDK’s from_env, so DOCKER_HOST applies: set ssh://user@host and the same server administers a remote engine. Two limits are deliberate and stated by the project — privileged options like --privileged and --cap-add/--cap-drop are not supported, and container configuration passes through the model, so no secrets belong in it.

Local📘
🔧

Kubernetes MCP Server

The Kubernetes MCP server (mcp-server-kubernetes, built by Flux159) brings cluster management capabilities into AI assistant workflows, letting developers and platform engineers query and manage Kubernetes resources through natural-language interactions with Claude, Cursor, and other MCP-compatible clients. It loads your existing kubeconfig automatically, so it works with any cluster — local minikube and kind setups, Amazon EKS, Google GKE, Azure AKS, or on-premises deployments — with no separate credential setup required. Core tools exposed by the server include: listing pods, deployments, services, and namespaces; describing individual resources and their status; fetching pod logs for debugging; applying and updating manifests; scaling deployments; checking rollout status and history; and querying resource utilization and cluster events. A built-in non-destructive mode can disable delete/scale-down operations entirely, making it safe to point at production clusters for read-only diagnostics. DevOps engineers use it to debug failing deployments by asking Claude to inspect pod logs and recent events, identify resource constraints causing OOMKilled pods, or summarize the current state of a namespace before a production release. For SREs responding to incidents, it enables rapid triage through conversational commands — no memorizing kubectl flags or switching terminal windows mid-incident — and optional OpenTelemetry integration adds observability into what the AI agent actually did against the cluster. Install with: `npx mcp-server-kubernetes`. Pairs well with the GitHub MCP server for full GitOps review workflows.

Local
💻

GitHub MCP Server

authenticated access to the whole GitHub platform — repositories, files, branches, issues, pull requests, Actions runs, security alerts, discussions and notifications — from Claude, Cursor, VS Code, Copilot CLI and any other MCP host. There is no npm package for this server, and that trips up most people who try to install it: `@github/mcp-server` is not published to the npm registry, so any `npx` line you find for it will fail. GitHub ships it three other ways. The easiest is the hosted remote server at https://api.githubcopilot.com/mcp/, which needs no install at all — point an HTTP-transport MCP client at that URL and log in with OAuth (VS Code 1.101+, Claude Desktop, Claude Code, Cursor and Windsurf all support this). The second is the official Docker image ghcr.io/github/github-mcp-server, which is what the copy-paste command on this page runs; on github.com it now performs a browser-based OAuth login on first use and keeps the token in memory only, which is why the published Docker configs map a fixed loopback callback port (-p 127.0.0.1:8085:8085 with GITHUB_OAUTH_CALLBACK_PORT=8085) so the container can receive the callback. Prefer a token? Set GITHUB_PERSONAL_ACCESS_TOKEN instead — it takes precedence over OAuth, and the minimum useful scopes are repo, read:org and read:packages. The third is the native Go binary from the repository's releases, which needs no fixed port for the OAuth flow. GitHub Enterprise Server has no hosted option: use the local server with --gh-host or GITHUB_HOST set to your instance (include the https:// scheme — it defaults to http://, which GHES rejects). Toolsets can be narrowed with GITHUB_TOOLSETS, and an insiders channel is available at /mcp/insiders or via the X-MCP-Insiders header.

Auth required📘
🌐

Fetch

Web content fetching and conversion for efficient LLM usage. Extract readable content from any URL.

Local

📚 More from the Blog