Running an MCP Server in Docker: Config & Gotchas
Most MCP servers don't need Docker — here's when a container actually earns its keep, and the config that works the first time.

Running an MCP server in Docker is straightforward: your client launches docker run instead of npx or python, and the container talks to the client over stdio just like any local process. The catch is that most servers don't need a container at all, and the ones that do have a few sharp edges — stdin handling, secrets, and mounted paths — that break silently if you get them wrong.
This guide covers when to containerise, the minimal config that works, and Docker's own MCP catalog. Roughly 90% of MCP servers run locally over stdio, so Docker is an optimization for a specific set of problems, not the default. If you're new to the protocol, start with what an MCP server is and come back.
When to run an MCP server in Docker (and when to skip it)
Containerise when the server has messy dependencies, needs isolation, or runs untrusted code — otherwise plain stdio is simpler and faster. A Node server you install with npx starts in under a second; wrapping it in Docker adds image pulls, a daemon dependency, and a longer cold start for no benefit.
Here's the honest breakdown:
| Situation | Docker? | Why |
|---|---|---|
| Simple Node/Python server (most cases) | Skip it | npx/uvx is faster and has fewer moving parts |
| Server needs system libs or a specific runtime | Yes | Pin the whole environment in the image |
| Running community/untrusted code | Yes | Filesystem and network isolation limits blast radius |
| Team wants one reproducible setup | Yes | Same image everywhere, no "works on my machine" |
| Server holds secrets you don't want on disk | Maybe | Env injection is cleaner, but so is a secrets manager |
The isolation point is the strongest argument. An MCP server runs with your user's permissions by default, so a buggy or malicious one can read anything you can. A container narrows that down to explicitly mounted paths. That trade-off is covered in depth in what actually matters for MCP security.
The minimal Docker config that works
Point your client at docker as the command and pass run -i --rm plus the image. The -i (interactive) flag is the one people forget, and without it the server can't read from stdin — the MCP transport just hangs.
{
"mcpServers": {
"github": {
"command": "docker",
"args": ["run", "-i", "--rm", "ghcr.io/github/github-mcp-server"],
"env": { "GITHUB_PERSONAL_ACCESS_TOKEN": "ghp_..." }
}
}
}
Three flags carry the weight. -i keeps stdin open so the JSON-RPC messages flow; --rm deletes the container on exit so you don't leak dead containers every time the client restarts; the image reference should be pinned to a tag or digest, not latest, so an upstream push can't change your tools underneath you.
Don't add -t (TTY). It's the reflex from running containers by hand, but a TTY mangles the stdio stream and breaks the protocol. If your config uses the config generator, it emits the correct flags for you.
Passing secrets and files without leaking them
Inject secrets as environment variables and mount only the directories the server truly needs — never bake a token into the image. The env block in the config above is read by your client and passed to the container at launch, so the token never lands in an image layer or a shell history file.
For file access, be surgical with -v:
{
"command": "docker",
"args": [
"run", "-i", "--rm",
"-v", "/Users/me/projects:/workspace",
"mcp/filesystem", "/workspace"
]
}
That mounts one project tree read-write into the container, and the server's allowed directory is /workspace — nothing above it is visible. This is the containerised version of the Filesystem reference server, which already restricts writes to allowed directories; the mount adds a second wall. Mount as read-only with :ro when the server only needs to read.
Two failure modes to expect. Paths inside the container are the container's paths, not your host's, so pass /workspace, not /Users/me/projects, as the server's argument. And on macOS or Windows, Docker Desktop must have the parent folder shared in its file-sharing settings or the mount silently comes up empty.
Docker's own MCP catalog and Gateway
Docker maintains an MCP Catalog and a Gateway that let you run vetted servers as containers behind a single endpoint. Instead of hand-writing a docker run block per server, you enable servers in Docker Desktop's MCP Toolkit and point one client entry at the Gateway, which multiplexes them.
The appeal is management, not raw capability. The Gateway handles credentials, per-server isolation, and gives you one place to see what's connected — useful once you're running more than three or four servers. The catalog images are pre-built and signed, so you skip the "is this npm package safe" question for anything listed there.
The GitHub MCP Server is a good example: it ships as an official Docker image and also as a hosted remote, so you can run it locally in a container for full control or point at GitHub's endpoint to skip infra entirely. Not everything is in the catalog, though — a doc-injection server like Context7 is typically wired in directly. For a broader shortlist of what's worth installing, see the best MCP servers roundup.
Remote MCP over HTTP instead of a local container
If the goal is running the server somewhere other than your laptop, a remote HTTP server often beats shipping a container to each developer. Docker gets a local server off your machine's dependency list, but it still runs on your machine. A remote server over streamable HTTP runs once, centrally, and every client just connects.
The trade-off is real: remote means auth, network latency, and a service you now operate. Local stdio (containerised or not) means zero network setup but no sharing. Most teams end up with a mix — local stdio for personal tools, remote HTTP for shared ones. The local vs remote MCP breakdown walks through picking per server.
One budget note that applies regardless of transport: coding clients cap out around 40 active tools before the model starts picking the wrong one. A container doesn't change that math — every server you add, Dockerised or not, spends from the same budget. Enable servers deliberately.
The bottom line
Use Docker for MCP servers that carry heavy dependencies, run untrusted code, or need to be identical across a team. For everything else, npx or uvx over stdio is the faster path and one fewer daemon to keep running. Get -i --rm right, pin your image, mount narrowly, and the container disappears into the background where it belongs. If a Dockerised server won't connect, the troubleshooting guide covers the usual suspects, and adding an MCP server has the client-by-client steps.