> ## Documentation Index
> Fetch the complete documentation index at: https://mcp-server-langgraph.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Kubernetes Deployment

> Deploy MCP Server with LangGraph on Kubernetes

### Overview

Deploy the MCP Server with LangGraph on Kubernetes for production-grade reliability, scalability, and high availability. This guide covers deployment with Keycloak SSO, Redis sessions, OpenFGA authorization, and observability.

<Info>
  **v2.4.0** includes updated infrastructure (OpenFGA v1.10.2, Keycloak 26.4.0, PostgreSQL 16), production-ready Kubernetes manifests with Keycloak, Redis, and comprehensive security configurations.
</Info>

### Architecture

```mermaid theme={null}
flowchart TB
    Internet[Internet]

    subgraph Kubernetes_Cluster["Kubernetes Cluster"]
        subgraph Ingress_Layer["Ingress Layer"]
            Ingress[Ingress Controller]
        end

        subgraph Application_Layer["Application Layer"]
            Agent1[Agent Pod 1]
            Agent2[Agent Pod 2]
            Agent3[Agent Pod 3]
        end

        subgraph Authentication_Layer["Authentication Layer"]
            KC1[Keycloak Pod 1]
            KC2[Keycloak Pod 2]
        end

        subgraph Session_Layer["Session Layer"]
            Redis1[Redis Master]
            Redis2[Redis Replica]
        end

        subgraph Authorization_Layer["Authorization Layer"]
            FGA[OpenFGA Pod]
        end

        subgraph Data_Layer["Data Layer"]
            PG[(PostgreSQL)]
        end

        subgraph Observability_Layer["Observability"]
            Jaeger[Jaeger]
            Prometheus[Prometheus]
        end
    end

    Internet --> Ingress
    Ingress --> Agent1
    Ingress --> Agent2
    Ingress --> Agent3
    Agent1 --> KC1
    Agent2 --> KC2
    Agent3 --> KC1
    Agent1 --> Redis1
    Agent2 --> Redis1
    Agent3 --> Redis1
    Redis1 --> Redis2
    Agent1 --> FGA
    Agent2 --> FGA
    Agent3 --> FGA
    KC1 --> PG
    KC2 --> PG
    FGA --> PG
    Agent1 --> Jaeger
    Agent2 --> Jaeger
    Agent3 --> Jaeger

    %% ColorBrewer2 Set3 palette - unique color per service type
    classDef externalStyle fill:#8dd3c7,stroke:#2a9d8f,stroke-width:2px,color:#333
    classDef ingressStyle fill:#fb8072,stroke:#e74c3c,stroke-width:2px,color:#333
    classDef agentStyle fill:#b3de69,stroke:#7cb342,stroke-width:2px,color:#333
    classDef authStyle fill:#bebada,stroke:#8e7cc3,stroke-width:2px,color:#333
    classDef sessionStyle fill:#80b1d3,stroke:#3498db,stroke-width:2px,color:#333
    classDef authzStyle fill:#d9d9d9,stroke:#95a5a6,stroke-width:2px,color:#333
    classDef dataStyle fill:#8dd3c7,stroke:#2a9d8f,stroke-width:2px,color:#333
    classDef tracingStyle fill:#ffffb3,stroke:#f1c40f,stroke-width:2px,color:#333
    classDef metricsStyle fill:#fdb462,stroke:#e67e22,stroke-width:2px,color:#333

    class Internet externalStyle
    class Ingress ingressStyle
    class Agent1,Agent2,Agent3 agentStyle
    class KC1,KC2 authStyle
    class Redis1,Redis2 sessionStyle
    class FGA authzStyle
    class PG dataStyle
    class Jaeger tracingStyle
    class Prometheus metricsStyle
```

### Prerequisites

