Tools: Running Docker MCP Gateway on Linux (Without Docker Desktop) (2026)

Tools: Running Docker MCP Gateway on Linux (Without Docker Desktop) (2026)

What We're Building

Prerequisites

Step 1 — Install the docker-mcp Binary

Step 2 — The docker-pass Workaround

Step 3 — Pull or Load Your MCP Images

Option A: Images from the official Docker MCP catalog

Option B: Custom/private images

Step 4 — Configure the Gateway

registry.yaml — which servers to enable

catalog.json — where to find catalog definitions

A catalog YAML for a custom server

Step 5 — Secrets

Step 6 — Test the Gateway

Step 7 — systemd Service

Connecting a Client

Claude Desktop

Troubleshooting

docker pass has not been installed

Warning: Secret '...' not found

Server shows 0 tools in dry-run but works live

cannot use --port with --transport=stdio

sessionid must be provided in mcp-remote

Keeping Things Updated

Upgrade docker-mcp

Update a custom MCP image

Summary Docker's MCP Toolkit is a great way to expose Model Context Protocol servers to

AI clients like Claude, n8n, or Cursor. Out of the box it's designed for DockerDesktop on macOS and Windows — but what if you want to run it on a headless Linuxserver? A Raspberry Pi, a VPS, a home lab box? This guide walks through setting it up from scratch on Linux (Debian/Ubuntu, arm64or amd64), including secrets management, custom MCP server images, and a systemdservice that starts automatically on boot. This guide is based on getting it actually working on a Raspberry Pi 5 runningDebian 13. Several things that look like they should work on Linux don't — I'llcall those out explicitly so you don't waste time on the same dead ends. A self-hosted MCP gateway that: The docker mcp command is a Docker CLI plugin. On Linux you install it directlyfrom the GitHub releases — no Docker Desktop needed. Check the releases page for thelatest version. This is the first Linux-specific gotcha. docker mcp CLI commands (likedocker mcp server ls) expect a Docker CLI plugin named docker-pass. This binaryships with Docker Desktop on macOS but not on Linux, causing this error: The fix: a small wrapper script that satisfies the Docker CLI plugin protocol anddelegates to docker-credential-pass. First, install docker-credential-pass: Then create the wrapper plugin: Verify Docker recognizes it: Note: This wrapper is only needed for docker mcp CLI commands. The gatewayitself uses a different mechanism for secrets — covered in Step 5. Any Docker image that implements the MCP stdio protocol can be used as an MCP server. Transfer from another machine: Create the config directory: The gateway needs to know where your catalog files live. By default it only readsthe official Docker MCP catalog. Register additional catalogs here: The catalog defines how a server runs and which env vars it needs. List allenv vars under secrets: — including non-sensitive ones like usernames. Linux gotcha: The config: field in catalog YAMLs is not used for env varinjection on Linux. Everything must be in secrets: to be passed to containers. This is the second major Linux gotcha. docker mcp uses a secrets engine(se:// URIs) that is Docker Desktop-only and doesn't work on Linux. Thedocker mcp secret set command will fail with docker pass has not been installedeven after you install the wrapper from Step 2. The solution is the --secrets flag, which points the gateway at a plain env file: The key names map to the name field in the catalog's secrets: list. Is this secure? The file is chmod 600 — readable only by your user, sameas ~/.ssh/id_rsa. Anyone who can read it already has root or is you. If you wantGPG encryption at rest, you can store sensitive values in pass and populate thefile from it — but for an unattended service the threat model is the same either way. Are secrets isolated between MCP servers? Yes, completely. The secrets file isnever passed to or mounted into any container. The gateway reads it internally anduses it purely as a lookup table. When spawning each container it passes only thespecific -e VAR=value flags declared in that server's catalog secrets: list.You can verify this with --dry-run --verbose — the docker run command for eachserver is logged in full, and you'll see that playwright gets zero secret env vars,while lanis-mcp only gets LANIS_*. There is no way for one MCP server to accessanother's credentials. You should see all your configured servers listed and their tools counted, with noWarning: Secret '...' not found lines. If warnings appear, check that the keynames in secrets.env exactly match the name fields in your catalog YAML. If everything looks good, start it live: Create the service file: Set a stable Bearer token that survives restarts: Without this, the gateway generates a new random token on every start — which means reconfiguring every client after each restart. The gateway runs on port 8811 with SSE transport: Add to ~/Library/Application Support/Claude/claude_desktop_config.json(macOS) or the equivalent on your OS: Two flags are required here that aren't obvious: The docker-pass CLI plugin wrapper is missing or not executable. Re-check Step 2. The key name in secrets.env doesn't match the name field in the catalog YAML,or you forgot to pass --secrets to the gateway. Check with: Some servers need the actual secrets present to respond to tool listing. This isnormal — the gateway still starts them correctly at runtime. You must specify --transport sse when using --port. The default transport isstdio (for direct client connections), not HTTP. Add --transport sse-only to the mcp-remote args. The default transport strategytries Streamable HTTP first, which the gateway doesn't support. The key differences from macOS Docker Desktop: Everything else — catalog YAMLs, registry.yaml, the docker mcp CLI — works

identically between macOS and Linux once the above pieces are in place. Templates let you quickly answer FAQs or store snippets for re-use. Hide child comments as well For further actions, you may consider blocking this person and/or reporting abuse

Code Block

Copy

AI Client → http://your-server:8811/sse → docker-mcp gateway → MCP containers AI Client → http://your-server:8811/sse → docker-mcp gateway → MCP containers AI Client → http://your-server:8811/sse → docker-mcp gateway → MCP containers # Create the CLI plugins directory sudo mkdir -p /usr/local/lib/docker/cli-plugins # Download the binary for your architecture # arm64 (Raspberry Pi 4/5, Apple Silicon VMs): sudo curl -fsSL \ https://github.com/docker/mcp-gateway/releases/download/v0.41.0/docker-mcp-linux-arm64.tar.gz \ | sudo tar -xz -C /usr/local/lib/docker/cli-plugins/ # amd64 (regular x86 server): sudo curl -fsSL \ https://github.com/docker/mcp-gateway/releases/download/v0.41.0/docker-mcp-linux-amd64.tar.gz \ | sudo tar -xz -C /usr/local/lib/docker/cli-plugins/ sudo chmod +x /usr/local/lib/docker/cli-plugins/docker-mcp # Verify docker mcp --version # Create the CLI plugins directory sudo mkdir -p /usr/local/lib/docker/cli-plugins # Download the binary for your architecture # arm64 (Raspberry Pi 4/5, Apple Silicon VMs): sudo curl -fsSL \ https://github.com/docker/mcp-gateway/releases/download/v0.41.0/docker-mcp-linux-arm64.tar.gz \ | sudo tar -xz -C /usr/local/lib/docker/cli-plugins/ # amd64 (regular x86 server): sudo curl -fsSL \ https://github.com/docker/mcp-gateway/releases/download/v0.41.0/docker-mcp-linux-amd64.tar.gz \ | sudo tar -xz -C /usr/local/lib/docker/cli-plugins/ sudo chmod +x /usr/local/lib/docker/cli-plugins/docker-mcp # Verify docker mcp --version # Create the CLI plugins directory sudo mkdir -p /usr/local/lib/docker/cli-plugins # Download the binary for your architecture # arm64 (Raspberry Pi 4/5, Apple Silicon VMs): sudo curl -fsSL \ https://github.com/docker/mcp-gateway/releases/download/v0.41.0/docker-mcp-linux-arm64.tar.gz \ | sudo tar -xz -C /usr/local/lib/docker/cli-plugins/ # amd64 (regular x86 server): sudo curl -fsSL \ https://github.com/docker/mcp-gateway/releases/download/v0.41.0/docker-mcp-linux-amd64.tar.gz \ | sudo tar -xz -C /usr/local/lib/docker/cli-plugins/ sudo chmod +x /usr/local/lib/docker/cli-plugins/docker-mcp # Verify docker mcp --version docker pass has not been installed docker pass has not been installed docker pass has not been installed # arm64: sudo curl -fsSL \ https://github.com/docker/docker-credential-helpers/releases/download/v0.9.5/docker-credential-pass-v0.9.5.linux-arm64 \ -o /usr/local/bin/docker-credential-pass # amd64: sudo curl -fsSL \ https://github.com/docker/docker-credential-helpers/releases/download/v0.9.5/docker-credential-pass-v0.9.5.linux-amd64 \ -o /usr/local/bin/docker-credential-pass sudo chmod +x /usr/local/bin/docker-credential-pass # arm64: sudo curl -fsSL \ https://github.com/docker/docker-credential-helpers/releases/download/v0.9.5/docker-credential-pass-v0.9.5.linux-arm64 \ -o /usr/local/bin/docker-credential-pass # amd64: sudo curl -fsSL \ https://github.com/docker/docker-credential-helpers/releases/download/v0.9.5/docker-credential-pass-v0.9.5.linux-amd64 \ -o /usr/local/bin/docker-credential-pass sudo chmod +x /usr/local/bin/docker-credential-pass # arm64: sudo curl -fsSL \ https://github.com/docker/docker-credential-helpers/releases/download/v0.9.5/docker-credential-pass-v0.9.5.linux-arm64 \ -o /usr/local/bin/docker-credential-pass # amd64: sudo curl -fsSL \ https://github.com/docker/docker-credential-helpers/releases/download/v0.9.5/docker-credential-pass-v0.9.5.linux-amd64 \ -o /usr/local/bin/docker-credential-pass sudo chmod +x /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 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 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 info --format '{{.ClientInfo.Plugins}}' | tr ',' '\n' | grep pass # Should show: ...pass /usr/local/lib/docker/cli-plugins/docker-pass... docker info --format '{{.ClientInfo.Plugins}}' | tr ',' '\n' | grep pass # Should show: ...pass /usr/local/lib/docker/cli-plugins/docker-pass... docker info --format '{{.ClientInfo.Plugins}}' | tr ',' '\n' | grep pass # Should show: ...pass /usr/local/lib/docker/cli-plugins/docker-pass... docker pull mcp/playwright:latest docker pull mcp/playwright:latest docker pull mcp/playwright:latest # On the source machine: docker save mcp/my-server:latest | ssh your-linux-host "docker load" # On the source machine: docker save mcp/my-server:latest | ssh your-linux-host "docker load" # On the source machine: docker save mcp/my-server:latest | ssh your-linux-host "docker load" mkdir -p ~/.docker/mcp/catalogs mkdir -p ~/.docker/mcp/catalogs mkdir -p ~/.docker/mcp/catalogs cat > ~/.docker/mcp/registry.yaml << 'EOF' registry: playwright: ref: "" my-server: ref: "" EOF cat > ~/.docker/mcp/registry.yaml << 'EOF' registry: playwright: ref: "" my-server: ref: "" EOF cat > ~/.docker/mcp/registry.yaml << 'EOF' registry: playwright: ref: "" my-server: ref: "" EOF cat > ~/.docker/mcp/catalog.json << 'EOF' { "catalogs": { "docker-mcp": { "displayName": "Docker MCP Catalog", "url": "https://desktop.docker.com/mcp/catalog/v2/catalog.yaml" }, "my-catalog": { "displayName": "My Custom Servers", "url": "/home/youruser/.docker/mcp/catalogs/my-catalog.yaml" } } } EOF cat > ~/.docker/mcp/catalog.json << 'EOF' { "catalogs": { "docker-mcp": { "displayName": "Docker MCP Catalog", "url": "https://desktop.docker.com/mcp/catalog/v2/catalog.yaml" }, "my-catalog": { "displayName": "My Custom Servers", "url": "/home/youruser/.docker/mcp/catalogs/my-catalog.yaml" } } } EOF cat > ~/.docker/mcp/catalog.json << 'EOF' { "catalogs": { "docker-mcp": { "displayName": "Docker MCP Catalog", "url": "https://desktop.docker.com/mcp/catalog/v2/catalog.yaml" }, "my-catalog": { "displayName": "My Custom Servers", "url": "/home/youruser/.docker/mcp/catalogs/my-catalog.yaml" } } } EOF registry: my-server: title: My MCP Server description: Does something useful image: mcp/my-server:latest type: server tools: [] secrets: - name: my-server.api_key env: API_KEY description: API key for the service - name: my-server.username env: USERNAME description: Your username name: my-catalog displayName: My Catalog registry: my-server: title: My MCP Server description: Does something useful image: mcp/my-server:latest type: server tools: [] secrets: - name: my-server.api_key env: API_KEY description: API key for the service - name: my-server.username env: USERNAME description: Your username name: my-catalog displayName: My Catalog registry: my-server: title: My MCP Server description: Does something useful image: mcp/my-server:latest type: server tools: [] secrets: - name: my-server.api_key env: API_KEY description: API key for the service - name: my-server.username env: USERNAME description: Your username name: my-catalog displayName: My Catalog # Create the secrets file cat > ~/.docker/mcp/secrets.env << 'EOF' my-server.api_key=your-api-key-here my-server.username=your-username EOF # Restrict permissions — only your user can read it chmod 600 ~/.docker/mcp/secrets.env # Create the secrets file cat > ~/.docker/mcp/secrets.env << 'EOF' my-server.api_key=your-api-key-here my-server.username=your-username EOF # Restrict permissions — only your user can read it chmod 600 ~/.docker/mcp/secrets.env # Create the secrets file cat > ~/.docker/mcp/secrets.env << 'EOF' my-server.api_key=your-api-key-here my-server.username=your-username EOF # Restrict permissions — only your user can read it chmod 600 ~/.docker/mcp/secrets.env docker mcp gateway run \ --dry-run \ --verbose \ --secrets ~/.docker/mcp/secrets.env \ 2>&1 docker mcp gateway run \ --dry-run \ --verbose \ --secrets ~/.docker/mcp/secrets.env \ 2>&1 docker mcp gateway run \ --dry-run \ --verbose \ --secrets ~/.docker/mcp/secrets.env \ 2>&1 docker mcp gateway run \ --transport sse \ --port 8811 \ --secrets ~/.docker/mcp/secrets.env docker mcp gateway run \ --transport sse \ --port 8811 \ --secrets ~/.docker/mcp/secrets.env docker mcp gateway run \ --transport sse \ --port 8811 \ --secrets ~/.docker/mcp/secrets.env 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/local/lib/docker/cli-plugins/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 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/local/lib/docker/cli-plugins/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 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/local/lib/docker/cli-plugins/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 sudo mkdir -p /etc/systemd/system/mcp-gateway.service.d TOKEN=$(openssl rand -hex 32) echo "Save this token: $TOKEN" sudo tee /etc/systemd/system/mcp-gateway.service.d/token.conf > /dev/null << EOF [Service] Environment=MCP_GATEWAY_AUTH_TOKEN=$TOKEN EOF sudo mkdir -p /etc/systemd/system/mcp-gateway.service.d TOKEN=$(openssl rand -hex 32) echo "Save this token: $TOKEN" sudo tee /etc/systemd/system/mcp-gateway.service.d/token.conf > /dev/null << EOF [Service] Environment=MCP_GATEWAY_AUTH_TOKEN=$TOKEN EOF sudo mkdir -p /etc/systemd/system/mcp-gateway.service.d TOKEN=$(openssl rand -hex 32) echo "Save this token: $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 mcp-gateway.service sudo systemctl start mcp-gateway.service sudo systemctl daemon-reload sudo systemctl enable mcp-gateway.service sudo systemctl start mcp-gateway.service sudo systemctl daemon-reload sudo systemctl enable mcp-gateway.service sudo systemctl start mcp-gateway.service sudo systemctl status mcp-gateway.service journalctl -u mcp-gateway.service -f sudo systemctl status mcp-gateway.service journalctl -u mcp-gateway.service -f sudo systemctl status mcp-gateway.service journalctl -u mcp-gateway.service -f URL: http://your-server:8811/sse Auth: Authorization: Bearer <your-token> URL: http://your-server:8811/sse Auth: Authorization: Bearer <your-token> URL: http://your-server:8811/sse Auth: Authorization: Bearer <your-token> { "mcpServers": { "my-gateway": { "command": "npx", "args": [ "mcp-remote", "http://your-server:8811/sse", "--header", "Authorization: Bearer <your-token>", "--allow-http", "--transport", "sse-only" ] } } } { "mcpServers": { "my-gateway": { "command": "npx", "args": [ "mcp-remote", "http://your-server:8811/sse", "--header", "Authorization: Bearer <your-token>", "--allow-http", "--transport", "sse-only" ] } } } { "mcpServers": { "my-gateway": { "command": "npx", "args": [ "mcp-remote", "http://your-server:8811/sse", "--header", "Authorization: Bearer <your-token>", "--allow-http", "--transport", "sse-only" ] } } } docker mcp gateway run --dry-run --verbose --secrets ~/.docker/mcp/secrets.env 2>&1 | grep Warning docker mcp gateway run --dry-run --verbose --secrets ~/.docker/mcp/secrets.env 2>&1 | grep Warning docker mcp gateway run --dry-run --verbose --secrets ~/.docker/mcp/secrets.env 2>&1 | grep Warning VERSION=v0.41.0 # replace with latest ARCH=arm64 # or amd64 sudo curl -fsSL \ https://github.com/docker/mcp-gateway/releases/download/$VERSION/docker-mcp-linux-$ARCH.tar.gz \ | sudo tar -xz -C /usr/local/lib/docker/cli-plugins/ sudo chmod +x /usr/local/lib/docker/cli-plugins/docker-mcp sudo systemctl restart mcp-gateway.service VERSION=v0.41.0 # replace with latest ARCH=arm64 # or amd64 sudo curl -fsSL \ https://github.com/docker/mcp-gateway/releases/download/$VERSION/docker-mcp-linux-$ARCH.tar.gz \ | sudo tar -xz -C /usr/local/lib/docker/cli-plugins/ sudo chmod +x /usr/local/lib/docker/cli-plugins/docker-mcp sudo systemctl restart mcp-gateway.service VERSION=v0.41.0 # replace with latest ARCH=arm64 # or amd64 sudo curl -fsSL \ https://github.com/docker/mcp-gateway/releases/download/$VERSION/docker-mcp-linux-$ARCH.tar.gz \ | sudo tar -xz -C /usr/local/lib/docker/cli-plugins/ sudo chmod +x /usr/local/lib/docker/cli-plugins/docker-mcp sudo systemctl restart mcp-gateway.service docker save mcp/my-server:latest | ssh your-linux-host "docker load" ssh your-linux-host "sudo systemctl restart mcp-gateway.service" docker save mcp/my-server:latest | ssh your-linux-host "docker load" ssh your-linux-host "sudo systemctl restart mcp-gateway.service" docker save mcp/my-server:latest | ssh your-linux-host "docker load" ssh your-linux-host "sudo systemctl restart mcp-gateway.service" - Runs any MCP server as a Docker container - Exposes them all behind a single HTTP endpoint with Bearer token auth - Starts automatically on boot via systemd - Linux host with Docker installed (Docker Engine, not Docker Desktop) - Your user in the docker group (sudo usermod -aG docker $USER) - SSH access if setting up remotely - --allow-http — mcp-remote blocks non-HTTPS URLs by default - --transport sse-only — the default http-first strategy sends a POST that the gateway rejects with sessionid must be provided