Skip to main content

Docker Setup

This guide covers deploying MCPHub using Docker, including development and production configurations.

Quick Start with Docker

Using Pre-built Image

The standard MCPHub image includes Node.js/pnpm, Python, uv/uvx, Git, and build tools. Use the latest-full image if you need Cargo and the stable Rust toolchain for Rust-based MCP servers.
The image defaults to /app/data/mcp_settings.json. Mount the entire data directory to persist settings, users, credential bindings, and their encryption key. For custom servers, create data/mcp_settings.json before the first launch, or edit the existing file afterward. With detached mode (-d), run docker logs mcphub to see the generated initial admin password. Existing file mounts at /app/mcp_settings.json are detected automatically and take precedence over the data directory, so existing Docker commands keep working. An explicit MCPHUB_SETTING_PATH always takes precedence over both locations. For full persistence, migrate the settings file and its existing .credentials.json and .credentials.key sidecars together into the mounted data directory. Explicit MCPHUB_SETTING_PATH values remain supported.

Building from Source

Building with Extended Features

The Docker image supports an INSTALL_EXT build argument to include additional tools. Published -full images are built with this enabled:
What’s included with INSTALL_EXT=true:
  • Stable Rust + Cargo: Installed through rustup for Rust-based MCP servers.
  • Docker Engine: Full Docker daemon with CLI for container management. The daemon auto-starts when the container runs in privileged mode.
  • Chrome + Firefox for Playwright (amd64 only): For browser automation tasks
The extended image is larger but provides additional capabilities for advanced use cases.
Docker-in-Docker Security Considerations:
  • Privileged mode (--privileged): Required for the Docker daemon to start inside the container. This gives the container elevated permissions on the host.
  • Docker socket mounting (/var/run/docker.sock): Gives the container access to the host’s Docker daemon. Both approaches should only be used in trusted environments.
  • For production, consider using Docker socket mounting instead of privileged mode for better security.

Docker Compose Setup

Basic Configuration

Create a docker-compose.yml file:

Persisting the stdio package cache

stdio servers download and install their package on first use: npx through npm’s caches, uvx and other uv-based commands through uv’s. The image sets no USER, so it runs as root and those installs land in: Environments created by uv tool install live in ~/.local/share/uv/tools instead. Those are baked into the image layer and are not affected by any of this. If you run the image as a non-root user, ~ is not /root: adjust both the paths above and the mount targets below to that user’s home directory. None of these are volumes by default, so they live in the container’s writable layer. That layer is discarded whenever the container is recreated — an image bump, docker compose down && up -d, or any change to a service’s configuration. The next start then reinstalls every stdio server from scratch, in parallel, before any of them can complete their MCP handshake.
docker restart keeps the writable layer, so it does not lose the cache. docker compose down followed by up -d removes and recreates the container, which does. If restarting appears to reinstall everything, this distinction is usually why.
Mounting the two paths keeps the installs on the host:
Named volumes survive docker compose down; only down -v removes them. A bind mount (./data/npm-cache:/root/.npm) also works and is easier to inspect or clear by hand, but it is not initialised from the image the way a named volume is — it hides whatever the image already holds at that path. On amd64 the -full image bakes a Playwright install into /root/.npm/_npx, so a bind mount there costs that download once. A few things worth knowing:
  • Version pinning is not required. An unpinned npx -y <package> reuses the package already installed in the cache instead of reinstalling it. npm still revalidates the package metadata against the registry on each run, so the registry (or your proxy) must stay reachable — the cache removes the install cost, not the network round trip.
  • The first start after adding the volumes is still cold. The volume begins empty, so that run pays the full install cost once; every recreate afterwards reuses it.
  • Budget for the size. Around 90 stdio servers occupy roughly 2 GB.
  • To recover from a corrupted install, use a server’s Reinstall action — available for npx and uvx servers only. For uvx it refreshes just that package, through a --refresh flag on the next spawn. For npx it deletes the whole shared /root/.npm/_npx directory, so every npx server reinstalls on its next start, not only the one you targeted. The _cacache volume is what keeps that reinstall cheap.
This matters most at scale. On a deployment with ~95 stdio servers, a cold recreate left 19 of them connected and 74 failing their connect with MCP error -32001: Request timed out, because every server was competing to install at once. The same restart with the cache mounted reached 73 connected and no timeouts — though that run also raised INIT_TIMEOUT from its 300000 default to 900000, so the two changes have to be read apart. Against the per-minute connect curve they split roughly as: the cache is worth about +45 servers inside the original 300-second budget, the longer timeout about +9. The dominant cost was reinstalling packages, not waiting.

Production Configuration with Nginx

Environment Variables

Create a .env file for Docker Compose:

Development Setup

Development Docker Compose

Create docker-compose.dev.yml:

Development Dockerfile

Create Dockerfile.dev:

Running the Application

Development Mode

Production Mode

Configuration Management

MCP Settings Volume Mount

Before the first launch, create data/mcp_settings.json:

Secrets Management

For production, use Docker secrets:

Data Persistence

Database Backups

Add backup service to your docker-compose.yml:
Create scripts/backup.sh:
Run backup:

Monitoring and Health Checks

Health Check Endpoint

Add to your application:

Docker Health Checks

Monitoring with Watchtower

Add automatic updates:

Troubleshooting

Common Issues

Container fails to start: Check logs with docker-compose logs mcphub Rate-limit warning about X-Forwarded-For: Set TRUST_PROXY=1 when MCPHub is behind Nginx, Traefik, a load balancer, or another reverse proxy. Recent versions also default to trusting one proxy automatically inside Docker/Kubernetes, but keeping it explicit in production compose files is recommended. Database connection errors: Ensure PostgreSQL is healthy and accessible Port conflicts: Check if ports 3000/5432 are already in use Volume mount issues: Verify file paths and permissions

Debug Commands

Performance Optimization

This Docker setup provides a complete containerized environment for MCPHub with development and production configurations.