//------------------------------------------------------------------- //-------------------------------------------------------------------
Docker MCP Gateway setup

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

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:

docker --version
docker compose version
groups $USER | grep docker

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

sudo usermod -aG docker $USER
newgrp 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:

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:

docker mcp --version
Output
v0.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:

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/bash
if [[ "$1" == "docker-cli-plugin-metadata" ]]; then
  echo '{"SchemaVersion":"0.1.0","Vendor":"Docker","Version":"v1.0.0","ShortDescription":"Docker Pass secrets helper"}'
  exit 0
fi
exec 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:

mkdir -p ~/.docker/mcp/catalogs
mkdir -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:

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:

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 API
EOF

Pull the standard catalog images you plan to use:

docker pull mcp/filesystem:latest
docker pull mcp/git:latest
docker 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.

cat > ~/.docker/mcp/secrets.env << 'EOF'
internal-api.token=replace-with-real-token
git.ssh_key_path=/home/youruser/.ssh/mcp_deploy_key
EOF

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:

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

Check the output for:

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:

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:

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:

sudo tee /etc/systemd/system/mcp-gateway.service > /dev/null << EOF
[Unit]
Description=Docker MCP Gateway
Requires=docker.service
After=docker.service network-online.target
Wants=network-online.target

[Service]
Type=simple
User=$(whoami)
Environment=HOME=$HOME
ExecStart=/usr/bin/docker mcp gateway run \
  --transport sse \
  --port 8811 \
  --secrets $HOME/.docker/mcp/secrets.env
Restart=on-failure
RestartSec=10

[Install]
WantedBy=multi-user.target
EOF

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

sudo mkdir -p /etc/systemd/system/mcp-gateway.service.d
TOKEN=$(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=$TOKEN
EOF

sudo systemctl daemon-reload
sudo systemctl enable --now mcp-gateway.service
sudo 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.

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: true
EOF

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:

docker compose up -d
docker compose ps
docker 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:

docker mcp server ls
docker 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:

{
  "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.

FAQs

What’s the difference between a profile and a catalog in Docker MCP Gateway?

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

Can one MCP server read another server’s secrets?

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

Do I need Docker Desktop for Docker MCP Gateway Setup?

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

Post Your Comment

PerLod delivers high-performance hosting with real-time support and unmatched reliability.

Contact us

Payment methods

payment gateway
Perlod Logo
Privacy Overview

This website uses cookies so that we can provide you with the best user experience possible. Cookie information is stored in your browser and performs functions such as recognising you when you return to our website and helping our team to understand which sections of the website you find most interesting and useful.