Dockerize Your MCP Server

Containerize your MCP server with Docker for consistent deployment, easy scaling, and production-ready packaging.


title: "Dockerize Your MCP Server" description: "Containerize your MCP server with Docker for consistent deployment, easy scaling, and production-ready packaging." order: 11 level: "advanced" duration: "20 min" keywords:

  • MCP Docker
  • Dockerize MCP server
  • MCP container
  • MCP Docker deployment
  • MCP Dockerfile date: "2026-04-01"

Quick Summary

Containerize your MCP server with Docker for consistent deployments. Learn to write optimized Dockerfiles with multi-stage builds, set up docker-compose for development and production, and configure containers for both stdio and SSE transports.

Why Docker for MCP Servers?

Docker provides reproducible builds, consistent environments, and simplified deployment. For MCP servers, Docker is especially useful for:

  • SSE transport servers that need to run as long-lived services
  • Servers with system dependencies (databases, native modules)
  • Team collaboration where everyone needs the same environment
  • Cloud deployments where containers are the standard unit
Multi-Stage Docker Build

A Docker build technique that uses multiple FROM statements. The first stage compiles/builds the application, and the final stage copies only the built artifacts. This produces much smaller production images.

Dockerfile for a TypeScript MCP Server

Basic Dockerfile

Create Dockerfile in your project root:

FROM node:20-alpine AS builder

WORKDIR /app

# Install dependencies first (better cache utilization)
COPY package*.json ./
RUN npm ci

# Copy source and build
COPY tsconfig.json ./
COPY src/ ./src/
RUN npm run build

# Production stage
FROM node:20-alpine AS production

WORKDIR /app

# Copy only production dependencies and built files
COPY package*.json ./
RUN npm ci --omit=dev

COPY --from=builder /app/dist ./dist

# Non-root user for security
RUN addgroup -S mcp && adduser -S mcp -G mcp
USER mcp

# Default to stdio transport
CMD ["node", "dist/index.js"]
Use Multi-Stage Builds

Always use multi-stage Docker builds for TypeScript MCP servers. The builder stage includes TypeScript, dev dependencies, and source code. The production stage only has the compiled JavaScript and runtime dependencies -- typically 70-80% smaller.

Optimized .dockerignore

Create .dockerignore:

node_modules
dist
.git
.env
*.md
.vscode
.DS_Store

Docker Compose for Development

Create docker-compose.yml:

services:
  mcp-server:
    build:
      context: .
      dockerfile: Dockerfile
    ports:
      - "3001:3001"
    environment:
      - PORT=3001
      - NODE_ENV=production
    env_file:
      - .env
    restart: unless-stopped
    healthcheck:
      test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:3001/health"]
      interval: 30s
      timeout: 10s
      retries: 3
      start_period: 10s

With a Database

For servers that need a database (like the database tutorial):

services:
  mcp-server:
    build: .
    ports:
      - "3001:3001"
    environment:
      - PORT=3001
      - DATABASE_URL=postgresql://mcp:mcppass@postgres:5432/mcpdb
    depends_on:
      postgres:
        condition: service_healthy
    restart: unless-stopped

  postgres:
    image: postgres:16-alpine
    environment:
      - POSTGRES_USER=mcp
      - POSTGRES_PASSWORD=mcppass
      - POSTGRES_DB=mcpdb
    volumes:
      - pgdata:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U mcp"]
      interval: 10s
      timeout: 5s
      retries: 5

volumes:
  pgdata:

SSE Server Dockerfile

For SSE transport servers that need Express:

FROM node:20-alpine AS builder

WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY tsconfig.json ./
COPY src/ ./src/
RUN npm run build

FROM node:20-alpine

WORKDIR /app
COPY package*.json ./
RUN npm ci --omit=dev

COPY --from=builder /app/dist ./dist

RUN addgroup -S mcp && adduser -S mcp -G mcp
USER mcp

EXPOSE 3001
ENV PORT=3001

HEALTHCHECK --interval=30s --timeout=10s --retries=3 \
  CMD wget --no-verbose --tries=1 --spider http://localhost:3001/health || exit 1

CMD ["node", "dist/index.js"]
Health Checks

Always add health checks to SSE server containers. This lets Docker (and container orchestrators like Kubernetes) know if your server is responsive and automatically restart it if it crashes.

Building and Running

1

Build the Docker image

docker build -t my-mcp-server .
2

Run with stdio transport

docker run --rm -i my-mcp-server

The -i flag is essential for stdio transport to work with Docker.

3

Run with SSE transport

docker run -d -p 3001:3001 --name mcp-server \
  -e PORT=3001 \
  -e API_KEY=your-key \
  my-mcp-server
4

Run with docker-compose

docker compose up -d
docker compose logs -f mcp-server

Claude Desktop with Docker

stdio transport via Docker

{
  "mcpServers": {
    "docker-server": {
      "command": "docker",
      "args": ["run", "--rm", "-i", "my-mcp-server"]
    }
  }
}

SSE transport via Docker

{
  "mcpServers": {
    "docker-sse-server": {
      "url": "http://localhost:3001/sse",
      "transport": "sse"
    }
  }
}
Run as Non-Root

Always create a non-root user in your Docker image and switch to it with the USER directive. This limits the damage if the container is compromised. The node user in Node.js base images works, or create a custom user as shown above.

Environment Variables and Secrets

Never bake secrets into your Docker image. Use environment variables:

# Via command line
docker run -e GITHUB_TOKEN=ghp_xxx my-mcp-server

# Via env file
docker run --env-file .env my-mcp-server

# Via docker-compose
docker compose --env-file .env.production up -d
Never Put Secrets in Dockerfiles

Do not use ENV or ARG to embed API keys in your Dockerfile. They end up in the image layers and can be extracted. Always pass secrets at runtime via environment variables.

Image Size Comparison

ApproachImage SizeBuild Time
Single-stage (node:20)~1.2 GBFast
Single-stage (node:20-alpine)~400 MBFast
Multi-stage (node:20-alpine)~120 MBModerate
Multi-stage + pruning~80 MBSlower

Frequently Asked Questions