Docker volumes: типы и когда какой использовать
Контейнер умирает — и вместе с ним пропадают данные внутри его 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, США, Франция и РФ. Оплата картой РФ и по СБП.
Арендовать VPStmpfs: временное хранилище в памяти
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 volume | Bind mount | tmpfs |
|---|---|---|---|
| Где хранится | Управляется 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 czf — tar 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, где данные не лежат по очевидному пути.