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 — іменований том. ./nginx.conf:/etc/nginx/nginx.conf — монтування bind, а :ro монтує його лише для читання. Це правильне значення за замовчуванням для конфігурації, яку контейнер не повинен перезаписувати. Якщо пропустити запис верхнього рівня volumes:, Compose завершить роботу з помилкою service "db" refers to undefined volume pgdata.
Запустіть його та перегляньте створені Docker об’єкти:
docker compose up -d
docker volume lsТом називається не pgdata. Його назва — <project>_pgdata, оскільки за замовчуванням назва проєкту відповідає назві каталогу, у якому міститься файл compose. Каталог із назвою myapp дає myapp_pgdata. Це важливо, оскільки перейменування каталогу створює новий порожній том, і здається, що застосунок втратив дані. Насправді старий том і далі відображається виводом docker volume ls. Зафіксуйте назву за допомогою name: у файлі compose або задайте COMPOSE_PROJECT_NAME, якщо каталог може змінитися. Такі параметри слід зберігати разом з іншими файлами середовища та секретами Compose.
Чому помилки доступу виникають лише для bind mount
Це головна практична відмінність. Вона випливає з одного правила: іменований том, який порожній під час першого використання, заповнюється з образу, а bind mount — ніколи.
Коли Docker монтує порожній іменований том поверх каталогу, який уже містить дані в образі, він копіює ці дані в том разом із правами власника та режимами доступу, заданими в образі. Офіційний образ Postgres постачає /var/lib/postgresql/data, власником якого є його власний користувач postgres. Тому том отримує той самий числовий ідентифікатор власника, і база даних запускається.
Bind mount працює навпаки. Контейнер бачить те, що є на хості, включно з інформацією про власника, а вміст образу за цим шляхом приховується. Якщо каталогу на хості немає, Docker daemon створює його. Оскільки daemon працює від імені root, каталог стає власністю root:root. Процес у контейнері, який працює від імені непривілейованого користувача, не може записувати до нього:
PermissionError: [Errno 13] Permission denied: '/data/app.db'Виправлення полягає в узгодженні числових ідентифікаторів. Власність у bind mount порівнюється за числовим ідентифікатором користувача, а не за іменем, оскільки контейнер має власний /etc/passwd. Користувач із іменем app у контейнері нічого не означає на хості. Uid 1000 означає uid 1000 з обох боків.
id -u
mkdir -p ./data
docker compose exec web id
sudo chown -R 1000:1000 ./datadocker compose exec web id виводить uid, від імені якого фактично працює процес у контейнері. Призначте каталогу на хості цей ідентифікатор або зафіксуйте для контейнера свій ідентифікатор за допомогою user: "1000:1000" у service. Фіксування user: є чистішим рішенням для застосунку, який ви написали самостійно. Зміна власника каталогу на хості є безпечнішою для образу, якого ви не писали, оскільки деякі образи запускають entrypoint від імені root, знижують привілеї та очікують певного власника для внутрішніх файлів і каталогів.
Варто знати ще про дві проблеми. У Fedora, RHEL та інших системах із примусовим режимом SELinux (security-enhanced Linux) доступ до bind mount заборонено, доки для нього не змінено мітки безпеки. Тому для шляху, спільного для кількох контейнерів, додайте :z, а для шляху, який має використовувати лише один контейнер, — :Z, записавши параметр як - ./data:/data:Z. Крім того, bind mount окремого файлу, а не каталогу, перестає працювати, якщо редактор замінює файл замість запису безпосередньо до нього, оскільки mount прив’язаний до початкового inode. Контейнер продовжує бачити старий вміст, доки ви не перезапустите його. Якщо файл часто редагують, монтуйте батьківський каталог.
Продуктивність: де різниця справді помітна
На Linux-сервері обидва типи проходять через той самий шлях у ядрі, тому різниця у пропускній здатності незначна. Не вибирайте тип сховища лише на цій підставі. Іменовані томи, що використовують стандартний драйвер local, розташовані в тій самій файловій системі, що й решта Docker, у каталозі /var/lib/docker/volumes/. Bind mount розташований там, куди ви його спрямували.
Різниця помітна в Docker Desktop для macOS і Windows, де контейнери працюють усередині віртуальної машини. Bind mount у цьому випадку проходить із файлової системи хоста до цієї віртуальної машини через рівень спільного доступу до файлів. Робочі навантаження з великою кількістю операцій над невеликими файлами, наприклад дерево залежностей Node.js або кеш PHP-фреймворку, помітно сповільнюються. Іменовані томи залишаються всередині віртуальної машини й не мають цих витрат. Тому в багатьох файлах compose для розробки каталог із вихідним кодом монтують через bind mount, а поверх node_modules оголошують іменований том.
Інша реальна відмінність полягає в тому, де зберігаються дані. Bind mount до /mnt/backup записує дані на цей диск. Іменований том розташовується у файловій системі, яка містить /var/lib/docker. На VPS це зазвичай кореневий диск. База даних, що зростає в іменованому томі, заповнює той самий диск, на якому зберігаються системні журнали. Перевірте це до виникнення інциденту:
docker system df -v
df -h /var/lib/dockerdocker system df -v виводить список усіх томів із зазначенням їхнього розміру та позначає ті, на які більше не посилається жоден контейнер.
Перевірка іменованого тому
Іменований том не є «чорною скринькою». Запитайте 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 є звичайним каталогом, тому будь-який інструмент для резервного копіювання на рівні файлів уже його обробляє. Укажіть для резервного копіювання шлях на хості — і все готово. Для іменованого тому потрібен додатковий крок, оскільки інструменту потрібно отримати до нього доступ. Підключіть том і каталог на хості до одного короткочасного контейнера, а потім створіть архів:
docker run --rm \
-v myapp_pgdata:/data:ro \
-v "$PWD":/backup \
alpine tar czf /backup/pgdata.tar.gz -C /data .Відновіть дані, виконавши операцію у зворотному напрямку до нового тому:
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 /datatar зберігає числові атрибути власника, коли виконується від імені root усередині контейнера. Саме це забезпечує придатність відновленого тому для використання застосунком.
Це попередження стосується обох типів. Копіювання файлів бази даних під час її роботи створює архів даних, які змінюються, тому відновлення може завершитися пошкодженим станом. Спочатку зупиніть службу або створіть дамп за допомогою власного інструмента бази даних, як у docker compose exec -T db pg_dump -U postgres appdb > appdb.sql. У результаті буде отримано звичайний файл, який можна додати до стандартної зашифрованої процедури резервного копіювання restic разом із файлами compose.
Перенесення bind mount до іменованого тому
Перенесення — це копіювання, а не перейменування. Воно триває приблизно хвилину.
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 зберігає власника, права доступу та часові мітки. Тому користувач контейнера, який мав доступ до старого каталогу, і надалі матиме доступ до нового тому. Потім змініть конфігурацію сервісу, щоб використовувати pgdata:/var/lib/postgresql/data, додайте pgdata до блоку верхнього рівня volumes:, виконайте docker compose up -d і перегляньте журнали застосунку, перш ніж видаляти старий каталог. У зворотному напрямку використовується та сама команда, але з поміняними місцями /from і /to.
Під час тестування пам’ятайте про одне. docker compose down не змінює іменовані томи, а docker compose down -v видаляє всі іменовані томи, оголошені проєктом, і скасувати цю дію неможливо. Bind mount зберігається в обох випадках, оскільки Docker ніколи не володів цим каталогом. Якщо команди керування життєвим циклом ще незнайомі, у посібнику з основ Docker Compose для VPS описано порядок їх використання.
Вибір для кожного сервісу
Визначте, хто записує файл. Конфігурація, яку ви редагуєте в текстовому редакторі та фіксуєте в git, має зберігатися в bind mount, підключеному через :ro, оскільки вона має бути доступною та версійованою. Стан застосунку, який ви ніколи не відкриваєте вручну, має зберігатися в іменованому томі, оскільки Docker правильно налаштовує дозволи, а дані не залежать від шляху на хості.
Медіафайли — проміжний випадок. Фототеку записує застосунок, але керуєте нею також ви. Вона часто достатньо велика, щоб розмістити її на окремому диску. Підключіть її через bind mount до шляху на цьому диску та один раз явно налаштуйте власника. Саме до такого підходу зазвичай приходять у більшості стеків для self-hosted систем: іменовані томи використовують для баз даних і кешів, а bind mount — для конфігурації та великого каталогу, який для вас важливий.
FAQ
У чому різниця між bind mount і іменованим томом?
bind mount підключає шлях на хості до контейнера. Обидві сторони бачать той самий каталог, і його можна редагувати звичайними інструментами. Іменований том — це сховище, яке Docker створює та керує ним. На нього посилаються за іменем і оголошують у блоці верхнього рівня volumes:. Практична відмінність полягає у відповідальності за дані: bind mount використовують для конфігурації, яку обслуговуєте ви, а іменовані томи — для даних, якими керує застосунок.
Чому під час використання bind mount я отримую помилку "permission denied", а з іменованим томом — ні?
Порожній іменований том заповнюється з образу. Тому він успадковує права власності, задані в образі, і користувач контейнера може записувати до нього. bind mount показує каталог хоста без змін. Якщо Docker мав створити цей каталог, він створив його власником root. Виконайте docker compose exec <service> id, щоб побачити числовий ідентифікатор, який використовує контейнер, потім sudo chown -R <uid>:<gid> для каталогу на хості або задайте user: "1000:1000" для сервісу.
Де Docker зберігає іменовані томи на диску?
За використання драйвера local за замовчуванням вони зберігаються в /var/lib/docker/volumes/<volume>/_data, а docker volume inspect <volume> виводить точний Mountpoint. Переглядайте його, якщо потрібно щось перевірити, але записуйте до нього лише через контейнер. Редагування від імені root на хості змінює права власності так, як контейнер не очікує.
Як створити резервну копію іменованого тому?
Запустіть тимчасовий контейнер, підключивши до нього і том, і каталог хоста. Потім заархівуйте дані з одного розташування в інше за допомогою docker run --rm -v myvol:/data:ro -v "$PWD":/backup alpine tar czf /backup/myvol.tar.gz -C /data .. Для бази даних використовуйте її власний інструмент дампа, а не копіюйте активні файли. Копія файлів, створена під час запису, може відновитися в пошкоджений стан.
Чи видаляє docker compose down мої томи?
docker compose down видаляє контейнери та мережі, але залишає іменовані томи. docker compose down -v також видаляє кожен іменований том, оголошений у проєкті, без можливості відновлення. bind mount не видаляється жодною з цих команд, оскільки цей каталог належить хосту, а не Docker.