Skip to content
Houtini.
Contact
How-to Guides ·15 August 2026

How to Stop MCP Servers Eating Your PC: One Docker Gateway for Every Claude Client

Discuss and expand Ask ChatGPT Email LinkedIn

My MCP list grew until orphaned node.exe were quietly eating a 128GB workstation by mid-afternoon. Here's how I put every server behind one Docker gateway - node on bare metal gone, secrets in one gitignored file, and a single URL every Claude client points at. One evening's work you'll feel every day after.

Docker Desktop's MCP Toolkit showing the enabled MCP servers sitting on 8.4GB of RAM, most of it Node.js.

I think this is a very good time to spend - two or three hours sorting out your PC before it's so slow you can barely do anything.

If there's an issue right now with AI-assistant adoption - and with Claude, where I have the actual hands-on experience - there's a massive problem brewing. The software leaves a mess of orphaned node executables behind it (both the Desktop app and the Code GUI), when honestly this shouldn't be a hard problem to solve. It will get solved. In the meantime, here's what to do: use the MCP container from Docker.

In today's article I'll take you step by step from the mess that is your current MCP setup to a fast, unhindered PC - released from the 300+ npm packages you didn't know you had installed.

Here's the machine this bit me on, because it matters for what follows. 128GB of DDR5, an i7 14-series (avoid the i9s, but that's a story for another day), and two 4090Ds - the Chinese-market 48GB cards. That's a lot of computer. And by four in the afternoon it was crawling. If a rig like that gets brought to its knees by a handful of little helper programs, your average 16GB office PC doesn't stand a chance. So when I say this isn't your fault, I mean it fairly literally.

The fix is three things, and I want them stated plainly up front so you know what you're buying with your evening. Speed back - no more dozens of orphaned node.exe holding RAM they'll never give up. Secrets safe - your API keys live in one gitignored file instead of scattered through config files and, God help us, chat windows. One place to manage everything - every Claude client points at the same gateway, and you add or remove a server once, not five times.

Houtini spends half its time bringing people to Claude and the other half telling them to install MCP servers. The proper setup in between - the bit that keeps the machine fast and your keys out of chat - is the part that gets skipped. This is that middle bit.

What's happening to your PC

The slowdown isn't mysterious once you see how these servers get launched.

An MCP server is a little program that hands Claude a new capability - web search, your Gmail, an SEO tool, whatever. You add it to Claude's config, and Claude launches it when it starts up. The wiring between Claude and your real tools, if you like.

Here's the bit that does the damage. The common way to launch one of these is as a local process talking over something called stdio, and most of them are Node.js programs - so each one is a node.exe in Task Manager. A lot of them start via npx, which also downloads the package the first time it runs. Fine so far. One program, one process.

Now the multiplier. Every Claude client instance starts its own copy of every configured server. The Desktop app spins up a full set. Then each Claude Code session spins up another full set - and if you're anything like me you've got three or four Code windows open across different projects. So it isn't servers you're counting. It's servers times sessions. Fourteen servers across four sessions is a lot of node, and that's before Desktop has had its go.

And then the actual bug. When you quit or restart Claude, those child processes are meant to be tidied up and killed. Frequently they aren't. They're orphaned - still running, still holding memory, with no live parent to answer to - and they sit there until you reboot the whole machine. They pile up across a working day like dishes nobody's washing.

The result on my workstation: the enabled MCP set was sitting on roughly 8.4GB of RAM, most of it node, and the steady state was somewhere between 50 and 100 orphaned node.exe at any given point. I sat and counted them in Task Manager the first time because I didn't believe the RAM figure. I believed it after.

Your secrets are in the wrong places too

This is the half of the problem nobody frames, and it's every bit as important as the speed.

To make those servers work, they need API keys. And the path of least resistance is to paste each key straight into a JSON config file. Or - and I've watched people do this, and done a version of it myself in a hurry - straight into a chat window, where it lingers in logs and history and backups for as long as those things live. Which is forever.

Then it gets worse, because there isn't one config file. There's claude_desktop_config.json, and a per-project .mcp.json in every repo you've wired something into, and a couple of half-remembered places besides. No single source of truth. Nothing gitignored by default. Any one of them a leak waiting to happen.

The fix, which we'll get to properly, is boring and correct: every secret goes in one gitignored .env file that the gateway reads. Never in chat. Never committed. One place to rotate a key when you need to. That's the whole discipline, and it's the bit most setups skip.

