MCP-сервер 1С в Docker без внешней сети и секретов хоста
Файлы
ВНИМАНИЕ: Файлы из Базы знаний - это исходный код разработки. Это примеры решения задач, шаблоны, заготовки, "строительные материалы" для учетной системы. Файлы ориентированы на специалистов 1С, которые могут разобраться в коде и оптимизировать программу для запуска в базе данных. Гарантии работоспособности нет. Возврата нет. Технической поддержки нет.
Подписка PRO — скачивайте любые файлы со скидкой до 85% из Базы знаний
Оформите подписку на компанию для решения рабочих задач
Оформить подписку и скачать решение со скидкойВы можете заказать платную доработку или адаптацию этой разработки под вашу конфигурацию на «Бирже заказов».
- 0% комиссии — оплата напрямую исполнителю;
- Исполнители любого масштаба — от отдельных специалистов до команд под проект;
- Прямой обмен контактами между заказчиком и исполнителем;
- Безопасная сделка — при необходимости;
- Рейтинги, кейсы и прозрачная система откликов.
13.2-companion.zip: Dockerfile, сервер, скрипт проверки, эталонный выводdocker build -f door.Dockerfile ..., затем python check_docker.pyoutput-docker-29.8.0.txt.Администратору и разработчику, которые запускают MCP-сервер рядом с базой 1С: флаги Docker для изоляции, что из контейнера доступно по сети, на диске и в окружении, и ловушка с журналом на файловой системе только для чтения. Прогон 27.09.2026 на Docker 29.8.0.
- С
--network noneсервер видит только свой loopback; для чтения 1С нужна сеть--internalс одной заглушкой. --read-onlyи--cap-drop ALLдалиCapEff=0и отказ записи в/app,/tmpи каталог журнала.- Старый сервер сначала обращался к 1С, потом падал на записи журнала; исправленный проверяет журнал до запуска.
- Переменная хоста без явного
-eв контейнер не попала;GPG_KEYбазового образа остаётся.
Флаги Docker закрывают сеть, диск и окружение, но журнал нужно разрешить явно томом, а проверку делать до запуска сервера.
Для кого: администраторам и разработчикам, которые запускают MCP-сервер рядом с базой 1С.
Допустим, вы хотите, чтобы ИИ-помощник в Cursor или Claude Code сам читал журнал регистрации базы 1С и разбирал ошибки. Для этого рядом с ним запускают небольшую программу, MCP-сервер: помощник просит записи журнала, программа обращается к HTTP-сервису базы и отдаёт ответ.
Запускает её сам помощник, командой из своего файла настроек, и работает она с правами того, кто её запустил. Значит, она видит всё, что видите вы: файлы, сеть и переменные окружения, где на машине разработчика могут лежать токены API. Серверу из всего этого нужен один адрес, HTTP-сервис 1С. Остальное ему ни к чему, и лучше, чтобы он не мог этим воспользоваться.
Похожие правила действуют в читальном зале архива. Верхнюю одежду и сумки сдают в камеру хранения. Дела выдают по запросу, выносить их из зала и писать на них нельзя. В начале каждого дела стоит лист использования: кто, когда и что делал. Работать это не мешает: читать документы и делать выводы можно.
MCP-сервер, читающий журнал регистрации 1С, в Docker-контейнере живёт по тем же правилам. Я проверила это на стенде: у каждого правила читального зала есть свой флаг, и каждый я прогнала командой.
| В читальном зале | В контейнере | Что показал прогон |
|---|---|---|
| Сумки и верхнюю одежду сдают в камеру хранения | переменные хоста не переданы (нет -e) |
тестовая переменная внутрь не попала |
| Дела выдают по запросу и из зала не выносят | сеть --network none или --internal, единственный внешний адрес - HTTP-сервис 1С |
внешний сервис и хост недоступны, заглушка 1С открыта только во внутренней сети |
| Писать на делах нельзя | --read-only, --cap-drop ALL |
запись в /app и /tmp отклонена, привилегий у процесса нет |
| В начале дела лист использования | отдельный том под журнал | единственное место, куда сервер пишет |
Первая версия зала вышла с изъяном: лист использования завели, а писать на нём не разрешили. Сервер получал документ у 1С, пробовал записать обращение в журнал, падал на записи, и по ответу клиенту казалось, что до 1С запрос не дошёл. Эту ошибку нашёл прогон. Ниже команды и результаты.
Сервер в выводах называется «дверью»: так звали прототип, через который агент читает журнал 1С. Вместо базы 1С в опыте работает HTTP-заглушка, настоящие учётные данные не использовались.
🔬 Что проверяет скрипт
Docker-прогон выполнен 27.09.2026 в 12:41 UTC, первая строка вывода:
docker 29.8.0, mcp (клиент) 2.2.0, python 3.12.10, 2026-09-27 12:41 UTC
Стенд - Docker Desktop на Windows в режиме Linux-контейнеров: контейнеры работают в виртуальной машине с ядром Linux (на стенде это дистрибутив WSL 2 docker-desktop), поэтому внутри обычная Linux-среда с /proc, /app и /tmp. Пакет mcp закреплён на 2.2.0 в образе и использован той же версии у клиента. Базовый образ - python:3.12-slim. Тег базового образа не закреплён по digest, поэтому новую сборку нельзя считать побайтно той же средой.
Скрипт check_docker.py проверяет вот что:
| Что проверено | Как получен результат |
|---|---|
Сеть none |
Попытки HTTP к loopback, заглушке, внешнему сервису и хосту |
Сеть --internal |
Те же обращения из сети с заглушкой |
| Права процесса | UID и поле CapEff из /proc/self/status |
Запись в /app |
Попытка открыть файл для добавления |
Запись в /tmp |
Такая же попытка записи |
| Окружение | Имена переменных и наличие маркера хоста |
| MCP через stdio | Инициализация, список инструментов, вызовы event_log |
HTTP-заглушка возвращает одну запись с пользователем Те***, событием _$Data$_.Update, метаданными Справочник.ТЕСТ и тестовой навигационной ссылкой. Для параметра metadata=502 она отвечает HTTP 502. Свой журнал заглушка ведёт тоже: путь обращения и есть ли заголовок X-Trace-ID.
Скрипт перед опытом пересоздаёт свои контейнеры, сеть и том с фиксированными именами (nopik-stub1c, nopik-1c-only, nopik-door-log). Если у вас эти имена уже заняты, запускайте его на отдельном стенде.
🐳 Как собран образ
Dockerfile исправленного сервера:
Файл сервера при сборке берётся из tools/archive_door.py; в контейнере он находится в /app/archive_door.py. Владелец кода - root, процесс запускается с UID 10001. Для журнала создан каталог с владельцем 10001 и правами 0700.
Команда сборки из стенда:
Пакет mcp ставится при сборке, в RUN pip install, и сеть для этого нужна. Флаг --network в docker run действует только на запуск. Для повторения нужен базовый образ: скачанный заранее или доступный из реестра.
Первый режим запуска - --network none. По документации Docker в нём остаётся loopback, и опыт это подтвердил: HTTP-сервер, поднятый в том же контейнере на 127.0.0.1, отвечал, а заглушка в соседнем контейнере - нет.
Второй режим создаёт внутреннюю сеть:
Заглушка nopik-stub1c и MCP-сервер подключаются к этой сети. Для заглушки проверочный скрипт использует python:3.12-slim, запускает встроенный HTTP-сервер на 0.0.0.0:8080 и задаёт --network nopik-1c-only. Порт на хост через -p в этой команде не публикуется.
В сети nopik-1c-only только эти два контейнера. Имя сети никого не ограничивает: подключите к ней ещё один контейнер, и сервер увидит и его. Белого списка адресов --internal не задаёт, он только отрезает сеть от внешнего мира.
Для обращения к источнику сервер получает адрес:
NOPIK_DOOR_URL=http://nopik-stub1c:8080/hs/nopik-door
Имя nopik-stub1c разрешается внутри сети Docker. 127.0.0.1 здесь указывал бы на loopback контейнера с MCP-сервером. Поэтому режим none годится только для проверки отказа связи, а читать заглушку сервер может из внутренней сети.
🌐 Что показали попытки соединения
Проба делает настоящие HTTP-запросы из проверяемого контейнера и для каждого адреса печатает «открыто» или тип исключения с началом причины. Адрес внешнего сервиса в тексте заменён на https://example.local/api/ask, результат по нему взят из исходного прогона.
Режим none:
флаги: --read-only --cap-drop ALL --network none uid=10001, CapEff=0000000000000000, cwd=/ loopback 127.0.0.1: открыто заглушка 1С: URLError: [Errno -3] Temporary failure in name resolution сервис на хосте (host.docker.internal:8000): URLError: [Errno -3] Temporary failure in name resolution
Строки о записи и окружении разобраны ниже. Имя сервиса на хосте обезличено, адрес и причина ошибки сохранены. Внешний сервис дал ту же ошибку разрешения имени.
Во внутренней сети заглушка открывается:
флаги: --read-only --cap-drop ALL --network nopik-1c-only uid=10001, CapEff=0000000000000000, cwd=/ loopback 127.0.0.1: открыто заглушка 1С: открыто сервис на хосте (host.docker.internal:8000): URLError: [Errno -3] Temporary failure in name resolution
Внешний сервис и здесь не открылся. Проба стучится в /hs/nopik-door/ping, MCP-вызовы потом идут в /hs/nopik-door/event_log: ping показывает, что источник доступен, вызов инструмента - что сервер разбирает его ответ.
Ошибка DNS относится к этим попыткам по имени. Обращения напрямую по IP и другие протоколы я не проверяла.
До Docker был опыт с Linux unshare -n: тот же Python-код в отдельном сетевом пространстве. Там loopback выключен, поэтому не открылась даже заглушка на 127.0.0.1:
mcp 2.2.0, python 3.11.15, uid 0 2. Сеть: тот же код в своём сетевом пространстве (unshare -n) заглушка 1С на 127.0.0.1: URLError: [Errno 101] Network is unreachable
Разница с Docker как раз в loopback: в --network none он работает. Права, которых unshare требует в вашей системе, проверяйте отдельно, на Windows я его не запускала.
🔒 Почему старый сервер падал на журнале
Сервер запускается с --read-only --cap-drop ALL. В обоих сетевых режимах UID и эффективные capabilities одинаковы:
uid=10001, CapEff=0000000000000000, cwd=/ запись: /app - нельзя (Read-only file system) ; cwd - нельзя (Read-only file system) ; /tmp - нельзя (Read-only file system) ; /var/log/door - нельзя (Read-only file system)
Проба открывает на добавление /app/x, door-calls.jsonl в текущем каталоге, /tmp/x и /var/log/door/x, содержимое существующих файлов она не трогает. Без записываемого тома запись запрещена везде.
Пользователь задан в образе (USER 10001). Флаг --user в docker run я не проверяла.
В старом образе журнал по умолчанию назывался door-calls.jsonl и открывался относительно текущего каталога /. Сервер сначала читал HTTP-ответ, затем пытался записать строку журнала. Под --read-only это закончилось двумя ошибками:
инструменты: ['event_log'] event_log(Справочник.ТЕСТ): isError=True «[Errno 30] Read-only file system: 'door-calls.jsonl'» event_log(502): isError=True «[Errno 30] Read-only file system: 'door-calls.jsonl'»
Из сообщения клиенту выходит, что вызов оборвался до 1С. Журнал заглушки говорит обратное: оба запроса до неё дошли, а в журнал старого сервера не записано ни строки.
Исправленный сервер открывает журнал на пробу до server.run("stdio"):
Без тома исправленный образ теперь останавливается до начала сеанса MCP:
код выхода 1: журнал двери недоступен для записи (/var/log/door/door-calls.jsonl: Read-only file system); дверь без журнала не открывается - задайте NOPIK_DOOR_LOG
Для рабочего запуска добавляется -v nopik-door-log:/var/log/door: корень остаётся только для чтения, каталог журнала живёт в томе. В явно подключённый том --read-only и --cap-drop ALL писать не мешают.
Проба на старте ловит только недоступный журнал при запуске, это и показал этап 5а. Что будет, если диск заполнится, права сменятся или запись сломается позже, я не проверяла.
🧰 Какие переменные попадают в контейнер
Без передачи маркера проба нашла в контейнере такие переменные:
окружение: 8 имён ['GPG_KEY', 'HOME', 'HOSTNAME', 'LANG', 'NOPIK_DOOR_LOG', 'PATH', 'PYTHON_SHA256', 'PYTHON_VERSION'] имена, похожие на секреты: ['GPG_KEY']; NOPIK_CANARY_TOKEN=нет
Проба печатает только имена и отмечает, есть ли тестовая переменная, значения не выводит. Пустым окружение не назовёшь: GPG_KEY приходит из базового образа.
Затем скрипт добавляет в docker run флаг -e NOPIK_CANARY_TOKEN без значения:
имена, похожие на секреты: ['GPG_KEY', 'NOPIK_CANARY_TOKEN']; NOPIK_CANARY_TOKEN=есть
-e без значения Docker заполнил из окружения процесса, который его запустил. Значение маркера в статью не попало. Если в вашей команде есть -e ИМЯ без значения, в контейнер уйдёт то, что лежит в окружении у запускающего процесса, то есть у MCP-клиента.
С обычным дочерним процессом всё наоборот. В отдельном Linux-опыте subprocess.run() без env унаследовал маркер родителя, а с env={"PATH": os.environ["PATH"]} маркера уже не было:
По документации Python env полностью заменяет окружение дочернего процесса, и тестовая переменная это подтвердила. Каким программам что ещё нужно в окружении, кроме PATH, этот пример не говорит.
MCP stdio-клиент в том же Linux-прогоне без явного env передал серверу только HOME, LC_CTYPE, PATH, SHELL и TERM, без NOPIK_DOOR_URL. Адрес появился, когда его передали явно:
env=None: 5 переменных: ['HOME', 'LC_CTYPE', 'PATH', 'SHELL', 'TERM']; NOPIK_DOOR_URL=нет
env={'NOPIK_DOOR_URL': ...}: 6 переменных: ['HOME', 'LC_CTYPE', 'NOPIK_DOOR_URL', 'PATH', 'SHELL', 'TERM']; NOPIK_DOOR_URL=http://10.0.0.5/ut11/hs/nopik-door
Так ведёт себя mcp 2.2.0 на Linux. Поэтому у адреса источника в сервере нет значения по умолчанию: без NOPIK_DOOR_URL сервер адрес не угадывает. В Docker адрес передаётся через -e NOPIK_DOOR_URL=....
Рабочая команда из исправленного опыта:
Её запускает MCP-клиент. -i держит открытым стандартный ввод, по нему идут сообщения протокола; -t и проброса порта нет. Если набрать команду в терминале руками, сервер запустится и будет молча ждать сообщений stdio.
Клиент создаёт StdioServerParameters(command="docker", args=args), открывает stdio_client и ClientSession, вызывает initialize(), берёт список инструментов и дважды вызывает event_log со временем в UTC. Во внутренней сети:
инструменты: ['event_log'] event_log(Справочник.ТЕСТ): isError=False, записей 1 event_log(502): isError=True «Error executing tool event_log: 1С ответила HTTP 502, TraceID 653abc12-6f69-40fb-a4a2-dcb7b9ac193f»
С сетью none список инструментов приходит, а чтение источника отказывает:
инструменты: ['event_log'] event_log(Справочник.ТЕСТ): isError=True «Error executing tool event_log: 1С недоступна: <urlopen error [Errno -3] Temporary failure in name resolution>, TraceID eb12b44a-bd4a-4be9-be64-8510c7a7b758» журнал двери в томе nopik-door-log: 3 строк, статусы ['ok', 'unavailable', 'unavailable'] канарейка в журнале: нет
Протокол MCP идёт через stdio, сеть ему не нужна, отказ касается только HTTP-источника. В томе журнала исправленного сервера лежат успех, HTTP 502 и недоступность источника из режима none.
У заглушки в журнале вызовы event_log с X-Trace-ID: по два от старого и от исправленного образа во внутренней сети. Вызов с сетью none до неё не дошёл. ping сетевой пробы идёт без TraceID.
🧭 Ограничения
Прогон сделан 27.09.2026 на Docker 29.8.0, клиенте mcp 2.2.0 и Python 3.12.10. Старый образ нужен только для повторения ошибки журнала, без него скрипт этот этап пропускает.
GPG_KEY приходит из базового образа, это открытый отпечаток ключа, проверка по имени помечает его как похожий на секрет. Опыт показывает, что маркер хоста без -e в контейнер не попал. Что лежит внутри вашего образа, проверяйте отдельно.
Заглушка 1С работает без авторизации, учётные данные службы 1С в контейнер не передавались. Рабочая публикация HTTP-сервиса 1С с TLS и правами базы в этом опыте не участвовала.
Обращения по IP, IPv6, дополнительные сети и правила маршрутизации я не проверяла, Kubernetes NetworkPolicy на стенде нет. Какие сети и тома у вас на самом деле подключены, видно только из вашей команды запуска.
От ошибок в коде MCP-сервера и от того, у кого есть доступ к Docker-демону, флаги контейнера не защищают. Размер HTTP-ответа, нагрузку и перезапуск сервера я не проверяла.
📚 Источники
- docker container run - параметры запуска, файловая система для чтения, capabilities, пользователь и окружение.
- docker network create - создание сети с
--internal. - Драйвер сети none - сетевой режим с loopback.
- unshare(1) - создание отдельного сетевого пространства.
- Python subprocess - управление окружением дочернего процесса через
env.
Опыты: check_sandbox.py (Linux) и check_docker.py (Docker), их выводы, описание Docker-стенда и door.Dockerfile. Значения тестовых маркеров и исходный внешний адрес не публикуются.
Предшественники на Infostart:
- MCP-сервер внутри 1C:EDT: даём AI-агентам «руки», не открывая ни одного порта наружу (galich, 08.07.2026).
- Зачем одинэснику docker? (sleemp, 08.05.2026).
- Как мы в Magnit Tech используем Docker для 1С (daniloffartur, 26.05.2026).
- Руководство по интеграции CI/CD 1С:Предприятие 8.3 на Linux в Docker с пробросом лицензий (Ninel_S, 28.08.2026).
📎 Файл к статье
В архиве к статье - 13.2-companion.zip: door.Dockerfile, сервер archive_door.py, скрипт проверки check_docker.py, вывод прогона output-docker-29.8.0.txt и README. Соберите образ, запустите check_docker.py на отдельном стенде и сравните его вывод с output-docker-29.8.0.txt.
Вступайте в нашу телеграмм-группу Инфостарт