<Steps>
  <Step title="Kubernetes Cluster">
    **Minimum requirements**:

    * Kubernetes 1.25+
    * 4 vCPUs, 8GB RAM
    * 50GB storage
    * kubectl configured

    **Recommended**:

    * 8+ vCPUs, 16GB+ RAM
    * 100GB+ SSD storage
    * Multi-zone cluster
    * Auto-scaling enabled
  </Step>

  <Step title="Container Registry">
    Build and push the Docker image:

    ```bash theme={null}
    # Build image
    docker build -t gcr.io/your-project/mcp-server-langgraph:v2.4.0 .

    # Push to registry
    docker push gcr.io/your-project/mcp-server-langgraph:v2.4.0
    ```

    Update `deployments/kubernetes/base/deployment.yaml`:

    ```yaml theme={null}
    spec:
      template:
        spec:
          containers:
          - name: mcp-server-langgraph
            image: gcr.io/your-project/mcp-server-langgraph:v2.4.0
    ```
  </Step>

  <Step title="Install Tools">
    ```bash theme={null}
    # kubectl
    curl -LO "https://dl.k8s.io/release/$(curl -L -s https://dl.k8s.io/release/stable.txt)/bin/linux/amd64/kubectl"
    chmod +x kubectl && sudo mv kubectl /usr/local/bin/

    # kustomize (optional)
    curl -s "https://raw.githubusercontent.com/kubernetes-sigs/kustomize/master/hack/install_kustomize.sh" | bash
    sudo mv kustomize /usr/local/bin/

    # helm (for dependencies)
    curl https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 | bash
    ```
  </Step>
</Steps>

### Quick Deploy

<CodeGroup>
  ```bash Complete Deployment theme={null}
  ## 1. Create namespace
  kubectl apply -f deployments/kubernetes/base/namespace.yaml

  ## 2. Create secrets
  kubectl create secret generic mcp-server-langgraph-secrets \
    --namespace=mcp-server-langgraph \
    --from-literal=anthropic-api-key="${ANTHROPIC_API_KEY}" \
    --from-literal=google-api-key="${GOOGLE_API_KEY}" \
    --from-literal=jwt-secret-key="$(openssl rand -base64 32)" \
    --from-literal=keycloak-client-secret="${KEYCLOAK_CLIENT_SECRET}" \
    --from-literal=redis-password="$(openssl rand -base64 32)" \
    --from-literal=openfga-store-id="${OPENFGA_STORE_ID}" \
    --from-literal=openfga-model-id="${OPENFGA_MODEL_ID}"

  ## 3. Deploy dependencies (Keycloak, Redis, OpenFGA)
  kubectl apply -f deployments/kubernetes/base/keycloak-deployment.yaml
  kubectl apply -f deployments/kubernetes/base/keycloak-service.yaml
  kubectl apply -f deployments/kubernetes/base/redis-session-deployment.yaml
  kubectl apply -f deployments/kubernetes/base/redis-session-service.yaml
  ## Note: OpenFGA deployment not shown - use Helm chart or manifest

  ## 4. Wait for dependencies
  kubectl wait --for=condition=available --timeout=300s \
    deployment/keycloak -n mcp-server-langgraph
  kubectl wait --for=condition=available --timeout=300s \
    deployment/redis-session -n mcp-server-langgraph

  ## 5. Deploy application
  kubectl apply -f deployments/kubernetes/base/

  ## 6. Wait for rollout
  kubectl rollout status deployment/mcp-server-langgraph -n mcp-server-langgraph

  ## 7. Verify
  kubectl get pods -n mcp-server-langgraph
  kubectl logs -l app=mcp-server-langgraph -n mcp-server-langgraph --tail=50
  ```

  ```bash Using Kustomize theme={null}
  ## Deploy with kustomize overlays
  kubectl apply -k deployments/kubernetes/overlays/production

  ## Or specific cloud provider
  kubectl apply -k deployments/kubernetes/overlays/gke
  kubectl apply -k deployments/kubernetes/overlays/eks
  kubectl apply -k deployments/kubernetes/overlays/aks
  ```
</CodeGroup>

### Step-by-Step Deployment

#### 1. Namespace and RBAC

```yaml theme={null}
## Create namespace
kubectl apply -f - << 'EOF'
apiVersion: v1
kind: Namespace
metadata:
  name: mcp-server-langgraph
  labels:
    name: mcp-server-langgraph
    environment: production
EOF

## Create service account
kubectl apply -f deployments/kubernetes/base/serviceaccount.yaml
```

#### 2. Configuration

