Kubernetes
6 min read
This guide covers deploying the MCP Hub Platform on Kubernetes. It provides example manifests for all components and guidance on production configuration.
Prerequisites
| Requirement | Minimum Version |
|---|---|
| Kubernetes | 1.27+ |
| kubectl | Matching cluster version |
| Helm (optional) | 3.12+ |
| Storage Class | With dynamic provisioning |
| Ingress Controller | Nginx, Traefik, or similar |
Architecture on Kubernetes
The MCP Hub Platform maps to the following Kubernetes resources:
| Component | Resource Type | Replicas | Notes |
|---|---|---|---|
| hub-web | Deployment + Service | 2+ | Stateless, horizontally scalable |
| hub-ingestion-worker | Deployment | 1+ | Stateless, scales with ingestion load |
| hub-results-worker | Deployment | 1+ | Stateless, scales with analysis throughput |
| scan-worker | Deployment | 2+ | CPU-intensive, scale based on analysis queue |
| registry | Deployment + Service | 2+ | Stateless, horizontally scalable |
| postgres | StatefulSet | 1 (or managed) | Use managed database in production |
| redis | StatefulSet | 1 (or managed) | Use managed Redis in production |
| minio | StatefulSet | 1 (or managed) | Use managed S3 in production |
| lavinmq | StatefulSet | 1 (or managed) | AMQP broker |
For production deployments, use managed PostgreSQL (RDS, Cloud SQL, Azure Database), managed Redis (ElastiCache, Memorystore), and managed S3 (AWS S3, GCS, Azure Blob) instead of running these as StatefulSets. This offloads operational burden for backups, failover, and scaling.
Namespace
Create a dedicated namespace:
apiVersion: v1
kind: Namespace
metadata:
name: mcp-hub
kubectl apply -f namespace.yaml
Secrets
Store sensitive configuration in Kubernetes Secrets:
apiVersion: v1
kind: Secret
metadata:
name: mcp-hub-secrets
namespace: mcp-hub
type: Opaque
stringData:
DATABASE_URL: "postgres://mcphub:STRONG_PASSWORD@postgres:5432/mcphub?sslmode=require"
REDIS_URL: "redis://redis:6390"
AMQP_URL: "amqp://guest:STRONG_PASSWORD@lavinmq:5672/"
S3_ACCESS_KEY_ID: "your-access-key"
S3_SECRET_ACCESS_KEY: "your-secret-key"
AUTH0_CLIENT_SECRET: "your-auth0-secret"
SESSION_SECRET: "your-session-secret-minimum-32-characters-long"
REGISTRY_SERVICE_TOKEN: "your-registry-service-token"
STRIPE_SECRET_KEY: "sk_live_..."
STRIPE_WEBHOOK_SECRET: "whsec_..."
For production, use an external secrets manager (AWS Secrets Manager, HashiCorp Vault, Google Secret Manager) with the External Secrets Operator instead of storing secrets directly in Kubernetes.
ConfigMap
Non-sensitive configuration shared across services:
apiVersion: v1
kind: ConfigMap
metadata:
name: mcp-hub-config
namespace: mcp-hub
data:
S3_ENDPOINT: "http://minio:9000"
S3_REGION: "us-east-1"
S3_BUCKET_SOURCES: "mcp-hub-sources"
S3_BUCKET_ANALYSIS: "mcp-hub-analysis"
S3_USE_PATH_STYLE: "true"
AMQP_EXCHANGE: "mcp.jobs"
LOG_LEVEL: "info"
REGISTRY_URL: "http://registry:8081"
AUTH0_DOMAIN: "your-tenant.auth0.com"
AUTH0_CLIENT_ID: "your-client-id"
AUTH0_CALLBACK_URL: "https://hub.yourdomain.com/auth/callback"
SCAN_MODE: "deep"
SCAN_TIMEOUT: "30m"
MAX_CONCURRENT: "5"
Hub Web Deployment
apiVersion: apps/v1
kind: Deployment
metadata:
name: hub-web
namespace: mcp-hub
labels:
app: hub-web
spec:
replicas: 2
selector:
matchLabels:
app: hub-web
template:
metadata:
labels:
app: hub-web
spec:
containers:
- name: hub-web
image: your-registry/mcp-hub:latest
command: ["web"]
ports:
- containerPort: 8080
name: http
envFrom:
- configMapRef:
name: mcp-hub-config
- secretRef:
name: mcp-hub-secrets
env:
- name: SERVER_PORT
value: "8080"
- name: SKIP_MIGRATIONS
value: "true"
resources:
requests:
cpu: 500m
memory: 256Mi
limits:
cpu: "2"
memory: 1Gi
livenessProbe:
httpGet:
path: /health
port: 8080
initialDelaySeconds: 10
periodSeconds: 15
readinessProbe:
httpGet:
path: /health
port: 8080
initialDelaySeconds: 5
periodSeconds: 10
---
apiVersion: v1
kind: Service
metadata:
name: hub-web
namespace: mcp-hub
spec:
selector:
app: hub-web
ports:
- port: 8080
targetPort: 8080
name: http
type: ClusterIP
Hub Workers
Ingestion Worker
apiVersion: apps/v1
kind: Deployment
metadata:
name: hub-ingestion-worker
namespace: mcp-hub
labels:
app: hub-ingestion-worker
spec:
replicas: 1
selector:
matchLabels:
app: hub-ingestion-worker
template:
metadata:
labels:
app: hub-ingestion-worker
spec:
containers:
- name: ingestion-worker
image: your-registry/mcp-hub:latest
command: ["ingest-worker"]
envFrom:
- configMapRef:
name: mcp-hub-config
- secretRef:
name: mcp-hub-secrets
env:
- name: WORKER_WORKSPACE
value: "/workspace"
- name: WORKER_MAX_CONCURRENT
value: "10"
resources:
requests:
cpu: 250m
memory: 256Mi
limits:
cpu: "1"
memory: 512Mi
volumeMounts:
- name: workspace
mountPath: /workspace
volumes:
- name: workspace
emptyDir:
sizeLimit: 5Gi
Results Worker
apiVersion: apps/v1
kind: Deployment
metadata:
name: hub-results-worker
namespace: mcp-hub
labels:
app: hub-results-worker
spec:
replicas: 1
selector:
matchLabels:
app: hub-results-worker
template:
metadata:
labels:
app: hub-results-worker
spec:
containers:
- name: results-worker
image: your-registry/mcp-hub:latest
command: ["results-worker"]
envFrom:
- configMapRef:
name: mcp-hub-config
- secretRef:
name: mcp-hub-secrets
resources:
requests:
cpu: 250m
memory: 256Mi
limits:
cpu: "1"
memory: 512Mi
Scan Worker
The scan worker is CPU-intensive and benefits from dedicated resource allocation:
apiVersion: apps/v1
kind: Deployment
metadata:
name: scan-worker
namespace: mcp-hub
labels:
app: scan-worker
spec:
replicas: 2
selector:
matchLabels:
app: scan-worker
template:
metadata:
labels:
app: scan-worker
spec:
containers:
- name: scan-worker
image: your-registry/mcp-scan:latest
command: ["worker"]
envFrom:
- configMapRef:
name: mcp-hub-config
- secretRef:
name: mcp-hub-secrets
env:
- name: MCP_SCAN_WORKER_HEALTH_PORT
value: "8083"
ports:
- containerPort: 8083
name: health
resources:
requests:
cpu: "1"
memory: 1Gi
limits:
cpu: "4"
memory: 4Gi
livenessProbe:
httpGet:
path: /healthz
port: 8083
initialDelaySeconds: 10
periodSeconds: 30
readinessProbe:
httpGet:
path: /healthz
port: 8083
initialDelaySeconds: 5
periodSeconds: 10
volumeMounts:
- name: scan-workspace
mountPath: /workspace
volumes:
- name: scan-workspace
emptyDir:
sizeLimit: 10Gi
Registry
apiVersion: apps/v1
kind: Deployment
metadata:
name: registry
namespace: mcp-hub
labels:
app: registry
spec:
replicas: 2
selector:
matchLabels:
app: registry
template:
metadata:
labels:
app: registry
spec:
containers:
- name: registry
image: your-registry/mcp-registry:latest
ports:
- containerPort: 8081
name: http
env:
- name: MCP_REGISTRY_SERVER_LISTEN
value: ":8081"
- name: MCP_REGISTRY_DB_DSN
valueFrom:
secretKeyRef:
name: mcp-hub-secrets
key: DATABASE_URL
- name: MCP_REGISTRY_STORAGE_TYPE
value: "s3"
- name: MCP_REGISTRY_STORAGE_S3_BUCKET
value: "mcp-registry"
- name: MCP_REGISTRY_STORAGE_S3_ENDPOINT
valueFrom:
configMapKeyRef:
name: mcp-hub-config
key: S3_ENDPOINT
- name: MCP_REGISTRY_AUTH_MODE
value: "oss"
- name: MCP_REGISTRY_PUBLIC_READ_CATALOG
value: "true"
- name: MCP_REGISTRY_PUBLIC_DOWNLOAD_ARTIFACTS
value: "true"
envFrom:
- secretRef:
name: mcp-hub-secrets
resources:
requests:
cpu: 250m
memory: 256Mi
limits:
cpu: "1"
memory: 512Mi
livenessProbe:
httpGet:
path: /healthz
port: 8081
initialDelaySeconds: 5
periodSeconds: 10
readinessProbe:
httpGet:
path: /healthz
port: 8081
initialDelaySeconds: 3
periodSeconds: 5
---
apiVersion: v1
kind: Service
metadata:
name: registry
namespace: mcp-hub
spec:
selector:
app: registry
ports:
- port: 8081
targetPort: 8081
name: http
type: ClusterIP
Database Migration Job
Run migrations as a Kubernetes Job before deploying application services:
apiVersion: batch/v1
kind: Job
metadata:
name: hub-migrate
namespace: mcp-hub
spec:
backoffLimit: 3
template:
spec:
restartPolicy: OnFailure
containers:
- name: migrate
image: your-registry/mcp-hub:latest
command: ["migrate", "up"]
envFrom:
- secretRef:
name: mcp-hub-secrets
env:
- name: LOG_LEVEL
value: "info"
Ingress
Expose the hub dashboard and registry through an Ingress:
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: mcp-hub-ingress
namespace: mcp-hub
annotations:
cert-manager.io/cluster-issuer: letsencrypt-prod
spec:
tls:
- hosts:
- hub.yourdomain.com
- registry.yourdomain.com
secretName: mcp-hub-tls
rules:
- host: hub.yourdomain.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: hub-web
port:
number: 8080
- host: registry.yourdomain.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: registry
port:
number: 8081
Persistent Volumes
If running PostgreSQL and MinIO as StatefulSets (development/testing only):
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: postgres-data
namespace: mcp-hub
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 20Gi
storageClassName: standard
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: minio-data
namespace: mcp-hub
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 50Gi
storageClassName: standard
Horizontal Pod Autoscaler
Scale scan workers based on CPU usage:
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: scan-worker-hpa
namespace: mcp-hub
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: scan-worker
minReplicas: 2
maxReplicas: 10
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 70
Deployment Order
Deploy resources in this order:
- Namespace and Secrets:
namespace.yaml,secrets.yaml,configmap.yaml - Infrastructure: PostgreSQL, Redis, MinIO, LavinMQ (or configure managed services)
- Migrations: Run the
hub-migrateJob - Registry: Deploy the registry Deployment and Service
- Application: Deploy hub-web, hub-ingestion-worker, hub-results-worker
- Workers: Deploy scan-worker
- Networking: Apply Ingress rules
- Autoscaling: Apply HPA configurations
Production Checklist
- Replace all default passwords and tokens with strong randomly generated values
- Use managed database (RDS, Cloud SQL) instead of StatefulSet PostgreSQL
- Use managed S3 (AWS S3, GCS) instead of MinIO
- Use managed Redis (ElastiCache, Memorystore) instead of StatefulSet
- Configure TLS via Ingress with cert-manager
- Set appropriate resource requests and limits for all pods
- Configure HPA for scan-worker and registry
- Set up monitoring with Prometheus and Grafana
- Configure backup strategy for PostgreSQL
- Use External Secrets Operator for secret management
- Enable network policies to restrict inter-service communication
- Set up log aggregation (ELK, Loki, CloudWatch)