Run Multiple MCP Servers Securely with Docker MCP Gateway and Isolated Profiles

Updated on Aug 22, 2026
Mila H
10 MINS READ
Table of Contents
Docker MCP Gateway setup

Running third-party MCP packages directly on your laptop or server with npx or uvx is risky; it can easily leak secrets or give an AI agent more access than intended. A proper Docker MCP Gateway setup fixes this by running each MCP server in its own container, behind one secure endpoint, with limited tools and secrets.

This guide shows you a complete Docker MCP Gateway setup. If you're planning to host this on a VPS rather than a personal machine, an affordable Linux VPS gives you the isolation and control this setup needs.

Why a Direct npx/uvx Workflow Is Risky

Most MCP quick-start guides tell you to run something like npx -y @some/mcp-server directly from your AI client's config file. Every time the client starts, that command downloads and runs code from npm or PyPI directly on your machine with full access to your files and network.

Docker's security research found command injection flaws in many published MCP servers, and real CVEs have involved MCP services left exposed with no authentication. A solid Docker MCP Gateway setup avoids all of this by running servers in containers instead of on your host. The gateway handles every tool call, so you get logging, secret protection, and network control built in.

What Docker MCP Gateway Actually Does

Docker MCP Gateway is an open-source proxy that sits between your AI client and a set of containerized MCP servers. Instead of configuring ten different tools inside every client you use, you configure the gateway once, group servers into named profiles, and point every client at the gateway.

How a Docker MCP Gateway setup works:

  • Each MCP server runs in its own container, with limited privileges, network access, and resources.
  • The gateway starts a server only when a client needs it, then stops it again.
  • Secrets are passed in as environment variables, never shared between containers.
  • Profiles control which servers or tools a specific client can see and use.
  • Every tool call is logged, so you have a record of what ran and when.

Prerequisites for Docker MCP Gateway Setup

Before starting, confirm the host is ready. This tutorial assumes you have a Linux server running Ubuntu 22.04 or newer.

  • Docker engine is installed.
  • Your user is added to the docker group so you don't need sudo for every command.
  • At least 2 vCPUs and 4 GB RAM as a starting point, and more if you plan to run several MCP servers concurrently.
  • SSH access with key-based authentication.

Check that Docker is installed and your user has access:

Bash
docker --versiondocker compose versiongroups $USER | grep docker

If docker isn't in your groups list, add it and re-login:

Bash
sudo usermod -aG docker $USERnewgrp docker

Step 1. Install the docker-mcp CLI Plugin

Docker Desktop bundles the MCP Toolkit automatically, but on a Linux server, you can install the docker-mcp CLI plugin manually as part of the Docker MCP Gateway setup.

Download the latest binary from the official releases:

Bash
mkdir -p ~/.docker/cli-plugins curl -fsSL \  https://github.com/docker/mcp-gateway/releases/download/v0.43.3/docker-mcp-linux-amd64.tar.gz \  | tar -xz -C ~/.docker/cli-plugins/ chmod +x ~/.docker/cli-plugins/docker-mcp

Verify the installation:

Bash
docker mcp --version
Bash
Outputv0.43.3

At this point, the plugin is registered correctly, and this stage of the Docker MCP Gateway setup is done.

Step 2. Fix the docker-pass Dependency on Linux

The docker mcp CLI expects a docker-pass plugin for secret storage, which ships with Docker Desktop on macOS but not on Linux. Without it, commands like docker mcp server ls fail with docker pass has not been installed.

Install the credential helper and a small wrapper script:

Bash
sudo mkdir -p /usr/local/lib/docker/cli-plugins curl -fsSL \  https://github.com/docker/docker-credential-helpers/releases/download/v0.9.8/docker-credential-pass-v0.9.8.linux-amd64 \  -o /usr/local/bin/docker-credential-pass sudo tee /usr/local/lib/docker/cli-plugins/docker-pass > /dev/null << 'EOF'#!/bin/bashif [[ "$1" == "docker-cli-plugin-metadata" ]]; then  echo '{"SchemaVersion":"0.1.0","Vendor":"Docker","Version":"v1.0.0","ShortDescription":"Docker Pass secrets helper"}'  exit 0fiexec docker-credential-pass "$@"EOF sudo chmod +x /usr/local/lib/docker/cli-plugins/docker-pass docker-credential-pass version

Note: You don't need this if you're using the --secrets file method later, but it stops CLI warnings and lets commands like docker mcp server ls run cleanly.

Step 3. Design Isolated Profiles Before You Add Servers

A profile is a named collection of MCP servers and their configuration. Instead of one big pool of tools every client can see, profiles let you scope exposure to exactly what each use case needs. This is the isolation piece of a proper Docker MCP Gateway setup.

