ClaudeMap

·MCP Servers

A 2026 guide to running MCP servers in Docker — the stdio-vs-container mismatch, the 300+ server MCP Catalog with signed images, the docker mcp CLI and Gateway (stdio/sse/streaming, auth, secrets), Claude Desktop and Claude Code wiring, Compose orchestration, and what the 2026-07-28 stateless spec means for K8s.

Deploying MCP Servers with Docker: Catalog, Gateway & Compose Orchestration (2026)

Running MCP servers in Docker containers buys you three things: dependencies that never pollute the host, servers that can't see your environment, and one auditable gateway in front of every tool call. Docker ships three layers for this — the MCP Catalog (300+ signed, containerized servers), the docker mcp CLI (the Toolkit), and the MCP Gateway (unified execution and control). Based on the Docker documentation and the docker/mcp-gateway repository as of September 2026, this guide covers the full path from single-machine stdio to Compose orchestration.

TL;DR

  • A stdio server is by spec a "client-launched subprocess" — at odds with resident containers. The MCP Gateway is the official answer: client → Gateway → containerized servers
  • Local clients keep their mcpServers config; only the command changes to docker mcp gateway run. Clients inside the container network hit http://gateway:9011/mcp directly
  • Catalog servers carry attestations and signed SBOMs; signature verification is on by default for mcp/ namespace images
  • sse / streaming modes require a Bearer token by default (MCP_GATEWAY_AUTH_TOKEN); browser Origins are localhost-only
  • The 2026-07-28 spec removed protocol-level sessions — server containers are now naturally stateless, and K8s rollouts no longer tear sessions apart

Why stdio MCP servers and Docker clash by design

The spec defines stdio as "newline-delimited messages over the standard streams of a client-launched subprocess" — the client owns the server's lifecycle. Containers are resident, independent, orchestrator-managed. Bolted together naively, questions pile up: who starts the container? When the client exits, should it stop? Where does a stateful server keep its data?

Docker's answer has two routes:

Route A: the Gateway as the client's stdio subprocess. The architecture is AI Client → MCP Gateway → MCP Servers (Docker Containers) — the client sees one process, and the Gateway starts servers as containers on demand ("if the server isn't already running, [it] starts it as a Docker container"), aggregates tools, and returns results. Zero client-side changes; the Gateway fully owns container lifecycle.

Route B: the Gateway resident over HTTP. docker mcp gateway run --transport streaming --port 8080 turns the Gateway into a network endpoint serving multiple clients — other services in the container network, CI jobs, a team-shared server pool.

Stateful servers get explicit switches: --long-lived (containers persist until the gateway stops) and --static (pre-started mode, servers run as standalone containers).

The Docker MCP Catalog: 300+ signed containerized servers

The Catalog is Docker's curated collection of verified MCP servers, packaged as container images and distributed through Docker Hub — 300+ strong. Four categories: verified community servers (versioned with SBOM metadata), partner tools (New Relic, Stripe, Grafana, and more), Docker-built-and-signed local servers, and remotely hosted services (GitHub, Notion, Linear).

The trust mechanism is the Catalog's core selling point: every server carries three attestations — a Docker Build Cloud build attestation, verifiable source provenance, and cryptographically signed SBOMs. Images in the mcp/ namespace have signature verification enabled by default: they must be referenced by digest and are verified before pull or run. Runtime verification is available via docker mcp gateway run --verify-signatures (note: that flag's default value differs between documentation sources; treat security.md's "enabled by default" as authoritative).

Browse via Docker Desktop's MCP Toolkit → Catalog tab, or the CLI: docker mcp catalog server ls mcp/docker-mcp-catalog. To get your own server listed, open a PR against the docker/mcp-registry repo — officially, it goes live within 24 hours of approval.

A timeliness note: the current docs.docker.com Gateway page carries a note that the Gateway "as part of Docker AI Governance is an invite-only feature" — the open-source implementation remains fully available on GitHub, but the commercial product's admission policy is in flux; check the latest docs before deploying.

The Toolkit and a docker mcp CLI cheat sheet

docker mcp commands need Docker Desktop 4.62+ (enable MCP Toolkit under Beta features; without Desktop, drop the CLI plugin binary into ~/.docker/cli-plugins/docker-mcp). The subcommand map:

