Docker и сервер

Docker bind-mount не видит правки: ловушка inode

Редактор сохраняет файл через rename и меняет inode, а mount держит старый. Почему HUP бесполезен и как обойтись без рестарта.

6 мин чтения

Симптом

Правите смонтированный в контейнер конфигурационный файл — nginx.conf, любой *.yml, — шлёте docker kill -s HUP для hot-reload, а изменения не применяются. Валидатор на хосте говорит OK, но контейнер ведёт себя по-старому: новых секций конфигурации внутри просто нет. Классическая ловушка bind-mount одиночного файла.

Механизм: mount держит inode, редактор его меняет

Docker bind-mount одного файла (-v /host/file.yml:/container/file.yml) привязан к inode, а не к пути. Почти все редакторы при сохранении пишут новый файл и делают rename — атомарная запись, у файла меняется inode. Старый inode, на который смотрит mount, остаётся со старым содержимым. SIGHUP заставляет процесс перечитать тот же смонтированный inode — и он честно видит старую конфигурацию.

Диагностика, подтверждающая гипотезу:

stat -c '%i' /host/path/app.yml                      # inode на хосте
docker exec <ctr> stat -c '%i' /etc/app/app.yml      # inode в контейнере
# разные числа → это оно
# контрольный: grep -c 'новый_кусок' внутри контейнера вернёт 0

Два выхода

  1. docker restart <ctr> — или docker compose up -d --force-recreate <svc>, что предпочтительнее для IaC. При старте mount резолвится по пути заново и подхватывает текущий inode. Минус — пауза сервиса около 10 секунд.
  2. In-place truncate-write — сохранить inode и обойтись reload без рестарта: записать в тот же файл через усечение, а не через rename:
c=$(sed 's/old/new/' file); printf '%s\n' "$c" > file
# перенаправление > усекает существующий inode, не создаёт новый
ls -i file   # inode до/после должен совпасть

Профилактика: монтировать директорию, а не одиночный файл (-v /host/dir:/container/dir) — тогда правки файлов внутри видны без рестарта.

Подводные камни

Вывод. Bind-mount одиночного файла держит inode, а атомарная запись редактора этот inode меняет — поэтому hot-reload видит старую конфигурацию при свежей правке на хосте. Лечение: docker restart / force-recreate либо in-place truncate-write, сохраняющий inode. Профилактика: монтировать директорию, а не файл.

Как подключиться за 2 минуты
Частые вопросы

Частые вопросы

Почему правки конфига не подхватываются после docker kill -s HUP?
Bind-mount одиночного файла привязан к inode, а не к пути. Редактор при сохранении делает rename и создаёт новый inode, а mount продолжает смотреть на старый — SIGHUP честно перечитывает старую версию.
Как убедиться, что дело именно в inode, а не в самой правке?
Сравнить stat -c '%i' на хосте и в контейнере через docker exec: если числа разные, гипотеза подтверждена. Контрольный grep нового куска конфигурации внутри контейнера при этом вернёт 0.
Как обновить конфиг без перезапуска сервиса?
Записать файл через усечение (перенаправление >), а не через rename редактора — это сохраняет прежний inode, и mount сразу видит новое содержимое.
Какой способ починки проще, но с паузой сервиса?
Docker restart или docker compose up -d --force-recreate — при старте mount резолвится по пути заново и подхватывает актуальный inode, но сервис останавливается примерно на 10 секунд.
Как избежать этой ловушки при следующих правках конфигов?
Монтировать в контейнер директорию, а не одиночный файл — тогда правки внутри видны без рестарта. Если монтируете именно файл, сразу планируйте restart или force-recreate и не пробуйте HUP вообще.
Похожее

Ещё в базе знаний

Подключите рабочий VPN

Инструкции базы знаний написаны под ключи VPN PRO: ключ выдаёт Telegram-бот за минуту, работоспособность гарантируем. Промокод VPN10 — 10 дней бесплатно, без карты.

Промокод VPN10 — 10 дней бесплатно · до 31 августа · передавайте друзьям