This guide covers deploying mcp-hub in Docker containers.
- Size: ~47MB
- Includes: Alpine Linux + Docker CLI + mcp-hub binary
- Use when: You need Docker transport support for running MCP servers in containers
- Best for: Production deployments with mixed transports
docker build -t mcp-hub:latest .- Size: ~800MB
- Includes: Node.js 20, Python 3, uv, curl, git, and mcp-hub binary
- Use when: You want to run local stdio-based MCP servers in the same container
- Best for: Self-contained deployments with Node.js/Python MCP servers
docker build -f Dockerfile.local -t mcp-hub:local .# Create config file
cat > config.json << 'JSON'
{
"mcpServers": {
"echo": {
"image": "mcp-echo:latest"
}
}
}
JSON
# Run the hub
docker run -d \
--name mcp-hub \
-p 8080:8080 \
-v $(pwd)/config.json:/app/config.json:ro \
-v /var/run/docker.sock:/var/run/docker.sock \
--restart unless-stopped \
mcp-hub:latestNotes:
- The container/binary respects the
--configflag (default path:/app/config.json). - You can override the listen address/port with the
MCP_HUB_PORTorPORTenvironment variable (same semantics as running locally).
# Using the provided docker-compose.yml
docker-compose up -dPass environment variables to enable config expansion:
docker run -d \
--name mcp-hub \
-p 8080:8080 \
-v $(pwd)/config.json:/app/config.json:ro \
-v /var/run/docker.sock:/var/run/docker.sock \
-e GITHUB_TOKEN=${GITHUB_TOKEN} \
-e BRAVE_API_KEY=${BRAVE_API_KEY} \
-e API_TOKEN=${API_TOKEN} \
mcp-hub:latestOr use an env file:
# .env file
GITHUB_TOKEN=ghp_xxxxx
BRAVE_API_KEY=BSA_xxxxx
API_TOKEN=secret123
# Run with env file
docker run -d \
--name mcp-hub \
-p 8080:8080 \
-v $(pwd)/config.json:/app/config.json:ro \
-v /var/run/docker.sock:/var/run/docker.sock \
--env-file .env \
mcp-hub:latest-
Configuration file:
-v $(pwd)/config.json:/app/config.json:ro -
Docker socket (if using Docker transport):
-v /var/run/docker.sock:/var/run/docker.sock
-
Data directories (for stdio servers that need file access):
-v /host/data:/data:ro
-
Plugin directories (for custom MCP server scripts):
-v $(pwd)/plugins:/plugins:ro
version: '3.8'
services:
mcp-hub:
image: mcp-hub:latest
container_name: mcp-hub
ports:
- "8080:8080"
volumes:
- ./config.json:/app/config.json:ro
- /var/run/docker.sock:/var/run/docker.sock
- ./data:/data:ro
environment:
- GITHUB_TOKEN=${GITHUB_TOKEN}
- BRAVE_API_KEY=${BRAVE_API_KEY}
restart: unless-stopped
networks:
- mcp-network
networks:
mcp-network:
driver: bridgeUse the mcp-hub:local image when you want to run everything in a single container without external services:
{
"mcpServers": {
"node-server": {
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-filesystem", "/data"]
},
"python-script": {
"command": "python3",
"args": ["/plugins/my_mcp_server.py"]
},
"custom-binary": {
"command": "/plugins/custom-mcp-tool"
},
"uv-project": {
"command": "uv",
"args": ["run", "python", "-m", "my_mcp_module"],
"env": {
"PYTHONPATH": "/plugins"
}
}
}
}Run with volume mounts for custom scripts:
docker run -d \
--name mcp-hub \
-p 8080:8080 \
-v $(pwd)/config.json:/app/config.json:ro \
-v $(pwd)/plugins:/plugins:ro \
-v /data:/data \
--restart unless-stopped \
mcp-hub:localOr with Docker Compose:
version: '3.8'
services:
mcp-hub:
image: mcp-hub:local
container_name: mcp-hub
ports:
- "8080:8080"
volumes:
- ./config.json:/app/config.json:ro
- ./plugins:/plugins:ro
- ./data:/data
environment:
- NODE_ENV=production
restart: unless-stopped{
"mcpServers": {
"stdio-local": {
"command": "node",
"args": ["/plugins/my-server.js"]
},
"docker-containerized": {
"image": "my-mcp-server:latest",
"volumes": {
"/data": "/data"
}
},
"http-remote": {
"type": "http",
"url": "https://api.example.com/mcp",
"headers": {
"Authorization": "Bearer ${API_TOKEN}"
}
}
}
}Mounting the Docker socket gives the container full control over Docker on the host:
# Run as non-root when possible (default in our image)
# The mcpuser (UID 1000) is created in the image
# If you need root access for Docker socket:
docker run --user root ...Always mount config as read-only:
-v $(pwd)/config.json:/app/config.json:roUse Docker networks to isolate MCP servers:
networks:
mcp-network:
driver: bridge
internal: true # No external accessSet resource limits:
services:
mcp-hub:
deploy:
resources:
limits:
cpus: '2'
memory: 1G
reservations:
cpus: '0.5'
memory: 256MapiVersion: apps/v1
kind: Deployment
metadata:
name: mcp-hub
spec:
replicas: 1
selector:
matchLabels:
app: mcp-hub
template:
metadata:
labels:
app: mcp-hub
spec:
containers:
- name: mcp-hub
image: mcp-hub:latest
ports:
- containerPort: 8080
volumeMounts:
- name: config
mountPath: /app/config.json
subPath: config.json
readOnly: true
- name: docker-sock
mountPath: /var/run/docker.sock
env:
- name: GITHUB_TOKEN
valueFrom:
secretKeyRef:
name: mcp-secrets
key: github-token
volumes:
- name: config
configMap:
name: mcp-config
- name: docker-sock
hostPath:
path: /var/run/docker.sock
---
apiVersion: v1
kind: Service
metadata:
name: mcp-hub
spec:
selector:
app: mcp-hub
ports:
- port: 8080
targetPort: 8080Add health checks to your deployment:
services:
mcp-hub:
healthcheck:
test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:8080/mcp/servers"]
interval: 30s
timeout: 10s
retries: 3
start_period: 10sView logs:
# Docker
docker logs mcp-hub
# Docker Compose
docker-compose logs -f mcp-hub
# Kubernetes
kubectl logs -f deployment/mcp-hubSolution: Ensure Docker socket is mounted and user has access:
docker run --user root -v /var/run/docker.sock:/var/run/docker.sock ...Solution: Check volume mount path:
docker exec mcp-hub ls -la /app/Solution: Pass env vars to container:
docker run -e GITHUB_TOKEN=${GITHUB_TOKEN} ...Solution: Use shared Docker network:
networks:
mcp-network:
external: false- Use specific image tags (not
latest) - Mount config as read-only
- Set resource limits
- Configure health checks
- Set up log aggregation
- Use secrets for sensitive env vars
- Enable restart policy
- Set up monitoring
- Test backup/restore procedures
- Document your configuration