Skip to content

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 на томе:

jsonc
{
  "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"
}
ПараметрПо умолчаниюЗначение
versionlatestВерсия @it-bbox/binnacle из npm. Закрепите её, чтобы пересборка не обновляла Binnacle у вас за спиной.
hostPort7717Порт хоста, на который опубликован или проброшен порт 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 и задаёт то, что общее для всех его контейнеров:

dockerfile
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 node
  • BINNACLE_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 с этим портом хоста; два тома; и команду запуска, которая выполняется при каждом старте контейнера:

yaml
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, который никогда бы не вернулся:

bash
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 или portshostPort или BINNACLE_ALLOWED_HOSTS
первый127.0.0.1:7717:77177717 или 127.0.0.1:7717,localhost:7717
второй127.0.0.1:7718:77177718 или 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 хоста, который не видит ни одной сессии контейнера. Автозапуск он там не предлагает: в контейнере нет менеджера служб, а демон запускает фича или команда запуска.

Затем, внутри контейнера:

bash
binn dashboard --print

Команда печатает адрес, который открывает хост, http://127.0.0.1:<hostPort>/ с токеном во фрагменте, который никогда не доходит до сервера. Откройте его в браузере хоста. Обычный binn dashboard печатает тот же адрес без токена и говорит, что не смог открыть браузер, потому что в контейнере его нет. После первого раза обычный адрес работает в этом браузере как закладка.

Остальное — как на хосте: Чек-лист установки и, о том, что принимает демон, Безопасность и устойчивость.

Сколько стоит один контейнер ​

Резидентная память одного демона и размер его домашнего каталога, по одному проекту на контейнер, измерены в демонстрации выхода эпика 23. Домашний каталог растёт вместе с сессиями, которые импортирует Binnacle, так что это его размер до первой.

МераЗначение
Резидентная память демона82 МиБ (VmRSS 84 380 кБ)
Размер домашнего каталога Binnacle284 КиБ
Когда и на чём измерено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

Apache-2.0