# macOS Docker Desktop — что НЕ проезжает в контейнер (virtiofs) > Создан 2026-09-26. Статус: **проверено живыми тестами** (alpine:3.20, linux/arm64, > **Colima** на macOS 26.6.1, Apple Silicon; `colima start --cpu 8 --memory 24 --disk 100`). > Тема: границы проброса путей из мира macOS в контейнер. > Все выводы получены прогонами, не по памяти. Команды воспроизведения — в §6. --- ## 0. Главный вывод одной строкой На macOS рантайм контейнеров (Colima, Docker Desktop) отдаёт контейнеру файлы **не через Linux `mount`**, а через **virtiofs** — демон, который строит маппинг путей **в момент старта контейнера** и знает только пути **мира macOS**, известные ему на старте. Всё, что появляется **позже** и **внутри VM** (ручной `mount`, шара, новый каталог), изнутри VM видно корректно — **до контейнера не доходит**. Это не кэш: рестарт не помогает, `sleep` не помогает. 📌 На Linux-хосте (нативный Docker) этих ограничений **нет** — там `nsenter`-трюки с `mount --bind` работают. Все выводы ниже — про **macOS**. --- ## 1. Симлинки в контейнер НЕ проезжают Симлинк, чья цель лежит **вне** смонтированного каталога, в контейнере **битый**. ``` host: /tmp/symtest/outside/file.txt = "REAL CONTENT ON HOST" bind: -v /tmp/symtest/bind:/mnt (внутри /mnt/target -> /tmp/symtest/outside) контейнер: cat /mnt/target/file.txt → can't open '/mnt/target/file.txt': No such file or directory ``` Сам симлинк внутри **виден** (`lrwxrwxrwx target -> /tmp/symtest/outside`), но цель резолвится **относительно корня контейнера**, где этого пути нет. Docker монтирует только реальные пути — симлинки не разворачивает. **Следствие:** папка с симлинками на внешние цели в контейнере нерабочая, даже если на хосте всё открывается. --- ## 2. Live `mount --bind` (nsenter) контейнеру НЕ виден Схема обычная для Linux-хоста: ```bash docker run --rm --privileged --pid=host alpine nsenter -t 1 -m \ mount --bind /host/source /host/agents/target ``` ⚠️ **Первая ловушка (до самого mount):** `nsenter -t 1 -m` заходит в **VM**, а не в macOS. Пути **мира macOS там не существуют**. Поэтому хост-путь для VM нужно создавать **внутри VM** — например, в том же `nsenter -t 1 -m sh -c '...'`. Проверено **тремя** прогонами, результат одинаковый: | Прогон | Условие | Результат | |---|---|---| | 1 | каталог создан **после** старта контейнера + live bind | в контейнере пусто | | 2 | ожидание 5 с (гипотеза «кэш атрибутов virtiofs») | пусто | | 3 | каталог существовал **до** старта контейнера + bind **поверх** него | **пусто** | Изнутри VM всё корректно: ``` VM: cat /tmp/bindtest2/agents/existing/f.txt → "DATA2" ✓ файл есть mount: BIND_OK ✓ смонтировалось контейнер: cat /agents/existing/f.txt → No such file or directory ✗ ``` **Вывод:** live-bind для добавления каталогов в контейнер на macOS **недоступен**. Единственный рабочий путь — **`-v` в compose + пересоздание контейнера** (`docker compose up -d` = 2-3 сек, `--privileged` не нужен). --- ## 3. Сетевые шары (SMB/NFS) в контейнер НЕ протянуть тем же путём - `mount_smbfs`/`mount_nfs` в macOS создаёт точку в **мире macOS** → virtiofs её не видит → `-v /Volumes/share:/data` **не работает**. - mount **внутри VM** через `nsenter` технически проходит, но: - **не переживает рестарт Docker** (нужен startup-хук); - требует `--privileged`; - SMB/NFS не дают hardlink между экспортами, блокировки файлов ведут себя иначе — сборщики/редакторы на шаре работают хуже, чем на локальном диске. **Рекомендация:** не тянуть шару в контейнер. Если данные на NAS — **`rsync` в локальную папку** по требованию. Копия переживает всё и не требует privileged. --- ## 4. Hardlink — чего он НЕ может (независимо от Docker) Это свойство ФС, не Docker: 1. **Hardlink на каталог не делается.** Ни в APFS, ни в ext4, ни в NTFS. `ln dir1 dir2` → `Operation not permitted`. У каталога появилась бы ссылка на себя → цикл в дереве. Запрещено на уровне syscall. 2. **Между томами не работает.** Только внутри одной ФС (один `st_dev`), иначе `EXDEV: Cross-device link`. **Следствие:** «подключить проект-каталог hardlink'ом извне» — **физически невозможно**. Hardlink годится только для **файлов внутри одного тома**. --- ## 5. Матрица: чем подключать путь в контейнер | Способ | Папку? | Через тома? | Видно в контейнере (macOS) | Итог | |---|---|---|---|---| | symlink | да | да | ✗ битый | ❌ для внешних целей | | hardlink | **✗ нет** | ✗ нет | — | ✓ только файлы внутри тома | | live `mount --bind` (nsenter) | да | да | ✗ | ❌ на macOS | | **`-v` в compose + recreate** | да | да | ✓ | ✅ **рабочий путь** | | rsync-копия | да | да | ✓ | ✓ когда данные на шаре/NAS | --- ## 6. Команды воспроизведения ```bash # 1. Симлинк в контейнер mkdir -p /tmp/symtest/outside /tmp/symtest/bind echo "REAL CONTENT ON HOST" > /tmp/symtest/outside/file.txt ln -sfn /tmp/symtest/outside /tmp/symtest/bind/target docker run --rm --platform linux/arm64 -v /tmp/symtest/bind:/mnt alpine:3.20 \ sh -c 'ls -la /mnt/; cat /mnt/target/file.txt' # → No such file or directory # 2. Live bind (внимание: пути создавать ВНУТРИ VM) docker run -d --name t -v /tmp/bindtest2/agents:/agents alpine:3.20 sleep 300 docker run --rm --privileged --pid=host alpine:3.20 nsenter -t 1 -m sh -c \ 'mkdir -p /tmp/bindtest2/agents/existing /tmp/bindtest2/ext2 echo DATA2 > /tmp/bindtest2/ext2/f.txt mount --bind /tmp/bindtest2/ext2 /tmp/bindtest2/agents/existing' docker exec t cat /agents/existing/f.txt # → No such file or directory # 3. Hardlink на каталог (должен упасть) ln /tmp/a /tmp/b # → Operation not permitted ``` ⚠️ Тестовые `docker run` тянут образы — гонять их на Mac, не на слабом хосте (Kraken: buildkit-кэш и слои лягут на диск зря). --- ## 7. Что делать в реальном проекте 1. **Внешние каталоги** — только через **`-v` в compose** + `docker compose up -d`. Манифест монтажей (список `name` + `host_path`) генерирует `compose.override.yaml` — composefile не правится скриптом вручную. 2. **auth/секреты CLI — на хосте, ВНЕ рабочего каталога агента.** Иначе агент, которому рабочий каталог смонтирован на запись, получает доступ к своим же токенам. Монтировать отдельным `-v`. 3. **Шары** — `rsync` в локальную папку, не прямой mount. 4. **Hardlink** — только файлы внутри одного тома, с проверкой `st_dev`. 5. **Не строить архитектуру на live-mount'ах** — на macOS это тупик. --- ## 8. ⚠️ Реализация на macOS: `docker-compose`, а не `docker compose` Проверено 2026-09-26 на этой машине (Docker CLI 29.4.1, macOS 26.6.1, Apple Silicon). Compose здесь — **отдельный бинарь**, не подкоманда `docker`: | Что | Значение | |---|---| | CLI | `/opt/homebrew/bin/docker` (v29.4.1) | | buildx | плагин, симлинк в `~/.docker/cli-plugins/docker-buildx` | | compose | `/opt/homebrew/bin/docker-compose` (**v5.1.3**), симлинк в `/opt/homebrew/lib/docker/cli-plugins/` | ```bash docker compose config # → docker: unknown command: docker compose docker compose config --quiet # → unknown flag: --quiet docker-compose config # ✅ работает docker-compose config --services ``` 📌 **Всегда `docker-compose` (с дефисом) в этом окружении.** Сообщение об ошибке выглядит как «docker не знает такой команды», а не «compose не установлен» — легко принять за сломанный Docker. ⚠️ Дефолтный билдер (`default` → `/var/run/docker.sock`) в статусе **`error`**. Для arm64-сборок использовать именованный билдер: ```bash docker buildx build --builder arm64builder --platform linux/arm64 \ --provenance=false --load -t . ``` ### Порядок файлов при генерации override `compose.override.yaml` подхватывается compose **автоматически**, но если он ссылается на несуществующий путь на хосте, `up` падает на монтировании. Поэтому порядок строго такой: ``` 1. .env ← env_file, иначе up падает сразу 2. agents-mounts.yaml ← манифест (можно пустой: mounts: []) 3. tools/render-mounts.py ← → compose.override.yaml 4. docker-compose up -d ``` ### Ловушка: `deploy.resources.limits` в не-swarm compose Секция `deploy.resources.limits.memory` **молча игнорируется** запуском через `docker-compose up` (это формат swarm). Лимит создаёт ложное ощущение защиты — что-то ограничивать нужно иначе (cgroups напрямую или `mem_limit`, зависят от версии). --- ## Связанные заметки - [[personal/plans/balda-mm-phase4-agents]] — применение этих выводов (Фаза 4) - [[personal/projects/balda/balda-mm-delegation-plan]] — общий план проекта - скилл `eagle-dashboard` — сервис-реестр, health-типы, launchd - скилл `docker-service-administration` — lifecycle контейнеров