Docker部署MCP Server完整教程:容器化Claude工具服务(含docker-compose生产配置)
本文介绍了使用Docker容器化部署MCP Server的完整方案。主要内容包括: 为什么要Docker化 - 解决环境隔离、进程守护、迁移困难等问题 项目结构设计 - 包含Dockerfile、docker-compose文件和环境变量配置 关键实现细节: 多阶段构建优化镜像大小 非root用户运行增强安全性 合理的日志和资源限制配置 生产环境配置 - 资源限制、日志管理等优化 扩展场景 - 支
·
Docker部署MCP Server完整教程:容器化Claude工具服务(含docker-compose生产配置)
环境要求: Docker 24+,docker-compose v2,已有可运行的 MCP Server(参考上篇)
难度: 中级
适用场景: 将 MCP Server 稳定运行在云服务器或本地局域网环境
目录
- 一、为什么要用Docker部署MCP Server
- 二、项目结构与准备工作
- 三、编写Dockerfile
- 四、编写docker-compose.yml
- 五、构建与运行
- 六、生产级配置:日志、健康检查、资源限制
- 七、多MCP Server编排
- 八、常见报错与解决方案
一、为什么要用Docker部署MCP Server
直接在宿主机跑 python server.py 有几个问题:
- 环境污染:不同项目的 Python 依赖版本冲突
- 进程守护:服务器重启后 MCP Server 不会自动拉起
- 迁移困难:换机器要重新配置环境
- 扩展麻烦:同时跑多个 MCP Server 端口管理混乱
Docker 解决的就是这些问题:一次打包,到处运行,进程自动守护,环境完全隔离。
二、项目结构与准备工作
2.1 完整项目结构
mcp-csv-server/
├── Dockerfile # 镜像定义
├── docker-compose.yml # 服务编排(开发环境)
├── docker-compose.prod.yml # 服务编排(生产环境覆盖)
├── .env # 本地环境变量(不提交git)
├── .env.example # 环境变量模板(提交git)
├── .dockerignore # 排除不需要打包的文件
├── requirements.txt # Python依赖
├── server.py # MCP Server主程序
└── data/
└── products.csv # 数据文件(挂载到容器)
2.2 requirements.txt
mcp==1.0.0
anthropic>=0.40.0
pydantic>=2.0.0
python-dotenv>=1.0.0
2.3 .env.example(提交到git的模板)
# 复制为 .env 并填写真实值
ANTHROPIC_API_KEY=your_api_key_here
ANTHROPIC_BASE_URL=https://gw.claudeapi.com
CSV_FILE=/app/data/products.csv
LOG_LEVEL=INFO
2.4 .dockerignore
__pycache__/
*.pyc
*.pyo
.env
.git/
.gitignore
*.md
tests/
.pytest_cache/
venv/
.venv/
注意:
.env必须加入.dockerignore,避免 API Key 被打包进镜像层。
三、编写Dockerfile
# ── 阶段一:依赖安装(构建层)──────────────────────────
FROM python:3.11-slim AS builder
WORKDIR /build
# 单独复制依赖文件,利用Docker层缓存
# 只要 requirements.txt 不变,这层就不会重新构建
COPY requirements.txt .
RUN pip install --no-cache-dir --upgrade pip \
&& pip install --no-cache-dir --prefix=/install -r requirements.txt
# ── 阶段二:运行时镜像(最终层)────────────────────────
FROM python:3.11-slim AS runtime
# 安全:不以root运行
RUN groupadd --gid 1000 appuser \
&& useradd --uid 1000 --gid appuser --shell /bin/bash --create-home appuser
WORKDIR /app
# 从构建层拷贝已安装的依赖(不携带pip等构建工具)
COPY --from=builder /install /usr/local
# 拷贝应用代码
COPY server.py .
# 数据目录(通过volume挂载,这里只创建挂载点)
RUN mkdir -p /app/data && chown -R appuser:appuser /app
USER appuser
# 声明数据目录为volume(可选,便于docker inspect查看)
VOLUME ["/app/data"]
# MCP Server 使用 stdio 通信,不需要暴露端口
# 如果你的Server使用HTTP模式,取消下面的注释
# EXPOSE 8000
# 健康检查:确认进程存在
HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
CMD pgrep -f "python server.py" || exit 1
# 启动命令
CMD ["python", "server.py"]
关键设计说明:
| 设计点 | 原因 |
|---|---|
| 多阶段构建 | 最终镜像不包含 pip、编译工具,体积减少约 40% |
| 非 root 用户 | 容器安全基本原则,防止容器逃逸风险 |
| 单独 COPY requirements.txt | 代码改动不会触发依赖重装,加快构建速度 |
--no-cache-dir |
不在镜像内保留 pip 缓存,减小体积 |
四、编写docker-compose.yml
4.1 开发环境(docker-compose.yml)
version: "3.9"
services:
mcp-csv:
build:
context: .
dockerfile: Dockerfile
target: runtime # 使用多阶段构建的runtime层
container_name: mcp-csv-server
restart: unless-stopped # 除手动停止外,自动重启
env_file:
- .env # 从.env文件注入环境变量
environment:
# 可以在这里覆盖.env中的值
LOG_LEVEL: DEBUG
volumes:
# 数据文件挂载(宿主机路径:容器路径)
- ./data:/app/data:ro # ro=只读,防止容器意外修改数据
# 开发时挂载代码目录,改代码不用重新build
- ./server.py:/app/server.py:ro
stdin_open: true # MCP stdio模式需要保持stdin开启
tty: false
logging:
driver: "json-file"
options:
max-size: "10m"
max-file: "3"
4.2 生产环境覆盖(docker-compose.prod.yml)
生产环境在开发配置基础上叠加,不重复写配置:
version: "3.9"
services:
mcp-csv:
build:
cache_from:
- mcp-csv-server:latest # 利用已有镜像作为构建缓存
environment:
LOG_LEVEL: WARNING # 生产环境减少日志输出
volumes:
# 生产环境:去掉代码挂载,只挂数据
- /data/mcp/products.csv:/app/data/products.csv:ro
# 资源限制:防止单个容器耗尽服务器资源
deploy:
resources:
limits:
cpus: "0.5"
memory: 256M
reservations:
cpus: "0.1"
memory: 64M
# 生产日志:写入文件便于收集
logging:
driver: "json-file"
options:
max-size: "50m"
max-file: "10"
labels: "service,env"
4.3 多MCP Server场景(共享网络)
version: "3.9"
networks:
mcp-network:
driver: bridge
services:
# CSV数据查询 MCP
mcp-csv:
build: .
container_name: mcp-csv
env_file: .env
volumes:
- ./data:/app/data:ro
networks:
- mcp-network
# 未来扩展:数据库查询 MCP(示例结构)
mcp-postgres:
build: ./mcp-postgres # 另一个MCP Server目录
container_name: mcp-postgres
env_file: .env.postgres
environment:
DB_HOST: postgres
DB_PORT: 5432
depends_on:
- postgres
networks:
- mcp-network
# 数据库服务
postgres:
image: postgres:15-alpine
environment:
POSTGRES_DB: products
POSTGRES_USER: mcp
POSTGRES_PASSWORD_FILE: /run/secrets/db_password
volumes:
- postgres_data:/var/lib/postgresql/data
networks:
- mcp-network
volumes:
postgres_data:
五、构建与运行
5.1 首次构建并启动
# 1. 复制环境变量模板
cp .env.example .env
# 编辑 .env,填入真实的 API Key
# 2. 构建镜像
docker compose build
# 3. 后台启动
docker compose up -d
# 4. 查看启动日志
docker compose logs -f mcp-csv
正常启动输出:
mcp-csv-server | [MCP Server] 已加载 8 条产品数据
mcp-csv-server | [MCP Server] 等待连接...
5.2 日常操作命令
# 查看运行状态
docker compose ps
# 重启服务(不重建镜像)
docker compose restart mcp-csv
# 代码修改后重新构建并重启
docker compose up -d --build mcp-csv
# 进入容器调试
docker exec -it mcp-csv-server bash
# 查看容器资源占用
docker stats mcp-csv-server
# 停止并删除容器(保留镜像)
docker compose down
# 停止并删除容器+镜像
docker compose down --rmi local
5.3 生产环境部署
# 合并开发+生产配置文件启动
docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d
# 推荐:设置别名简化命令
alias dc-prod="docker compose -f docker-compose.yml -f docker-compose.prod.yml"
dc-prod up -d
dc-prod logs -f
六、生产级配置
6.1 结构化日志输出
修改 server.py,将日志改为 JSON 格式,便于 ELK/Loki 收集:
import logging
import json
import sys
from datetime import datetime
class JsonFormatter(logging.Formatter):
def format(self, record: logging.LogRecord) -> str:
return json.dumps({
"timestamp": datetime.utcnow().isoformat() + "Z",
"level": record.levelname,
"service": "mcp-csv-server",
"message": record.getMessage(),
"module": record.module,
}, ensure_ascii=False)
def setup_logging():
level = os.getenv("LOG_LEVEL", "INFO").upper()
handler = logging.StreamHandler(sys.stderr) # 日志写stderr,不干扰stdio通信
handler.setFormatter(JsonFormatter())
logging.basicConfig(level=level, handlers=[handler])
logger = logging.getLogger(__name__)
# 使用示例
def load_data():
global _products
with open(CSV_FILE, encoding="utf-8") as f:
_products = list(csv.DictReader(f))
logger.info("数据加载完成", extra={"count": len(_products)})
重点: MCP Server 使用 stdio 通信,日志必须写到 stderr,不能写 stdout,否则会破坏 MCP 协议数据流。
6.2 优雅关闭
import signal
import asyncio
_shutdown_event = asyncio.Event()
def handle_shutdown(sig, frame):
logger.info(f"收到信号 {sig},准备关闭...")
_shutdown_event.set()
async def main():
# 注册信号处理
loop = asyncio.get_event_loop()
for sig in (signal.SIGTERM, signal.SIGINT):
loop.add_signal_handler(sig, lambda s=sig: handle_shutdown(s, None))
load_data()
logger.info("MCP Server 启动")
async with stdio_server() as streams:
server_task = asyncio.create_task(
server.run(streams[0], streams[1],
server.create_initialization_options())
)
shutdown_task = asyncio.create_task(_shutdown_event.wait())
done, pending = await asyncio.wait(
[server_task, shutdown_task],
return_when=asyncio.FIRST_COMPLETED,
)
for task in pending:
task.cancel()
logger.info("MCP Server 已关闭")
6.3 数据热更新(不重启容器)
CSV 文件通过 volume 挂载,更新数据文件后发送 SIGUSR1 信号触发重新加载:
def handle_reload(sig, frame):
logger.info("收到 SIGUSR1,重新加载数据...")
load_data()
logger.info(f"数据已更新,共 {len(_products)} 条")
# 在 main() 中注册
signal.signal(signal.SIGUSR1, handle_reload)
触发热更新:
# 替换宿主机上的CSV文件
cp new_products.csv ./data/products.csv
# 发送信号给容器内进程
docker exec mcp-csv-server kill -USR1 1
七、多MCP Server编排
实际生产中通常有多个 MCP Server,用 profiles 按需启动:
version: "3.9"
services:
# 核心服务:始终启动
mcp-csv:
build: ./mcp-csv
restart: unless-stopped
volumes:
- ./data/products.csv:/app/data/products.csv:ro
# 可选服务:按需启动
mcp-files:
build: ./mcp-files
profiles: ["full"] # 只有 --profile full 时才启动
restart: unless-stopped
volumes:
- /home/user/documents:/workspace:ro
mcp-web:
build: ./mcp-web
profiles: ["full"]
restart: unless-stopped
environment:
ALLOWED_DOMAINS: "*.claudeapi.com,github.com"
启动命令:
# 只启动核心服务
docker compose up -d
# 启动全部服务(含可选)
docker compose --profile full up -d
八、常见报错与解决方案
| 报错信息 | 原因 | 解决方法 |
|---|---|---|
permission denied: /app/data/products.csv |
宿主机文件权限不对 | chmod 644 ./data/products.csv |
[Errno 2] No such file or directory: '/app/data/products.csv' |
volume挂载路径错误 | 检查 docker-compose.yml 中volumes路径,用 docker inspect 确认挂载 |
| 容器启动后立即退出(exit code 0) | stdio模式下没有连接方,进程正常退出 | 正常现象,Claude Code 连接时会自动拉起 |
Error: API key is not set |
.env 未正确加载 | 确认 env_file: .env 路径,用 docker compose config 检查变量 |
| 日志乱码 | 容器内未设置UTF-8 | Dockerfile 加 ENV PYTHONIOENCODING=utf-8 LANG=C.UTF-8 |
| 镜像体积过大(>500MB) | 使用了完整 Python 镜像 | 改用 python:3.11-slim,并启用多阶段构建 |
docker compose up 后改了代码没生效 |
开发环境未挂载代码文件 | 检查 volumes 是否挂载了 ./server.py:/app/server.py |
调试技巧
# 查看最终生效的环境变量(排查.env问题)
docker compose config
# 不启动容器,只检查镜像内容
docker run --rm --entrypoint sh mcp-csv-server:latest -c "ls -la /app && cat /app/server.py"
# 检查volume挂载是否生效
docker inspect mcp-csv-server | python -m json.tool | grep -A5 Mounts
总结
本文完整演示了:
- Dockerfile 最佳实践:多阶段构建、非root用户、层缓存优化
- docker-compose 分环境配置:开发/生产配置分离,按需 profiles
- 生产级细节:JSON结构化日志(写 stderr)、优雅关闭、数据热更新
- 多服务编排:多个 MCP Server 共享网络,profiles 按需启动
完整部署流程回顾:
cp .env.example .env # 1. 配置环境变量
vim .env # 2. 填写 API Key
docker compose build # 3. 构建镜像
docker compose up -d # 4. 启动服务
docker compose logs -f # 5. 查看日志确认正常
相关阅读
更多推荐



所有评论(0)