SSD Nodes Learn Hosting plans →
Посібники Matt ConnorВід Matt Connor · Оновлено 2026-08-28

Docker Compose: bind mount чи іменований том

Порівняйте bind mount та іменований том у Docker Compose: для конфігурацій і даних, помилки прав доступу, а також перевірка, резервне копіювання та міграція.

Bind mount чи іменований том: коротка відповідь

Томи Docker Compose бувають двох типів, і вибір залежить від того, хто відповідає за файли. Використовуйте bind mount для файлів, які ви самостійно записуєте та читаєте, наприклад конфігурацій, шаблонів і статичних сайтів. Використовуйте іменований том для даних, за які відповідає застосунок, наприклад файлів бази даних, пошукових індексів і завантажених медіафайлів. Bind mount вказує на шлях на хості, який можна відкрити в редакторі. Іменований том — це сховище, яке Docker створює та відстежує, а доступ до нього ви отримуєте через Docker.

Обидва типи задаються під одним ключем volumes: усередині сервісу, тому їх часто плутають. Відмінність визначає ліва частина двокрапки. Якщо ліва частина починається з . або /, це шлях на хості, тобто bind mount. Будь-яке інше значення є ім’ям, тобто іменованим томом, і це ім’я також потрібно оголосити у блоці volumes: верхнього рівня.

Два синтаксиси у compose-файлі

services:
  db:
    image: postgres:17
    environment:
      POSTGRES_PASSWORD: changeme
    volumes:
      - pgdata:/var/lib/postgresql/data
  web:
    image: nginx:1.27
    volumes:
      - ./nginx.conf:/etc/nginx/nginx.conf:ro
      - ./site:/usr/share/nginx/html:ro

volumes:
  pgdata:

pgdata:/var/lib/postgresql/data — це іменований volume. ./nginx.conf:/etc/nginx/nginx.conf — bind mount, а :ro монтує його лише для читання. Це правильне значення за замовчуванням для конфігурації, яку контейнер не повинен перезаписувати. Якщо пропустити запис volumes: на верхньому рівні, Compose завершить роботу з service "db" refers to undefined volume pgdata.

Запустіть стек і перегляньте створені Docker об’єкти:

docker compose up -d
docker volume ls

Volume називається не pgdata. Його назва — <project>_pgdata, оскільки за замовчуванням назва проєкту відповідає назві каталогу, у якому міститься compose-файл. Каталог із назвою myapp дає myapp_pgdata. Це важливо, оскільки після перейменування каталогу ви отримаєте новий порожній volume, і може здатися, що застосунок утратив дані. Насправді старий volume усе ще відображається командою docker volume ls. Зафіксуйте назву за допомогою name: у compose-файлі або задайте COMPOSE_PROJECT_NAME, якщо каталог може змінитися. Такі параметри слід зберігати разом з іншими файлами середовища та секретами Compose.

Чому помилки доступу виникають лише для bind mount

Це найважливіша практична відмінність. Вона випливає з одного правила: іменований volume під час першого використання заповнюється даними з image, якщо спочатку порожній, а bind mount — ніколи.

Коли Docker монтує порожній іменований volume поверх каталогу, який уже містить дані в image, він копіює ці дані у volume разом із власником і правами, заданими в image. В офіційному image Postgres /var/lib/postgresql/data належить користувачу postgres, тому volume отримує той самий числовий ідентифікатор власника, і база даних запускається.

Bind mount працює навпаки. Контейнер бачить саме те, що є на host, разом із власником, а вміст image за цим шляхом приховується. Якщо каталог на host не існує, Docker daemon створює його. Daemon працює від root, тому каталог стає власністю root:root. Процес у контейнері, який працює від non-root користувача, не може записувати в цей каталог:

PermissionError: [Errno 13] Permission denied: '/data/app.db'

Потрібно узгодити числові ідентифікатори. Власник у bind mount порівнюється за числовим user id, а не за іменем, оскільки контейнер має власний /etc/passwd. Ім’я користувача app усередині контейнера нічого не означає на host. Uid 1000 означає uid 1000 з обох боків.

id -u
mkdir -p ./data
docker compose exec web id
sudo chown -R 1000:1000 ./data

docker compose exec web id виводить uid, від якого фактично працює процес у контейнері. Змініть власника каталогу на host відповідно до цього числа або зафіксуйте контейнер на своєму числовому ідентифікаторі за допомогою user: "1000:1000" у service. Фіксування user: — чистіший варіант для застосунку, який ви написали самостійно. Зміна власника каталогу на host безпечніша для image, який ви не розробляли, оскільки деякі image запускають entrypoint від root, знижують привілеї та очікують певного власника для вкладених каталогів.

