DOCKER
#docker#docker-compose#volumes#storage

Docker Compose: volumes vs bind mounts — что использовать и когда

Разбор volumes и bind mounts в Docker Compose: различия, сценарии использования и типичные ошибки при работе с данными.

Как использовать

  1. Скопируйте нужный фрагмент кода.
  2. Вставьте в свой проект и при необходимости измените под задачу.
  3. Проверьте зависимости и окружение (версии, переменные).

Различия между volume и bind mount в Docker Compose: когда что использовать и как не наступить на типичные грабли. Ниже — строго по официальной документации Docker Storage и Compose File (volumes).


Что такое volume и bind mount

Volume — хранилище данных, которым управляет Docker (daemon). Данные лежат в каталоге на хосте, но путь выбирает Docker; к ним обращаются только через монтирование в контейнер. Volume переживает удаление контейнера и подходит для долгоживущих данных.

Bind mount — привязка конкретного пути на хосте к пути внутри контейнера. Контейнер видит ровно то, что лежит по этому пути на хосте; хост и контейнер могут одновременно изменять одни и те же файлы.

Из документации Storage:

  • Volumes: «managed by the Docker daemon», «retain data even after the containers using them are removed», «ideal for performance-critical data processing and long-term storage».
  • Bind mounts: «direct link between a host system path and a container», «aren’t isolated by Docker», «both non-Docker processes on the host and container processes can modify the mounted files simultaneously».

Ключевые различия

КритерийVolumeBind mount
Кто владеет путёмDocker (путь на хосте выбирает daemon)Вы (указываете путь на хосте)
ПереносимостьДа: один и тот же Compose-файл на любой машинеНет: путь хоста может не существовать или отличаться
Создание каталогаDocker создаёт volume при первом использованииCompose/legacy создаёт каталог по пути, если его нет (можно отключить long syntax)
Типичное использованиеДанные приложения, БД, productionЛокальная разработка: код, конфиги с хоста

В Compose в секции сервиса можно указать оба типа через атрибут volumes; тип задаётся либо коротким синтаксисом (по формату пути/имени), либо длинным с type: volume или type: bind. См. Services — volumes.


Когда использовать bind mount (локальная разработка)

Bind mount уместен, когда нужно:

  • редактировать код на хосте и сразу видеть изменения в контейнере (без пересборки образа);
  • подмонтировать конфиг или один файл с хоста;
  • отладить приложение, подставляя файлы с хоста.

Из документации: «Use bind mounts when you need to be able to access files from both the container and the host.»

Пример: приложение в контейнере, код лежит в ./app на хосте, в контейнере он должен быть в /app.

services:
  app:
    build: .
    ports:
      - "3000:3000"
    volumes:
      - ./app:/app

Любое изменение в ./app на хосте сразу видно в контейнере. Путь относительный (от каталога с Compose-файлом); на другой машине достаточно склонировать репозиторий — структура каталогов та же.


Когда использовать volume (production)

Volume уместен, когда нужно:

  • хранить данные БД, кэши, загрузки и т.п. между перезапусками контейнеров;
  • не привязываться к конкретному пути на хосте (деплой на разные серверы, CI);
  • чтобы данными управлял Docker (создание, бэкапы через docker run -v и т.д.).

Из документации: volumes «retain data even after the containers using them are removed» и подходят для «long-term storage».

Пример: PostgreSQL хранит данные в named volume db-data; при docker compose down данные не пропадают (если volume не удалён отдельно).

services:
  db:
    image: postgres:16
    environment:
      POSTGRES_USER: app
      POSTGRES_PASSWORD: secret
      POSTGRES_DB: appdb
    volumes:
      - db-data:/var/lib/postgresql/data
    # порты, healthcheck и т.д.

volumes:
  db-data:

Имя db-data в секции volumes объявляет named volume; Compose создаёт его при первом docker compose up, если его ещё нет. См. Define and manage volumes in Docker Compose.


Пример docker-compose.yml для обоих вариантов

Один файл: приложение (код с хоста через bind mount) и БД (данные в volume).

services:
  app:
    build: .
    ports:
      - "3000:3000"
    environment:
      DATABASE_URL: postgresql://app:secret@db:5432/appdb
    volumes:
      # bind mount: код с хоста для разработки
      - ./src:/app/src
    depends_on:
      - db

  db:
    image: postgres:16
    environment:
      POSTGRES_USER: app
      POSTGRES_PASSWORD: secret
      POSTGRES_DB: appdb
    volumes:
      # named volume: данные БД, не привязаны к пути на хосте
      - db-data:/var/lib/postgresql/data

volumes:
  db-data:
  • Разработка: меняешь файлы в ./src — контейнер app видит изменения без пересборки.
  • Данные БД: лежат в volume db-data; при пересоздании контейнера db данные сохраняются.

Длинный синтаксис для явного указания типа (из Services — volumes):

volumes:
  - type: bind
    source: ./src
    target: /app/src
  - type: volume
    source: db-data
    target: /var/lib/postgresql/data
    volume:
      nocopy: true

Для одного сервиса можно комбинировать оба типа в одном списке volumes.


Типичная ошибка новичков

Ошибка: Использовать bind mount с жёстко прописанным абсолютным путём хоста для данных приложения или БД в Compose-файле, который потом используют на другой машине или в CI.

Пример плохо:

volumes:
  - /home/developer/myproject/data:/var/lib/postgresql/data

На другой машине каталога /home/developer/myproject/data может не быть, либо там другие данные — композ перестаёт быть переносимым.

Правильно: для данных, которые должны жить между запусками и не зависеть от пути на хосте, объявлять named volume в секции volumes и монтировать его в сервис. Путь на хосте остаётся заботой Docker.

Ещё одна ошибка: ожидать, что при первом запуске с volume в каталоге volume уже лежат файлы из образа (например, инициализация БД). По умолчанию при создании volume Docker может копировать начальное содержимое из образа в volume; если нужно монтировать только подпуть или отключить копирование, в long syntax используется опция volume.nocopy: true. Для БД обычно оставляют поведение по умолчанию (без nocopy), чтобы образ postgres мог проинициализировать каталог при первом запуске.

Резюме: для кода и конфигов с хоста — bind mount (как правило, относительный путь ./...). Для постоянных данных приложения и БД — named volume в Compose.


Кратко

  1. Volume — управляется Docker, путь на хосте не важен, данные переживают контейнер; использовать для БД и долгоживущих данных, в т.ч. в production.
  2. Bind mount — жёсткая связь пути на хосте и в контейнере; использовать для кода и конфигов в разработке, когда нужен доступ с обеих сторон.
  3. В одном docker-compose.yml можно комбинировать: bind mount для кода, named volume для данных.
  4. Не завязывать production/общие сценарии на абсолютные пути хоста в bind mount; для данных использовать named volume.

Ссылки на документацию: