Files
obsidian-vault/personal/tech/macos-docker-virtiofs-mount-limits.md
T

168 lines
9.3 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# macOS Docker Desktop — что НЕ проезжает в контейнер (virtiofs)
> Создан 2026-09-26. Статус: **проверено живыми тестами** (alpine:3.20, linux/arm64,
> Docker Desktop на macOS 26.6.1, Apple Silicon).
> Тема: границы проброса путей из мира macOS в контейнер.
> Все выводы получены прогонами, не по памяти. Команды воспроизведения — в §5.
---
## 0. Главный вывод одной строкой
На macOS 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 это тупик.
---
## Связанные заметки
- [[personal/plans/balda-mm-phase4-agents]] — применение этих выводов (Фаза 4)
- [[personal/projects/balda/balda-mm-delegation-plan]] — общий план проекта
- скилл `eagle-dashboard` — сервис-реестр, health-типы, launchd
- скилл `docker-service-administration` — lifecycle контейнеров