A simple starting setup:

  • dev-profile: Filesystem, Git, and a code search server, scoped to a single project directory.
  • research-profile: A web-search or fetch server, no filesystem access at all.
  • ops-profile: Infrastructure tools with read-only mounts.

Keeping these separate means a compromised or misbehaving tool in research-profile never gets anywhere near your filesystem or infrastructure credentials. Treat each profile in your Docker MCP Gateway setup as its own trust boundary, not just an organizational label.

Step 4. Build the Server Catalog

The gateway reads server definitions from catalog files. Create a working directory and a registry listing which servers are enabled:

Bash
mkdir -p ~/.docker/mcp/catalogsmkdir -p ~/.docker/mcp/secrets cat > ~/.docker/mcp/registry.yaml << 'EOF'registry:  filesystem:    ref: ""  git:    ref: ""  fetch:    ref: ""EOF

Point the gateway at both the official Docker MCP Catalog and your own custom catalog:

Bash
cat > ~/.docker/mcp/catalog.json << 'EOF'{  "catalogs": {    "docker-mcp": {      "displayName": "Docker MCP Catalog",      "url": "https://desktop.docker.com/mcp/catalog/v2/catalog.yaml"    },    "custom-catalog": {      "displayName": "Custom Servers",      "url": "/home/youruser/.docker/mcp/catalogs/custom-catalog.yaml"    }  }}EOF

For any custom or private MCP server image, define it explicitly, including every environment variable it needs under secrets:

Bash
cat > ~/.docker/mcp/catalogs/custom-catalog.yaml << 'EOF'registry:  internal-api:    title: Internal API Server    description: Wraps an internal REST API as MCP tools    image: yourregistry/internal-mcp:latest    type: server    tools: []    secrets:      - name: internal-api.token        env: API_TOKEN        description: Bearer token for the internal APIEOF

Pull the standard catalog images you plan to use:

Bash
docker pull mcp/filesystem:latestdocker pull mcp/git:latestdocker pull mcp/fetch:latest

Step 5. Isolate Secrets Per Server

On Linux, the Docker Desktop keychain-based secrets engine isn't available, so this Docker MCP Gateway setup uses a permission-locked secrets file instead. This keeps credentials out of individual container images and out of your shell history.

Bash
cat > ~/.docker/mcp/secrets.env << 'EOF'internal-api.token=replace-with-real-tokengit.ssh_key_path=/home/youruser/.ssh/mcp_deploy_keyEOF chmod 600 ~/.docker/mcp/secrets.env

Two things to check here:

  • The secrets file itself never goes into any container; the gateway reads it once and only passes each server the variables listed in its own catalog entry.
  • If a server's catalog entry doesn't list a secret, that server never gets it, even if another server running alongside it does.

You can confirm this behavior with a dry run before going live, covered in the next step.

Step 6. Dry-Run and Launch the Gateway

Always check your setup before going live. A dry run shows which containers would start and which secrets each one would get without starting anything:

Bash
docker mcp gateway run \  --dry-run \  --verbose \  --secrets ~/.docker/mcp/secrets.env

Check the output for:

Bash
Warning: Secret '...' not found

This means a name mismatch between secrets.env and your catalog file. Fix any warnings before continuing this Docker MCP Gateway setup.

Once the dry run is clean, start the gateway for real over HTTP/SSE so remote clients can reach it:

Bash
docker mcp gateway run \  --transport sse \  --port 8811 \  --secrets ~/.docker/mcp/secrets.env

For local, single-client use, run it over stdio instead; it's the default and doesn't need a port:

Bash
docker mcp gateway run --profile dev-profile

Step 7. Run the Gateway as a systemd Service

For a production Docker MCP Gateway setup on a VPS, you want the gateway to survive reboots and crashes automatically rather than running it in a terminal session. To do this, use:

Bash
sudo tee /etc/systemd/system/mcp-gateway.service > /dev/null << EOF[Unit]Description=Docker MCP GatewayRequires=docker.serviceAfter=docker.service network-online.targetWants=network-online.target [Service]Type=simpleUser=$(whoami)Environment=HOME=$HOMEExecStart=/usr/bin/docker mcp gateway run \  --transport sse \  --port 8811 \  --secrets $HOME/.docker/mcp/secrets.envRestart=on-failureRestartSec=10 [Install]WantedBy=multi-user.targetEOF

Generate a stable auth token so clients don't need reconfiguring after every restart:

