Запропонувати правкуПокращити цю статтюДопрацюйте відповідь до «Чим volume відрізняється від bind mount?». Ваші зміни проходять модерацію перед публікацією.Потрібне підтвердженняКонтентЩо ви змінюєте🇺🇸EN🇺🇦UAПереглядЗаголовок (UA)Коротка відповідь (UA)**Volume** це Docker-managed сховище з логічним іменем; **bind mount** це пряме відображення довільного host-шляху у container. Volume портативні і керовані; bind mount прив'язує тебе до конкретного шляху на хості. ```bash docker run -v mydata:/app/data myimg # named volume (Docker керує) docker run -v /home/me/code:/app/code myimg # bind mount (саме цей host-шлях) ``` **Головне:** named volume для прод-стану (бази, аплоади), Docker портативно, декларативно, бекапи/міграція простіші. Bind mount для dev-workflow, де редагуєш на хості і перезавантажуєш у container, і для кейсів, що потребують конкретного host-шляху.Показується над повною відповіддю для швидкого нагадування.Відповідь (UA)Зображення**Volume і bind mount** це два способи, якими Docker дає container доступ до даних поза шарами image. У використанні виглядають схоже, але відрізняються тим, до чого тебе прив'язують і як Docker їх обробляє. ## Теорія ### TL;DR - **Named volume** = Docker-managed директорія з логічним іменем. Живе під `/var/lib/docker/volumes/<name>/_data`. Створюється/знищується Docker. - **Bind mount** = пряме відображення host-шляху (будь-якого шляху, будь-де) у container. Docker лише монтує; host filesystem володіє життєвим циклом. - **tmpfs mount** = третій тип, тримається повністю у RAM. Volatile, швидкий, для secret і scratch. - Volume портативні між хостами (Docker їх перестворює на новій машині з твого compose-файлу). Bind mount залежать від точного layout host-filesystem. - Бери **volume** для персистентного стану застосунку (DB, аплоади). Бери **bind mount** для dev-workflow (live source mounting), config injection і спеціальних filesystem. ### Швидкий приклад ```bash # Volume: Docker сам керує, де він лежить $ docker run -d --name db1 \ -v pgdata:/var/lib/postgresql/data \ postgres:16 # Сховище: /var/lib/docker/volumes/pgdata/_data/ (managed by Docker) # Bind mount: ти показуєш на точний host-шлях $ docker run -d --name dev1 \ -v /home/me/projects/api/src:/app/src \ node:22 npm run dev # Сховище: /home/me/projects/api/src/ (твій код, що редагуєш у IDE) ``` Той самий флаг `-v`, дуже різні прив'язки. ### Таблиця порівняння | Аспект | Named volume | Bind mount | |---|---|---| | Ідентифікатор source | логічне ім'я (`pgdata`) | абсолютний host-шлях (`/home/me/x`) | | Місце зберігання | `/var/lib/docker/volumes/...` (Docker-managed) | де покажеш | | Створюється через | `docker volume create` або перший `docker run -v` | Docker лише монтує; ти сам створюєш директорії | | Життєвий цикл | Docker (`volume rm`, `volume prune`) | host (`rm -rf`, `mv`) | | Портативність між хостами | Висока, перестворюється на свіжому хості з compose | Низька, залежить від точного layout host'а | | Швидкість на Linux | Native filesystem speed | Native filesystem speed | | Швидкість на Mac/Win | Native (живе у VM) | Повільна (cross-VM-boundary syncing) | | Початкове копіювання з image | Так (image-вміст копіюється у порожній volume при першому mount) | Ні (mount це просто remap; image-вміст затіняється) | | Обробка permissions | Власник container; Docker керує UID/GID | Успадковує host-permissions; UID/GID mismatch поширений | | Підходить для | Прод-стан, декларативні ops | Live-reload dev, config injection, host-side editing | ### Як вони поводяться при «first mount with content» Це найтонша різниця. ```dockerfile FROM nginx:1.27-alpine # Image вже має /usr/share/nginx/html/index.html ``` ```bash # Volume mount: Docker копіює існуючі файли image у порожній volume $ docker run -d -v webroot:/usr/share/nginx/html nginx:1.27-alpine $ docker run --rm -v webroot:/data alpine ls /data index.html # ← дефолтний файл image тепер у volume # Bind mount: просто remap, image-вміст затінено $ mkdir /tmp/webroot # порожня $ docker run -d -v /tmp/webroot:/usr/share/nginx/html nginx:1.27-alpine $ docker exec <id> ls /usr/share/nginx/html # (порожньо, index.html image захований порожнім bind mount) ``` Volume mount: дружнє first-time копіювання. Bind mount: буквальне накладення. ### Коли брати volume - Стан бази у проді (Postgres, Mongo, Redis з AOF) - User uploads у веб-застосунку - Все, що має пережити `docker rm` і бути портативним на новий хост - Stateful навантаження у CI, де хочеш передбачуваний cleanup через `docker volume rm` ### Коли брати bind mount - **Локальний dev з живим source-кодом:** монтуй `./src` у container, щоб зміни файлів на лептопі одразу відображалися у запущеному застосунку. Уся історія `docker compose -f compose.dev.yaml up`. - **Інжект config-файлів:** монтуй один файл (`./nginx.conf:/etc/nginx/nginx.conf:ro`) у container без rebuild image. - **Sharing secret-файлу при runtime** у спосіб, що дозволяє ротувати без rebuild. - **Спеціальні filesystem:** NFS-mount, швидкий NVMe-пристрій, мережева розшарка, bind-mount host-шлях, що його вже має. ### Типові помилки **Bind-mount неіснуючого host-шляху** ```bash $ docker run -v /tmp/does-not-exist:/data alpine ls /data # (порожньо, Docker авто-створив /tmp/does-not-exist на host як root-owned) ``` Docker автоматично створює відсутні host-шляхи (як root). У результаті отримуєш зайву порожню директорію на host, яку можливо не хотів. **UID mismatch з bind mount** ```bash $ ls -ld /home/me/data drwx------ 2 me me 4096 ... # власник host UID 1000 $ docker run -v /home/me/data:/data alpine touch /data/x Файл створений, але з UID=root всередині container. # На host: /home/me/data/x належить UID 0, нечитабельний для `me`. ``` Container крутиться під root за замовчуванням, пише як UID 0. Host бачить ці файли як root-owned. Фікс через `--user $(id -u):$(id -g)` або явний `chown` у Dockerfile. **Повільні Mac/Windows білди через bind mount sync** Docker Desktop на macOS/Windows крутить Linux у VM. Bind mount перетинає host↔VM межу, що повільно для багатьох малих файлів (`node_modules`, привіт). Обхід: бери named volume для `node_modules` навіть у dev, або consistency-режими `:cached`/`:delegated`, або remote dev container. **Bind mount там, де volume справився б** ```bash # Якщо тобі не треба читати/писати з host, бери volume: $ docker run -v /opt/myapp/data:/data myapp # bind $ docker run -v myapp_data:/data myapp # volume, без host-прив'язки ``` Якщо host-шлях це просто сховище, до якого ти ніколи прямо не торкаєшся, volume чистіший: портативний, декларативний, без permission-сюрпризів. ### Реальне застосування - **Прод:** named volume для баз. Майже універсально. - **Локальний dev:** bind mount для source-коду (live reload), config-файлів; volume для персистентного стану сервісів (DB, кеш). - **CI runners:** bind mount workspace у build-container (`-v $PWD:/work`); ефемерний cleanup через `docker rm` плюс `--rm` на run. - **Інжект конфігурації:** read-only bind mount окремих config-файлів (`-v ./prometheus.yml:/etc/prometheus/prometheus.yml:ro`), редагуєш файл на host і рестартуєш container. ### Питання для поглиблення **Q:** Чи можна перейти з bind mount на volume без втрати даних? **A:** Так. Зупини container. `docker run --rm -v old-bind-host-path:/from -v newvol:/to alpine cp -a /from/. /to/`. Тоді стартуй новий container з `-v newvol:/data`. Volume тепер тримає те, що було у bind mount. **Q:** Volume швидші за bind mount? **A:** На Linux ні, обидва йдуть через ту саму filesystem. На macOS/Windows volume помітно швидші, бо живуть всередині Linux VM і уникають cross-boundary sync, який платить bind mount. **Q:** Як зробити бекап Docker volume? **A:** Підніми тимчасовий container з volume + tar bind-mounted назовні: ```bash docker run --rm -v pgdata:/data -v $PWD:/backup alpine \ tar czf /backup/pgdata.tar.gz -C /data . ``` Для bind mount просто `tar` host-шлях напряму, без Docker-посередництва. **Q:** Чи можу я монтувати read-only? **A:** Так, дописуй `:ro`. Працює для обох форм: `-v pgdata:/data:ro` або `-v ./conf:/etc/conf:ro`. Container не може писати у цей шлях. Корисно для config-файлів і shared read-only datasets. **Q:** (Senior) Коли вибір volume vs bind mount реально впливає на прод-архітектуру? **A:** Коли переходиш на multi-host оркестратори (Swarm, Kubernetes). Bind mount припускає конкретну host-filesystem, прив'язує навантаження до одного node. Named volume можуть мати driver-плагіни на network storage (NFS, EBS, Ceph), що переживають втрату node. Дизайн під портативні volume на ранньому етапі економить переписування при масштабуванні поза одним хостом. ## Приклади ### Dev compose з bind mount для коду, volume для стану ```yaml # compose.dev.yaml services: api: image: node:22-alpine working_dir: /app volumes: - ./api/src:/app/src # bind mount: живий код - ./api/package.json:/app/package.json:ro - api_node_modules:/app/node_modules # named volume: dep-кеш command: npm run dev db: image: postgres:16 environment: POSTGRES_PASSWORD: devpass volumes: - pgdata:/var/lib/postgresql/data # named volume: персистентний стан volumes: api_node_modules: pgdata: ``` Це типовий dev-сетап: **код через bind mount** (редагуєш на host, container одразу бачить зміни), **node_modules як named volume** (уникаємо повільного Mac/Win cross-boundary sync), **DB-дані як named volume** (чистий життєвий цикл). ### Read-only інжект конфігурації ```bash $ docker run -d \ -v ./prometheus.yml:/etc/prometheus/prometheus.yml:ro \ -v promdata:/prometheus \ -p 9090:9090 \ prom/prometheus ``` Config-файл: bind-mount, read-only, редагуєш на host. Time-series дані: named volume, персистентні. Два типи сховища у одному container, кожен обраний за тим, у чому він добрий.Для рев’юераПримітка для модератора (необов’язково)Бачить лише модератор. Прискорює рев’ю.