The fix in one picture

Instead of N servers times M sessions of bare-metal node, you run one thing:

Claude Code  ─┐
Claude Desktop├─▶  http://localhost:8811/mcp  ─▶  [ mcp-gateway container ]  ─▶  one shared
Cowork        ─┘        (one gateway)                (docker/mcp-gateway)         container
                                                                                  per server

One gateway container serves every client over a single URL. It launches each MCP server as its own shared, long-lived container - started once, reused by everyone, managed by Docker rather than by a pile of orphaned processes. Bare-metal node drops to near zero. There's one small exception, which I'll flag when we get to it: the Desktop app needs a single thin bridge process, because it can't speak to a URL directly. One process, versus the old fourteen-servers-times-every-session. I'll take that trade.

What you need before you start

Docker Desktop, with the MCP Toolkit switched on. That's it. The MCP Toolkit is a fairly recent addition to Docker Desktop - it's Docker's own first-party support for MCP, which is worth sitting with for a second: the tool that fixes the mess is made by Docker, not by some third party bolting a fix onto the side. It gives you a Catalog of ready-made servers and the docker mcp command line.

You'll want to be comfortable in a terminal and happy editing a couple of config files. If you're running several Claude clients already, you're technical enough for this. I'm not going to hold your hand through what a JSON file is, but I will earn every step with a sentence on why it's there, because following commands you don't understand is how you end up back here in six months.

New to Claude Code itself? You can start with a free week before you commit to a subscription - worth doing before you build any of this out.

Step one: install Docker and switch on the MCP Toolkit

Why: Docker is what runs the gateway and the servers as tidy, managed containers instead of loose processes. It's the whole foundation.

Go to docker.com/products/docker-desktop and download the Windows build - the AMD64 one, which is the normal build for any Intel or AMD PC. Run the installer, and leave "Use WSL 2 instead of Hyper-V" ticked. WSL 2 is Windows' built-in Linux layer, and Docker uses it to run the containers; the installer can set it up for you if it isn't already. You may get asked to reboot. Let it.

Launch Docker Desktop, accept the agreement, and wait for the whale icon in the system tray to settle. When it stops animating, the engine's running. The free "Personal" plan is all you need here - no payment, and you can skip past the sign-in prompts.

You'll know it worked when: opening PowerShell and running docker --version prints a version. Mine says Docker version 29.2.1.

Then, inside Docker Desktop, open the MCP Toolkit from the left sidebar. If it isn't there, look under Settings, in the beta or in-development features, and enable it - it's still officially finding its feet, so the exact location drifts between Docker releases. The "My servers" tab is where you'll see what's enabled (this is the 8.4GB view from the top of the article). The Catalog tab is the good bit.

The Docker MCP Catalog - the library of pre-built MCP servers you enable with a toggle.

The Catalog is a library of pre-built servers - Brave search, Firecrawl, Fetch, DuckDuckGo, hundreds of them - and you enable the ones you want with a toggle, or from the terminal with docker mcp server enable <name>. These are already containerised and waiting, which is why you're about to get all this for free without building anything yourself.

You'll know it worked when: docker mcp version prints a version. Mine's v0.39.1.

Step two: put your secrets in one file

Why: this is the "keys out of chat, in one place" fix made real. One gitignored file the gateway reads, and nothing else ever needs to know a key.

Create a file at ~/.docker/mcp/gateway-secrets.env, one key=value per line. The names matter here: the catalog expects dotted secret names, and each server lists exactly what it wants under secrets: in ~/.docker/mcp/catalogs/docker-mcp.yaml. So it looks like this:

brave.api_key=...
firecrawl.api_key=...

Then gitignore it, and mean it. This file is now the single source of truth for every key you own. When Firecrawl rotates your key in a year, this is the one line you change - and because the gateway lets you reuse a secret across servers by referencing the same name, one firecrawl.api_key can feed both Firecrawl and, say, an SEO server that leans on the same account. One place. That's the point.

Step three: run the gateway

Why: this container is the always-on hub. Every client is going to talk to it instead of spawning its own servers, so this is the thing that replaces the mess.

Save this as docker-compose.yml. The full working version lives in the repo, but these are the load-bearing parts:

