Binnacle в контейнере
English version: 07-containers.md. Этот файл — русский дубликат; обновляется вместе с 07-containers.md.
Как поставить Binnacle в каждый контейнер приложения, рядом с harness, и открыть его dashboard в браузере хоста, не открыв его своей сети. Нужен Binnacle 0.7.1 или новее: версии до 0.7.0 привязываются к собственному loopback контейнера, до которого опубликованный порт не доходит, а binn init версии 0.7.0 включает автозапуск, который контейнер без systemd выполнить не может. В контейнере, обновлённом с 0.7.0 на месте, без пересборки, его выключает binn daemon autostart off.
Почему в каждом контейнере приложения
Binnacle ставится туда, где работает harness. Его демон читает файлы сессий harness и checkout с диска, а harness запускает binn mcp как локальный процесс. Binnacle в отдельном контейнере не увидел бы ни того, ни другого: ни сессий, ни checkout, ни binn, который можно запустить.
Поэтому у каждого контейнера приложения свой Binnacle: свой демон, домашний каталог, токен и dashboard, для одного проекта внутри. Двадцать контейнеров — двадцать демонов, каждый на своём порту хоста. Сколько стоит один, сказано в разделе Сколько стоит один контейнер.
С фичей dev container
Фича Binnacle ставит @it-bbox/binnacle из npm, держит домашний каталог Binnacle на томе этого контейнера, привязывает демон ко всем интерфейсам внутри контейнера, принимает адрес со стороны хоста и запускает демон вместе с контейнером. devcontainer.json добавляет её после Node 24, публикует порт и держит домашний каталог harness на томе:
{
"features": {
"ghcr.io/devcontainers/features/node:1": { "version": "24" },
"ghcr.io/it-bbox/binnacle/binnacle:1": { "version": "0.7.1", "hostPort": "7717" }
},
"appPort": ["127.0.0.1:7717:7717"],
"containerEnv": { "CLAUDE_CONFIG_DIR": "/home/node/.claude" },
"mounts": ["source=claude-${devcontainerId},target=/home/node/.claude,type=volume"],
"postCreateCommand": "sudo chown node /home/node/.claude"
}| Параметр | По умолчанию | Значение |
|---|---|---|
version | latest | Версия @it-bbox/binnacle из npm. Закрепите её, чтобы пересборка не обновляла Binnacle у вас за спиной. |
hostPort | 7717 | Порт хоста, на который опубликован или проброшен порт 7717 контейнера. Демон принимает 127.0.0.1 и localhost на этом порту. |
- Порт.
appPortпубликует порт 7717 контейнера на loopback хоста,127.0.0.1:<hostPort>:7717; никогда не пишите его голым<hostPort>:7717(почему). Редактор, который пробрасывает порты, может пробросить его вместо этого,"forwardPorts": [7717]: он берёт на хосте 7717, если порт свободен, и другой, если нет, иhostPortдолжен называть порт, который он показывает; при его смене нужна пересборка. - Домашний каталог harness. Claude Code держит свои настройки, файлы сессий и плагин Binnacle в
~/.claude, а свои MCP-серверы, включая тот, что регистрируетbinn init, — в~/.claude.json. Том — это каталог, а не файл, поэтомуCLAUDE_CONFIG_DIRкладёт оба в один каталог, здесь/home/node/.claude, и том сохраняет их при пересборке. Binnacle читает их там без дополнительной настройки. Если в образе нет такого каталога, Docker создаёт точку монтирования нового тома от root, поэтомуpostCreateCommandотдаёт её пользователю контейнера черезsudo, который есть в образахmcr.microsoft.com/devcontainers; образ безsudoсам создаёт этот каталог, как Dockerfile ниже./home/nodeиnode— пользователь образов Node, подставьте своего. - Разрешённые адреса записаны в команде запуска фичи,
/usr/local/share/binnacle-start.sh: послеbinn daemon stopзапускайте демон снова ею, потому что обычныйbinn daemon startне примет адрес хоста. - Фича ничего не пишет в ваш репозиторий, не публикует порт и не монтирует домашний каталог harness: всё это делает ваш
devcontainer.json.
Вручную
Для образа, собранного без фич, или для сервиса docker compose. Образ ставит Binnacle и задаёт то, что общее для всех его контейнеров:
FROM node:24-bookworm-slim
RUN npm install -g @it-bbox/binnacle@0.7.1 \
&& mkdir -p /var/lib/binnacle /home/node/.claude \
&& chmod 1777 /var/lib/binnacle \
&& chown node:node /home/node/.claude
ENV BINNACLE_HOME=/var/lib/binnacle/home \
BINNACLE_HOST=0.0.0.0 \
CLAUDE_CONFIG_DIR=/home/node/.claude
USER nodeBINNACLE_HOST=0.0.0.0привязывает демон ко всем интерфейсам внутри контейнера, чтобы опубликованный порт до него доходил. Клиенты внутри по-прежнему достают его через loopback контейнера.BINNACLE_HOME— каталог на томе, смонтированном в/var/lib/binnacle, который сохраняет регистрацию, токен и историю при пересборке. Оба каталога создаются в образе, чтобы новый том с самого начала был доступен на запись:/var/lib/binnacleоткрыт всем пользователям, как/tmp, потому что dev container может сменить UID пользователя после сборки, а демон создаёт внутри свой домашний каталог, доступный только его пользователю;/home/node/.claudeпринадлежит пользователю, и эта смена его учитывает.
Дальше каждый контейнер задаёт четыре вещи: порт, опубликованный на loopback хоста; BINNACLE_ALLOWED_HOSTS с этим портом хоста; два тома; и команду запуска, которая выполняется при каждом старте контейнера:
services:
app:
build: .
ports:
- "127.0.0.1:7717:7717"
environment:
BINNACLE_ALLOWED_HOSTS: 127.0.0.1:7717,localhost:7717
volumes:
- .:/workspace
- binnacle:/var/lib/binnacle
- claude:/home/node/.claude
command: sh -c 'binn daemon status >/dev/null 2>&1 || { rm -f "$$BINNACLE_HOME/runtime.json"; binn daemon start; }; exec sleep infinity'
volumes:
binnacle:
claude:Команда запуска удаляет runtime-файл, оставшийся от остановленного контейнера, потому что процесс, который он называет, умер вместе с контейнером, и запускает демон; уже отвечающий демон остаётся работать. Замените exec sleep infinity командой своего приложения. Dev container, собранный из этого образа, задаёт порт, переменную и тома в appPort, containerEnv и mounts, а демон запускает в postStartCommand, без sleep, который никогда бы не вернулся:
binn daemon status >/dev/null 2>&1 || { rm -f "$BINNACLE_HOME/runtime.json"; binn daemon start; }Контейнер, в котором демон не запустился, разрабатывается как прежде, без Binnacle.
Публикуйте только на 127.0.0.1
Публикуйте порт как 127.0.0.1:<port>:7717, никогда не голым <port>:7717. Голый порт слушает на всех интерфейсах хоста, и Docker пишет для него собственные правила файрвола, раньше правил ufw: ufw, запрещающий этот порт, не мешает локальной сети достучаться до dashboard. На loopback хоста до него достаёт только ваша собственная машина.
Токен по-прежнему охраняет каждую операцию, но /health и файлам dashboard он не нужен, и никому в вашей сети незачем их вызывать.
Свой порт для каждого контейнера
Каждый демон слушает 7717 внутри своего контейнера. На хосте каждому контейнеру нужен свой порт: 7717 первому, 7718 второму и так далее.
| Контейнер | appPort или ports | hostPort или BINNACLE_ALLOWED_HOSTS |
|---|---|---|
| первый | 127.0.0.1:7717:7717 | 7717 или 127.0.0.1:7717,localhost:7717 |
| второй | 127.0.0.1:7718:7717 | 7718 или 127.0.0.1:7718,localhost:7718 |
Разрешённый Host должен называть порт со стороны хоста. Браузер шлёт Host: 127.0.0.1:7718, а демон, слушающий 7717, отказывает любому Host, который ему не велено принимать: если порт опубликован под одним номером, а разрешён под другим, dashboard отвечает отказом. binn daemon status перечисляет адреса, которые демон принимает.
Настроить проект и открыть dashboard
Выполните binn init один раз на проект, внутри контейнера, в checkout: он регистрирует проект в Binnacle контейнера, а MCP-сервер Binnacle — в harness, оба на своих томах, так что пересборка их сохраняет. Выполненный на хосте, он зарегистрировал бы проект в Binnacle хоста, который не видит ни одной сессии контейнера. Автозапуск он там не предлагает: в контейнере нет менеджера служб, а демон запускает фича или команда запуска.
Затем, внутри контейнера:
binn dashboard --printКоманда печатает адрес, который открывает хост, http://127.0.0.1:<hostPort>/ с токеном во фрагменте, который никогда не доходит до сервера. Откройте его в браузере хоста. Обычный binn dashboard печатает тот же адрес без токена и говорит, что не смог открыть браузер, потому что в контейнере его нет. После первого раза обычный адрес работает в этом браузере как закладка.
Остальное — как на хосте: Чек-лист установки и, о том, что принимает демон, Безопасность и устойчивость.
Сколько стоит один контейнер
Резидентная память одного демона и размер его домашнего каталога, по одному проекту на контейнер, измерены в демонстрации выхода эпика 23. Домашний каталог растёт вместе с сессиями, которые импортирует Binnacle, так что это его размер до первой.
| Мера | Значение |
|---|---|
| Резидентная память демона | 82 МиБ (VmRSS 84 380 кБ) |
| Размер домашнего каталога Binnacle | 284 КиБ |
| Когда и на чём измерено | 2026-10-30: Binnacle 0.7.1 и фича 1.0.0 в mcr.microsoft.com/devcontainers/javascript-node:24, Docker Desktop 4.89.0 (Engine 29.7.2) в Ubuntu 24.04.5, один проект, зарегистрированный binn init, ещё без сессий harness |