Варто знати ще про дві проблеми. У Fedora, RHEL та інших системах із увімкненим SELinux (security-enhanced Linux) доступ через bind mount заборонено, доки для нього не встановлено правильні security labels. Тому додайте :z для шляху, спільного для кількох контейнерів, або :Z для шляху, який має використовувати лише один контейнер, у форматі - ./data:/data:Z. Bind mount окремого файлу, а не каталогу, також перестає працювати, якщо редактор замінює файл замість запису в нього безпосередньо, оскільки mount прив’язаний до початкового inode. Контейнер продовжує бачити старий вміст, доки ви не перезапустите його. Якщо файл часто редагується, монтуйте батьківський каталог.

Продуктивність: де різниця справді помітна

На Linux-сервері обидва типи проходять через той самий шлях у ядрі, тому різниця в пропускній здатності настільки мала, що не варто вибирати тип на цій підставі. Іменовані volumes, які використовують стандартний драйвер local, зберігаються в тій самій файловій системі, що й решта Docker, у каталозі /var/lib/docker/volumes/. Bind mount зберігається там, куди ви його вказали.

Різниця стає помітною в Docker Desktop для macOS і Windows, де контейнери працюють усередині віртуальної машини. У такому разі bind mount проходить із файлової системи хоста до цієї віртуальної машини через шар спільного доступу до файлів. Навантаження з великою кількістю операцій над малими файлами, наприклад дерево залежностей Node.js або кеш PHP-фреймворку, помітно сповільнюються. Іменовані volumes залишаються всередині віртуальної машини й не мають цих витрат. Тому в багатьох development compose-файлах вихідний каталог підключають через bind mount, а для node_modules оголошують іменований volume.

Інша реальна відмінність — місце зберігання даних. Bind mount до /mnt/backup записує дані на цей диск. Іменований volume зберігає дані у файловій системі, де розташований /var/lib/docker; на VPS це зазвичай root-диск. База даних, яка збільшується всередині іменованого volume, заповнює той самий диск, де зберігаються системні журнали. Перевірте це до виникнення інциденту:

docker system df -v
df -h /var/lib/docker

docker system df -v перелічує всі volumes із зазначенням їхнього розміру та позначає ті, на які більше не посилається жоден контейнер.

Перевірка іменованого тому

Іменований том не є «чорною скринькою». Запитайте Docker, де він розташований:

docker volume inspect myapp_pgdata

Поле Mountpoint містить фактичний шлях на хості, зазвичай /var/lib/docker/volumes/myapp_pgdata/_data. Його можна прочитати за допомогою sudo ls, і це зручно для швидкої перевірки. Не редагуйте файли безпосередньо в цьому каталозі. Запис від імені root знову спричинить проблему з правами власника, описану вище. Крім того, цей шлях є особливістю драйвера local, якої немає в інших драйверах томів.

Безпечний спосіб переглянути вміст — використати тимчасовий контейнер, змонтований до тому:

docker run --rm -v myapp_pgdata:/vol alpine ls -la /vol

Цей спосіб працює з будь-яким драйвером, показує ті самі права доступу, які бачить реальний контейнер, і не залишає нічого після завершення завдяки --rm.

Резервне копіювання кожного типу

Bind mount — це звичайний каталог, тому його обробляє будь-який інструмент резервного копіювання на рівні файлів. Вкажіть для резервного копіювання шлях на хості — і все готово. Для іменованого volume потрібен додатковий крок, оскільки інструмент має отримати до нього доступ. Підключіть volume і каталог на хості до одного тимчасового контейнера, а потім створіть архів:

docker run --rm \
  -v myapp_pgdata:/data:ro \
  -v "$PWD":/backup \
  alpine tar czf /backup/pgdata.tar.gz -C /data .

Відновлюйте дані у новий volume у зворотному порядку:

docker volume create myapp_pgdata_restored
docker run --rm \
  -v myapp_pgdata_restored:/data \
  -v "$PWD":/backup \
  alpine tar xzf /backup/pgdata.tar.gz -C /data

tar зберігає числові права власності, коли команда виконується від імені root усередині контейнера. Саме це забезпечує коректну роботу відновленого volume із застосунком.

Це застереження стосується обох типів. Копіювання файлів бази даних під час її роботи створює архів даних, які змінюються, і під час відновлення це може призвести до пошкодженого стану. Спочатку зупиніть сервіс або створіть дамп за допомогою штатного інструмента бази даних, як у docker compose exec -T db pg_dump -U postgres appdb > appdb.sql. У результаті ви отримаєте звичайний файл, який можна додати до стандартної зашифрованої процедури резервного копіювання restic разом із файлами compose.

