MCP Servers with Docker: Build, Containerize, and Deploy Model Context Protocol Servers
Running MCP servers in Docker means packaging each tool an AI agent can call — a filesystem reader, a database connector, a GitHub client — as an isolated container, so that untrusted or third-party tool code cannot reach the host system, and so that dozens of tools can be started, updated, and secured consistently instead of each becoming its own bespoke local install. 🔌
The Model Context Protocol (MCP) makes it easy for an AI agent to call arbitrary tools. That same ease is the risk: an MCP server is, in effect, code you're trusting to run alongside your agent with access to whatever the server's process can reach. Skip containerization and every MCP server you add runs with the same privileges as your own user account, sharing your filesystem, your network, and your environment variables — including any secrets sitting in them. Get the container boundary right, and adding a new tool becomes a scoped, revocable decision instead of an open-ended trust exercise. 🔒
📑 In This Post
- Foundations: what MCP is, and why the transport matters for containers
- Container mechanics: running stdio and Streamable HTTP servers
- Isolation and secrets: least privilege for tool containers
- The gateway pattern: one endpoint for many containerized servers
- Worked example: a multi-tool agent behind a gateway
- Implementation: Dockerfile, Compose, and gateway configuration
- Enterprise rollout: governance, auditing, and access control
- Common mistakes and why they hurt in production
- FAQ
- References & further reading
- Summary
🔀 Quick Comparison: Ways to Run MCP Servers
The right approach depends on how many tools you're running and how much you trust their source.
| Approach | Isolation | Setup effort | Best fit |
|---|---|---|---|
| Local process (npx/uvx) | None — shares host privileges | Lowest | Quick personal experiments with trusted, first-party servers |
| Single MCP container | Per-container, if configured deliberately | Low | One or two tools, self-managed |
| MCP Catalog + Toolkit | Per-server container, curated images | Low (managed via Docker Desktop/CLI) | Individuals or small teams wanting vetted, ready-made servers |
| MCP Gateway + fleet of containers | Centralized policy across all servers | Medium | Teams running many tools across multiple AI clients |
🎯 Use this table to decide whether you need a single containerized server or a gateway managing many of them with shared policy.
1. Foundations — What MCP Is, and Why the Transport Matters for Containers
🧸 Kid-friendly analogy: Think of an AI agent as a traveler who needs to plug into different countries' electrical outlets. MCP is the universal adapter — one standard plug shape the traveler always uses, no matter which appliance (tool) is on the other end. Docker is the surge-protected power strip that keeps a faulty appliance from frying the whole house.
The Model Context Protocol is an open standard, originally released by Anthropic in November 2024, for connecting AI applications to external tools and data sources through a consistent client-server interface. An MCP client (an AI application such as an agent or IDE integration) sends JSON-RPC requests to an MCP server, which exposes specific capabilities — reading files, querying a database, calling an external API — as callable tools.
The protocol specification defines two standard transports, and which one a server uses directly shapes how you containerize it:
- stdio: the client launches the server as a subprocess and exchanges JSON-RPC messages over the subprocess's standard input and output streams. This is the default for local, single-machine tool integrations.
- Streamable HTTP: the client sends HTTP POST requests to a server endpoint, with responses returned directly or streamed back over Server-Sent Events. This suits networked, potentially remote, or multi-client server deployments.
A container that hosts an stdio server has to keep that subprocess's stdin and stdout wired through to the client — it behaves like an interactive process, not a typical detached background service. A container hosting a Streamable HTTP server behaves like an ordinary networked service with a published port. Confusing the two is one of the most common early mistakes when containerizing a first MCP server.
2. Container Mechanics — Running stdio and Streamable HTTP Servers
🧸 Kid-friendly analogy: An stdio server in a container is like a walkie-talkie conversation — both sides have to keep holding the button and listening the whole time. A Streamable HTTP server is more like a mailbox — you can drop a letter in anytime and check back for a reply, without staying on the line.
What it does: for an stdio-based MCP server, the client (or, in a Docker-based setup, a gateway acting on the client's behalf) starts the container attached to its input and output streams, rather than running it detached in the background. For a Streamable HTTP server, the container runs like any other web service, listening on a published port for POST requests.
Why it is needed: stdio is a subprocess-oriented transport by design — the specification describes the client as launching the server as a subprocess and communicating over its stdin/stdout. A containerized stdio server has to preserve that relationship, which means the container must be started interactively with stdin kept open, not run with a typical fire-and-forget detached flag.
How it works, step by step, for a containerized stdio server:
- The client (or gateway) runs the container in interactive mode with stdin attached, roughly analogous to
docker run -irather thandocker run -d. - The MCP server process inside the container reads JSON-RPC requests from its stdin as they arrive.
- The server writes JSON-RPC responses and notifications to its stdout, which the container forwards back to the client's input stream.
- When the client disconnects or the session ends, the container exits — it is not meant to persist as a long-running background service the way a typical web server container would.
For a Streamable HTTP server, the steps are more familiar to anyone who has containerized a web API: publish a port, keep the container running detached, and let the MCP client connect over HTTP whenever it needs to, with the container potentially serving multiple client sessions concurrently.
What fails without matching the transport to the container's run mode: starting an stdio-based MCP server container detached, without its stdin properly attached, results in a server that appears to start but never receives or responds to any request — a common, confusing failure mode for anyone containerizing their first MCP server without checking which transport it expects.
✅ Worked example: Docker's own MCP Toolkit and MCP Gateway are built around exactly this distinction — the gateway manages each catalog server's container lifecycle, starting containers on demand and wiring stdio transports correctly, and can also run itself in a streaming mode to serve multiple clients over HTTP rather than one-at-a-time stdio sessions.
3. Isolation and Secrets — Least Privilege for Tool Containers
🧸 Kid-friendly analogy: Giving every MCP server full access to your machine is like handing every repair person who visits your house a copy of every key you own, just in case. A well-run household gives each visitor only the key to the room they're actually working in.
What it does: containerizing an MCP server lets you scope exactly what that one tool can see and touch — which directories are mounted, whether it has network access at all, and which credentials it's handed — independently of every other tool the agent can call.
Why it is needed: an agent might use a dozen MCP servers from a dozen different sources. Without containerization, all of them inherit the same ambient privileges as the user running the agent. A single poorly-audited or compromised server then has the same reach as the user itself — access to every file, every environment variable, every network destination the host can reach.
How it works, step by step:
- Mount only the specific directories a given server actually needs (a filesystem MCP server scoped to one project folder, not the whole home directory), rather than granting broad host access by default.
- Keep credentials out of the container image and out of plain environment variables where avoidable; a secrets-management layer — such as Docker Desktop's secrets handling used by the MCP Toolkit — injects credentials at runtime rather than baking them into a Dockerfile or docker-compose file.
- Restrict network access per server to only the destinations it legitimately needs to call, rather than giving every tool container full outbound internet access by default.
- Run each server as its own container rather than combining multiple tools into one shared container, so a compromise or bug in one tool doesn't automatically expose every other tool's data and credentials.
What fails without it: a filesystem MCP server mounted against an entire home directory instead of a scoped project folder can let an agent read or modify far more than intended, especially if the agent's own reasoning about which files to touch is ever wrong or manipulated by content it processes. A GitHub or database MCP server with a credential sitting in a plain environment variable is one accidental log line or debugging session away from leaking that credential.
💡 Trade-off: tighter per-server isolation (narrow mounts, restricted egress, no shared containers) is more setup effort per tool than simply running everything with broad access. That upfront cost buys you a security boundary that pays off the moment any one tool turns out to be buggy, outdated, or malicious — which is a matter of when, not if, across a large enough set of third-party tools.
4. The Gateway Pattern — One Endpoint for Many Containerized Servers
🧸 Kid-friendly analogy: Instead of giving a visitor a separate key and a separate door for every single room in a building, a gateway is like a front-desk receptionist: one person the visitor talks to, who then fetches whichever room's contents are actually needed, following the building's own rules about who gets to see what.
What it does: a gateway sits between one or more MCP clients and a fleet of containerized MCP servers, presenting a single connection point while managing the lifecycle, credentials, and access policy of each underlying server container. Docker's own MCP Gateway is one concrete implementation of this pattern: an open-source CLI plugin that runs each catalog server in its own isolated container, wires up stdio or streaming transports as needed, and centralizes secrets and access policy rather than leaving each AI client to manage its own separate server processes.
Why it is needed: without a gateway, every AI client (an IDE plugin, a desktop agent, a custom application) that wants to use the same set of tools has to independently know how to launch and configure each server's container, each secret, and each access rule — configuration that then has to be kept consistent by hand across every client.
How it works, step by step:
- An administrator configures which MCP servers from a catalog are available, along with any credentials each one needs.
- An AI client connects to the single gateway endpoint instead of to each server individually.
- When the client calls a tool, the gateway starts (or reuses) the corresponding server's container, handling stdio wiring or HTTP routing as appropriate for that server's transport.
- The gateway can log every tool call, enforce per-tool or per-client access rules, and apply rate or budget limits centrally, rather than relying on each server implementing that itself.
What fails without it: as the number of MCP servers in use grows, managing each one's container lifecycle, credentials, and access rules per client becomes an operational burden that scales poorly — and, worse, makes it hard to answer a basic security question like "which tools can call out to the internet, and with what credentials?" across the whole fleet at once.
5. Worked Example — A Multi-Tool Agent Behind a Gateway (hypothetical, for illustration)
Consider a hypothetical engineering team building an internal coding assistant that needs to read repository files, query an issue tracker, and look up internal documentation.
How it works, step by step:
- The team runs three separate MCP servers as containers: a filesystem server scoped only to the specific repository checkout, an issue-tracker server holding its own API token, and a documentation-search server with outbound access to only the internal docs host.
- All three sit behind a single MCP Gateway instance, so every AI client the team uses (an IDE plugin and a chat-based assistant) connects to the same gateway rather than each reimplementing server management.
- Credentials for the issue tracker are injected by the gateway's secrets layer at container start time, never written into a Dockerfile or checked into configuration alongside the rest of the setup.
- The gateway logs every tool call, which lets the team later audit exactly which files were read or which issues were queried during any given agent session.
What this illustrates: the security-relevant decisions — which directories are mounted, which network destinations are reachable, where credentials live — are made once per server container, and then apply consistently no matter which AI client is using the tools.
🎯 Use this pattern when more than one AI client or more than a couple of tools are in play, since that's where centralized management starts paying for its setup cost.
6. Implementation — Dockerfile, Compose, and Gateway Configuration
A minimal Dockerfile for a custom stdio-based MCP server keeps the image small and avoids unnecessary host access:
# Illustrative example only — adapt to your server's actual runtime and dependencies
FROM python:3.12-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY server.py .
# No EXPOSE needed for a stdio server — it communicates over stdin/stdout, not a port
ENTRYPOINT ["python", "server.py"]
Running it as an stdio server means starting it interactively rather than detached:
docker run -i --rm -v /path/to/scoped/project:/data:ro my-mcp-server:latestA Streamable HTTP server, by contrast, looks like an ordinary networked service in Compose:
# Illustrative example only
services:
docs-mcp:
build: ./docs-server
ports:
- "8081:8081"
environment:
- MCP_TRANSPORT=streamable-http
read_only: true
networks: [internal_only]
networks:
internal_only:
Illustrative examples only — verify actual flags, base images, and dependencies against your chosen MCP server's own documentation.
When using a gateway rather than managing containers directly, the equivalent configuration shifts to declaring which catalog servers are enabled and which secrets they require, letting the gateway handle container lifecycle and transport wiring on your behalf — the security-relevant decisions (scoped mounts, restricted egress, per-server credentials) are the same ones, just expressed through the gateway's configuration instead of a hand-written Dockerfile.
7. Enterprise Rollout — Governance, Auditing, and Access Control
Once MCP servers move from an individual developer's convenience to something an organization's agents rely on, several concerns become non-optional:
- Approved-image governance. Restrict which MCP server images can be run to a vetted, versioned set — whether from a curated catalog or an internally reviewed registry — rather than allowing any container image to be wired up as a tool.
- Centralized secrets management. Store API keys and credentials in a dedicated secrets store that injects them into containers at runtime, keeping them out of images, environment files, and version control.
- Per-tool and per-client access policy. Decide, and enforce centrally through the gateway, which AI clients or teams can call which tools — not every user of an internal agent needs access to every connected system.
- Audit logging of every tool call. Because MCP servers can take real actions (querying data, calling APIs), a complete log of what was called, by which client, with what arguments, is essential for incident investigation and for basic accountability.
- Rate and budget limits. Apply per-tool or per-client limits at the gateway to contain the impact of a misbehaving agent loop or a runaway automated workflow calling the same tool repeatedly.
- Image scanning and patching cadence. Treat MCP server images like any other production dependency — scan for known vulnerabilities and keep a defined update cadence, since a stale, unpatched tool container is a persistent, easily-overlooked attack surface.
- Network segmentation. Place tool containers on restricted networks that reach only what they need, so a compromised or misbehaving server can't pivot to unrelated internal systems.
8. Common Mistakes
- Running an stdio MCP server container detached. Starting it like a typical background service, without stdin attached, breaks the subprocess-style communication the transport depends on, and the server silently never receives requests.
- Mounting broad host directories "to be safe." Giving a filesystem MCP server access to an entire home directory instead of a scoped project folder defeats the purpose of containerizing it in the first place — the container boundary only helps if the mounts inside it are also scoped.
- Baking credentials into the image or a checked-in environment file. This turns a single leaked image or repository into a credential leak, when a runtime secrets-injection mechanism would have kept the credential out of any persisted artifact entirely.
- Running multiple unrelated tools in one shared container. This collapses the isolation boundary between tools, so a vulnerability or bug in one tool can expose the data and credentials of every other tool sharing that container.
- Trusting every catalog or third-party server equally. Not every MCP server carries the same provenance or review; treating an unverified community server the same as a signed, versioned, vetted one skips exactly the risk assessment containerization was meant to support.
- Skipping image updates for tool containers. MCP server containers are running code with real capabilities (file access, API calls); leaving them unpatched is a common, quiet way vulnerabilities persist long after they're publicly known.
❓ FAQ
Why does my containerized MCP server never respond to requests?
The most common cause is running an stdio-based server detached instead of interactively. stdio servers need their stdin and stdout attached to the client (or gateway) the whole session, not run like a typical background container.
Should every MCP server get its own container?
Generally yes. Running each tool in its own container keeps a bug or compromise in one tool from exposing the data and credentials of every other tool, which is the main security benefit containerization provides here.
What is the difference between running MCP servers directly and using a gateway?
Running servers directly means each AI client manages its own connections and configuration for every tool. A gateway centralizes container lifecycle, secrets, and access policy behind one endpoint that any number of clients can share.
Where should credentials for an MCP server live?
In a dedicated secrets-management layer that injects them into the container at runtime — not baked into the image, not in a plain environment file that could be checked into version control or logged accidentally.
Is stdio or Streamable HTTP the "right" transport for a containerized MCP server?
Neither is universally right — stdio suits single-client, local tool integrations, while Streamable HTTP suits networked servers that may serve multiple clients or run remotely. The protocol specification itself defines both as standard options for different situations.
🔗 References & Further Reading
- Model Context Protocol — official specification, "Transports" (stdio and Streamable HTTP)
- Docker official documentation — "Docker MCP Catalog"
- Docker official documentation — "Docker MCP Toolkit"
- Docker MCP Gateway — official GitHub repository (docker/docker-mcp)
"Docker," "Model Context Protocol," and "MCP" are trademarks or projects of their respective owners.
📝 Summary
- MCP standardizes how AI agents call tools; Docker adds the isolation boundary that makes trusting a growing set of third-party tools practical.
- A server's transport — stdio or Streamable HTTP — determines whether its container needs to run interactively or as a normal networked service.
- Scoped mounts, restricted network egress, runtime-injected secrets, and one container per tool are what actually deliver the security benefit of containerizing MCP servers.
- A gateway centralizes container lifecycle, credentials, and access policy across many servers and many AI clients, rather than leaving each client to manage its own tool connections.
- A worked example shows scoped, per-server containers behind a shared gateway turning "add a new tool" into a bounded, auditable decision.
- Enterprise rollout adds approved-image governance, centralized secrets, audit logging, rate limits, and a patching cadence on top of individual container hygiene.
- Most real incidents trace back to a short list of avoidable mistakes: detached stdio containers, over-broad mounts, baked-in credentials, and shared containers across unrelated tools.
Hopefully this gives you a clear, first-principles map of how MCP and Docker fit together — and which boundary to tighten first as you add more tools to an agent. 🙌
Comments
Post a Comment