Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Build the image around the transport your clients need. Use stdio when a local host launches your MCP process; use Streamable HTTP when clients connect to a deployed service. Package an official MCP SDK server, its locked dependencies and only the required source files in a small, reproducible image. For HTTP, serve the SDK’s /mcp endpoint behind TLS and configure host/origin allowlists before exposing it publicly.

Choose the transport before writing the Dockerfile

The transport determines whether the container needs a listening port and how it is tested.

Transport Use it when Container consequence
stdio A local application (such as an IDE or desktop agent) starts the server process No listening port. Keep standard output exclusively for JSON-RPC messages; send logs to standard error.
Streamable HTTP Remote clients, several clients, or a managed deployment need an endpoint Expose an HTTP port, normally mount the MCP application at /mcp, and configure host/origin security.
HTTP+SSE You must support an older client Use only for compatibility. The current TypeScript SDK recommends Streamable HTTP for new remote servers.

A single source tree can support both modes, but run each mode with an explicit command. Do not make a production container guess from an environment variable unless you have tested both startup paths.

Create a minimal Python MCP server

Python SDK v2 requires Python 3.10 or newer. The following example registers one tool and defaults to stdio, which is the safest starting point for a locally spawned container.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

server.py for stdio

from mcp.server.fastmcp import FastMCP

mcp = FastMCP("docker-demo")

@mcp.tool()
def add(a: int, b: int) -> int:
    """Add two integers."""
    return a + b

if __name__ == "__main__":
    mcp.run(transport="stdio")

Anything written to stdout can be interpreted as protocol data. Use print(..., file=sys.stderr) or the logging module configured for stderr for diagnostics. One accidental stdout log line can break the client handshake.

HTTP entrypoint

The Python SDK exposes a Starlette ASGI application through streamable_http_app(). Put the server object and the HTTP launcher in a separate entrypoint so the image command is unambiguous.

from mcp.server.fastmcp import FastMCP
import uvicorn

mcp = FastMCP("docker-demo")

@mcp.tool()
def add(a: int, b: int) -> int:
    """Add two integers."""
    return a + b

app = mcp.streamable_http_app()

if __name__ == "__main__":
    uvicorn.run(app, host="0.0.0.0", port=8000)

This application normally answers at http://host:8000/mcp. The process manager, TLS termination, worker count and load balancer are deployment concerns rather than MCP protocol features.

TypeScript option

The current MCP TypeScript first-server guide requires Node.js 20 or newer and ES modules. A stdio server can be kept very small:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";

const server = new McpServer({ name: "docker-demo", version: "1.0.0" });

server.tool(
  "add",
  "Add two integers",
  { a: z.number().int(), b: z.number().int() },
  async ({ a, b }) => ({ content: [{ type: "text", text: String(a + b) }] })
);

const transport = new StdioServerTransport();
await server.connect(transport);

Set "type": "module" in package.json and compile with your chosen TypeScript configuration. The protocol channel is stdout: use console.error() for logs and never console.log() in a stdio server. For a remote TypeScript deployment, use the SDK’s Streamable HTTP transport with an HTTP framework and mount the endpoint at the path expected by your client.

Write a reproducible Dockerfile

Python stdio image

FROM python:3.12-slim

ENV PYTHONDONTWRITEBYTECODE=1 
    PYTHONUNBUFFERED=1
WORKDIR /app

# requirements.txt should contain the SDK and your application dependencies.
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

COPY server.py .
RUN useradd --create-home --uid 10001 mcpuser
USER mcpuser

ENTRYPOINT ["python", "server.py"]

For production, generate a lock file or a hash-checked requirements file and pin the base image by digest. Copy manifests before source code so Docker can reuse the dependency layer. Do not copy local virtual environments, test data, credentials or .env files; use a .dockerignore.

Python HTTP image

FROM python:3.12-slim
ENV PYTHONDONTWRITEBYTECODE=1 PYTHONUNBUFFERED=1
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY http_server.py .
RUN useradd --create-home --uid 10001 mcpuser
USER mcpuser
EXPOSE 8000
CMD ["python", "http_server.py"]

Only the HTTP variant needs EXPOSE; that instruction documents the port and does not publish it by itself.

TypeScript image

FROM node:20-bookworm-slim AS build
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY tsconfig.json ./
COPY src ./src
RUN npm run build