**ConfigMap** for application configuration:

```bash theme={null}
kubectl apply -f deployments/kubernetes/base/configmap.yaml
```

<Accordion title="View configmap.yaml" icon="file">
  ```yaml theme={null}
  apiVersion: v1
  kind: ConfigMap
  metadata:
    name: mcp-server-langgraph-config
    namespace: mcp-server-langgraph
  data:
    # Service
    environment: "production"
    log_level: "INFO"

    # LLM
    llm_provider: "anthropic"
    model_name: "claude-sonnet-4-5-20250929"
    enable_fallback: "true"

    # Authentication
    auth_provider: "keycloak"
    auth_mode: "session"

    # Keycloak
    keycloak_server_url: "http://keycloak:8080"
    keycloak_realm: "mcp-server-langgraph"
    keycloak_client_id: "langgraph-client"

    # Redis Sessions
    session_backend: "redis"
    redis_url: "redis://redis-session:6379/0"
    session_ttl_seconds: "86400"

    # OpenFGA
    openfga_api_url: "http://openfga:8080"
  ```
</Accordion>

**Secrets** for sensitive data:

```bash theme={null}
## Generate secrets
export JWT_SECRET=$(openssl rand -base64 32)
export REDIS_PASSWORD=$(openssl rand -base64 32)

## Create secret
kubectl create secret generic mcp-server-langgraph-secrets \
  --namespace=mcp-server-langgraph \
  --from-literal=anthropic-api-key="${ANTHROPIC_API_KEY}" \
  --from-literal=google-api-key="${GOOGLE_API_KEY}" \
  --from-literal=openai-api-key="${OPENAI_API_KEY}" \
  --from-literal=jwt-secret-key="${JWT_SECRET}" \
  --from-literal=keycloak-client-secret="${KEYCLOAK_CLIENT_SECRET}" \
  --from-literal=redis-password="${REDIS_PASSWORD}" \
  --from-literal=openfga-store-id="${OPENFGA_STORE_ID}" \
  --from-literal=openfga-model-id="${OPENFGA_MODEL_ID}"
```

<Warning>
  **Never commit secrets to Git!** Use external secret managers (see [Infisical Setup](/guides/infisical-setup)) or cloud-native solutions (GCP Secret Manager, AWS Secrets Manager, Azure Key Vault).
</Warning>

#### 3. Deploy Keycloak

```bash theme={null}
## Deploy Keycloak with PostgreSQL
helm install keycloak bitnami/keycloak \
  --namespace mcp-server-langgraph \
  --set auth.adminUser=admin \
  --set auth.adminPassword="$(openssl rand -base64 32)" \
  --set postgresql.enabled=true \
  --set postgresql.auth.password="$(openssl rand -base64 32)" \
  --set replicaCount=2 \
  --set ingress.enabled=true \
  --set ingress.hostname=sso.yourdomain.com

## Wait for deployment
kubectl wait --for=condition=available --timeout=300s \
  deployment/keycloak -n mcp-server-langgraph

## Initialize Keycloak
python scripts/setup/setup_keycloak.py

## Save client secret to Kubernetes secret
kubectl patch secret mcp-server-langgraph-secrets \
  -n mcp-server-langgraph \
  --patch "{\"data\":{\"keycloak-client-secret\":\"$(echo -n $CLIENT_SECRET | base64)\"}}"
```

See [Keycloak SSO Guide](/guides/keycloak-sso) for detailed setup.

#### 4. Deploy Redis

```bash theme={null}
## Deploy Redis with replication
helm install redis-session bitnami/redis \
  --namespace mcp-server-langgraph \
  --set auth.password="${REDIS_PASSWORD}" \
  --set master.persistence.enabled=true \
  --set master.persistence.size=10Gi \
  --set replica.replicaCount=2 \
  --set replica.persistence.enabled=true \
  --set sentinel.enabled=true

## Wait for deployment
kubectl wait --for=condition=ready pod \
  -l app.kubernetes.io/name=redis \
  -n mcp-server-langgraph \
  --timeout=300s

## Test connection
kubectl run redis-test --rm -i --tty \
  --image redis:7-alpine \
  --namespace mcp-server-langgraph \
  -- redis-cli -h redis-session -a $REDIS_PASSWORD ping
## Expected: PONG
```

