Package your MCP server in a Docker container for consistent deployment. Create a Dockerfile with the correct base image and build steps, then run the container with 'docker run -i' for stdio transport or with exposed ports for HTTP transport. Configure Claude Desktop and other clients to launch Docker-based MCP servers directly.
Running MCP Servers in Docker Containers
Docker containers solve the 'works on my machine' problem for MCP servers. Instead of requiring users to install Node.js, Python, and dependencies, you package everything in a container image. Users just need Docker installed.
For stdio transport, Docker containers work seamlessly with MCP clients by running with the -i flag (interactive) to keep stdin open. For HTTP transport, you expose the server port. This tutorial covers Dockerfiles for both TypeScript and Python servers, client configuration, environment variable handling, and production deployment patterns.
Prerequisites
- Docker Desktop or Docker Engine installed and running
- A working MCP server (TypeScript or Python) ready to containerize
- Basic familiarity with Docker concepts (images, containers, Dockerfile)
- An MCP client (Claude Desktop) for testing
Step-by-step guide
Write a Dockerfile for a TypeScript MCP server
Write a Dockerfile for a TypeScript MCP server
Create a multi-stage Dockerfile that installs dependencies, compiles TypeScript, and creates a minimal production image. Use a Node.js LTS base image and copy only the compiled output and production dependencies to the final stage. This keeps the image small and secure.
1# Dockerfile for TypeScript MCP server2FROM node:22-slim AS builder34WORKDIR /app5COPY package.json package-lock.json ./6RUN npm ci7COPY tsconfig.json ./8COPY src/ ./src/9RUN npm run build1011FROM node:22-slim AS runtime12WORKDIR /app13COPY package.json package-lock.json ./14RUN npm ci --omit=dev15COPY --from=builder /app/dist ./dist1617ENTRYPOINT ["node", "dist/index.js"]Expected result: A Dockerfile that produces a small, production-ready image with only the compiled server and runtime dependencies.
Write a Dockerfile for a Python MCP server
Write a Dockerfile for a Python MCP server
For Python MCP servers, use a slim Python base image. Install dependencies from requirements.txt and copy the server file. Python does not need a build step, so the Dockerfile is simpler.
1# Dockerfile for Python MCP server2FROM python:3.12-slim34WORKDIR /app5COPY requirements.txt ./6RUN pip install --no-cache-dir -r requirements.txt7COPY server.py ./89ENTRYPOINT ["python", "server.py"]Expected result: A Dockerfile that packages the Python MCP server with all dependencies.
Build and run with stdio transport
Build and run with stdio transport
Build the Docker image, then run it with 'docker run -i' to keep stdin open for the MCP protocol. The -i flag is critical — without it, the container's stdin is closed and the stdio transport cannot receive messages. Add --rm to clean up the container when it stops.
1# Build the image2docker build -t my-mcp-server .34# Run with stdio transport (the -i flag is essential)5docker run -i --rm my-mcp-server67# With environment variables8docker run -i --rm \9 -e DATABASE_URL="postgresql://user:pass@host:5432/db" \10 -e API_KEY="sk-your-key" \11 my-mcp-server1213# With a volume for local files14docker run -i --rm \15 -v /path/to/project:/data:ro \16 my-mcp-serverExpected result: The container starts, runs the MCP server on stdio, and can communicate with MCP clients through stdin/stdout.
Configure Claude Desktop to use a Docker MCP server
Configure Claude Desktop to use a Docker MCP server
In Claude Desktop's configuration file, set the command to 'docker' and args to the run command. Claude Desktop launches the container as a child process, exactly like a local server, and communicates via stdio.
1// claude_desktop_config.json2{3 "mcpServers": {4 "my-docker-server": {5 "command": "docker",6 "args": [7 "run", "-i", "--rm",8 "-e", "API_KEY=sk-your-key",9 "my-mcp-server"10 ]11 }12 }13}1415// With volume mount for project access:16{17 "mcpServers": {18 "project-server": {19 "command": "docker",20 "args": [21 "run", "-i", "--rm",22 "-v", "/Users/me/project:/data:ro",23 "my-mcp-server"24 ]25 }26 }27}Expected result: Claude Desktop launches the Docker container on startup and communicates with it via stdio.
Expose HTTP transport from Docker
Expose HTTP transport from Docker
For Streamable HTTP transport servers, expose the port with -p and run the container in the background with -d. Configure clients to connect via the HTTP URL instead of launching the container. This is useful for shared servers that multiple users connect to. For production Docker deployments of MCP servers, RapidDev can help set up container orchestration, health checks, and auto-scaling.
1# Run HTTP transport server in background2docker run -d --rm \3 --name mcp-http-server \4 -p 3000:3000 \5 -e API_KEY="sk-your-key" \6 my-mcp-http-server78# Claude Desktop config for HTTP server9# {10# "mcpServers": {11# "remote-server": {12# "url": "http://localhost:3000/mcp"13# }14# }15# }1617# Health check18curl -s http://localhost:3000/health1920# View logs21docker logs mcp-http-server2223# Stop24docker stop mcp-http-serverExpected result: The HTTP MCP server runs in a Docker container accessible at localhost:3000.
Complete working example
1# Multi-stage Dockerfile for TypeScript MCP server2# Stage 1: Build3FROM node:22-slim AS builder4WORKDIR /app56# Install dependencies first (better layer caching)7COPY package.json package-lock.json ./8RUN npm ci910# Build TypeScript11COPY tsconfig.json ./12COPY src/ ./src/13RUN npm run build1415# Stage 2: Production runtime16FROM node:22-slim17WORKDIR /app1819# Production dependencies only20COPY package.json package-lock.json ./21RUN npm ci --omit=dev && npm cache clean --force2223# Copy compiled output24COPY --from=builder /app/dist ./dist2526# Non-root user for security27RUN addgroup --system mcp && adduser --system --ingroup mcp mcp28USER mcp2930# Health check for HTTP transport (optional)31# HEALTHCHECK --interval=30s --timeout=5s \32# CMD curl -f http://localhost:3000/health || exit 13334# Stdio transport: no EXPOSE needed35# HTTP transport: EXPOSE 30003637ENTRYPOINT ["node", "dist/index.js"]Common mistakes when containerizing an MCP server with Docker
Why it's a problem: Running docker run without -i for stdio servers
How to avoid: The -i flag keeps stdin open. Without it, the container's stdin is immediately closed and the MCP server cannot receive messages.
Why it's a problem: Using -it instead of just -i
How to avoid: The -t flag allocates a TTY which can corrupt the binary MCP protocol. Use only -i for MCP stdio servers.
Why it's a problem: Hardcoding secrets in the Dockerfile
How to avoid: Pass secrets as environment variables with -e at runtime, never bake them into the image with ENV in the Dockerfile.
Why it's a problem: Not using multi-stage builds
How to avoid: Single-stage builds include TypeScript compiler, dev dependencies, and source code in the final image. Use multi-stage builds to keep images small.
Why it's a problem: Running as root inside the container
How to avoid: Add a non-root user (RUN adduser) and switch to it (USER mcp) for security.
Best practices
- Use multi-stage Docker builds to keep images small (builder stage + runtime stage)
- Run containers as a non-root user for security
- Pass secrets via -e at runtime, never hardcode in Dockerfile
- Use -i (not -it) for stdio transport to avoid TTY corruption
- Pin base image versions (node:22-slim, python:3.12-slim) for reproducible builds
- Add .dockerignore for node_modules, .git, and other unnecessary files
- Add a health check endpoint for HTTP transport containers
- Use Docker Compose for servers that need databases or other services
Still stuck?
Copy one of these prompts to get a personalized, step-by-step explanation.
I have a TypeScript MCP server that uses stdio transport. Write a multi-stage Dockerfile to containerize it, and show me how to configure Claude Desktop to launch it with docker run -i. Include environment variable handling.
Create a Dockerfile for my MCP server at [path]. It uses [TypeScript/Python] and [stdio/HTTP] transport. Include multi-stage build, non-root user, and the correct docker run command for connecting with Claude Desktop.
Frequently asked questions
Why do I need -i when running Docker MCP servers?
The -i flag keeps the container's stdin open. MCP stdio transport reads JSON-RPC messages from stdin. Without -i, stdin is closed immediately and the server receives no input.
Can I use Docker Compose for MCP servers?
Yes. Docker Compose is useful when your MCP server needs a database or other services. Define the server, database, and network in docker-compose.yml. However, stdio transport works best with standalone docker run.
How do I update a Docker MCP server?
Rebuild the image (docker build -t my-mcp-server .), stop the old container, and restart. For stdio servers in Claude Desktop, restart Claude Desktop to pick up the new image.
Can I publish my MCP server image to Docker Hub?
Yes. Tag and push: docker tag my-mcp-server username/my-mcp-server:latest && docker push username/my-mcp-server:latest. Users can then reference your image directly in their Claude Desktop config.
How do I debug a Docker MCP server?
Check container logs: docker logs container-name. For stdio servers, add console.error() logging (which goes to Docker logs, not the MCP protocol). You can also run the server locally without Docker for debugging.
What about network access from Docker containers?
By default, Docker containers can access the internet but not the host network. Use --network host (Linux) or host.docker.internal (Mac/Windows) to access host services like local databases. For complex networking setups with MCP servers, the RapidDev team can help configure the right Docker network topology.
Talk to an Expert
Our team has built 600+ apps. Get personalized help with your project.
Book a free consultation