services:
  mcp-gateway:
    image: docker/mcp-gateway:latest
    container_name: mcp-gateway
    restart: unless-stopped
    ports: ["127.0.0.1:8811:8811"]
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock                 # so it can launch server containers
      - "C:/Users/<you>/.docker/mcp:/root/.docker/mcp"            # catalogs + config
      - "C:/Users/<you>/.docker/mcp/gateway-secrets.env:/.env:ro" # your secrets
    command:
      - "--port=8811"
      - "--transport=streaming"     # the endpoint is served at /mcp
      - "--host=0.0.0.0"
      - "--long-lived"              # keep server containers up and shared
      - "--allow-unauthenticated"   # fine because we bind to localhost only
      - "--secrets=/.env"
      - "--servers=brave,context7,firecrawl,supadata,fetch,duckduckgo"   # list them explicitly

A couple of those flags earn a word. --long-lived is what keeps the server containers up and shared between clients rather than spinning one up and killing it per call - it's the difference between "managed" and "back where we started". --allow-unauthenticated looks alarming and isn't, because we've bound the port to 127.0.0.1 - it's only reachable from your own machine. (If you ever change that bind to 0.0.0.0 to reach it from another machine, you must add an auth token. Don't leave an unauthenticated gateway open to the network. I shouldn't have to say it, but I'm saying it.)

Bring it up:

docker compose up -d
docker logs mcp-gateway

You'll know it worked when: the log tells you it listed some number of tools and prints Gateway URL: http://localhost:8811/mcp. That /mcp on the end is not decoration - remember it, because it catches people in the next step.

Step four: point your clients at the gateway

Why: this is where each client stops spawning its own node and starts using the one gateway. It's also where the single most annoying gotcha in the whole exercise lives, so read this one properly.

Claude Code takes a native URL. No node, no bridge, nothing:

{ "mcpServers": { "MCP_DOCKER": { "type": "http", "url": "http://localhost:8811/mcp" } } }

Claude Desktop is different, and here's where the evening cost me an hour. The Desktop app can only launch stdio servers - it has no URL client at all. So it needs one small bridge process, mcp-remote, to translate. This is the single node process we keep, and it's worth keeping for the one bridge versus the old swarm:

{ "mcpServers": { "MCP_DOCKER": { "command": "npx",
    "args": ["-y","mcp-remote","http://localhost:8811/mcp","--allow-http"] } } }

Do not put the native URL form into the Desktop config expecting it to work like Code does. It won't. Worse, it won't tell you it won't - Desktop silently drops the entry ("Skipped invalid MCP server config entries" buried in a log you weren't reading) and shows nothing connected, so it looks like the whole gateway is broken when you've just fed the wrong client the wrong shape. Code needs the URL. Desktop needs the bridge. They are not interchangeable, and I learned that the slow way.

Cowork connects from Anthropic's cloud rather than your machine, so it can't see localhost at all. To use the gateway there you'd expose it on a public HTTPS URL (via cloudflared or ngrok) and add it as a custom connector with an auth token. Most people won't bother, and that's fine.

Restart the client. Its tools now arrive through the gateway, as containers, not as node.

Step five: bring your own servers in

Why: most readers can skip this. But if you've written or installed your own npm-published MCP servers, the trick here is neat and worth the two minutes.

You do not build a Docker image per server, which is the obvious-but-wrong thing to reach for. A catalog entry can set a command on top of a shared image, so you build one node image with all your servers baked in:

FROM node:22-slim
RUN npm install -g @houtini/yubhub @houtini/gemini-mcp @houtini/seo-audit-console @houtini/lm

Then one line each in your own catalog at ~/.docker/mcp/catalogs/houtini.yaml, differing only by which binary they run:

  yubhub: { type: server, image: houtini-mcp-npm:latest, command: [ yubhub-mcp ] }
  gemini: { type: server, image: houtini-mcp-npm:latest, command: [ gemini-mcp ] }

Add their keys to the same .env, list the names in --servers, and run docker compose up -d --force-recreate. Prefer npm where you can - a published, scoped package with a bin just drops in with no fuss. Build from local source only when the thing isn't on npm, and honestly, publishing it first is usually the better move anyway. Full field-by-field settings and the add-a-server recipe are in the repo's REFERENCE.md.

Step six: clean up the old mess

Why: this is the "300+ packages you didn't know you had" payoff, and I won't pretend it isn't the satisfying bit. It's also where you claw the disk and the RAM back for good.

