# Docker MCP: как запускать MCP-сервер в контейнере

> Когда MCP-серверу нужен контейнер, чем образ для stdio отличается от образа для Streamable HTTP, и где кончаются правила протокола и начинается практика Docker

Страница: https://mcpdoc.ru/deployment/docker/
Указатель сайта: https://mcpdoc.ru/llms.txt

Контейнер с MCP-сервером бывает двух видов. Сервер stdio клиент запускает сам командой `docker run -i --rm`, портов и проверки состояния у него нет. Сервер Streamable HTTP работает как постоянный сервис с одним адресом, который принимает POST.

Спецификация MCP про Docker не говорит ничего. Она задаёт только поведение транспорта. Всё остальное на этой странице - практика Docker, и она так и помечена.

описана редакция 2026-07-28, сверено со спецификацией и документацией Docker 4 октября 2026

Совместимость Modern страница описывает редакцию 2026-07-28; отличия старых редакций названы в тексте

- **Страница описывает редакцию:** 2026-07-28

- **Затрагивает устаревшее:** нет

- **Затрагивает удалённое в редакции 2026-07-28:** нет

- **Проверено:** 2026-10-04

- **Источник:** [modelcontextprotocol.io](https://modelcontextprotocol.io/specification/2026-07-28/basic/transports)

## Два вида образа

| | Образ для stdio | Образ для Streamable HTTP |
|---|---|---|
| Кто запускает | клиент, как дочерний процесс | вы или система управления контейнерами |
| Время жизни | пока нужен клиенту | постоянно |
| Сеть | порт не нужен | один адрес, принимающий POST |
| Журнал | `stderr` | `stdout` и `stderr` |
| Проверка состояния | не нужна | возможна, вне адреса MCP |

Какой транспорт выбрать, решает не Docker, а то, где работает сервер. Правила обоих транспортов разобраны на странице [Транспорты MCP](https://mcpdoc.ru/protocol/transports/).

## Что требует протокол

Эти требования взяты из спецификации и действуют в контейнере так же, как без него.

### Сервер stdio

По странице [stdio](https://modelcontextprotocol.io/specification/2026-07-28/basic/transports/stdio):

- Сервер не должен писать в `stdout` ничего, кроме сообщений MCP. Строка от программы установки пакетов или приветствие сценария запуска ломает обмен.
- Сервер может писать журнал в `stderr`. Клиенту не следует считать вывод в `stderr` признаком сбоя.
- Клиент не должен писать в `stdin` сервера ничего, кроме сообщений MCP.
- Серверу следует завершаться сразу, как только `stdin` закрыт или чтение вернуло конец файла. Спецификация называет это основным и единственным переносимым сигналом завершения.
- Если процесс сервера завершился неожиданно, клиенту следует запустить его заново. Незавершённые запросы теряются.

### Сервер Streamable HTTP

По странице [Streamable HTTP](https://modelcontextprotocol.io/specification/2026-07-28/basic/transports/streamable-http):

- Сервер обязан давать один адрес, который принимает POST.
- Сервер обязан проверять заголовок `Origin` у всех входящих соединений. Если заголовок есть и он недопустим, сервер обязан ответить `403 Forbidden`.
- При локальном запуске серверу следует слушать только `127.0.0.1`, а не все сетевые интерфейсы.
- Серверу следует проверять подлинность всех соединений.
- Открывая поток SSE в ответ на запрос, серверу следует добавить заголовок `X-Accel-Buffering: no`.

Рекомендация слушать `127.0.0.1` написана для сервера, запущенного прямо на машине. Внутри контейнера у процесса свой сетевой стек. Как именно сервис в контейнере становится доступен снаружи, определяют параметры публикации портов Docker, а не спецификация MCP. Требование проверять `Origin` при этом остаётся в силе.

## Что меняется между эпохами

В самом образе - ничего. Меняется только то, как сервис стоит за балансировщиком.

- В Legacy (редакции с `2025-03-26` по `2025-11-25`) сервер мог выдать клиенту номер сессии в заголовке `MCP-Session-Id`, и клиент был обязан присылать его в каждом следующем запросе. Если состояние сессии жило в памяти одного экземпляра, запросы приходилось направлять на тот же экземпляр.
- В Modern сессий протокола нет: [журнал изменений редакции 2026-07-28](https://modelcontextprotocol.io/specification/2026-07-28/changelog) удаляет их вместе с заголовком `Mcp-Session-Id`. По [общим правилам протокола](https://modelcontextprotocol.io/specification/2026-07-28/basic) сервер не должен полагаться на прежние запросы по тому же соединению, и ему не следует требовать от клиента того же соединения или процесса для связанных операций.

Отсюда следствие: сам протокол Modern не требует привязывать клиента к одному экземпляру. Если ваш сервер хранит собственное состояние, оно обязано адресоваться явным идентификатором, который клиент передаёт в каждом запросе. Где хранить такое состояние, чтобы его видели все экземпляры, спецификация не определяет.

Сервер для обеих эпох на одном адресе описан на странице [Сервер для двух эпох](https://mcpdoc.ru/deployment/dual-era/).

## Практика, не правило MCP

Контейнеру с сервером Streamable HTTP нужна машина с постоянным адресом. Виртуальный сервер с Docker можно арендовать у [FirstVDS](https://mcpdoc.ru/go/firstvds/) или [Aéza](https://mcpdoc.ru/go/aeza/).

Всё ниже - рекомендации Docker из его собственной документации. Спецификация MCP их не требует и о них не упоминает.

### Запуск сервера stdio

Клиент запускает контейнер так же, как любую другую команду. В настройках клиента командой становится `docker`, а параметрами - `run`, `-i`, `--rm` и имя образа.

```json title="Схема записи в настройках клиента"
{
  "command": "docker",
  "args": ["run", "-i", "--rm", "ВАШ_ОБРАЗ:ВАШ_ТЕГ"]
}
```

Пример схематичный, составлен по справке Docker и здесь не запускался. Имена внешних полей зависят от клиента, смотрите страницу [Клиенты MCP](https://mcpdoc.ru/reference/clients/).

По [справке команды `docker run`](https://docs.docker.com/reference/cli/docker/container/run/):

- `-i` оставляет `stdin` открытым. Без него сервер stdio не получит ни одного сообщения.
- `--rm` удаляет контейнер и его безымянные тома после выхода.

Параметр `-t` в этой схеме не используется: по той же справке он выделяет псевдотерминал, а обмен по stdio идёт по обычным потокам.

### Образ для сервера на Node.js

```dockerfile title="Dockerfile, схема для Node.js"
# Тег базового образа выберите сами и закрепите
FROM node:ВАШ_ТЕГ

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

# Пользователь node входит в официальный образ node
USER node

CMD ["node", "server.js"]
```

Пример схематичный, составлен по документации Docker и здесь не запускался. Имя файла `server.js` условное.

### Образ для сервера на Python

```dockerfile title="Dockerfile, схема для Python"
# Тег базового образа выберите сами и закрепите
FROM python:ВАШ_ТЕГ

WORKDIR /app
COPY requirements.txt ./
RUN pip install --no-cache-dir -r requirements.txt
COPY . .

RUN groupadd -r mcp && useradd --no-log-init -r -g mcp mcp
USER mcp

CMD ["python", "server.py"]
```

Пример схематичный, составлен по документации Docker и здесь не запускался. Имя файла `server.py` условное.

### На чём основаны эти образы

- **Закреплённый тег базового образа.** По [рекомендациям Docker по сборке](https://docs.docker.com/build/building/best-practices/) теги образов изменяемы: издатель может перевести тег на новый образ. Там же описано закрепление версии базового образа. Какой тег взять, решаете вы. Для Node.js [страница выпусков](https://nodejs.org/en/about/previous-releases) советует в рабочей среде только ветки в статусе Active LTS или Maintenance LTS. Для Python сроки поддержки веток приведены в [руководстве разработчика Python](https://devguide.python.org/versions/).
- **Непривилегированный пользователь.** Те же рекомендации: если сервис может работать без привилегий, переключитесь инструкцией `USER` на пользователя без прав root. Что делает `USER`, описано в [справке по Dockerfile](https://docs.docker.com/reference/dockerfile/). В образах `node` для этого уже есть пользователь `node` - так сказано в [рекомендациях к официальному образу](https://github.com/nodejs/docker-node/blob/main/docs/BestPractices.md).
- **Никаких секретов в образе.** По странице [о секретах сборки](https://docs.docker.com/build/building/secrets/) аргументы сборки и переменные окружения для секретов не подходят, потому что остаются в готовом образе. Для секретов, нужных при сборке, Docker предлагает подключаемые секреты. Ключи доступа, нужные серверу во время работы, передаются при запуске контейнера, а не записываются в образ.
- **Сборка в несколько этапов.** Рекомендации Docker описывают её как способ уменьшить итоговый образ, отделив сборку от результата. В схемах выше её нет, чтобы не усложнять пример.

### Журнал

По странице [о журналах контейнеров](https://docs.docker.com/engine/logging/) команда `docker logs` по умолчанию показывает `stdout` и `stderr` процесса.

Для сервера Streamable HTTP подходят оба потока. Для сервера stdio поток `stdout` занят сообщениями MCP, поэтому журнал идёт только в `stderr`. Контейнер, запущенный клиентом с `--rm`, после выхода удаляется вместе со своим журналом. Сохраняет ли клиент вывод `stderr`, зависит от клиента: спецификация разрешает ему собирать этот вывод, передавать дальше или не читать вовсе.

### Проверка состояния

Инструкция `HEALTHCHECK` по [справке по Dockerfile](https://docs.docker.com/reference/dockerfile/) сообщает Docker, как проверить, что контейнер ещё работает.

- Для сервера Streamable HTTP она имеет смысл. Спецификация MCP адреса для такой проверки не задаёт. Не используйте для неё GET к адресу MCP: серверу только для Modern следует отвечать на GET кодом `405 Method Not Allowed`. Удобнее отдельный адрес вашего приложения.
- Для сервера stdio она не нужна. Контейнер живёт столько, сколько нужен клиенту, порта у него нет, и опрашивать нечего.

## Чего здесь нет и что неизвестно

- Docker MCP Catalog, Toolkit и Gateway на этой странице не разбираются: их документация для этой страницы не проверялась.
- Размеры образов, объём памяти, время запуска и другие числа не приводятся. Их нет ни в спецификации, ни в документации Docker, и измерять их нужно на вашем сервере.
- Оркестрация (Kubernetes, Compose, Swarm) не рассматривается.
- Как конкретный клиент ведёт себя с командой `docker` в настройках (сколько ждёт выхода, сохраняет ли `stderr`), по спецификации установить нельзя. Смотрите страницу своего клиента.
- Режим Docker без прав root для самого демона описан в [документации Docker](https://docs.docker.com/engine/security/rootless/). С MCP он не связан и здесь не разбирается.

## Что читать дальше

- [Транспорты MCP](https://mcpdoc.ru/protocol/transports/)
- [Streamable HTTP и переход с HTTP+SSE](https://mcpdoc.ru/deployment/http-sse/)
- [Linux](https://mcpdoc.ru/deployment/ubuntu/)
- [Безопасность сервера](https://mcpdoc.ru/security/server/)
