MAATRIX / Блог / Docker volumes: типы и когда какой использовать

Docker volumes: типы и когда какой использовать

Docker volumes: типы и когда какой использовать

MAATRIX

Контейнер умирает — и вместе с ним пропадают данные внутри его writable-слоя. Это первое, с чем сталкивается каждый, кто перезапускает docker run с БД внутри и обнаруживает пустую таблицу. У Docker есть три штатных способа держать данные вне контейнера: named volumes, bind mounts и tmpfs. Они решают разные задачи, и путать их — источник половины продакшен-инцидентов с потерей данных. Разберём, чем они отличаются на практике и как не наступить на грабли, которые встречаются почти у всех новичков.

Named volumes: хранилище, которым управляет Docker

Named volume — это область на диске, которую полностью создаёт и обслуживает сам Docker. Вы не указываете путь на хосте — только имя, а где физически лежат данные, решает демон (обычно /var/lib/docker/volumes/<name>/_data на Linux).

Создать volume явно:

docker volume create pgdata
docker volume ls
docker volume inspect pgdata

Использовать в контейнере:

docker run -d \
  --name postgres \
  -e POSTGRES_PASSWORD=secret \
  -v pgdata:/var/lib/postgresql/data \
  postgres:16

Если volume pgdata не существовал — Docker создаст его сам при первом запуске. Это и есть главное удобство: не нужно заранее готовить директорию на хосте, следить за правами доступа, беспокоиться о том, что путь совпадёт с системным.

Когда это лучший выбор: для баз данных, очередей сообщений, любых сервисов с состоянием, где важна целостность данных и переносимость. Named volumes легко бэкапить через отдельный контейнер:

docker run --rm \
  -v pgdata:/data \
  -v $(pwd):/backup \
  alpine tar czf /backup/pgdata-backup.tar.gz -C /data .

Они также корректно работают с volume-драйверами (например, для сетевых хранилищ) — bind mount так не умеет, он жёстко привязан к локальной файловой системе хоста.

Частая ошибка новичков: удалять контейнер флагом docker rm -v не глядя, полагая, что volume «принадлежит» контейнеру и должен исчезнуть вместе с ним. На деле named volume создан отдельно и переживает контейнер по умолчанию — но если volume был анонимным (создан без явного имени через -v /var/lib/postgresql/data), -v при удалении контейнера действительно снесёт и его. Отсюда правило: для всего, что жалко потерять, используйте именованные volumes, а не анонимные.

Bind mounts: прямой доступ к файлам хоста

Bind mount пробрасывает в контейнер конкретную папку или файл с хоста по точному пути. Docker здесь ничем не управляет — это просто монтирование существующей директории.

docker run -d \
  --name nginx \
  -v /home/user/site:/usr/share/nginx/html:ro \
  -p 8080:80 \
  nginx:alpine

Флаг :ro делает монтирование доступным только для чтения — полезно для конфигов и статики, которые контейнер не должен менять.

Когда это лучший выбор: разработка, когда нужно видеть изменения в коде без пересборки образа, и раздача конфигурационных файлов, которые редактируются на хосте вручную — nginx.conf, docker-compose.override.yml, SSL-сертификаты из certbot.

docker run -d \
  --name app \
  -v $(pwd)/src:/app/src \
  -v /etc/letsencrypt:/etc/letsencrypt:ro \
  -p 3000:3000 \
  node:20 npm run dev

Такой подход даёт мгновенный hot-reload в разработке и предсказуемое место на хосте, куда можно зайти обычным ls без специальных команд Docker.

Частая ошибка новичков: монтировать в контейнер широкую системную директорию хоста — например, весь /var/lib/docker или домашнюю папку целиком — вместо узкой, конкретной поддиректории. Это не просто небрежность: контейнер с root-правами внутри получает возможность менять файлы Docker-демона на хосте, а при компрометации приложения — фактически root-доступ к хост-системе. То же самое с монтированием / или /etc «для удобства», чтобы не думать над путями. Правило простое: пробрасывайте ровно ту директорию, которая нужна приложению, и по возможности с флагом :ro, если контейнеру не нужна запись.

Вторая типичная ошибка — рассчитывать на bind mount в проде так же, как в разработке, и потом удивляться, что при переносе на другой сервер путь /home/user/site там просто не существует. Bind mount не переносится вместе с образом — это ответственность того, кто разворачивает окружение.

Нужен сервер под эту задачу?

Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.

Арендовать VPS

tmpfs: временное хранилище в памяти

tmpfs-монтирование существует только в оперативной памяти хоста и никогда не попадает на диск. Как только контейнер останавливается, данные исчезают безвозвратно.

docker run -d \
  --name app \
  --tmpfs /app/tmp:rw,size=64m,mode=1777 \
  myapp:latest

Или через docker run --mount, более явный синтаксис:

docker run -d \
  --name app \
  --mount type=tmpfs,destination=/run/secrets,tmpfs-size=16m \
  myapp:latest

Когда это лучший выбор: секреты, которые расшифровываются при старте приложения и должны существовать только в момент работы процесса — токены API, приватные ключи, временные пароли сессии. Если такой файл ляжет на обычный диск, он попадёт в снапшоты, бэкапы volume и, возможно, в дамп при аварийном завершении контейнера. tmpfs исключает этот риск в принципе — данные физически не пишутся на носитель.

Второй сценарий — временные файлы с высокой частотой записи (кеши рендеринга, сессии PHP, буферы обработки), где важна скорость и не важна сохранность между перезапусками. Запись в память быстрее записи на диск, особенно на VPS с сетевым хранилищем.

