MCP server with Docker: the compose file and update script
Running an MCP server with Docker is how MCP Self-hosted is delivered: one container, one image from the AddoCore registry, and a docker-compose.yml generated for your license. This article goes through that file line by line, explains why you start and update it with ./mcp-update.sh instead of calling Docker Compose yourself, and what the port, the volumes and the memory limit mean for your host.
Why run an MCP server with Docker?
Because the host then needs only Docker with Docker Compose. Node.js and every tool run inside the image. That image is the same for every customer: it contains all tools, and your license decides at runtime which of them load. What makes your MCP server with Docker yours is the configuration next to it, mainly the .env file.
That split keeps updates of an MCP server with Docker simple. A new MCP Self-hosted version is a new image. Your settings, the license state and any Brain notes live in folders on the host, so swapping the image leaves them alone.
What does the generated docker-compose.yml contain?
It contains one service, mcp-core, and nothing else. The download from My Account ships this file next to .env, .env.registry and mcp-update.sh:
services:
mcp-core:
image: <AddoCore registry path>:${MCP_VERSION}
container_name: mcp-core
restart: unless-stopped
env_file:
- .env
volumes:
- ./vault:/vault
- ./state:/app/state
- ./logs:/app/logs
ports:
- "127.0.0.1:3200:3200"
deploy:
resources:
limits:
memory: 128M
Line by line, this is what each setting does for your MCP server with Docker:
image: the tag comes fromMCP_VERSION. Docker Compose reads the.envin the same folder to fill in${MCP_VERSION}, so the version is set in one place.container_name: mcp-core: a fixed name, sodocker logs mcp-coreanddocker exec mcp-core npm run refresh-licensework on every install.restart: unless-stopped: Docker starts the container again after a crash or a host reboot, unless you stopped it yourself.env_file: the same.envis passed into the container. It holds the license key, the license endpoint, the auth strategy, your first seat’s token and the variables for each tool on your license.volumes,portsanddeploy: covered below.
There is no second service. No database container, no proxy, and no auto-updater: the image only changes when you run the update script.
What do the three volumes of the MCP server with Docker hold?
They hold everything that has to survive an update. The zip already contains the three empty folders, so the mounts work on the first start.
./vault:/vault: the markdown knowledge base of the Brain add-on. It stays empty if you do not use Brain../state:/app/state: the license cache (.license-cache.json), the instance id (.instance-id) and, from version 1.2.0,.picker-secretfor seats made through the seat picker. From 1.2.0 the cache is signed by AddoCore and holds no seat tokens; do not edit these files../logs:/app/logs: the server logs. They stay on your host.
Treat state/ with care. The instance id in it is how AddoCore recognises this installation as your activation, and MCP Self-hosted includes one. Delete the folder and the next start generates a new id, which counts as a new installation. You would then have to release the old one on the Advanced tab of your license. The vault sits in a separate folder for this reason: you can wipe or move your notes without touching the activation.
Why is the MCP server with Docker bound to 127.0.0.1:3200?
So that only the host itself can reach the server. The mapping 127.0.0.1:3200:3200 publishes the container port on the loopback address only. On the host, the MCP endpoint is http://localhost:3200/mcp and the health check is http://localhost:3200/health. The Claude Code plugin in the download is already set to that address.
To reach the server from other machines, put a reverse proxy with HTTPS in front of it. If port 3200 is already taken on the host, change the left number of the mapping, for example 127.0.0.1:3300:3200, and use the new port in your Claude configuration. The container side stays 3200.
What does the 128 MB memory limit do for an MCP server with Docker?
It caps the container at 128 MB of memory through deploy.resources.limits. The server cannot take more than that from the host, so it runs next to other services without competing for memory. If the process ever hits the limit, Docker stops it, and the unless-stopped restart policy starts it again.
Why do you never run docker compose directly?
Because the image sits in a private registry, and pulling it for your MCP server with Docker needs a login first. ./mcp-update.sh does the login, the pull, a version fallback and the start in one command:
- It reads
.env.registryand logs in toregistry.gitlab.comwith--password-stdin, so the registry token never appears in your shell history. - It pulls the image for the version in
MCP_VERSION. - If that version does not exist in the registry, because of a typo or an old version that was cleaned up, it sets
MCP_VERSION=latestin.envand pulls again. Any other pull error is shown as it is, and the script stops. - It starts the container in the background.
cd /path/to/unzipped-bundle
chmod +x mcp-update.sh
./mcp-update.sh
Run it from the bundle folder; it reads ./.env.registry from there. The registry login is renewed from time to time. When that happens you get an email with a fresh .env.registry attached: replace the old file and run the script once to confirm the login. The running server keeps working during that change.
How do you update an MCP server with Docker?
You run the same script again. It pulls whatever MCP_VERSION points to and restarts the container. Your .env, vault, state and logs are on the host and are not touched. By default MCP_VERSION is pinned to the version in your bundle, so a plain rerun gives you that same version back. Two other options:
# always the newest release
MCP_VERSION=latest
# pin to a specific version, or roll back
MCP_VERSION=<version number>
Buying an add-on later changes nothing on disk. The tool is already in the image; the add-on only adds an entitlement. Enter the tool’s variables and run docker exec mcp-core npm run refresh-license, or wait for the next license check.
How do you check that your MCP server with Docker runs?
Read the logs or call the health endpoint. In docker logs -f mcp-core, look for the HTTP/MCP: http://localhost:3200/mcp line and a line starting with Check OK with valid=true and your entitlements. curl http://localhost:3200/health returns the status, the transports, the auth strategy, the license state and the active tools as JSON. The full sequence is in Install MCP Self-hosted.
An MCP server with Docker comes down to one service, three folders you keep, a port that only the host can reach and one script for installs and updates. If you would rather not run the container yourself, self-hosted vs hosted MCP compares the options. Otherwise, get MCP Self-hosted, run ./mcp-update.sh, and connect Claude to the server.