Docker部署MCP Server完整教程:容器化Claude工具服务(含docker-compose生产配置)

环境要求: Docker 24+,docker-compose v2,已有可运行的 MCP Server(参考上篇)
难度: 中级
适用场景: 将 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

总结

本文完整演示了:

  1. Dockerfile 最佳实践:多阶段构建、非root用户、层缓存优化
  2. docker-compose 分环境配置:开发/生产配置分离,按需 profiles
  3. 生产级细节:JSON结构化日志(写 stderr)、优雅关闭、数据热更新
  4. 多服务编排:多个 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. 查看日志确认正常

相关阅读


Logo

欢迎加入DeepSeek 技术社区。在这里,你可以找到志同道合的朋友,共同探索AI技术的奥秘。

更多推荐