FROM node:20-bookworm-slim
WORKDIR /app
ENV NODE_ENV=production
COPY package*.json ./
RUN npm ci --omit=dev
COPY --from=build /app/dist ./dist
RUN useradd --create-home --uid 10001 mcpuser
USER mcpuser
CMD ["node", "dist/server.js"]

Keep package-lock.json in the build context so npm ci is deterministic. If your server listens for HTTP, add EXPOSE and an HTTP command instead of the stdio command.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Build, run and verify the image

  1. Create a narrow build context. Add a .dockerignore containing .git, node_modules, virtual environments, caches, local secrets and test output.
  2. Build a versioned tag.
    docker build --pull -t docker-demo-mcp:1.0.0 .
  3. Run a stdio server interactively.
    docker run --rm -i docker-demo-mcp:1.0.0

    Keep -i; without an attached stdin, a stdio client cannot communicate with the process. Your MCP client should launch this exact command and exchange JSON-RPC over the container’s stdin/stdout.

  4. Run the HTTP server locally.
    docker run --rm --name docker-demo-mcp 
      -p 8000:8000 
      -e API_TOKEN 
      docker-demo-mcp:1.0.0

    Do not put the token in the Dockerfile or image history.

  5. Test with the same shape as production. Point an MCP client or MCP Inspector at http://localhost:8000/mcp. Check startup logs, tool discovery and one real tool call. A generic HTTP health probe is useful, but it does not prove that MCP initialization and session handling work.
  6. Record the immutable artifact. Push the tested tag to your registry and deploy by image digest where your platform supports it.

Expose /mcp safely

Localhost defaults are intentionally restrictive. In a deployed Python HTTP server, configure the SDK’s allowed_hosts and allowed_origins for the exact public hostname and origins that should connect. A missing host entry can produce 421 Misdirected Request; an origin rejected before MCP handling can produce 403 Forbidden.

Recommended boundary controls

  • Terminate TLS at a managed ingress or reverse proxy and forward only to the private container port.
  • Allow the canonical hostname, not a wildcard, unless you have a documented reason.
  • Use platform identity, a gateway-issued credential or another authenticated boundary. Treat MCP tools as privileged actions.
  • Pass API keys and cookies at runtime through your orchestrator or Docker MCP secret mechanisms; never bake them into layers or source control.
  • Restrict each client to the tools and downstream credentials it actually needs.
  • Keep the container non-root and make its filesystem read-only when the SDK and application permit it.

For stateful Streamable HTTP sessions, ensure your load balancer keeps a client on a compatible worker or that your server stores session state in a shared backend. A stateless design is easier to scale horizontally, but only if your tools do not depend on in-memory conversation or connection state.

Use Docker MCP Toolkit and Gateway

Docker MCP Toolkit organizes servers and clients with profiles. Docker MCP Gateway centralizes routing, credentials, access control and server lifecycle. The Gateway starts a server container when a requested tool is not already running, so clients do not each need to manage separate container commands.

The Docker MCP Catalog lists more than 300 verified servers packaged as container images with versioning, provenance and security updates. The documented Toolkit interface applies to Docker Desktop 4.62 and later. A practical flow is:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
  1. Create a profile for the client or team.
  2. Add the server image (from the Catalog or your registry) and declare required secrets.
  3. Connect the MCP client to the Gateway rather than directly to every container.
  4. Invoke a harmless discovery call and then a representative tool call.
  5. Review which credentials and tools the profile can access before sharing it.

Gateway management does not remove the need for a correct image: the container still needs a deterministic startup command, clean stdio or a reachable HTTP endpoint, and useful diagnostics.

Production checklist

  • Use Python 3.10+ or Node.js 20+ as required by the current SDK guides.
  • Pin application dependencies and preferably the base-image digest; rebuild deliberately for security updates.
  • Run as a non-root user and drop unnecessary Linux capabilities.
  • Keep stdout protocol-clean for stdio; send all logs to stderr.
  • Expose only the HTTP port required by Streamable HTTP and keep it private behind authenticated ingress.
  • Configure exact host and origin allowlists; do not disable SDK protections as a shortcut.
  • Define startup and liveness diagnostics outside the MCP protocol stream.
  • Set timeouts for slow tools and cap concurrency according to downstream service limits.
  • Test the built image, not merely the source tree, with the same transport and endpoint path production clients use.
  • Capture image digests, dependency versions and configuration in deployment records so a failing release can be rolled back.

Performance, reliability and cost decisions

Image and startup performance