See [Redis Sessions Guide](/guides/redis-sessions) for details.

#### 5. Deploy OpenFGA

```yaml theme={null}
## Deploy OpenFGA with PostgreSQL
kubectl apply -f - << 'EOF'
apiVersion: apps/v1
kind: Deployment
metadata:
  name: openfga
  namespace: mcp-server-langgraph
spec:
  replicas: 2
  selector:
    matchLabels:
      app: openfga
  template:
    metadata:
      labels:
        app: openfga
    spec:
      containers:
      - name: openfga
        image: openfga/openfga:v1.10.2
        args:
        - run
        env:
        - name: OPENFGA_DATASTORE_ENGINE
          value: postgres
        - name: OPENFGA_DATASTORE_URI
          value: "postgres://openfga:password@postgres:5432/openfga"
        ports:
        - containerPort: 8080
          name: http
        - containerPort: 8081
          name: grpc
---
apiVersion: v1
kind: Service
metadata:
  name: openfga
  namespace: mcp-server-langgraph
spec:
  selector:
    app: openfga
  ports:
  - name: http
    port: 8080
    targetPort: 8080
  - name: grpc
    port: 8081
    targetPort: 8081
EOF

## Initialize OpenFGA
python scripts/setup/setup_openfga.py
```

#### 6. Deploy Application

```bash theme={null}
## Deploy all manifests
kubectl apply -f deployments/kubernetes/base/deployment.yaml
kubectl apply -f deployments/kubernetes/base/service.yaml
kubectl apply -f deployments/kubernetes/base/hpa.yaml
kubectl apply -f deployments/kubernetes/base/pdb.yaml
kubectl apply -f deployments/kubernetes/base/ingress-http.yaml

## Wait for rollout
kubectl rollout status deployment/mcp-server-langgraph -n mcp-server-langgraph

## Check pods
kubectl get pods -n mcp-server-langgraph -l app=mcp-server-langgraph
```

#### 7. Configure Ingress

<Tabs>
  <Tab title="NGINX Ingress">
    ```yaml theme={null}
    apiVersion: networking.k8s.io/v1
    kind: Ingress
    metadata:
      name: mcp-server-langgraph
      namespace: mcp-server-langgraph
      annotations:
        cert-manager.io/cluster-issuer: letsencrypt-prod
        nginx.ingress.kubernetes.io/ssl-redirect: "true"
        nginx.ingress.kubernetes.io/proxy-body-size: "10m"
    spec:
      ingressClassName: nginx
      tls:
      - hosts:
        - api.yourdomain.com
        secretName: mcp-server-langgraph-tls
      rules:
      - host: api.yourdomain.com
        http:
          paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: mcp-server-langgraph
                port:
                  number: 8000
    ```

    ```bash theme={null}
    kubectl apply -f ingress.yaml
    ```
  </Tab>

  <Tab title="GKE Ingress">
    ```yaml theme={null}
    apiVersion: networking.k8s.io/v1
    kind: Ingress
    metadata:
      name: mcp-server-langgraph
      namespace: mcp-server-langgraph
      annotations:
        kubernetes.io/ingress.class: "gce"
        kubernetes.io/ingress.global-static-ip-name: "mcp-server-langgraph-ip"
        networking.gke.io/managed-certificates: "mcp-server-langgraph-cert"
    spec:
      rules:
      - host: api.yourdomain.com
        http:
          paths:
          - path: /*
            pathType: ImplementationSpecific
            backend:
              service:
                name: mcp-server-langgraph
                port:
                  number: 8000
    ---
    apiVersion: networking.gke.io/v1
    kind: ManagedCertificate
    metadata:
      name: mcp-server-langgraph-cert
      namespace: mcp-server-langgraph
    spec:
      domains:
      - api.yourdomain.com
    ```
  </Tab>

  <Tab title="AWS ALB">
    ```yaml theme={null}
    apiVersion: networking.k8s.io/v1
    kind: Ingress
    metadata:
      name: mcp-server-langgraph
      namespace: mcp-server-langgraph
      annotations:
        kubernetes.io/ingress.class: alb
        alb.ingress.kubernetes.io/scheme: internet-facing
        alb.ingress.kubernetes.io/target-type: ip
        alb.ingress.kubernetes.io/certificate-arn: arn:aws:acm:...
        alb.ingress.kubernetes.io/listen-ports: '[{"HTTPS":443}]'
    spec:
      rules:
      - host: api.yourdomain.com
        http:
          paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: mcp-server-langgraph
                port:
                  number: 8000
    ```
  </Tab>
