Короткий ответ
Сначала отличите named volume app_data от bind (/opt/app/data:/var/lib/app). Ошибка mount — это source на хосте, тип, readonly, а на хостах с SELinux ещё метки :z/:Z. Ubuntu 22.04/24.04 по умолчанию AppArmor, не SELinux: :z там обычно безвреден, но копипаста из RHEL без понимания путает. Пустой каталог в контейнере часто значит, что Docker создал директорию вместо отсутствующего файла-bind или вы смотрите не тот volume. Не лечите docker volume rm app_data.
Права UID внутри уже смонтированного тома — соседняя статья: права на volume.
Симптомы и как отличить
Error response from daemon: invalid mount config for type "bind": bind source path does not exist;- контейнер стартует,
/var/lib/appпустой, на хосте данные в другом пути; stat /opt/app/nginx.confна хосте — файл, в контейнере по пути — directory;- на Rocky/RHEL: AVC, на Ubuntu этого нет.
| Тип в inspect | Source | Риск rm |
|---|---|---|
| volume | /var/lib/docker/volumes/app_data/_data | потеря данных |
| bind | путь хоста как есть | rm контейнера не удалит хост |
| tmpfs | память | нет персиста |
Возможные причины
- Относительный bind
./dataот другого cwd, не от/opt/app. - Файл-источник не существовал — Docker создал каталог.
- Путаница
volumes: - app_data:/datavs./app_data:/data(named vs bind). longsyntaxtype: bindбезbind.create_host_pathна 27.x при отсутствии пути — ошибка.- SELinux (не Ubuntu) без
:zна bind. - NFS-путь хоста не смонтирован, bind на пустой mountpoint.
- Редко:
consistency/propagationдля shared mounts.
Диагностика
1. Что реально смонтировано
cd /opt/app
docker compose config --volumes
docker inspect app-web-1 --format '{{json .Mounts}}' | python3 -m json.toolСмотрите Type, Source, Destination, RW, Propagation.
2. Source на хосте
ls -ld /opt/app/data /opt/app/nginx.conf /var/lib/docker/volumes/app_data/_data 2>&1
stat /opt/app/nginx.conf 2>&1Если хостовый nginx.conf — directory, вы уже в ловушке bind-файла. Не копируйте туда бинарник поверх, пока не разберётесь.
3. Имя volume
docker volume ls | grep app
docker volume inspect app_data
docker volume inspect app_app_dataCompose без name: app_data создаёт app_app_data (проект + ключ). YAML ссылается на ключ app_data, API — на полное имя. Путаница «volume не тот».
4. SELinux vs Ubuntu
getenforce 2>/dev/null || echo no_selinux
aa-status 2>/dev/null | headgetenforce нет или Disabled — не вините :z. AVC на Ubuntu не появятся; будет AppArmor DENIED в journal.
Решение
Сценарий A. Bind path does not exist
Создайте правильный тип объекта:
sudo mkdir -p /opt/app/data
# файл, не каталог:
sudo test -f /opt/app/nginx.conf || sudo install -m 644 /dev/null /opt/app/nginx.confПотом recreate. Если Docker уже создал директорию вместо файла:
sudo rmdir /opt/app/nginx.conf # только если пустая директория-ошибка
sudo install -m 644 /opt/app/nginx.conf.real /opt/app/nginx.confНе rm -rf каталога с данными.
Сценарий B. Нужен named volume app_data
services:
web:
volumes:
- app_data:/var/lib/app
volumes:
app_data:
name: app_dataname: фиксирует имя без префикса проекта — удобно для бэкапа. Создание:
docker compose up -d
docker volume inspect app_data --format '{{.Mountpoint}}'Пустой новый volume — норма, пока приложение не запишет. Не копируйте в Mountpoint в обход, если контейнер запущен.
Сценарий C. Смотрите не тот каталог
Данные лежат в bind /opt/app/data, а вы inspect named. Выровняйте YAML. Миграция bind→named — копирование в окно, см. резерв volumes.
Сценарий D. SELinux (RHEL-совместимый хост)
Bind:
volumes:
- /opt/app/data:/var/lib/app:Z:z — shared label между контейнерами, :Z — private. На Ubuntu это не лечение Permission denied от UID.
Сценарий E. NFS mountpoint пустой
Сначала findmnt /srv/nfs/app, потом bind. Docker смонтирует пустой dir и замаскирует поздний nfs mount (классика). Порядок: сеть/NFS, затем docker.
На Ubuntu 22.04/24.04 bind на NFS-mountpoint, который ещё не смонтировался, даёт «тихий» пустой каталог: Docker не проверяет, что за ФС под путём. После mount -a данные NFS появляются на хосте, но контейнер продолжает видеть тот каталог, который был в момент docker start — это тот же inode mountpoint, уже с содержимым, если mount был поверх. Если же docker стартовал раньше autofs и записал свои файлы в локальный dir, поздний NFS скроет их. Порядок в systemd: RequiresMountsFor= на unit docker или drop-in, который After=remote-fs.target, плюс не класть app_data на автомонтируемый путь без зависимостей.
Относительные bind в compose считаются от файла compose в современных Compose v2, но systemd WorkingDirectory= для обёртки docker compose up всё ещё путает людей: они запускают те же YAML из /root руками и получают другой ./data. Держите абсолютные пути в проде. Проверка: docker inspect → Source должен быть каноническим /opt/app/data, не /root/data.
Не копируйте содержимое named volume через cp на живом Postgres. Для миграции bind↔named используйте stop и tar, как в процедуре резервирования. consistency: delegated в YAML на Linux Engine не меняет семантики и не лечит «не монтируется».
Как проверить, что проблема устранена
docker inspect app-web-1 --format '{{range .Mounts}}{{.Type}} {{.Source}} -> {{.Destination}} rw={{.RW}}{{"\n"}}{{end}}'
docker compose exec web ls -la /var/lib/app | head
touch /tmp/aaadmin-vol-test
# только если это bind тестовый каталог, не прод-БД:
docker compose exec web test -d /var/lib/app && echo mount_okФайл, созданный приложением, виден на Source хоста (для bind) или в volume inspect Mountpoint (для named, осторожно с consistency).
Если не помогло
- Mount ок, запись нет — UID/GID, не тип volume.
read-only file system—:roв YAML или XFS mount usero.- Windows-путь в YAML на Linux (
C:\) — не тот compose. subpathв Compose: проверьте версию плагина; старый v2 без subpath даст странные ошибки.- Контейнер сразу выходит из-за пустого конфига — после фикса mount читайте stderr.
Профилактика
- В репозитории README: named vs bind, абсолютные пути.
name: app_dataдля продакшен-данных.- Запрет относительных bind в systemd
WorkingDirectory≠/opt/app. - Чеклист: файл vs директория для конфигов.
- Бэкап volume до любых
down -v.
FAQ
Почему volume пустой после up?
Новый named volume. Старые данные могли быть в другом имени (app_app_data). Inspect ls.
bind быстрее named?
Не аргумент для прода. Named проще бэкапить штатно и переживает смену пути проекта.
Нужен ли :delegated?
Это Docker Desktop/macOS. На Ubuntu Engine 27 игнорируйте копипасту.
Можно ли rm контейнера, том останется?
Да для named. Bind вообще не в Docker lifecycle. down -v — нет, том умрёт.
AppArmor vs :z
Разные MAC. На Ubuntu DENIED смотрите journal AppArmor, не SELinux-метки.