Running an MCP Server in Docker: A Practical Guide
You can run open-context's MCP server in Docker with one command:
docker run -i --rm -v opencontext-data:/root/.opencontext adityakarnam/open-context:latest node dist/mcp/index.js.
That single image also serves the web UI and REST API — the difference is just which command you pass at
the end, and a named volume that keeps your saved context across restarts and upgrades.
Key takeaways
- open-context ships one Docker image (built on
node:25-slim) that bundles the React UI, REST API, and MCP server together. - The default
CMDstarts the HTTP server on port 3000; override it withnode dist/mcp/index.jsto run the same image in MCP stdio mode instead. - Mount a named volume at
/root/.opencontext— that's wherecontexts.json, preferences, and generated memory docs live, so they survive image upgrades. - On Linux,
host.docker.internalisn't resolved automatically — add--add-host=host.docker.internal:host-gatewayif the container needs to reach a host-side Ollama instance. - Connecting Claude Desktop or Claude Code just means pointing their
mcpServersconfig at adocker runcommand — no local Node.js install required.
Why run an MCP server in a container at all
MCP
servers are just local processes that speak a specific protocol over stdio or HTTP, so nothing forces you
to containerize one. But Docker has been standardizing around MCP distribution — Docker's own
MCP Catalog and Toolkit
now ships a registry of MCP servers as container images under the mcp/ namespace on Docker
Hub specifically so they can be pulled and run without a local language runtime. The same logic applies to
open-context's MCP server: a container gives you a pinned Node.js
version, no global npm install, and a clean way to isolate the process's filesystem access to
one mounted volume.
How open-context packages its MCP server
The official image
(adityakarnam/open-context:latest) is built in three stages: the React UI is compiled first,
the TypeScript CLI/server/MCP code is compiled second, and both outputs are copied into a slim production
stage with only production dependencies installed. The result is one image that can run three different
ways depending on the command you pass it:
node dist/server.js(the default) — starts the Express HTTP server: web UI + REST API on port 3000.node dist/mcp/index.js— starts the MCP server over stdio, for Claude Desktop or Claude Code to launch directly.
Both modes read and write the same /root/.opencontext/ directory, which is why mounting a
persistent volume matters even if you only ever use MCP stdio mode.
Step-by-step: run the MCP server in Docker
1. Pull the image
No clone or build required — the image is published to Docker Hub:
docker pull adityakarnam/open-context:latest
2. Run it in MCP stdio mode
Override the default command and add -i so Docker keeps stdin open for the stdio transport:
docker run -i --rm \
-v opencontext-data:/root/.opencontext \
adityakarnam/open-context:latest \
node dist/mcp/index.js
--rm is safe here because nothing important lives in the container itself — all state is in
the opencontext-data volume, which Docker keeps around after the container exits.
3. Confirm what's in the volume
The mounted volume is the only place open-context writes to:
File in /root/.opencontext/ | Contents |
|---|---|
contexts.json | MCP context entries Claude has saved via save_context |
preferences.json | Structured preferences from the web UI form |
preferences.md | Generated Claude preferences doc |
memory.md | Generated Claude memory doc |
Connect Claude Desktop or Claude Code
Both clients read an mcpServers block from a config file and launch the command you give
them — pointing that command at docker run means Claude starts and stops the container for
you on each session. Add this to ~/.claude/settings.json (Claude Code) or
claude_desktop_config.json (Claude Desktop, under ~/Library/Application Support/Claude/
on macOS):
{
"mcpServers": {
"open-context": {
"command": "docker",
"args": ["run", "-i", "--rm", "-v", "opencontext-data:/root/.opencontext",
"adityakarnam/open-context:latest", "node", "dist/mcp/index.js"]
}
}
}
Restart the client after saving. Claude now has save_context, recall_context,
list_contexts, search_contexts, update_context, and
delete_context tools backed by the same volume every time it starts the container — see
how MCP persistent memory works for what
each tool does in practice.
Environment variables worth setting
| Variable | Default | When to change it |
|---|---|---|
OPENCONTEXT_STORE_PATH | /root/.opencontext/contexts.json | Rarely — only if you're mounting the volume somewhere other than the default path |
OLLAMA_HOST | http://host.docker.internal:11434 | If Ollama runs on a different machine, e.g. a GPU box on your LAN |
PORT | 3000 | Only relevant in HTTP server mode, not MCP stdio mode |
Common Docker + MCP problems
Container can't reach Ollama on the host. host.docker.internal resolves
automatically on Docker Desktop for Mac and Windows, but not on Linux. Add
--add-host=host.docker.internal:host-gateway to your docker run command, or set
OLLAMA_HOST directly to your host's LAN IP.
Container exits immediately with ERR_MODULE_NOT_FOUND. This was caused by
missing .js extensions in older ESM builds. Pull the latest tag and it's resolved:
docker pull adityakarnam/open-context:latest.
Claude doesn't see the MCP tools after editing the config. Both Claude Desktop and Claude
Code only read mcpServers config on startup — fully quit and reopen the client rather than
just closing the window.
Give Claude persistent memory with one Docker command.
Get started with open-context →FAQ
Do I need to build the Docker image myself?
No. adityakarnam/open-context:latest is published on Docker Hub, so docker pull adityakarnam/open-context:latest gets you a working image without cloning the repo or running a build.
How do I switch the same image between the web UI and MCP stdio mode?
The image's default command runs the HTTP server (node dist/server.js) on port 3000. Override the command with node dist/mcp/index.js and pass -i to docker run to get MCP stdio mode instead — no separate image is needed.
Why can't my containerized MCP server reach Ollama on the host?
On Docker Desktop (Mac/Windows), host.docker.internal resolves automatically. On Linux it doesn't by default — add --add-host=host.docker.internal:host-gateway to your docker run command, or set OLLAMA_HOST to your host machine's LAN IP.
Will I lose my saved contexts if I update the image?
No, as long as you mount a named volume at /root/.opencontext. The contexts.json file, preferences, and generated memory docs live in that volume, not in the container's writable layer, so pulling a new image tag and restarting the container preserves them.
Does the MCP server need internet access to run in Docker?
No. The MCP server itself only reads and writes its local JSON store. The container makes no outbound calls unless you separately enable Ollama-based preference analysis, which is optional and stays on your own network.