Multi-stage builds keep compilers and development dependencies out of the runtime image. Layer dependency installation before source copying to maximize cache reuse. A smaller image downloads faster, but do not remove certificates, fonts or OS libraries your tools actually require.

HTTP scaling

Streamable HTTP supports remote and multi-client use, but each connection and tool call consumes server, proxy and downstream capacity. Set explicit proxy timeouts, cap request body sizes, and load-test the slowest tool. If sessions are in memory, use connection affinity or shared state before adding replicas.

Failure and billing boundaries

Container cost is determined by your Docker host or cloud platform, not by MCP itself. Budget for registry storage, egress, CPU, memory and any downstream API calls. A managed platform can simplify TLS and identity; direct Docker runs give more control but leave patching, monitoring, failover and secret rotation to you.

What’s actually slowing this PC down?

Pick the symptom - the matching free tool is one click away.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Troubleshooting

The client reports an invalid JSON-RPC response

In stdio mode, a log line or framework banner is probably on stdout. Move every diagnostic to stderr, disable colorful startup output, rebuild the image and retry with an interactive stdin.

The container exits immediately

Inspect docker logs and verify the entrypoint, working directory, executable permissions and required environment variables. For stdio, an absent or closed stdin can make a correctly behaving server terminate.

HTTP requests return 404 at the root path

The MCP application is normally mounted at /mcp, not /. Point the client and reverse proxy at the complete path and preserve it when forwarding.

HTTP returns 421 or 403 before a tool runs

Check the requested host and the browser/client origin against allowed_hosts and allowed_origins. Include the public hostname used by the proxy, not only the container’s internal name.

Free tools Windows power users keep installed

One-click scans. No signup required.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

The client connects but discovery hangs

Confirm that the proxy supports the streaming behavior required by Streamable HTTP, that idle timeouts exceed the longest expected operation, and that the server’s worker is not blocked by synchronous code.

Works locally but fails in Gateway

Compare the Gateway profile’s image tag, command, environment variables, mounted secrets, network policy and endpoint path with the command you tested manually. Verify that the Gateway can resolve and reach the registry and downstream services.

New replicas lose sessions

Your deployment is stateful. Add shared session storage or connection affinity, or redesign the server so each request carries everything needed and workers can remain stateless.

Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

Or skip the browser setup

If your MCP project also needs website captures for documentation, visual checks or agent workflows, ScreenshotNeo provides a one-call screenshot API and MCP server. It accepts consent banners before capture and removes more than 60 known consent platforms, newsletter popups and chat widgets; bot checks, blank pages, failed loads and cache hits are not billed, and response headers identify the page verdict and billing status. AI clients can use its MCP tools take_screenshot, get_page_info and capture_pdf.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Start with the ScreenshotNeo API documentation and call:

curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp

There is a free allowance of 1,000 screenshots per month with no card. Paid plans start at $5 for 3,000 screenshots, and every feature is included on every plan. Create a free ScreenshotNeo account.

Best Value
Docker Container Linux Devops Programming Coding T-Shirt
  • Docker, Docker Swarm, Docker Compose, Programmer, Developer, Coding, Programming, Software Engineer, Code, DevOps, Deploy, Deployment, Kubernetes, Salt, Puppet, Chef, Terraform, Container, AWS, Azure, Cloud, Geek, Funny, Computer, Software, Tech, IT
  • Integration, Scrum, Compile, Compilation, Science, Bug, Debug, Python, Linux, Java, Javascript, Scala, Dotnet, Kotlin
  • Lightweight, Classic fit, Double-needle sleeve and bottom hem

FAQ

Can one image support both stdio and Streamable HTTP?

Yes. Keep shared tool registration in one module and provide separate stdio and HTTP entrypoints or commands. Deploy them as distinct workloads so each has clear health checks and security settings.

Do I need Kubernetes to run an MCP image?

No. Docker Engine, a registry and a suitable host are enough. Kubernetes or a managed container service becomes useful when you need automated rollouts, replicas, ingress and centralized secrets.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Should I expose the container port directly to the internet?

Generally no. Put the HTTP server behind TLS, authentication and an ingress or gateway that limits hosts, origins, methods and network access.

Is HTTP+SSE unusable?

No. It remains relevant for older clients. For a new remote implementation, Streamable HTTP is the recommended direction in the TypeScript SDK documentation.

What belongs in the image versus the deployment configuration?

Put application code and runtime dependencies in the image. Keep secrets, hostnames, origins, credentials, replica counts and environment-specific limits in the deployment system.

Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.