Deploying MCP Servers in Self-Hosted and VPC Environments
Self-hosted deployment gives you complete control over your MCP servers, data, and infrastructureโperfect for enterprises with strict compliance requirements.
What is Self-Hosted Deployment?
Self-hosted deployment runs MCP servers in your infrastructure:
- On-premise data centers
- Virtual Private Clouds (VPC)
- Private clouds (OpenStack, VMware)
- Dedicated servers or colocation
โ
Complete data control - All data stays in your environment
โ
Custom compliance - Meet any regulatory requirements (HIPAA, SOC2, etc.)
โ
No vendor lock-in - Full ownership of infrastructure
โ
Custom security - Implement your own security policies
Prerequisites
Before self-hosting MCP servers, ensure you have:
- Server infrastructure (bare metal, VMs, or private cloud)
- Docker or Kubernetes for container orchestration
- Load balancer (HAProxy, nginx, cloud LB)
- SSL/TLS certificate for encrypted communication
- Network security expertise (firewalls, VPNs, access controls)
Infrastructure Options
Option 1: VMs/Bare Metal (Traditional)
Best for: Legacy environments, maximum control
# Ubuntu/Debian setup
sudo apt update
sudo apt install -y docker.io docker-compose
# CentOS/RHEL setup
sudo yum install -y docker docker-compose
# Start Docker
sudo systemctl start docker
sudo systemctl enable docker
Option 2: Docker Swarm
Best for: Simple orchestration, no Kubernetes complexity
# Initialize Docker Swarm
docker swarm init --advertise-addr <MANAGER-IP>
# Deploy stack
docker stack deploy -c docker-compose.yml mcp-stack
# View services
docker service ls
docker-compose.yml:
version: '3.8'
services:
postgres-mcp:
image: your-registry/postgres-mcp:latest
environment:
- DATABASE_URL=postgresql://user:pass@postgres:5432/mcp
- SSE_ENABLED=true
ports:
- "8080:8080"
deploy:
replicas: 3
restart_policy:
condition: on-failure
postgres:
image: postgres:15
environment:
POSTGRES_DB: mcp
POSTGRES_USER: user
POSTGRES_PASSWORD: pass
volumes:
- postgres-data:/var/lib/postgresql/data
volumes:
postgres-data:
Option 3: Kubernetes (Self-Managed)
Best for: Full orchestration, auto-scaling, multi-region
Cluster Setup:
# Install kubeadm, kubelet, kubectl
sudo apt-get update
sudo apt-get install -y apt-transport-https ca-certificates curl gpg
curl -fsSL https://pkgs.k8s.io/core/stable/v1.29/deb/Release.key | sudo gpg --dearmor -o /etc/apt/keyrings/kubernetes-apt-keyring.gpg
echo 'deb [signed-by=/etc/apt/keyrings/kubernetes-apt-keyring.gpg] https://pkgs.k8s.io/core/stable/v1.29/deb/ /' | sudo tee /etc/apt/sources.list.d/kubernetes.list
sudo apt-get update
sudo apt-get install -y kubelet kubeadm kubectl
Init Cluster:
sudo kubeadm init --pod-network-cidr=192.168.0.0/16
mkdir -p $HOME/.kube
sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
sudo chown $(id -u):$(id -g) $HOME/.kube/config
Install Calico:
kubectl apply -f https://docs.projectcalico.org/manifests/calico.yaml
Network Security
Firewall Configuration
# UFW (Ubuntu)
ufw allow 22/tcp # SSH
ufw allow 80/tcp # HTTP
ufw allow 443/tcp # HTTPS
ufw allow 6443/tcp # Kubernetes API
ufw enable
# firewalld (CentOS)
firewall-cmd --permanent --add-port=22/tcp
firewall-cmd --permanent --add-port=80/tcp
firewall-cmd --permanent --add-port=443/tcp
firewall-cmd --reload
VPN Setup
WireGuard (Recommended):
# Install WireGuard
sudo apt install -y wireguard
# Generate keys
wg genkey | tee privatekey | wg pubkey > publickey
# Configure
sudo nano /etc/wireguard/wg0.conf
wg0.conf:
[Interface]
Address = 10.0.0.1/24
SaveConfig = true
PrivateKey = <SERVER_PRIVATE_KEY>
ListenPort = 51820
[Peer]
PublicKey = <CLIENT_PUBLIC_KEY>
AllowedIPs = 10.0.0.2/32
Zero Trust Network
Use Tailscale or similar for secure mesh networking:
# Install Tailscale
curl -fsSL https://tailscale.com/install.sh | sh
# Connect to your network
tailscale up --authkey=tskey-auth-xxxx
# Access controls (ACL)
# tailscale ACL configuration
{
"acls": [
{
"action": "accept",
"src": ["group:engineering"],
"dst": ["tag:mcp-server:8080"]
}
]
}
Deployment Patterns
Pattern 1: Single Server per Host
Simpler but less efficient:
# docker-compose.yml
version: '3.8'
services:
github-mcp:
image: github-mcp:latest
container_name: github-mcp
restart: unless-stopped
environment:
- GITHUB_TOKEN=${GITHUB_TOKEN}
ports:
- "8081:8080"
postgres-mcp:
image: postgres-mcp:latest
container_name: postgres-mcp
restart: unless-stopped
environment:
- DATABASE_URL=${DATABASE_URL}
ports:
- "8082:8080"
Pattern 2: Multi-Server per Host (Recommended)
More efficient resource usage:
# docker-compose.yml
version: '3.8'
services:
mcp-gateway:
image: mcp-gateway:latest
ports:
- "80:8080"
- "443:8443"
volumes:
- ./config:/app/config
github-mcp:
image: github-mcp:latest
expose:
- "8080"
postgres-mcp:
image: postgres-mcp:latest
expose:
- "8080"
Pattern 3: Kubernetes with Ingress
Production-ready with load balancing:
# kubernetes-deployment.yaml
apiVersion: v1
kind: Namespace
metadata:
name: mcp-servers
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: mcp-ingress-controller
namespace: mcp-servers
spec:
replicas: 2
selector:
matchLabels:
app: mcp-ingress
template:
metadata:
labels:
app: mcp-ingress
spec:
containers:
- name: nginx
image: nginx:alpine
ports:
- containerPort: 80
- containerPort: 443
volumeMounts:
- name: config
mountPath: /etc/nginx/conf.d
volumes:
- name: config
configMap:
name: nginx-config
---
apiVersion: v1
kind: ConfigMap
metadata:
name: nginx-config
namespace: mcp-servers
data:
default.conf: |
upstream github-mcp {
server github-mcp:8080;
}
upstream postgres-mcp {
server postgres-mcp:8080;
}
server {
listen 80;
server_name mcp.internal.company.com;
location /github {
proxy_pass http://github-mcp;
proxy_set_header Host $host;
}
location /postgres {
proxy_pass http://postgres-mcp;
proxy_set_header Host $host;
}
}
SSL/TLS Configuration
Letโs Encrypt (Free SSL)
# Install certbot
sudo apt install -y certbot python3-certbot-nginx
# Obtain certificate
sudo certbot --nginx -d mcp.internal.company.com
# Auto-renewal
echo "0 3 * * * /usr/bin/certbot renew --quiet" | sudo tee -a /etc/crontab
Self-Signed Certificates (Internal)
# Generate CA
openssl genrsa -out ca.key 4096
openssl req -new -x509 -days 3650 -key ca.key -out ca.crt
# Generate server certificate
openssl genrsa -out server.key 4096
openssl req -new -key server.key -out server.csr
openssl x509 -req -in server.csr -CA ca.crt -CAkey ca.key -CAcreateserial -out server.crt -days 3650
nginx configuration:
server {
listen 443 ssl http2;
server_name mcp.internal.company.com;
ssl_certificate /etc/ssl/certs/server.crt;
ssl_certificate_key /etc/ssl/private/server.key;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers HIGH:!aNULL:!MD5;
ssl_prefer_server_ciphers on;
# ... rest of config
}
Observability
Prometheus & Grafana
# prometheus.yml
global:
scrape_interval: 15s
scrape_configs:
- job_name: 'mcp-servers'
static_configs:
- targets:
- 'github-mcp:8080'
- 'postgres-mcp:8080'
Dashboard Metrics:
- Request rate and latency
- Error rates and types
- Resource usage (CPU, memory)
- Active connections
ELK Stack (Logging)
# docker-compose for ELK
version: '3.8'
services:
elasticsearch:
image: elasticsearch:8.11.0
environment:
- xpack.security.enabled=false
- discovery.type=single-node
ports:
- "9200:9200"
logstash:
image: logstash:8.11.0
volumes:
- ./logstash.conf:/usr/share/logstash/pipeline/logstash.conf
ports:
- "5000:5000"
kibana:
image: kibana:8.11.0
ports:
- "5601:5601"
Backup & Disaster Recovery
Database Backups
#!/bin/bash
# backup.sh
DATE=$(date +%Y%m%d_%H%M%S)
docker exec postgres pg_dump -U user mcp > /backups/mcp_backup_$DATE.sql
gzip /backups/mcp_backup_$DATE.sql
find /backups -name "mcp_backup_*.sql.gz" -mtime +7 -delete
Configuration Backup
# Backup all configs
tar -czf /backups/mcp-configs-$(date +%Y%m%d).tar.gz \
/etc/docker-compose \
/etc/kubernetes/manifests \
/etc/nginx/conf.d
Security Best Practices
1. Network Segmentation
- Isolate MCP servers in dedicated VLAN
- Use private subnets for backends
- Only expose load balancers publicly
2. Secrets Management
# Use Docker secrets (Swarm)
echo "your-secret" | docker secret create db_password -
# Use Kubernetes secrets
kubectl create secret generic db-password --from-literal=password=your-secret
3. Runtime Security
# Use distroless images
FROM gcr.io/distroless/nodejs:18
COPY --from=builder /app /app
CMD ["/app/server.js"]
# Scan for vulnerabilities
docker scan your-registry/mcp-server:latest
4. Access Control
- Implement RBAC in Kubernetes
- Use service accounts with minimal permissions
- Regular access reviews
Compliance
SOC 2 Type II
- Document all infrastructure changes
- Implement audit logging
- Regular penetration testing
- Access control reviews
HIPAA (Healthcare)
- Encrypt data at rest and in transit
- Implement audit trails
- Business Associate Agreements (BAAs)
- Access logging and monitoring
GDPR (EU)
- Data processing agreements
- Right to be forgotten implementation
- Data portability features
- Consent management
Cost Optimization
Right-Sizing
# Monitor resource usage
kubectl top pods -n mcp-servers
# Adjust requests/limits
resources:
requests:
cpu: "100m"
memory: "128Mi"
limits:
cpu: "500m"
memory: "512Mi"
Spot Instances
# Kubernetes node selector
nodeSelector:
-cloud.google.com/gke-spot: "true"
Monitoring & Alerting
Key Metrics
- Availability: 99.9%+ uptime
- Latency: < 100ms p99
- Error Rate: < 0.1%
- Resource: CPU < 70%, Memory < 80%
Alerting Rules (Prometheus)
groups:
- name: mcp-alerts
rules:
- alert: HighErrorRate
expr: rate(http_requests_total{status=~"5.."}[5m]) > 0.01
for: 5m
labels:
severity: warning
- alert: InstanceDown
expr: up == 0
for: 1m
labels:
severity: critical
Troubleshooting
Common Issues
1. Container Wonโt Start:
- Check logs:
docker logs container-name - Verify environment variables
- Check port conflicts
2. Network Connectivity:
- Test with
curlinside container - Check firewall rules
- Verify DNS resolution
3. Performance Degradation:
- Monitor resource usage
- Check for memory leaks
- Scale horizontally
4. SSL/TLS Errors:
- Verify certificate validity
- Check certificate chain
- Ensure proper cipher configuration
Next Steps
Ready to add enterprise features? Check out:
Need enterprise support? Contact our team for custom deployment assistance.