Skip to main content
RapidDev - Software Development Agency
mcp-tutorial

How to containerize an MCP server with Docker

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.

What you'll learn

  • How to write a Dockerfile for TypeScript and Python MCP servers
  • How to run Docker MCP servers with stdio transport using docker run -i
  • How to configure Claude Desktop to launch Docker containers
  • How to expose HTTP transport MCP servers from Docker
  • How to pass environment variables and secrets to containerized servers
Book a free consultation
4.9Clutch rating
600+Happy partners
17+Countries served
190+Team members
Intermediate6 min read20-30 minDocker 20+, MCP TypeScript SDK 1.x, MCP Python SDK 1.xMarch 2026RapidDev Engineering Team
TL;DR

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

1

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.

typescript
1# Dockerfile for TypeScript MCP server
2FROM node:22-slim AS builder
3
4WORKDIR /app
5COPY package.json package-lock.json ./
6RUN npm ci
7COPY tsconfig.json ./
8COPY src/ ./src/
9RUN npm run build
10
11FROM node:22-slim AS runtime
12WORKDIR /app
13COPY package.json package-lock.json ./
14RUN npm ci --omit=dev
15COPY --from=builder /app/dist ./dist
16
17ENTRYPOINT ["node", "dist/index.js"]

Expected result: A Dockerfile that produces a small, production-ready image with only the compiled server and runtime dependencies.

2

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.

typescript
1# Dockerfile for Python MCP server
2FROM python:3.12-slim
3
4WORKDIR /app
5COPY requirements.txt ./
6RUN pip install --no-cache-dir -r requirements.txt
7COPY server.py ./
8
9ENTRYPOINT ["python", "server.py"]

Expected result: A Dockerfile that packages the Python MCP server with all dependencies.

3

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.

typescript
1# Build the image
2docker build -t my-mcp-server .
3
4# Run with stdio transport (the -i flag is essential)
5docker run -i --rm my-mcp-server
6
7# With environment variables
8docker run -i --rm \
9 -e DATABASE_URL="postgresql://user:pass@host:5432/db" \
10 -e API_KEY="sk-your-key" \
11 my-mcp-server
12
13# With a volume for local files
14docker run -i --rm \
15 -v /path/to/project:/data:ro \
16 my-mcp-server

Expected result: The container starts, runs the MCP server on stdio, and can communicate with MCP clients through stdin/stdout.

4

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.

typescript
1// claude_desktop_config.json
2{
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}
14
15// 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.

5

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.

typescript
1# Run HTTP transport server in background
2docker run -d --rm \
3 --name mcp-http-server \
4 -p 3000:3000 \
5 -e API_KEY="sk-your-key" \
6 my-mcp-http-server
7
8# Claude Desktop config for HTTP server
9# {
10# "mcpServers": {
11# "remote-server": {
12# "url": "http://localhost:3000/mcp"
13# }
14# }
15# }
16
17# Health check
18curl -s http://localhost:3000/health
19
20# View logs
21docker logs mcp-http-server
22
23# Stop
24docker stop mcp-http-server

Expected result: The HTTP MCP server runs in a Docker container accessible at localhost:3000.

Complete working example

Dockerfile
1# Multi-stage Dockerfile for TypeScript MCP server
2# Stage 1: Build
3FROM node:22-slim AS builder
4WORKDIR /app
5
6# Install dependencies first (better layer caching)
7COPY package.json package-lock.json ./
8RUN npm ci
9
10# Build TypeScript
11COPY tsconfig.json ./
12COPY src/ ./src/
13RUN npm run build
14
15# Stage 2: Production runtime
16FROM node:22-slim
17WORKDIR /app
18
19# Production dependencies only
20COPY package.json package-lock.json ./
21RUN npm ci --omit=dev && npm cache clean --force
22
23# Copy compiled output
24COPY --from=builder /app/dist ./dist
25
26# Non-root user for security
27RUN addgroup --system mcp && adduser --system --ingroup mcp mcp
28USER mcp
29
30# Health check for HTTP transport (optional)
31# HEALTHCHECK --interval=30s --timeout=5s \
32# CMD curl -f http://localhost:3000/health || exit 1
33
34# Stdio transport: no EXPOSE needed
35# HTTP transport: EXPOSE 3000
36
37ENTRYPOINT ["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.

ChatGPT Prompt

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.

MCP Prompt

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.

RapidDev

Talk to an Expert

Our team has built 600+ apps. Get personalized help with your project.

Book a free consultation

Need help with your project?

Our experts have built 600+ apps and can accelerate your development. Book a free consultation — no strings attached.

Book a free consultation

We put the rapid in RapidDev

Need a dedicated strategic tech and growth partner? Discover what RapidDev can do for your business! Book a call with our team to schedule a free, no-obligation consultation. We'll discuss your project and provide a custom quote at no cost.