Bash
sudo mkdir -p /etc/systemd/system/mcp-gateway.service.dTOKEN=$(openssl rand -hex 32)echo "Save this token securely: $TOKEN" sudo tee /etc/systemd/system/mcp-gateway.service.d/token.conf > /dev/null << EOF[Service]Environment=MCP_GATEWAY_AUTH_TOKEN=$TOKENEOF sudo systemctl daemon-reloadsudo systemctl enable --now mcp-gateway.servicesudo systemctl status mcp-gateway.service

Safe Docker Compose Configuration for MCP Gateway

The CLI approach above is the most flexible way to run the gateway on Docker Engine, but you can also describe the same Docker MCP Gateway setup in a Compose file. This is useful when you want the gateway and its dependencies under version control together.

Bash
mkdir mcp-gateway && cd mcp-gateway cat > docker-compose.yml << 'EOF' services:  mcp-gateway:    image: docker/mcp-gateway:latest    container_name: mcp-gateway    restart: unless-stopped    command:      - "gateway"      - "run"      - "--transport=sse"      - "--port=8811"      - "--secrets=/run/secrets/mcp_secrets"    ports:      - "127.0.0.1:8811:8811"    volumes:      - ./catalog.yaml:/etc/mcp/catalog.yaml:ro      - ./registry.yaml:/etc/mcp/registry.yaml:ro    secrets:      - mcp_secrets    read_only: true    tmpfs:      - /tmp    cap_drop:      - ALL    security_opt:      - no-new-privileges:true    deploy:      resources:        limits:          cpus: "1.0"          memory: 512M    networks:      - mcp_internal   mcp-filesystem:    image: mcp/filesystem:latest    restart: unless-stopped    volumes:      - ./workspace:/workspace:ro    cap_drop:      - ALL    security_opt:      - no-new-privileges:true    networks:      - mcp_internal    deploy:      resources:        limits:          cpus: "0.5"          memory: 256M secrets:  mcp_secrets:    file: ./secrets.env networks:  mcp_internal:    driver: bridge    internal: trueEOF

Key security-safe choices built into this Docker MCP Gateway setup:

  • The gateway listens only on 127.0.0.1, so you reach it via a reverse proxy or SSH tunnel, not directly from the internet.
  • The mcp_internal network is internal: true, so MCP containers can’t talk to the public internet unless you route them through the gateway.
  • Filesystem mounts use :ro (read-only) wherever a server doesn’t need write access.
  • cap_drop: ALL and no-new-privileges remove extra Linux capabilities and block privilege escalation for each container.
  • CPU and memory limits make sure one bad tool can’t consume all resources on the host.

Bring it up and check container status:

Bash
docker compose up -ddocker compose psdocker compose logs -f mcp-gateway

Review MCP Tools Before Use

After launch, always confirm which tools the gateway is actually exposing before connecting any client. This is a critical verification step in any Docker MCP Gateway setup:

Bash
docker mcp server lsdocker mcp tools list --profile dev-profile

Compare the command output with what you expect from your registry and catalog files. If you see a tool you never added, you probably pulled in an extra catalog by mistake or misconfigured a profile so its boundary is too wide.

Most MCP-aware clients, including Claude Desktop, Cursor, Continue, n8n, and Windsurf, can connect either over stdio or to a remote SSE/HTTP endpoint. For a remote Docker MCP Gateway setup, use mcp-remote as a small bridge between the client and the gateway:

JSON
{  "mcpServers": {    "gateway": {      "command": "npx",      "args": [        "mcp-remote",        "http://your-server:8811/sse",        "--header",        "Authorization: Bearer YOUR_TOKEN_HERE",        "--allow-http",        "--transport",        "sse-only"      ]    }  }}

Two flags are important here: --allow-http because mcp-remote blocks HTTP by default, and --transport sse-only to avoid the sessionid must be provided error when the client tries Streamable HTTP first. Once connected, the client should only show the tools from the profile you chose, proving that this Docker MCP Gateway setup keeps tools isolated as intended.

Conclusion

Running MCP servers directly on your host with npx or uvx is easy, but it seriously increases your security risk. A Docker MCP Gateway setup with isolated profiles, per-server secrets, read-only mounts, and dropped capabilities gives you the same tools with less danger, and proper logs you can review.

Once the gateway, catalog, and Compose file are in place, adding a new MCP server becomes a quick change instead of a new attack surface on your main machine.

Use isolated MCP profiles on a PerLod Linux VPS instead of exposing your development machines to third-party tools.

We hope you enjoy this guide.

For automated deployment workflows once your gateway stack is running, you can check how to install Dokploy on a Linux VPS to manage Compose-based services through a UI.

A catalog defines available servers; a profile is a named subset of those servers exposed to a specific client or use case.

No. The gateway only injects the secrets listed in each server’s own catalog entry.

No. This guide uses Docker Engine on a Linux server; the CLI plugin is installed manually.