First, see what's installed globally:

npm ls -g --depth=0
A terminal running npm rm -g on old MCP servers and leaked library dependencies: removed 390 packages in 4s.

You'll find old MCP servers you replaced months ago and forgot to remove. You'll also find something odder: leaked library dependencies that were never meant to be global at all. On my machine it was a whole cluster of HTML-parsing libraries - cheerio, parse5, htmlparser2, domutils, domelementtype - transitive dependencies that rode in on some MCP install and got left sitting in the global namespace. I never chose to install any of them. They were just there, freeloading.

So remove the lot - the old servers and the leaked libs:

npm rm -g <old-mcp-1> <old-mcp-2> <leaked-lib-1> <leaked-lib-2> ...

When I ran mine, npm rm -g removed 390 packages in 4 seconds. Three hundred and ninety. I ran the count twice because the number felt made up. That's what had been quietly riding along on this machine, and it's reversible if you're nervous - anything you need, npm i -g puts straight back.

Then clear the npx download cache, which is pure cache and refetches on demand, so there's nothing to lose:

rm -rf "C:/Users/<you>/AppData/Local/npm-cache/_npx"

Last, go into your Claude configs - the per-project .mcp.json files and the Desktop config - and strip out the old stdio server entries, leaving just the single MCP_DOCKER gateway entry. While you're in Claude's Connectors settings, a quick bit of housekeeping that ties straight back to the node bloat: the Desktop connectors (things like Desktop Commander, or the Chrome ones) are local processes too and add to the load, whereas the Web connectors (GitHub, Gmail, Calendar, Drive) are Anthropic-hosted and cost your machine nothing. Keep one Chrome, drop the duplicate, bin whatever you don't use.

Step seven: check the before and after

Why: you want to see the win, not just take my word for it. And the before/after is the thing that makes it stick.

Before, if you'd caught it: Task Manager, Details tab, sorted by name, and a wall of node.exe scrolling past with the RAM total climbing. After a restart, the same view shows a handful at most. Docker Desktop's Containers view now shows the gateway plus each server as its own shared container, all managed, all accounted for.

Docker Desktop's Containers view after the fix - the gateway plus each MCP server as its own shared container.

Both client configs now hold nothing but the gateway URL. Every server runs as a container Docker owns. Bare-metal node: gone, bar the one Desktop bridge. And the machine stays fast at four in the afternoon, which was the entire point of the exercise.

The problems that cost me time

The steps above are the 90%. Here's the 10% that bit me - none of it obvious, all of it real. I've kept the two that'll save you here, and pushed the deep containerisation traps into their own section below so they don't clog the main flow.

Docker's Windows secret store is broken, and the badge is lying to you

Docker Desktop has its own secret keychain, and on Windows it's broken - you'll see engine.sock: An invalid argument if you go anywhere near it. Do not try to fix it. That's exactly why we passed --secrets /.env back in step three and read keys from our own file instead. The catch is that Docker Desktop's UI doesn't know you did that, so it'll show a "SECRETS REQUIRED" badge that looks like a real problem and isn't. It's a false alarm. Your .env is the real source of truth; ignore the badge and move on.

Claude Code and Claude Desktop are wired differently on purpose

I said this in step four but it's worth its own line here because it's the mistake people make twice: Code takes the native http URL, Desktop takes the mcp-remote bridge, and swapping them fails silently. If Desktop shows nothing connected, this is almost always why - not a broken gateway, just the wrong config shape fed to the wrong client. Check that before you go tearing the gateway apart.

Power-user gotchas: the deeper container traps

If you're only enabling Catalog servers you'll never meet these, so skip freely. If you're bringing your own: the containerised gateway ignores your locally-enabled server list and defaults to the remote catalog, so servers must be named in --servers, not just toggled in the UI. It runs servers with --pull never, so any image has to be built or pulled first or the server just fails. SQLite data throws disk I/O error on a Windows bind mount - use a named volume instead. A server that needs a service on your host (my local model proxy on :4000, same workstation as my local-LLM coding setup ) must reach it via http://host.docker.internal:4000, never 127.0.0.1. And the sneaky one: the gateway restricts bind mounts to /tmp, and its MCP_GATEWAY_DOCKER_BIND_ALLOWED_PATHS allow-list is colon-separated - so a Windows path like C:/MCP splits on the drive colon into C and /MCP and never matches, no matter what you put in the list. The fix is to put credential files and SQLite data in named volumes: copy the file in once, mount the volume. Doubly cruel, the host-CLI test enforces none of this, so it only bites in the real container. Full details in the repo REFERENCE.md.

