---
title: Run MCP Server in Docker
description: "Containerize your .NET stdio MCP server with Docker—Dockerfile setup, build and run commands, and end-to-end testing with Claude Desktop and MCP clients."
image: "https://wavepillars.com/learn/mcp-og.webp"
url: "https://wavepillars.com/learn/ai/run-mcp-server-in-docker/"
---

**Docker is deployment, not transport.** Containers package the same MCP server you run on the host; the client still speaks **stdio** over the container’s stdin and stdout. This guide takes the **Echo stdio server** from [Building MCP Server Using .NET](/learn/ai/build-mcp-server-stdio-dotnet/), adds a production-ready Dockerfile, and shows how to build, run, and test it with Claude Desktop.

For remote HTTP-based MCP (Streamable HTTP or legacy SSE), containerize a web API entrypoint and expose a port—see [Build MCP Server with Streamable HTTP](/learn/ai/build-mcp-server-streamable-http-dotnet/) (recommended) or [Build MCP Server with SSE Transport](/learn/ai/build-mcp-server-sse-dotnet/) (legacy).

## Key Takeaways

- Claude Desktop can launch MCP servers via docker run with stdio attached.
- Use -i, pass secrets with env vars, and keep stdout reserved for MCP protocol traffic.
- Pin image versions and keep containers ephemeral with --rm where appropriate.
- HTTP MCP runs as a long-lived container; stdio MCP runs as a spawned process.
- Prebuilt images and patterns live on mcpsolutions.dev for common integrations.

## Prerequisites