Перенесення bind mount до named volume

Перенесення виконує копіювання, а не перейменування, і займає приблизно хвилину.

docker compose down
docker volume create myapp_pgdata
docker run --rm \
  -v "$PWD/data":/from \
  -v myapp_pgdata:/to \
  alpine sh -c 'cp -a /from/. /to/'

cp -a зберігає власника, режими доступу та часові мітки, тому користувач контейнера, який мав доступ до старого каталогу, і надалі зможе читати новий volume. Потім змініть конфігурацію сервісу, щоб він використовував pgdata:/var/lib/postgresql/data, додайте pgdata до верхньорівневого блоку volumes:, виконайте docker compose up -d і перегляньте журнали застосунку, перш ніж видаляти старий каталог. Для зворотного перенесення використовуйте ту саму команду, помінявши місцями /from і /to.

Під час тестування пам’ятайте про одну річ. docker compose down не змінює named volumes, але docker compose down -v видаляє кожен named volume, оголошений проєктом, і скасувати цю дію неможливо. Bind mount переживає обидві операції, оскільки Docker ніколи не володів цим каталогом. Якщо команди керування життєвим циклом ще незнайомі, у посібнику з основ Docker Compose для VPS описано їх використання.

Вибір для кожного сервісу

З’ясуйте, хто записує файл. Конфігурація, яку ви редагуєте в текстовому редакторі та фіксуєте в git, має зберігатися в bind mount, змонтованому через :ro, оскільки ви хочете бачити ці файли та зберігати їх у системі контролю версій. Стан застосунку, який ви ніколи не редагуєте вручну, має зберігатися в іменованому томі, оскільки Docker правильно налаштовує права доступу, а дані не залежать від шляху на хості.

Змішаний випадок — медіафайли. Фототека записується застосунком, але також керується вами, і часто має такий великий розмір, що для неї потрібен окремий диск. Підключіть її через bind mount до шляху на цьому диску та один раз явно налаштуйте власника. Саме до такої схеми зазвичай приходять self-hosted стеки: іменовані томи для баз даних і кешів, bind mount для конфігурації та для великого каталогу, який для вас важливий. Система підтримки на кшталт Chatwoot, запущеного на VPS працює саме за такою схемою: Postgres зберігається в іменованому томі, а завантажені вкладення — за шляхом, для якого можна налаштувати резервне копіювання.

FAQ

Яка різниця між bind mount і named volume?

Bind mount підключає шлях на хості до контейнера, тому обидві сторони бачать той самий каталог, а редагувати його можна звичайними інструментами. Named volume — це сховище, яке Docker створює та обслуговує; на нього посилаються за іменем і оголошують у блоці верхнього рівня volumes:. Практична відмінність полягає у володінні даними: bind mount використовують для конфігурації, яку обслуговуєте ви, а named volume — для даних, якими керує застосунок.

Чому з bind mount я отримую "permission denied", а з named volume — ні?

Порожній named volume заповнюється з image, тому успадковує права власника, задані в image, і користувач контейнера може записувати до нього. Bind mount показує каталог хоста без змін. Якщо Docker довелося створити цей каталог, він призначив його власником root. Виконайте docker compose exec <service> id, щоб переглянути числовий ідентифікатор, який використовує контейнер, а потім sudo chown -R <uid>:<gid> для каталогу на хості або задайте user: "1000:1000" для сервісу.

Де Docker зберігає named volumes на диску?

За використання стандартного драйвера local вони зберігаються в /var/lib/docker/volumes/<volume>/_data, а docker volume inspect <volume> виводить точний Mountpoint. Переглядайте його, якщо потрібно щось перевірити, але записуйте до нього лише через контейнер, оскільки редагування від імені root на хості змінює власника так, як контейнер не очікує.

Як створити резервну копію named volume?

Запустіть короткоживучий контейнер, до якого підключені і volume, і каталог хоста, а потім заархівуйте дані з одного до іншого за допомогою docker run --rm -v myvol:/data:ro -v "$PWD":/backup alpine tar czf /backup/myvol.tar.gz -C /data .. Для бази даних використовуйте її власний інструмент дампування, а не копіюйте файли під час роботи. Копіювання файлів у момент активного запису може призвести до відновлення пошкодженого стану.

Чи видаляє docker compose down мої volumes?

docker compose down видаляє контейнери й мережі, але залишає named volumes. docker compose down -v також назавжди видаляє всі named volumes, оголошені проектом. Bind mounts не видаляються жодною з цих команд, оскільки каталог належить хосту, а не Docker.