← Back to Blog

Docker MCP Gateway: Run MCP Servers in Sandboxed Containers

Docker MCP Gateway: run MCP servers in sandboxed containers

Look at the MCP config of someone who runs Claude Desktop, Cursor, and VS Code side by side. The same four servers are defined three times. Each one launches with npx, each one reads an API key from a plaintext JSON file, and each one runs as a child process with the same filesystem access as the editor that spawned it. Change a server version and you edit three files. Rotate a key and you repeat the same edit.

Docker MCP Gateway moves that arrangement into Docker. Servers become OCI images, each one runs in its own container with capped CPU and memory, secrets come from the OS keychain, and every client on the machine talks to one gateway process instead of N servers.

Prerequisites

  • Docker Desktop 4.62 or later for the docker mcp CLI commands, or Docker Engine with one extra install step (Step 1 below)
  • A running Docker engine
  • An MCP client: Claude Desktop, VS Code, Cursor, or anything that speaks stdio or Streamable HTTP
  • No Node.js install needed. That is part of the point.

Step 1: get the CLI plugin

Check what you have:

docker mcp version

If that answers with "docker: 'mcp' is not a docker command", either your Docker Desktop is older than 4.62 or you are on plain Docker Engine. On Engine, the gateway ships as a separate binary that Docker picks up as a CLI plugin:

# download the latest release binary from github.com/docker/mcp-gateway/releases
chmod +x docker-mcp
mkdir -p ~/.docker/cli-plugins
mv docker-mcp ~/.docker/cli-plugins/docker-mcp
docker mcp version

That path is the same on Linux and macOS. On Windows the plugin lives in %USERPROFILE%\.docker\cli-plugins.

Step 2: browse the catalog

The gateway pulls server definitions from an OCI catalog. List what is in the official one:

docker mcp catalog server ls mcp/docker-mcp-catalog

Each line gives you a server name such as playwright or github-official. That name is the server ID you use in the next command. In Docker Desktop you can find the same value under MCP Toolkit > Catalog > Server ID.

Step 3: create a profile and add servers

A profile is a named set of servers and their config. It is what the gateway loads when it starts, and it is what makes "my web-dev tools" a thing you can switch on.

docker mcp profile create --name web-dev

docker mcp profile server add web-dev \
  --server catalog://mcp/docker-mcp-catalog/github-official \
  --server catalog://mcp/docker-mcp-catalog/playwright

A server reference can point at four kinds of source:

URI form Source
catalog://<catalog>/<server-id> An OCI catalog, the usual case
docker://<image>:<tag> Any Docker image
https://<registry>/v0/servers/<id> The MCP community registry
file://<path> A local YAML or JSON definition

The file form is how you wrap something nobody has published yet. It needs a name, an image, and a description:

name: my-server
title: My Server
type: server
image: myimage:latest
description: Description of the server
docker mcp profile server add web-dev --server file://./my-server.yaml
docker mcp profile show web-dev

Step 4: set config values and secrets

Servers that need a token or an endpoint take it through profile config:

docker mcp profile config web-dev --set github-official.personal_access_token=<token>
docker mcp profile config web-dev --get-all

Keep in mind where that value lands. For anything sensitive, use the secret store instead, which keeps the value in the OS keychain rather than in the profile file:

docker mcp secret --help

Servers that need OAuth (GitHub's official server, for one) get authorized in Docker Desktop after you add them, under MCP Toolkit. There is no terminal prompt for the consent step.

Step 5: run the gateway

For a single local client, stdio is enough. The client spawns the gateway and the gateway spawns the server containers on demand:

docker mcp gateway run --profile web-dev

That answers the lifecycle question: nothing starts until a tool call arrives. The gateway identifies which server owns the tool, starts its container, injects the credentials, and forwards the request. Watch it happen in a second terminal:

docker ps

For several clients at once, or for a headless box where you want one long-lived endpoint, switch to a network transport:

docker mcp gateway run --profile web-dev --port 8080 --transport streaming

--transport accepts stdio, sse, or streaming. Leave out --port and it stays on stdio.

Useful flags once the basics work:

  • --servers server1,server2 with --tools server1:* --tools server2:tool2 narrows the tool list to an allowlist. Fewer tools means fewer descriptions in the model's context.
  • --secrets=docker-desktop:./.env adds a fallback secret source after the keychain.
  • --verbose --log-calls prints call traces, which is what you want when a tool call returns nothing useful.
  • --dry-run starts the gateway without listening, so you can validate a profile before wiring a client to it.
  • --watch reloads the config when files change, handy while iterating on a profile.

Two safety defaults are already on: --block-secrets keeps secrets from being sent to tools, and --verify-signatures checks image signatures. Container limits default to 1 CPU and 2 GB per server, and both are adjustable with --cpus and --memory.

Step 6: point your clients at it

Claude Desktop reads claude_desktop_config.json. One entry replaces every server you had there:

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

For clients Docker knows about, let the CLI write the file:

docker mcp client connect vscode --profile web-dev

That drops a .vscode/mcp.json in the current directory, which is exactly as user-specific as it sounds:

echo ".vscode/mcp.json" >> .gitignore

Step 7: run it without Docker Desktop

On a server or in CI you often have no Desktop at all. The gateway is a container image too:

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

Note what that mount grants. The Unix socket is root-equivalent on the host, so the gateway can start sibling containers. Run this on machines you control, not on a shared runner you do not.

When to use it, and when not to

Setup Isolation Config points Fits when
Servers via npx in each client config None, same user as your editor One per client One client, two servers, trying things out
Docker MCP Gateway One container per server, CPU and memory caps, keychain secrets One Several clients, several servers, servers you did not write
Hosted remote MCP server with OAuth Runs on the vendor's side One per client plus a consent flow Vendor-managed integrations, no local runtime desired

The honest tradeoff: the gateway adds a dependency (Docker) and a build step for anything not already in the catalog. If you run a single MCP server in a single client, the plain stdio config is lighter and you should keep it.

Troubleshooting

  • docker: 'mcp' is not a docker command: Docker Desktop older than 4.62, or the plugin binary is not in ~/.docker/cli-plugins. Check with docker info for the plugin directory Docker actually scans.
  • Gateway starts but no server container appears: nothing is wrong. Containers are created on the first tool call. Confirm with docker mcp gateway run --verbose and a real prompt.
  • The model ignores your tools: the gateway may be exposing more than it should. Use --tools to cut the list to what the task needs.
  • A server cannot reach its API: check the profile config value first, then the --block-network and --block-secrets defaults.
  • Client shows zero tools: run docker mcp profile show web-dev and confirm the servers are attached to the profile you passed with --profile.

Next steps

Add a --tools allowlist to your most-used profile, then compare how many tool definitions your client sends before and after. If you want per-user access control on a remote server rather than a local sandbox, that is a different problem, and it belongs to the OAuth layer of the MCP spec, not to the gateway.

Need Help Implementing This?

I help teams design and build scalable cloud infrastructure, DevOps pipelines, and production-grade systems.

Book a Free Consultation