- Completed [Building MCP Server Using .NET](/learn/ai/build-mcp-server-stdio-dotnet/) — project `MyMcpServer`, `WithStdioTransport()`, and the `Echo` tool in `MyTools`
- [Docker Desktop](https://www.docker.com/products/docker-desktop/) or Docker Engine installed and running
- Optional: [What is the MCP Protocol?](/learn/ai/what-is-mcp-server/) — stdio transport and how clients spawn servers

## Project recap

You should already have a working stdio server from the .NET tutorial. The pieces that matter for Docker:

### Console app project

Stdio MCP servers run as a **console app**, not a web API. Create the project with:

```bash
dotnet new console -n MyMcpServer
cd MyMcpServer
```

If you started from `dotnet new webapi`, switch to a console template or a new `console` project—stdio transport does not need Kestrel or HTTP listeners.

### Required NuGet packages

```bash
dotnet add package Microsoft.Extensions.Hosting
dotnet add package ModelContextProtocol
```

Restore locally to confirm the project compiles: `dotnet restore && dotnet build`.

- **Transport:** `WithStdioTransport()` — no HTTP port, no `MapMcp()` required for stdio-only
- **Tools:** `MyTools` with an `Echo` tool the client can call
- **Logging:** write diagnostics to **stderr only**. **stdout** carries the JSON-RPC protocol stream; anything else on stdout (including `Console.WriteLine`) breaks MCP inside and outside Docker

In `Program.cs`:

```csharp
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;

var builder = Host.CreateEmptyApplicationBuilder(settings: null);

builder.Services
    .AddMcpServer()
    .WithStdioServerTransport()
    .WithToolsFromAssembly();

var app = builder.Build();

await app.RunAsync();
```

From the project root (`MyMcpServer/`), confirm the server runs locally before containerizing:

```bash
dotnet run
```

Stop it with Ctrl+C once verified. The MCP client (or Claude) will launch the process and attach to stdio—you do not browse to a URL.

## Write the Dockerfile

Place a `Dockerfile` in the project root (same folder as `MyMcpServer.csproj`). This multi-stage build caches NuGet restores and produces a small runtime image. There is **no `EXPOSE`** — stdio servers do not listen on a port.

```dockerfile
# Use the official .NET Core SDK image as the base image
FROM mcr.microsoft.com/dotnet/sdk:10.0 AS build

# Copy project files
COPY . ./app

# Publish app to out folder
WORKDIR /app
RUN dotnet publish -c Release -o out

# Build the runtime image
FROM mcr.microsoft.com/dotnet/runtime:10.0-alpine AS runtime
WORKDIR /app
COPY --from=build /app/out .

# Entry point when the container starts
ENTRYPOINT ["dotnet", "stdio-mcp-dotnet-docker.dll"]
```

If your project file has a different name, replace `MyMcpServer.csproj` and `MyMcpServer.dll` accordingly.

## Build the image

From the directory that contains the `Dockerfile`:

```bash
docker build -t my-mcp-server:latest .
```

## Run and test (without Claude)

Stdio MCP requires an **interactive** container so the client can write JSON-RPC to stdin and read responses from stdout.

```bash
docker run -i --rm my-mcp-server:latest
```

**Manual JSON-RPC test:** with the container running, paste this line into the terminal and press Enter:

```text
{"jsonrpc":"2.0","id":1,"method":"tools/list","params":{}}
```

You should see a JSON response on stdout listing available tools (for example `Echo`). If nothing appears, the server may require an `initialize` request first—see the [MCP specification](https://modelcontextprotocol.io/). Use stderr for any debug output so stdout stays valid JSON-RPC.

| Flag | Role |
|------|------|
| `-i` | Keep stdin open — required for JSON-RPC |
| `--rm` | Remove the container when the client exits |

**Smoke test:** the container should start and **wait** (no immediate exit, no crash loop). That means the process is listening on stdio for MCP messages.

**Common failures:**

- **Missing `-i`** — client cannot send requests; connection fails silently or immediately
- **Wrong image tag** — `docker images` and align the tag with your config
- **Logs on stdout** — use stderr for logging; polluted stdout corrupts the protocol
- **Image not rebuilt** — old tool definitions after code changes

## Test with Claude Desktop

Claude Desktop spawns the MCP server as a subprocess. For Docker, the subprocess is `docker run` with stdio attached to the container.

### Prerequisites

- [Claude Desktop](https://claude.ai/download) installed
- Docker running
- Image built: `my-mcp-server:latest`

### Configure Claude Desktop

Edit `claude_desktop_config.json`:

- macOS: `~/Library/Application Support/Claude/claude_desktop_config.json`
- Windows: `%APPDATA%\Claude\claude_desktop_config.json`

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

The `-i` flag in `args` is mandatory — without it, JSON-RPC cannot reach the server.

### Step-by-step

1. Build the image: `docker build -t my-mcp-server:latest .`
2. Add the config above to `claude_desktop_config.json`
3. **Fully quit** Claude Desktop and relaunch (config is read at startup)
4. Open Claude and check the MCP/tools indicator (hammer icon) — `my-mcp-server` should appear as connected
5. Send a test prompt, for example: *"Use the echo tool to repeat the phrase hello docker"*
6. Confirm Claude calls `Echo` and returns the echoed text

### Verify and troubleshoot

- **Claude logs:** Help → View Logs (or Developer settings) — look for Docker or MCP connection errors
- **Docker not running:** start Docker Desktop before launching Claude
- **Image missing:** run `docker images` and confirm `my-mcp-server` is listed
- **Stale config:** quit Claude completely (not just close the window), then relaunch
- **Forgot `-i`:** connection fails; ensure `"args"` includes `-i` before the image name
- **Old image:** rebuild after code changes, then restart Claude
- **Protocol errors:** check that the app logs only to stderr, not stdout

## FAQ

### How do I run an MCP server in Docker for Claude Desktop?

Publish your .NET MCP server as a self-contained or framework-dependent image, then reference docker run in Claude Desktop config with stdio attached (-i) and environment variables for secrets. The desktop client spawns the container as a subprocess the same way it would a local binary. Keep images small and pin versions for reproducibility.

### What Docker flags does stdio MCP require?

Interactive stdio requires -i (keep stdin open) and often --rm for ephemeral containers. Pass env vars with -e or --env-file. Do not daemonize the server process inside the container—the client expects to own the process lifetime. Log to stderr; stdout is reserved for MCP protocol messages.

### Can Docker MCP servers use HTTP instead of stdio?

Yes—run Streamable HTTP inside the container, publish ports, and point remote clients at the HTTPS URL. Claude Desktop stdio mode specifically expects a spawned process with piped stdin/stdout; team-wide HTTP deployment uses a long-running container behind a load balancer instead.

### Where can I find prebuilt MCP server images?

The mcpsolutions.dev catalog lists packaged MCP servers and integration patterns for common business systems. You can also build custom images following the stdio or HTTP guides in this Learn series when off-the-shelf servers do not match your domain.

## Internal Links

- [What is the MCP Protocol?](/learn/ai/what-is-mcp-server/)
- [Building MCP Server Using .NET](/learn/ai/build-mcp-server-stdio-dotnet/)
- [Build MCP Server with Streamable HTTP in .NET](/learn/ai/build-mcp-server-streamable-http-dotnet/)
- [Build MCP Server with SSE Transport in .NET](/learn/ai/build-mcp-server-sse-dotnet/)
- [MCP Solutions product catalog](/builds/)

```json
{"@context":"https://schema.org","@graph":[{"@type":"BlogPosting","headline":"Run MCP Server in Docker","description":"Containerize your .NET stdio MCP server with Docker—Dockerfile setup, build and run commands, and end-to-end testing with Claude Desktop and MCP clients.","datePublished":"2025-07-26","dateModified":"2026-07-11","url":"https://wavepillars.com/learn/ai/run-mcp-server-in-docker/","author":{"@type":"Person","name":"Kiryl Bahdanovich"},"publisher":{"@type":"Organization","name":"WAVEPILLARS","url":"https://wavepillars.com/"},"image":"https://wavepillars.com/learn/mcp-og.webp","timeRequired":"PT15M"},{"@type":"FAQPage","mainEntity":[{"@type":"Question","name":"How do I run an MCP server in Docker for Claude Desktop?","acceptedAnswer":{"@type":"Answer","text":"Publish your .NET MCP server as a self-contained or framework-dependent image, then reference docker run in Claude Desktop config with stdio attached (-i) and environment variables for secrets. The desktop client spawns the container as a subprocess the same way it would a local binary. Keep images small and pin versions for reproducibility."}},{"@type":"Question","name":"What Docker flags does stdio MCP require?","acceptedAnswer":{"@type":"Answer","text":"Interactive stdio requires -i (keep stdin open) and often --rm for ephemeral containers. Pass env vars with -e or --env-file. Do not daemonize the server process inside the container—the client expects to own the process lifetime. Log to stderr; stdout is reserved for MCP protocol messages."}},{"@type":"Question","name":"Can Docker MCP servers use HTTP instead of stdio?","acceptedAnswer":{"@type":"Answer","text":"Yes—run Streamable HTTP inside the container, publish ports, and point remote clients at the HTTPS URL. Claude Desktop stdio mode specifically expects a spawned process with piped stdin/stdout; team-wide HTTP deployment uses a long-running container behind a load balancer instead."}},{"@type":"Question","name":"Where can I find prebuilt MCP server images?","acceptedAnswer":{"@type":"Answer","text":"The mcpsolutions.dev catalog lists packaged MCP servers and integration patterns for common business systems. You can also build custom images following the stdio or HTTP guides in this Learn series when off-the-shelf servers do not match your domain."}}]}]}
```