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

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

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

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

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

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

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

Виправлення полягає в тому, щоб узгодити числові id. Власник у bind mount порівнюється за числовим user id, а не за іменем, оскільки в контейнера є власний /etc/passwd. Користувач із назвою app у контейнері нічого не означає на host. Uid 1000 означає uid 1000 з обох боків, якщо daemon не працює з user-namespace remapping (userns-remap, заданим у /etc/docker/daemon.json або в командному рядку dockerd). Remapping переміщує кожен id контейнера в subordinate range на host. У прикладі Docker із range, що починається з 231072, uid 0 контейнера відповідає uid 231072 на host, тому uid 1000 контейнера відповідає uid 232072 на host.

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

docker compose exec web id виводить uid, від імені якого фактично працює процес контейнера. Без remapping призначте каталогу на host цей номер або зафіксуйте контейнер на своєму номері за допомогою user: "1000:1000" у service. Якщо remapping увімкнено, user: не може зафіксувати контейнер на вашому host number, оскільки заданий вами id також переміщується: user: "1000:1000" у цьому прикладі все одно відповідатиме uid 232072 на host. Натомість змініть власника каталогу на host на відповідний mapped host id. Ваш власний range указано в /etc/subuid і /etc/subgid. У документації Docker зазначено, що вимкнути remapping для одного контейнера можна лише за допомогою --userns=host у docker container create, run або exec. Для застосунку, який ви написали самі, чистішим рішенням буде зафіксувати user:. Для image, який ви не писали, безпечніше змінити власника каталогу на host, оскільки деякі image запускають entrypoint від імені root, знижують привілеї та очікують певного власника для внутрішніх каталогів.

Варто знати ще про дві проблеми. У Fedora, RHEL та інших системах, де ввімкнено enforcing для SELinux (security-enhanced Linux), bind mount блокується, доки для нього не буде змінено security label. Тому для шляху, спільного для кількох контейнерів, додайте :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, оскільки вона має бути доступною для перегляду та керування версіями. Стан застосунку, який ви ніколи не відкриваєте вручну, має зберігатися в named volume, оскільки Docker налаштовує права доступу на основі image, а дані не залежать від шляху на хості.

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

FAQ

У чому різниця між bind mount і named volume?

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

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

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

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

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

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

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

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

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

Чи була ця стаття корисною?