| Subcommand | Purpose | |---|---| | catalog | manage and browse server catalogs | | profile | bundle servers + config into reusable sets (auto-enabled in Desktop) | | config | manage server configuration values | | secret | credential management (local OS keychain) | | gateway | run the MCP gateway | | server / tools | server management; tool listing/inspection/calls | | client | wire the Gateway into Claude Code, VS Code, etc. | | oauth / feature / version | OAuth management, experimental features, version |

A typical flow: docker mcp profile create --name web-devdocker mcp profile server add web-dev --server catalog://mcp/docker-mcp-catalog/github-officialdocker mcp profile config <id> --set <server>.<key>=<value>docker mcp gateway run --profile web-dev. Server references support four schemes: catalog:// (OCI catalog), docker:// (any image), https:// (community registry), file:// (local YAML/JSON).

Credentials never touch config files: echo <token> | docker mcp secret set github_token stores the value in the local keychain, and servers reference it declaratively.

docker mcp gateway run: stdio, sse, or streaming

The Gateway's runtime parameters are few but architecture-defining:

  • --transport stdio|sse|streaming — stdio by default (attached to one client); HTTP mode (--port 8080 --transport streaming) serves many.
  • HTTP mode requires auth by default: requests must carry a Bearer token (read from MCP_GATEWAY_AUTH_TOKEN or gateway-generated); --allow-unauthenticated is the explicit opt-out. Browser requests with an Origin header are accepted from localhost only — a direct defense against DNS rebinding.
  • --secrets docker-desktop:./.env — credential source; Desktop keychain first, .env as fallback.
  • The hardening trio: --block-secrets (on by default; scans tool-call arguments and responses for secret-like values), --block-network, and --cpus/--memory (1 CPU / 2GB per server by default).

One number to memorize: the Gateway takes about 15-25 seconds to start cold — set the client startup timeout to 60 seconds (the official recommendation). The 10-second default fails during initialization, and it's the most common first-day complaint.

Wiring up Claude Desktop and Claude Code

Local client config barely changes — the server's command becomes the Gateway:

{
  "mcpServers": {
    "MCP_DOCKER": {
      "command": "docker",
      "args": ["mcp", "gateway", "run", "--profile", "web-dev"]
    }
  }
}

On the Claude Code side, claude mcp list should show MCP_DOCKER: docker mcp gateway run - ✓ Connected. You can also run docker mcp client connect claude-code (13 clients supported) to write the config automatically. The semantics of per-server mcpServers entries are unchanged; for the full client-side picture see the MCP servers configuration guide.

Containerizing your own Streamable HTTP server

A server you wrote doesn't need the Gateway to spawn it — run it as a resident network service and follow the 2026-07-28 Streamable HTTP contract:

  • Single POST endpoint: the server MUST provide one MCP endpoint (e.g. /mcp); GET/DELETE return 405.
  • Required headers: every POST carries MCP-Protocol-Version and Mcp-Method/Mcp-Name; Accept must list both application/json and text/event-stream.
  • Reverse proxy config: SSE responses should set X-Accel-Buffering: no (nginx named explicitly by the spec), and long streams send periodic comment lines as keep-alives.
  • Health checks: the Gateway exposes /health (deliberately unauthenticated) for probes; the official Compose example uses wget -O- http://localhost:9011/health with 1s interval and 60 retries, and the client service waits on depends_on: condition: service_healthy before connecting to http://gateway:9011/mcp.
  • Security baseline: validate Origin; bind to 127.0.0.1 when running locally.

Docker Compose orchestration: one server to a full stack

The official examples directory is a graduated path. The minimal orchestration is three elements:

services:
  gateway:
    image: docker/mcp-gateway
    command: [--servers=duckduckgo]
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock

Build up from there: secrets orchestration (gateway command --secrets=docker-desktop:/run/secrets/mcp_secret plus top-level compose secrets pointing at .env); --static mode pre-starting servers as standalone services (digest-pinned images, no-new-privileges, CPU/memory limits, docker-mcp-* labels so the Gateway claims them); then a client service over the streaming endpoint — a full client + gateway + multi-server stack. Both access patterns for clients (the mcpServers config for local ones, direct URLs for container-network ones) have official examples.

The 2026-07-28 statelessness: the biggest win for containerized deployment