Частая ошибка новичков: не задавать size, из-за чего tmpfs ограничен только доступной памятью контейнера (или хоста, если лимиты не выставлены) — при неконтролируемом росте временных файлов это может привести к OOM и падению всего контейнера, а не только к ошибке записи. Второй промах — держать на tmpfs данные, которые на самом деле нужны после перезапуска: разработчик тестирует локально, контейнер не перезапускается неделями, всё работает — а после первого docker restart в проде данные исчезают, и это обнаруживается уже на инциденте.

Сравнение: что выбрать в конкретной ситуации

КритерийNamed volumeBind mounttmpfs
Где хранитсяУправляется DockerКонкретный путь на хостеОперативная память
Переживает перезапуск контейнераДаДаНет
Переносимость между хостамиВысокая (бэкап/restore)Низкая (путь фиксирован)Не применимо
Виден напрямую в файловой системе хостаНе напрямую (через _data)Да, обычным путёмНет
Типичное применениеБД, очереди, состояние сервисаРазработка, конфиги, сертификатыСекреты, временные буферы
Риск для безопасности хостаНизкийВысокий при широких путяхНизкий

Если сомневаетесь — начните с named volume для всего, что должно пережить перезапуск и не обязано лежать в конкретном месте на диске. Bind mount берите точечно, когда нужен именно этот путь на хосте. tmpfs — когда данные принципиально не должны попасть на диск.

Практические сценарии: БД, конфиги, секреты

PostgreSQL в docker-compose с бэкапом:

services:
  db:
    image: postgres:16
    environment:
      POSTGRES_PASSWORD: secret
    volumes:
      - pgdata:/var/lib/postgresql/data

volumes:
  pgdata:

Nginx с конфигом и сертификатами с хоста, только для чтения:

services:
  web:
    image: nginx:alpine
    volumes:
      - ./nginx.conf:/etc/nginx/nginx.conf:ro
      - /etc/letsencrypt:/etc/letsencrypt:ro
    ports:
      - "443:443"

Приложение, расшифровывающее секрет при старте, без записи на диск:

services:
  app:
    image: myapp:latest
    tmpfs:
      - /run/secrets:size=8m,mode=1700
    environment:
      SECRET_SOURCE: vault

Обратите внимание: в compose-файле часто комбинируют все три типа в одном сервисе — это нормально. Данные состояния идут в named volume, конфиги и сертификаты — bind mount, расшифрованные секреты в момент работы — tmpfs.

Если разворачиваете такой стек на арендованном сервере, разница между типами становится ощутимее: на VPS с сетевым диском запись через bind mount и volume идёт медленнее, чем на NVMe с локальным хранилищем, а tmpfs всегда работает со скоростью RAM независимо от типа диска под VPS.

Частые ошибки новичков

Кроме перечисленных выше по каждому типу, есть несколько сквозных ошибок:

  • Смешивать анонимные и именованные volumes без разбора. Анонимный volume (-v /data без имени слева от двоеточия) получает случайное имя-хеш и легко теряется среди десятков таких же после docker system prune.
  • Не проверять права доступа внутри контейнера. Если процесс в контейнере работает не от root (USER app в Dockerfile), а volume создан с правами root, приложение получит Permission denied при первой записи. Проверяется просто: docker exec -it <container> ls -la /path/to/volume.
  • Держать в docker-compose.yml секреты прямо в bind-mount конфиге, который потом коммитится в git. Это не проблема volumes как таковых, но именно bind mounts провоцируют такую привычку, потому что файл «прямо под рукой».
  • Не чистить неиспользуемые volumes. Они не удаляются автоматически вместе с контейнерами и образами и со временем съедают диск:
docker volume ls -f dangling=true
docker volume prune

Если место на диске уже кончилось из-за разросшихся volumes и логов, отдельно разбирали, что делать, когда Docker занимает всё место на диске.

Нужен сервер под эту задачу?

Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.

Арендовать VPS

Нужны сами нейросети для контента?

Генерируйте изображения, видео и озвучку нейросетями на falapi.io — десятки моделей в одном окне. Оплата картой РФ и по СБП.

Частые вопросы

Можно ли примонтировать один volume к нескольким контейнерам одновременно?

Да, несколько контейнеров могут использовать один и тот же named volume параллельно. Для баз данных так делать не стоит — два процесса Postgres с одним data-каталогом приведут к повреждению данных. А вот для общего кеша или файлов между приложением и воркером это штатный паттерн.

Чем -v отличается от --mount?

Функционально для volumes и bind mounts почти ничем — --mount более явный и многословный синтаксис (type=volume|bind|tmpfs,source=...,target=...), который Docker рекомендует для скриптов и compose-файлов из-за меньшей двусмысленности. Для tmpfs --mount даёт больше контроля (например, tmpfs-size), чем короткий флаг --tmpfs.

Как перенести named volume на другой сервер?

Через промежуточный tar-архив: смонтировать volume во временный контейнер, заархивировать содержимое, перенести архив, распаковать в volume на новом хосте. Тот же принцип, что и в примере бэкапа выше, только вместо tar czftar xzf на другом конце.

tmpfs работает в Docker на Windows или Mac?

Официально tmpfs-монтирования поддерживаются на Linux-хостах. Docker Desktop на Mac и Windows запускает контейнеры внутри Linux-VM, и тип tmpfs там технически принимается, но ведёт себя не всегда идентично продакшен-серверу на Linux — для критичных по безопасности сценариев лучше тестировать на том же дистрибутиве, что и прод.

Нужно ли делать бэкап bind mount так же, как named volume?

Данные bind mount — это обычные файлы на хосте, поэтому их можно бэкапить любым способом резервного копирования файловой системы (rsync, tar, снапшоты диска), без специфичных для Docker команд. Отдельная инструкция про бэкап Docker volume и частые ошибки касается именно named volumes, где данные не лежат по очевидному пути.