Skip to main content
Tutorial7 min read

Running MCP servers with Docker: production best practices

Why Docker is the right way to run MCP servers for teams — image pinning, secret passing, network scoping, and a battle-tested config template.

For a solo developer, npx-based MCP servers are fine. For a team or a production agent, Docker is the right layer. Here is why — and the config template we use ourselves.

Why Docker, specifically

  • Version pinningghcr.io/vendor/mcp:v1.4.2 is deterministic. npx -y package fetches latest on every launch.
  • Language isolation — a Python MCP server does not need Python on your host.
  • Network scoping — you can block outbound network except to specific hosts via --network rules.
  • Secret rotation — env vars passed per launch; you don’t leave tokens in config files.
  • Team sync — commit a docker-compose.yml to the repo; everyone runs the same version.

Minimal Dockerized config in Claude Desktop

{
  "mcpServers": {
    "github": {
      "command": "docker",
      "args": [
        "run", "--rm", "-i",
        "-e", "GITHUB_PERSONAL_ACCESS_TOKEN",
        "ghcr.io/github/github-mcp-server:v1.4.2"
      ],
      "env": {
        "GITHUB_PERSONAL_ACCESS_TOKEN": "ghp_..."
      }
    }
  }
}

Key flags:

  • --rm — container removed after exit, no state drift
  • -i — interactive stdio (required for MCP stdio transport)
  • -e VAR — pass env var by name, value supplied separately. Token never appears in ps output.

Pin the tag. Always.

Using ghcr.io/vendor/mcp:latest guarantees a silent breakage the moment the vendor publishes a major version. Pin to a semver tag and bump explicitly:

ghcr.io/github/github-mcp-server:v1.4.2  // ✔
ghcr.io/github/github-mcp-server:latest   // ❌

Volume mounts for local data

For Filesystem MCP or SQLite MCP, mount the host dir:

{
  "filesystem": {
    "command": "docker",
    "args": [
      "run", "--rm", "-i",
      "-v", "/Users/you/projects:/workspace:rw",
      "mcp/filesystem:v1.0.0",
      "/workspace"
    ]
  }
}

:ro instead of :rw for read-only mounts when you want the agent to look but not touch.

Network scoping (important for security)

Default Docker bridge gives full internet. Restrict with:

  • --network none — no outbound at all. Fine for filesystem / SQLite servers.
  • Custom bridge with iptables rules — only allow api.github.com, etc.

Resource limits

Prevent a rogue server from eating your laptop:

--memory=512m --cpus=1.0

Team workflow: share config via Git

Commit mcp/servers.d/*.json and a script that merges them into claude_desktop_config.json:

#!/bin/sh
jq -s '.[0] * { mcpServers: ([.[].mcpServers] | add) }' \
   mcp/servers.d/*.json > ~/Library/Application\ Support/Claude/claude_desktop_config.json

New hire runs the script, gets the full team stack.

Rolling your own MCP image

Minimal Dockerfile for a Node-based MCP server:

FROM node:22-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci --omit=dev
COPY dist ./dist
USER node
ENTRYPOINT ["node", "dist/index.js"]

Build, push, reference by tag:

docker build -t ghcr.io/yourco/mcp-internal:v0.1.0 .
docker push ghcr.io/yourco/mcp-internal:v0.1.0

Related reads

Loadout

Build your AI agent loadout

Directory
Contact
© 2026 Loadout. Built on Angular 21 SSR.