</Tabs>

### High Availability

#### Horizontal Pod Autoscaling

```yaml theme={null}
## deployments/kubernetes/base/hpa.yaml
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: mcp-server-langgraph
  namespace: mcp-server-langgraph
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: mcp-server-langgraph
  minReplicas: 3
  maxReplicas: 10
  metrics:
  - type: Resource
    resource:
      name: cpu
      target:
        type: Utilization
        averageUtilization: 70
  - type: Resource
    resource:
      name: memory
      target:
        type: Utilization
        averageUtilization: 80
  behavior:
    scaleDown:
      stabilizationWindowSeconds: 300
      policies:
      - type: Percent
        value: 50
        periodSeconds: 60
    scaleUp:
      stabilizationWindowSeconds: 60
      policies:
      - type: Percent
        value: 100
        periodSeconds: 60
```

#### Pod Disruption Budget

```yaml theme={null}
## deployments/kubernetes/base/pdb.yaml
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
  name: mcp-server-langgraph
  namespace: mcp-server-langgraph
spec:
  minAvailable: 2
  selector:
    matchLabels:
      app: mcp-server-langgraph
```

#### Pod Anti-Affinity

Spread pods across availability zones:

```yaml theme={null}
affinity:
  podAntiAffinity:
    preferredDuringSchedulingIgnoredDuringExecution:
    - weight: 100
      podAffinityTerm:
        labelSelector:
          matchExpressions:
          - key: app
            operator: In
            values:
            - mcp-server-langgraph
        topologyKey: topology.kubernetes.io/zone
```

### Security

#### Network Policies

```yaml theme={null}
## deployments/kubernetes/base/networkpolicy.yaml
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: mcp-server-langgraph
  namespace: mcp-server-langgraph
spec:
  podSelector:
    matchLabels:
      app: mcp-server-langgraph
  policyTypes:
  - Ingress
  - Egress
  ingress:
  - from:
    - namespaceSelector:
        matchLabels:
          name: ingress-nginx
    ports:
    - protocol: TCP
      port: 8000
  egress:
  - to:
    - podSelector:
        matchLabels:
          app: keycloak
    ports:
    - protocol: TCP
      port: 8080
  - to:
    - podSelector:
        matchLabels:
          app: redis-session
    ports:
    - protocol: TCP
      port: 6379
  - to:
    - podSelector:
        matchLabels:
          app: openfga
    ports:
    - protocol: TCP
      port: 8080
  - to:
    - namespaceSelector: {}
    ports:
    - protocol: TCP
      port: 443  # HTTPS for LLM APIs
```

#### Pod Security Standards

```yaml theme={null}
apiVersion: v1
kind: Namespace
metadata:
  name: mcp-server-langgraph
  labels:
    pod-security.kubernetes.io/enforce: restricted
    pod-security.kubernetes.io/audit: restricted
    pod-security.kubernetes.io/warn: restricted
```

#### Security Context

```yaml theme={null}
securityContext:
  runAsNonRoot: true
  runAsUser: 1000
  fsGroup: 1000
  seccompProfile:
    type: RuntimeDefault
  capabilities:
    drop:
    - ALL
  readOnlyRootFilesystem: true
  allowPrivilegeEscalation: false
```

### Observability

#### Prometheus Metrics

```yaml theme={null}
## Pod annotations for Prometheus scraping
annotations:
  prometheus.io/scrape: "true"
  prometheus.io/port: "8000"
  prometheus.io/path: "/metrics/prometheus"
```

Deploy Prometheus:

```bash theme={null}
helm install prometheus prometheus-community/kube-prometheus-stack \
  --namespace observability --create-namespace
```