And one more, because we should hold ourselves to the same standard we're setting. One of our own MCP servers - an AI-text detector we run - loaded a 1.7GB model's libraries before it answered the connection handshake. So it timed out on startup, and because one server hanging the handshake makes the gateway look sick, it made every server appear disconnected. Maddening to diagnose, obvious in hindsight. The lesson is a good one for anyone writing a server: answer the handshake instantly, and load the heavy things lazily, in the background, after you've said hello. We practise what we preach, eventually.

If you can't set up Docker tonight

Not everyone's going to sink two hours into this today, and I'm not going to pretend otherwise. So here's the stop-gap. I wrote a small tool called node-session-reaper that kills orphaned MCP node processes - the ones with no live Claude parent - while leaving your active sessions untouched. It's open-source, at github.com/houtini-ai/node-session-reaper .

Be clear about what it is, though. It's a bandage. It cleans up the mess after the fact instead of stopping the mess happening, and the moment the vendors fix the orphaning upstream you won't need it at all. The gateway is the actual cure - keys out of chat, node off bare metal, one thing to manage. The reaper just buys you a faster machine tonight while you find the evening to do it properly.

The missing middle

Houtini spends half its time getting people onto Claude and the other half telling them which MCP servers to install . The proper setup in between - secrets stored safely and out of chat, no runaway node torrent wrecking the working day - is the bit that gets skipped, and it's exactly where good intentions turn into a slow, insecure mess. That gap is the whole reason this article exists.

If that's you, and the setup here is more than you fancy taking on, this is precisely the kind of thing we do for teams: secure secrets, machines that stay fast, one managed gateway every client points at. Get in touch and we'll sort it. And if you'd rather do it yourself, everything's here - the gateway config, the reaper, all of it open. Build the gateway. Your four o'clock self will thank you.

By email

Get new posts by email.

Drop your email below and we will send you the next article when it lands. No spam, unsubscribe anytime.

More like this

Continue reading.

How to Use the Gemini API (and Why I Run It Next to Claude)
How-to Guides

How to Use the Gemini API (and Why I Run It Next to Claude)

Get a Gemini API key, make your first call in curl and Python, dodge the thinking-token trap that returns an empty answer, and see why running Gemini next to Claude is the real unlock. Written from production - and the bills.

Claude Desktop Makes a Brilliant Coding Assistant - Here's How to Set It Up
Beginner's Guides

Claude Desktop Makes a Brilliant Coding Assistant - Here's How to Set It Up

Everyone says if you want AI coding help, you use Cursor. Or Cline. Or GitHub Copilot. Basically anything that hooks into your IDE with inline autocomplete and tab-completion magic. Claude Desktop? That's for chat, not code. I respectfully…

How to Do a Technical SEO Audit with Claude
AI Tools

How to Do a Technical SEO Audit with Claude

A free, step-by-step technical SEO audit with Claude: your Search Console history and a first-party crawl merged in one local database, ranked by recoverable clicks - the Screaming Frog alternative you run by conversation.

Claude Code API Key Security: A Guide to Token Hygiene
How-to Guides

Claude Code API Key Security: A Guide to Token Hygiene

The simplest possible setup that keeps your production tokens out of AI chat windows. 1Password CLI, op run, and the conversational discipline that makes the rest of it work.

Swapping the Engine: How to Run Claude Code on Local Silicon for Zero Pennies
How-to Guides

Swapping the Engine: How to Run Claude Code on Local Silicon for Zero Pennies

Claude Code's real power isn't the Anthropic model sitting behind it, it's the agentic : the file-system access, the tool use, the way it chains tasks together without you babysitting every step. I figured this out the expensive way. I ran…

Which AI is right for your job? ChatGPT, Claude, Gemini and Copilot, task by task
AI at Work

Which AI is right for your job? ChatGPT, Claude, Gemini and Copilot, task by task

Everyone's talking about AI assistants - ChatGPT, Claude, Gemini, Copilot - and if you've a real job to get through, you mostly want to know one thing: which of them will build Monday's deck, sort the badly-exported spreadsheet, write up the meeting, and clear the inbox? Here's the answer, task by task.