Running Docker MCP Gateway on Linux (Without Docker Desktop) — DeepSeek Blog | Neura Market
    Neura MarketNeura Market/DeepSeek
    ChatGPTChatGPTClaudeClaudeGeminiGeminiCursorCursorGrokGrokPerplexityPerplexityDeepSeekDeepSeek
    CoPilotCoPilotStable DiffusionStable DiffusionMidjourneyMidjourney
    View All Directories
    OverviewRulesPromptsMCPsAgentsBlogVideosGuidesCoursesCommunityTrendingGenerate
    DeepSeekBlogRunning Docker MCP Gateway on Linux (Without Docker Desktop)
    Back to Blog
    Running Docker MCP Gateway on Linux (Without Docker Desktop)
    docker

    Running Docker MCP Gateway on Linux (Without Docker Desktop)

    Daniel Schroeder April 9, 2026
    0 views

    Docker's MCP Toolkit is a great way to expose Model Context Protocol servers to AI clients like...

    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 Docker Desktop on macOS and Windows — but what if you want to run it on a headless Linux server? A Raspberry Pi, a VPS, a home lab box? This guide walks through setting it up from scratch on Linux (Debian/Ubuntu, arm64 or amd64), including secrets management, custom MCP server images, and a systemd service that starts automatically on boot. > This guide is based on getting it actually working on a Raspberry Pi 5 running > Debian 13. Several things that look like they should work on Linux don't — I'll > call those out explicitly so you don't waste time on the same dead ends. --- ## What We're Building A self-hosted MCP gateway that: - 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 ```plaintext AI Client → http://your-server:8811/sse → docker-mcp gateway → MCP containers ``` --- ## Prerequisites - 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 --- ## Step 1 — Install the docker-mcp Binary The `docker mcp` command is a Docker CLI plugin. On Linux you install it directly from the GitHub releases — no Docker Desktop needed. ```bash # 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 ``` Check the [releases page](https://github.com/docker/mcp-gateway/releases) for the latest version. --- ## Step 2 — The docker-pass Workaround This is the first Linux-specific gotcha. `docker mcp` CLI commands (like `docker mcp server ls`) expect a Docker CLI plugin named `docker-pass`. This binary ships with Docker Desktop on macOS but not on Linux, causing this error: ```plaintext docker pass has not been installed ``` The fix: a small wrapper script that satisfies the Docker CLI plugin protocol and delegates to `docker-credential-pass`. First, install `docker-credential-pass`: ```bash # 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 ``` Then create the wrapper plugin: ```bash 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 ``` Verify Docker recognizes it: ```bash docker info --format '{{.ClientInfo.Plugins}}' | tr ',' '\n' | grep pass # Should show: ...pass /usr/local/lib/docker/cli-plugins/docker-pass... ``` > **Note**: This wrapper is only needed for `docker mcp` CLI commands. The gateway > itself uses a different mechanism for secrets — covered in Step 5. --- ## Step 3 — Pull or Load Your MCP Images Any Docker image that implements the MCP stdio protocol can be used as an MCP server. ### Option A: Images from the official Docker MCP catalog ```bash docker pull mcp/playwright:latest ``` ### Option B: Custom/private images Transfer from another machine: ```bash # On the source machine: docker save mcp/my-server:latest | ssh your-linux-host "docker load" ``` --- ## Step 4 — Configure the Gateway Create the config directory: ```bash mkdir -p ~/.docker/mcp/catalogs ``` ### registry.yaml — which servers to enable ```bash cat > ~/.docker/mcp/registry.yaml << 'EOF' registry: playwright: ref: "" my-server: ref: "" EOF ``` ### catalog.json — where to find catalog definitions The gateway needs to know where your catalog files live. By default it only reads the official Docker MCP catalog. Register additional catalogs here: ```bash 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 ``` ### A catalog YAML for a custom server The catalog defines how a server runs and which env vars it needs. List **all** env vars under `secrets:` — including non-sensitive ones like usernames. > **Linux gotcha**: The `config:` field in catalog YAMLs is not used for env var > injection on Linux. Everything must be in `secrets:` to be passed to containers. ```yaml 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 ``` --- ## Step 5 — Secrets 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. The `docker mcp secret set` command will fail with `docker pass has not been installed` even after you install the wrapper from Step 2. The solution is the `--secrets` flag, which points the gateway at a plain env file: ```bash # 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 ``` 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, same as `~/.ssh/id_rsa`. Anyone who can read it already has root or is you. If you want GPG encryption at rest, you can store sensitive values in `pass` and populate the file 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 is never passed to or mounted into any container. The gateway reads it internally and uses it purely as a lookup table. When spawning each container it passes only the specific `-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 each server is logged in full, and you'll see that playwright gets zero secret env vars, while my-server only gets `USERNAME` and `API_KEY`. There is no way for one MCP server to access another's credentials. --- ## Step 6 — Test the Gateway Do a dry run first: ```bash docker mcp gateway run \ --dry-run \ --verbose \ --secrets ~/.docker/mcp/secrets.env \ 2>&1 ``` You should see all your configured servers listed and their tools counted, with no `Warning: Secret '...' not found` lines. If warnings appear, check that the key names in `secrets.env` exactly match the `name` fields in your catalog YAML. If everything looks good, start it live: ```bash docker mcp gateway run \ --transport sse \ --port 8811 \ --secrets ~/.docker/mcp/secrets.env ``` --- ## Step 7 — systemd Service Create the service file: ```bash 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 ``` Set a stable Bearer token that survives restarts: ```bash 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 ``` Without this, the gateway generates a new random token on every start — which means reconfiguring every client after each restart. Enable and start: ```bash sudo systemctl daemon-reload sudo systemctl enable mcp-gateway.service sudo systemctl start mcp-gateway.service ``` Check it's running: ```bash sudo systemctl status mcp-gateway.service journalctl -u mcp-gateway.service -f ``` --- ## Connecting a Client The gateway runs on port `8811` with SSE transport: ```http URL: http://your-server:8811/sse Auth: Authorization: Bearer <your-token> ``` ### Claude Desktop Add to `~/Library/Application Support/Claude/claude_desktop_config.json` (macOS) or the equivalent on your OS: ```json { "mcpServers": { "my-gateway": { "command": "npx", "args": [ "mcp-remote", "http://your-server:8811/sse", "--header", "Authorization: Bearer <your-token>", "--allow-http", "--transport", "sse-only" ] } } } ``` Two flags are required here that aren't obvious: - `--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` --- ## Troubleshooting ### `docker pass has not been installed` The `docker-pass` CLI plugin wrapper is missing or not executable. Re-check Step 2. ### `Warning: Secret '...' not found` 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: ```bash docker mcp gateway run --dry-run --verbose --secrets ~/.docker/mcp/secrets.env 2>&1 | grep Warning ``` ### Server shows 0 tools in dry-run but works live Some servers need the actual secrets present to respond to tool listing. This is normal — the gateway still starts them correctly at runtime. ### `cannot use --port with --transport=stdio` You must specify `--transport sse` when using `--port`. The default transport is stdio (for direct client connections), not HTTP. ### `sessionid must be provided` in mcp-remote Add `--transport sse-only` to the `mcp-remote` args. The default transport strategy tries Streamable HTTP first, which the gateway doesn't support. --- ## Keeping Things Updated ### Upgrade docker-mcp ```bash 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 ``` ### Update a custom MCP image ```bash docker save mcp/my-server:latest | ssh your-linux-host "docker load" ssh your-linux-host "sudo systemctl restart mcp-gateway.service" ``` --- ## Summary The key differences from macOS Docker Desktop: | Concern | macOS (Docker Desktop) | Linux (headless) | |---|---|---| | `docker-mcp` binary | Bundled with Docker Desktop | Downloaded from GitHub releases | | `docker-pass` plugin | Proprietary binary | Wrapper script → `docker-credential-pass` | | Secrets injection | `docker mcp secret set` + keychain | `--secrets <env-file>` (chmod 600) | | Auto-start | Docker Desktop | systemd service | | Transport | stdio or SSE | SSE with `--transport sse` | | mcp-remote | Default settings work | Needs `--allow-http --transport sse-only` | Everything else — catalog YAMLs, registry.yaml, the `docker mcp` CLI — works identically between macOS and Linux once the above pieces are in place.

    Tags

    dockerlinuxmcptutorial

    Comments

    More Blog

    View all
    How I'm using ASTs and Gemini to solve the "Codebase Onboarding" problem 🧠ai

    How I'm using ASTs and Gemini to solve the "Codebase Onboarding" problem 🧠

    Hi everyone! 👋 I’m Tara, a Senior Software Engineer and Consultant. Over the years, I've jumped...

    T
    tworrell
    Local AI Will Save Us All (The Math Says So, Trust Me)ai

    Local AI Will Save Us All (The Math Says So, Trust Me)

    Every few weeks a take goes viral in tech circles making the case for ditching cloud AI and running...

    S
    Sebastian Schürmann
    Lost in the AI Hype, I Started Smallai

    Lost in the AI Hype, I Started Small

    And it helped me get back into tech without drowning TL;DR at the end Coming back to...

    R
    Rohini Gaonkar
    Building a Replay-Tested Interactive Brokers Client in Gogo

    Building a Replay-Tested Interactive Brokers Client in Go

    I wanted an IBKR library that felt like Go and had testing I could trust. So I wrote one.

    T
    Thomas Marcelis
    Playwright in Pictures: Fully Parallel Modeplaywright

    Playwright in Pictures: Fully Parallel Mode

    Playwright’s fullyParallel mode is often treated as a simple performance switch. In practice, it...

    V
    Vitaliy Potapov
    Designing a CLI for Both Humans and Agentscli

    Designing a CLI for Both Humans and Agents

    Learn how Alpic designed its CLI for both human developers and AI agents — covering tradeoffs like polling, context windows, interactivity, and statelessness.

    J
    Julien Vallini

    Stay up to date

    Get the latest DeepSeek prompts, rules, and resources delivered to your inbox weekly.

    Neura Market LogoNeura Market

    Discover the best AI prompts, plugins, and resources for DeepSeek and more.

    Content Types

    • Rules
    • Prompts
    • MCPs
    • Agents
    • Guides

    Platforms

    • ChatGPT Directory
    • Claude Directory
    • Gemini Directory
    • Cursor Directory
    • Grok Directory
    • Perplexity Directory
    • DeepSeek Directory
    • CoPilot Directory
    • Stable Diffusion Directory
    • Midjourney Directory
    • All Directories

    Resources

    • Blog
    • Documentation
    • Help Center
    • Marketplace

    Legal

    • Privacy Policy
    • Terms of Service

    © 2026 Neura Market. All rights reserved.

    |

    Not affiliated with any AI platform vendors.