#### OpenTelemetry

Deploy OpenTelemetry Collector:

```bash theme={null}
helm install opentelemetry-collector open-telemetry/opentelemetry-collector \
  --namespace observability \
  --set mode=deployment \
  --set config.exporters.otlp.endpoint=jaeger:4317
```

#### Jaeger Tracing

```bash theme={null}
helm install jaeger jaegertracing/jaeger \
  --namespace observability \
  --set provisionDataStore.cassandra=false \
  --set allInOne.enabled=true \
  --set storage.type=memory
```

Access Jaeger UI:

```bash theme={null}
kubectl port-forward svc/jaeger-query 16686:16686 -n observability
## Open: http://localhost:16686
```

### Health Checks

The deployment includes comprehensive health checks:

```yaml theme={null}
startupProbe:
  httpGet:
    path: /health/startup
    port: 8000
  initialDelaySeconds: 10
  periodSeconds: 5
  failureThreshold: 30

livenessProbe:
  httpGet:
    path: /health
    port: 8000
  periodSeconds: 10
  failureThreshold: 3

readinessProbe:
  httpGet:
    path: /health/ready
    port: 8000
  periodSeconds: 5
  failureThreshold: 3
```

**Check health:**

```bash theme={null}
kubectl exec -it deploy/mcp-server-langgraph -n mcp-server-langgraph -- \
  curl http://localhost:8000/health
```

### Troubleshooting

<AccordionGroup>
  <Accordion title="Pods not starting">
    ```bash theme={null}
    # Check pod status
    kubectl get pods -n mcp-server-langgraph

    # Describe pod
    kubectl describe pod POD_NAME -n mcp-server-langgraph

    # Check logs
    kubectl logs POD_NAME -n mcp-server-langgraph

    # Check events
    kubectl get events -n mcp-server-langgraph --sort-by='.lastTimestamp'
    ```
  </Accordion>

  <Accordion title="Init containers failing">
    ```bash theme={null}
    # Check init container logs
    kubectl logs POD_NAME -n mcp-server-langgraph -c wait-for-keycloak
    kubectl logs POD_NAME -n mcp-server-langgraph -c wait-for-redis
    kubectl logs POD_NAME -n mcp-server-langgraph -c wait-for-openfga

    # Test connectivity manually
    kubectl run test --rm -it --image=busybox -- sh
    nc -zv keycloak 8080
    nc -zv redis-session 6379
    nc -zv openfga 8080
    ```
  </Accordion>

  <Accordion title="Health checks failing">
    ```bash theme={null}
    # Check readiness
    kubectl get pods -n mcp-server-langgraph -o wide

    # Test health endpoint
    kubectl exec -it deploy/mcp-server-langgraph -n mcp-server-langgraph -- \
      curl -v http://localhost:8000/health

    # Check dependencies
    kubectl logs deploy/mcp-server-langgraph -n mcp-server-langgraph | grep -i error
    ```
  </Accordion>

  <Accordion title="High memory usage">
    ```bash theme={null}
    # Check resource usage
    kubectl top pods -n mcp-server-langgraph

    # View metrics
    kubectl get --raw /apis/metrics.k8s.io/v1beta1/namespaces/mcp-server-langgraph/pods

    # Adjust limits in deployment.yaml
    resources:
      limits:
        memory: 4Gi  # Increase limit
    ```
  </Accordion>
</AccordionGroup>

### Next Steps

<CardGroup cols={2}>
  <Card title="Helm Deployment" icon="helm" href="/deployment/helm">
    Deploy using Helm charts
  </Card>

  <Card title="Production Checklist" icon="clipboard-check" href="/deployment/production-checklist">
    Pre-deployment verification
  </Card>

  <Card title="Scaling Guide" icon="arrow-up-right-dots" href="/deployment/scaling">
    Auto-scaling and performance tuning
  </Card>

  <Card title="Disaster Recovery" icon="life-ring" href="/deployment/disaster-recovery">
    Backup and restore procedures
  </Card>
</CardGroup>

***

<Check>
  **Production Ready**: Your MCP Server is now running on Kubernetes with high availability and enterprise-grade security!
</Check>
