Deploy an MCP Server to Production with Docker (Complete Guide)
Nikhil Tiwari
MCP Playground
TL;DR
- Use StreamableHTTP transport for remote/production MCP servers (not stdio)
- Containerize with Docker for isolation, portability, and reproducible deploys
- Expose a single
/mcpendpoint that handles POST (requests) and GET+SSE (streaming) - Add TLS, CORS, auth, and rate limiting before going to production
Most MCP tutorials show stdio servers — great for local use with Claude Desktop or Cursor. But when you want your MCP server accessible over the network — for remote agents, team use, or production workloads — you need HTTP transport, a container, and proper infrastructure.
This guide takes you from a local MCP server to a production-ready Docker deployment.
Why Docker for MCP Servers?
Dependencies contained. No conflicts with host system.
Same image runs on laptop, cloud VM, or Kubernetes.
Sandboxes server from host filesystem. Limits blast radius.
Share a Dockerfile, not installation docs.
Step 1: Create the MCP Server (Python)
A simple server with two tools, using the MCP Python SDK's built-in FastMCP:
# server.py
from mcp.server.fastmcp import FastMCP
mcp = FastMCP("Production Server")
@mcp.tool()
def search_docs(query: str) -> str:
"""Search internal documentation."""
# Replace with your actual search logic
return f"Found 3 results for '{query}'"
@mcp.tool()
def get_status(service: str) -> dict:
"""Get the status of an internal service."""
return {"service": service, "status": "healthy", "uptime": "99.9%"}
if __name__ == "__main__":
mcp.run(transport="streamable-http", host="0.0.0.0", port=8000)
Key: Use transport="streamable-http" instead of the default stdio. This exposes a /mcp HTTP endpoint.
# requirements.txt
mcp[cli]>=1.0.0
uvicorn
Step 2: Write the Dockerfile
# Dockerfile
FROM python:3.12-slim
WORKDIR /app
# Install dependencies
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
# Copy server code
COPY server.py .
# Expose the MCP port
EXPOSE 8000
# Run the server
CMD ["python", "server.py"]
Step 3: Build and Run
# Build the image
docker build -t my-mcp-server .
# Run the container
docker run -d -p 8000:8000 --name mcp-server my-mcp-server
# Test it's working
curl -X POST http://localhost:8000/mcp \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","method":"tools/list","id":1}'
Step 4: Docker Compose (With Dependencies)
Most production servers need a database, cache, or other services. Use Docker Compose:
# docker-compose.yml
version: "3.8"
services:
mcp-server:
build: .
ports:
- "8000:8000"
environment:
- DATABASE_URL=postgresql://app:secret@postgres:5432/mcpdata
- API_KEY=${API_KEY}
depends_on:
postgres:
condition: service_healthy
restart: unless-stopped
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8000/mcp"]
interval: 30s
timeout: 10s
retries: 3
postgres:
image: postgres:16-alpine
environment:
POSTGRES_DB: mcpdata
POSTGRES_USER: app
POSTGRES_PASSWORD: secret
volumes:
- pgdata:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U app -d mcpdata"]
interval: 10s
timeout: 5s
retries: 5
volumes:
pgdata:
# Start everything
docker compose up -d
# Check logs
docker compose logs -f mcp-server
Step 5: Connect Clients
Once running, any MCP client can connect to your server via HTTP:
Cursor
{
"mcpServers": {
"my-server": {
"url": "http://localhost:8000/mcp"
}
}
}
Claude Code
claude mcp add --transport http my-server http://localhost:8000/mcp
OpenAI Agents SDK
from agents.mcp import MCPServerStreamableHttp
async with MCPServerStreamableHttp(
name="my-server",
params={"url": "http://localhost:8000/mcp"},
) as server:
# Use server with an agent
Production Hardening
1. Add a Reverse Proxy (Nginx)
# nginx.conf
server {
listen 443 ssl;
server_name mcp.yourcompany.com;
ssl_certificate /etc/ssl/certs/cert.pem;
ssl_certificate_key /etc/ssl/private/key.pem;
location /mcp {
proxy_pass http://mcp-server:8000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
}
2. Security Checklist
| Concern | Action |
|---|---|
| TLS | Always use HTTPS in production. Terminate TLS at Nginx or your load balancer. |
| Authentication | Add Bearer token or OAuth validation. Check the Authorization header. |
| CORS | Restrict Access-Control-Allow-Origin to known clients. |
| Rate limiting | Add rate limits at the Nginx or application level to prevent abuse. |
| Input validation | Validate all tool parameters. Sanitise inputs before database queries or shell commands. |
| Secrets | Use Docker secrets or environment variables. Never bake credentials into images. |
| Non-root user | Add USER 1000 in Dockerfile. Don't run as root inside containers. |
3. Stateful vs Stateless
| Pattern | When to use | Session storage |
|---|---|---|
| Stateless | Serverless, auto-scaling, Kubernetes. Each request is independent. | None — fresh instance per request |
| Stateful | Multi-step workflows, persistent context across tool calls. | Redis, database, or in-memory with sticky sessions |
For stateful deployments with multiple replicas, use Redis for session storage and configure your load balancer for sticky sessions.
Cloud Deployment Options
| Platform | How | Notes |
|---|---|---|
| AWS ECS / Fargate | Push image to ECR, create task definition, run as service | Good for auto-scaling. Use ALB for load balancing. |
| Google Cloud Run | Push image, deploy with gcloud run deploy |
Scales to zero. Good for stateless servers. |
| Azure Container Apps | Deploy container image with Azure CLI | Managed Kubernetes under the hood. |
| Railway / Render / Fly.io | Connect Git repo or push Dockerfile | Simplest. Good for small teams. |
| Kubernetes | Deployment + Service + Ingress manifests | Full control. Use for multi-server setups. |
Key Resources
| Resource | Link |
|---|---|
| Docker MCP blog post | docker.com/blog/build-to-prod-mcp-servers-with-docker |
| StreamableHTTP reference (stateful) | github.com/yigitkonur/example-mcp-server-streamable-http |
| StreamableHTTP reference (stateless) | github.com/yigitkonur/...stateless |
| Docker MCP Gateway | docs.docker.com/ai/mcp-gateway |
| MCP Python SDK | github.com/modelcontextprotocol/python-sdk |
Test Your Deployed MCP Server
Paste your server URL into MCP Playground to verify it's working
Open MCP Playground →Related Content
- Build Your First MCP Server with Python and FastMCP
- Remote MCP Servers — Test and Connect Online
- MCP vs Function Calling vs APIs
- MCP Security: Tool Poisoning and OWASP Top 10
Frequently Asked Questions
Can I deploy an MCP server without Docker?
What's the difference between stdio and StreamableHTTP transport?
/mcp endpoint, allowing remote access over the network. Use stdio for local development, StreamableHTTP for production and remote access.How do I handle secrets in Docker?
docker run -e API_KEY=xxx) or Docker secrets. Never put credentials in your Dockerfile or image. In Docker Compose, use environment with variable substitution (${API_KEY}) and keep actual values in a .env file that is not committed to git.Should I use stateful or stateless mode?
Written by Nikhil Tiwari
15+ years in product development. AI enthusiast building developer tools that make complex technologies accessible to everyone.
Related Resources