The 2026-07-28 spec removed protocol-level sessions and Mcp-Session-Id (SEP-2567) and the initialize handshake (SEP-2575) — every request is now self-contained (version and capabilities in _meta, routing metadata mirrored into HTTP headers). For containerized deployment this deserves its own section (the following is engineering inference from those verified facts):

  • Rolling updates no longer tear sessions: with nothing to pin, Kubernetes replacing a server container is an ordinary pod swap.
  • Load balancing routes per request: the spec says intermediaries can route and inspect via the mirrored headers — no session affinity needed.
  • Old clients get explicit compat behavior: GET/DELETE to the MCP endpoint get a 405; Mcp-Session-Id and Last-Event-ID headers are ignored — the migration window can coexist peacefully.

The paired deprecation matters too: the old HTTP+SSE transport (2024-11-05) is now formally Deprecated, and the containerized migration target is Streamable HTTP.

Security hardening checklist

  • Credentials: docker mcp secret set into the keychain (stored in the Desktop VM since 4.43.0), declaratively referenced per server; Compose injects via secrets.
  • Leak prevention: --block-secrets is on by default and scans tool-call arguments and responses for secret-like values; call logs record tool names and argument shapes only — raw values are not logged by default.
  • Network: --block-network plus per-server disableNetwork/allowHosts; note that egress is not globally denied by default — tighten deliberately instead of assuming. Remote URLs are forced to public HTTPS by default, rejecting loopback/private/metadata addresses.
  • Container hardening: server containers start with Docker isolation, no-new-privileges, and CPU/memory limits; host path binds default to read-only, with writable binds behind an exact-path allowlist; server containers do not receive your host environment by default.
  • Images: signature verification on by default for mcp/; pin your own images by digest and sign them too.

Production: logging, monitoring, troubleshooting

The Gateway has built-in call tracing (--log-calls on by default); /health exists for probes; the spec defines OTel trace-context propagation conventions (traceparent/tracestate/baggage via _meta) and the repo ships an OTel metrics example. The three-step troubleshooting drill: docker mcp gateway run --verbose --dry-run (see which servers are enabled, which images pulled, how many tools aggregated), docker mcp tools ls --verbose, then docker mcp tools call search query=Docker — run these after every deploy and most config mistakes surface on the spot. To learn by doing, Docker runs an official interactive lab (seven modules, from secret injection to custom servers).

Related guides

Frequently asked questions

Can stdio MCP servers be containerized at all?

Yes — but not by treating the container as the subprocess. Let the MCP Gateway be the client's subprocess (command: docker, args: [mcp, gateway, run]); it starts and manages servers as containers on demand. To the client, the Gateway is just another well-behaved stdio server.

Does the Gateway's HTTP mode require authentication?

By default yes: sse / streaming requests must carry a Bearer token (from MCP_GATEWAY_AUTH_TOKEN or gateway-generated), and --allow-unauthenticated is the explicit opt-out. Browser-originated requests are accepted from localhost Origins only. /health is the sole unauthenticated endpoint.

Are servers in the Docker MCP Catalog trustworthy?

Every server carries three attestations — build attestation, source provenance, and signed SBOMs; mcp/ namespace images have signature verification enabled by default (enforcing digest references). Beyond the mechanism, apply least privilege as always: enable only the servers and tools the task needs.

Why does my client time out right as the Gateway starts?

The Gateway takes roughly 15-25 seconds to start cold, and many clients default to a 10-second timeout. Raise the client startup timeout to 60 seconds (the official recommendation), or switch to --static pre-started mode.

What does the 2026-07-28 spec actually change for containerized deployment?

Protocol-level sessions are gone (SEP-2567), the handshake is gone (SEP-2575), and requests are fully self-contained — restarting, replacing, or scaling server containers no longer has a "broken session" concept, making K8s rolling updates and per-request load balancing ordinary operations. Old clients coexist with well-defined compat behavior (405s, ignored session headers).

Can I use this without Docker Desktop?

Yes. The CLI plugin installs manually (binary into ~/.docker/cli-plugins/docker-mcp), and containerized environments or WSL2 use DOCKER_MCP_IN_CONTAINER=1 to bypass the Desktop check. Some Desktop-integrated features need substitutes — --secrets pointing at a .env file instead of the keychain, and docker mcp feature enable profiles for profile support.

Official references

This article reflects the Docker documentation and the docker/mcp-gateway repository as of September 4, 2026 (including the invite-only note for the commercial Gateway); MCP Gateway evolves quickly — the official docs win.