Тома и хранение данных: volumes и bind mounts

Рейтинг: 80.6% · 16 голосов
Практический курс по Docker: образы, контейнеры, тома, сети, Compose и продакшен. Уроки по главам с обсуждением.
Ответить
Аватара пользователя
Marina_DevOps
Сообщения: 54
Зарегистрирован: 11 май 2026, 05:31

Тома и хранение данных: volumes и bind mounts

Сообщение Marina_DevOps »

Оглавление курса (46)
  1. Что такое Docker и какие задачи он решает
  2. Установка Docker и запуск первого контейнера
  3. Образы: слои, теги и реестр Docker Hub
  4. Пишем свой Dockerfile
  5. Тома и хранение данных: volumes и bind mounts (вы здесь)
  6. Сети в Docker: связываем контейнеры между собой
  7. Переменные окружения и конфигурация контейнеров
  8. Docker Compose: поднимаем многоконтейнерное приложение
  9. Оптимизация образов: multi-stage сборка, размер и кэш слоёв
  10. Логи, отладка и мониторинг контейнеров
  11. Базовая безопасность контейнеров
  12. Подготовка к продакшену: что важно учесть
  13. Dockerfile глубже: ENTRYPOINT и CMD, HEALTHCHECK, .dockerignore, запуск не от root
  14. Реестры образов: приватные registry, push и pull, теги и digest, imagePullSecrets
  15. BuildKit и buildx: multi-arch сборки, секреты сборки, экспорт кэша
  16. Docker в CI/CD: автосборка, сканирование образов (Trivy, Docker Scout), публикация
  17. Итоговый проект и куда расти: от Dockerfile до прода, обзор оркестрации (Kubernetes, Podman, OCI)
  18. Архитектура Docker: dockerd, containerd, runc и OCI
  19. Как работает изоляция: namespaces и cgroups
  20. Образы изнутри: слои, OverlayFS и storage driver
  21. Жизненный цикл контейнера и политики перезапуска
  22. Отладка контейнеров: exec, attach, nsenter, debug
  23. Сети Docker глубже: bridge, overlay, macvlan и iptables
  24. Docker Compose глубже: profiles, healthcheck-зависимости, override
  25. Данные и тома глубже: драйверы, бэкап, права
  26. Секреты и конфигурация: как не светить пароли
  27. Ограничение ресурсов: CPU, память, OOM и cgroups
  28. Здоровье и автозапуск: HEALTHCHECK, init и сигналы
  29. Логирование контейнеров: драйверы, ротация, сбор
  30. Мониторинг контейнеров: stats, cAdvisor, Prometheus
  31. Безопасность глубже: rootless, user namespaces, seccomp, capabilities
  32. Supply chain: сканирование, SBOM и подпись образов
  33. Глубокая оптимизация образов: distroless, scratch, кэш
  34. Multi-arch и сборка под ARM: buildx и эмуляция
  35. Реестры в продакшене: Harbor и облачные registry
  36. Docker без Docker: Podman, containerd, nerdctl, Buildah
  37. От Compose к оркестрации: когда контейнеров становится много
  38. Docker Swarm: встроенная оркестрация
  39. Сборка образов в CI без демона: kaniko, BuildKit, Buildah
  40. Траблшутинг Docker: типичные ошибки и как чинить
  41. Производительность и эксплуатация Docker-хоста
  42. Docker Desktop, WSL2 и альтернативы на Windows и Mac
  43. Контейнеризация приложений: бэкенд, фронтенд, БД
  44. Docker и Kubernetes: containerd, миграция, kompose
  45. Лучшие практики и антипаттерны Docker
  46. Сквозной проект: production-ready стек и путь дальше
Боль, с которой все знакомятся однажды

Запускаешь Postgres в контейнере, заливаешь туда схему, тестовые данные, пару дней работаешь. Потом обновляешь образ на свежий тег, делаешь

Код: Выделить всё

docker rm -f db && docker run ...
- и база пустая. Данных нет. Их никогда и не было "в безопасности": они лежали внутри писчего слоя контейнера, а ты этот контейнер удалил.

Это не баг, это дизайн. Контейнер эфемерен по своей природе. Под капотом его файловая система - это OverlayFS: стопка read-only слоёв образа плюс тонкий writable-слой сверху (upperdir). Всё, что процесс пишет внутрь, садится в этот верхний слой. Удалил контейнер - удалил upperdir вместе со всем, что там накопилось. Контейнер задуман как одноразовый: его должно быть не страшно убить и пересоздать. А значит, состояние (state) в нём держать нельзя. Состояние надо вынести наружу - и ровно для этого существуют docker volume и bind mount. Разберём хранение данных в Docker до самого дна: где физически лежат байты, кто их владелец по UID, как не потерять и как забэкапить.

Изображение

Три способа дать контейнеру память: volume, bind mount, tmpfs

У Docker три типа монтирования, и путать их - классика граблей.

Named volume (именованный том) - область хранения, которой управляет сам Docker. Ты говоришь "хочу том pgdata", а где он физически лежит и кто там владелец - забота движка. По умолчанию на Linux это

Код: Выделить всё

/var/lib/docker/volumes/<имя>/_data
. Это рекомендованный способ для продакшн-данных: БД, загруженные файлы, состояние брокеров. Docker volume переживает удаление контейнера, его можно подключить к новому, расшарить между несколькими.

Bind mount (привязка к каталогу хоста) - ты руками говоришь "вот эта папка на хосте = вот этот путь в контейнере". Никакого посредника, прямой проброс. Идеально для разработки: монтируешь исходники с ноутбука в контейнер, правишь код в IDE, внутри сразу подхватывается. Docker bind mount не управляется движком: за каталог, права и существование отвечаешь ты. Команды

Код: Выделить всё

docker volume
про него ничего не знают.

tmpfs mount - монтирование в оперативную память. Данные живут только пока жив контейнер, на диск не попадают вообще. Нужно для секретов, которые нельзя ронять на диск, для горячих временных файлов, для ускорения тяжёлых временных записей. Только Linux. После остановки контейнера - всё исчезает, это его смысл.

Аналогия. Named volume - камера хранения на вокзале: сдал чемодан, тебе дали номер ячейки, где она физически - не твоя забота, придёшь за вещами в любой момент. Bind mount - ящик у тебя дома, в который ты дал контейнеру ключ: полный контроль, но и порядок наводишь сам. tmpfs - карман: удобно, быстро, но вывернул куртку (остановил контейнер) - и пусто.
Эмпирика: данные приложения, которые должны жить - named volume. Код и конфиги, которые ты активно правишь в редакторе - bind mount. Секреты и временное, что не должно касаться диска - tmpfs.
Жизненный цикл тома: create, ls, inspect, rm, prune

Named volume - самостоятельный объект. Его можно создать заранее, до всякого контейнера:

Код: Выделить всё

docker volume create pgdata
docker volume ls

Код: Выделить всё

DRIVER    VOLUME NAME
local     pgdata
Драйвер local - встроенный, он и работает с

Код: Выделить всё

/var/lib/docker/volumes
. Бывают сетевые драйверы (NFS, облачные плагины), но 99% случаев - local. Теперь заглянем внутрь:

Код: Выделить всё

docker volume inspect pgdata

Код: Выделить всё

[
    {
        "CreatedAt": "2026-06-15T10:22:14Z",
        "Driver": "local",
        "Labels": null,
        "Mountpoint": "/var/lib/docker/volumes/pgdata/_data",
        "Name": "pgdata",
        "Options": null,
        "Scope": "local"
    }
]
Главное поле - Mountpoint: точный путь на хосте, где лежат реальные байты. Это и есть ответ на вопрос "где живут данные docker том". Можешь зайти туда (под root) и увидеть файлы БД своими глазами.

Важная тонкость про Docker Desktop. На Mac и Windows никакого

Код: Выделить всё

/var/lib/docker
на твоей машине нет - движок крутится в виртуалке (на Windows - в WSL2). Поэтому Mountpoint - путь внутри той ВМ, а не на твоём диске. Просто открыть Finder и зайти в

Код: Выделить всё

/var/lib/docker/volumes
не выйдет. Это частый источник недоумения у тех, кто пришёл с Mac.

Если том не создавать заранее, Docker создаст его сам при первом упоминании в

Код: Выделить всё

docker run
или compose. Удаление и уборка:

Код: Выделить всё

docker volume rm pgdata
docker volume prune
docker volume prune сносит все тома, которые сейчас не подключены ни к одному контейнеру. Звучит безобидно, пока ты не остановил на ночь стек с базой, не запустил prune "почистить мусор" - и не снёс продовый том, потому что в момент уборки он формально был anonymous и dangling. С версии 23 по умолчанию трогает только анонимные тома, а named требует флага , но привычку проверять дважды это не отменяет. Перед массовой уборкой всегда

Код: Выделить всё

docker volume ls
и думай головой.

Два синтаксиса монтирования: -v против --mount

Подключить том к контейнеру можно двумя способами, и это исторически больное место.

Короткий -v (он же

Код: Выделить всё

--volume
) - три части через двоеточие:

Код: Выделить всё

источник:цель:опции
.

Код: Выделить всё

docker run -d --name db \
  -v pgdata:/var/lib/postgresql/data \
  postgres:17
Если первая часть - просто имя (pgdata) - это named volume. Если первая часть - абсолютный путь (начинается с / или с буквы диска) - это bind mount. И вот тут притаилась граната: при , если указанного каталога-источника на хосте нет, Docker молча создаст пустую папку и примонтирует её. Ты ждал свои конфиги, а контейнер видит пустоту - и приложение либо падает, либо инициализируется с дефолтами. Опечатка в пути не даёт ошибки, она даёт тихий неверный результат.

Длинный --mount - явные пары ключ=значение:

Код: Выделить всё

docker run -d --name db \
  --mount type=volume,source=pgdata,target=/var/lib/postgresql/data \
  postgres:17
Для bind:

Код: Выделить всё

docker run -d --name app \
  --mount type=bind,source=/srv/app/src,target=/app/src,readonly \
  myapp:dev
Разница не косметическая. У с

Код: Выделить всё

type=bind
, если исходного каталога нет, Docker выдаст ошибку, а не создаст пустышку. Поэтому правило простое: для bind mount в проде и в скриптах используй - он честнее.

Код: Выделить всё

type=tmpfs
тоже доступен только через (или отдельный ). Опция

Код: Выделить всё

readonly
это ) монтирует только для чтения - незаменимо, когда отдаёшь контейнеру конфиг, который он не должен трогать.

Права и владелец: где новички теряют полдня

Самая болезненная тема хранения - не "куда", а "кто владеет". Внутри контейнера процесс работает под каким-то UID. Файлы на томе и на bind mount принадлежат конкретному числовому UID/GID, и ядру глубоко всё равно, как этого пользователя зовут.

Named volume. Когда том пустой и его первый раз монтируют в контейнер, Docker копирует туда содержимое целевого пути из образа вместе с владельцем и правами. Поэтому official-образы вроде postgres сами раскладывают данные под нужным UID, и обычно всё работает из коробки. Это ещё один довод за named volume: меньше ручной возни с правами.

Bind mount. Тут магии нет. Docker монтирует твою хостовую папку как есть, с её UID/GID. Классика:

Код: Выделить всё

docker run --rm -v $(pwd)/data:/data alpine \
  sh -c 'echo test > /data/file.txt'

Код: Выделить всё

sh: can't create /data/file.txt: Permission denied
Внутри alpine процесс - root (UID 0), а вот более типичный случай: образ запускает приложение под UID 1000 (node, nginx-unprivileged и т.п.), а каталог на хосте принадлежит другому UID. Ядро смотрит на числа, видит несовпадение - и Permission denied. Имена пользователей внутри и снаружи никак не связаны, важны только цифры.

Лечится осознанно, а не

Код: Выделить всё

chmod 777
(это маскировка, не решение). Узнай, под каким UID работает образ:

Код: Выделить всё

docker run --rm postgres:17 id -u
Дальше варианты. Выставить владельца хостовой папки под нужный UID:

Код: Выделить всё

sudo chown -R 999:999 ./pgdata
Или запустить контейнер под своим UID, чтобы созданные файлы принадлежали тебе на хосте:

Код: Выделить всё

docker run --rm -u $(id -u):$(id -g) \
  -v $(pwd)/data:/data alpine touch /data/file.txt
В rootless-режиме Docker (демон под обычным пользователем) и при включённом

Код: Выделить всё

userns-remap
картина смещается: root внутри контейнера маппится на непривилегированный UID хоста, и файлы на bind mount будут принадлежать этому смещённому диапазону. Named volume в таких режимах удобнее - Docker сам раскладывает владельцев в нужном пространстве имён, и ты реже дерёшься с правами вручную.

Бэкап тома и перенос между машинами

Named volume нельзя просто заархивировать из домашней папки - до

Код: Выделить всё

/var/lib/docker/volumes
на Docker Desktop ты вообще не достанешь. Канонический приём: запустить одноразовый служебный контейнер, примонтировать в него и том, и каталог для архива, и запаковать.

Код: Выделить всё

docker run --rm \
  -v pgdata:/data:ro \
  -v $(pwd):/backup \
  alpine \
  tar czf /backup/pgdata-2026-06-15.tar.gz -C /data .
Том монтируем , чтобы во время бэкапа ничего не дописалось. На выходе - обычный tar.gz рядом. Восстановление - зеркально: создать чистый том и распаковать в него.

Код: Выделить всё

docker volume create pgdata-restore
docker run --rm \
  -v pgdata-restore:/data \
  -v $(pwd):/backup \
  alpine \
  tar xzf /backup/pgdata-2026-06-15.tar.gz -C /data
Оговорка про БД: бэкап файлов "живой" базы под нагрузкой может дать неконсистентную копию. Для Postgres/MySQL надёжнее логический дамп (,

Код: Выделить всё

mysqldump
) либо остановка/заморозка записи на время архивации. tar тома хорош для файлового состояния и как быстрый снимок остановленного сервиса.

Тома в Compose v2

В реальной жизни тома почти всегда описаны в compose.yaml, а не в длинных

Код: Выделить всё

docker run
. Современный Compose v2 - без устаревшего

Код: Выделить всё

version:
:

Код: Выделить всё

services:
  db:
    image: postgres:17
    environment:
      POSTGRES_PASSWORD: secret
    volumes:
      - pgdata:/var/lib/postgresql/data
      - ./initdb:/docker-entrypoint-initdb.d:ro
    tmpfs:
      - /tmp

volumes:
  pgdata:
Здесь сразу три типа в одном сервисе: named volume pgdata под данные, bind mount

Код: Выделить всё

./initdb
(относительный путь от файла compose) на чтение под init-скрипты и tmpfs на . Блок верхнего уровня

Код: Выделить всё

volumes:
объявляет том. Имя тома Compose префиксует именем проекта, так что в

Код: Выделить всё

docker volume ls
увидишь что-то вроде

Код: Выделить всё

myproject_pgdata
- учитывай при ручном бэкапе.

Грабли и антипаттерны
  • Опечатка в пути bind mount при не даёт ошибки - Docker создаёт пустую папку, приложение тихо стартует без данных. Используй для bind.
  • Код: Выделить всё

    chmod 777
    на томе - не починка, а дыра. Разбирайся с UID, а не глуши права.
  • Код: Выделить всё

    docker volume prune
    без проверки

    Код: Выделить всё

    docker volume ls
    сносит данные. Особенно опасно на dev-машине, где много стеков.
  • Код: Выделить всё

    docker rm -f
    контейнера БД без вынесенного тома - мгновенная потеря данных, потому что состояние было в writable-слое.
  • Bind mount на каталог с node_modules или vendor с хоста в контейнер затирает то, что собралось внутри образа: монтирование скрывает содержимое целевого пути. Для node_modules часто делают anonymous volume поверх bind, чтобы зависимости из образа не перетёрлись хостом.
  • Anonymous volumes (без имени) плодятся незаметно при каждом и копят мусор и место. Периодически чисти осознанно.
  • Хранить состояние в writable-слое и надеяться на

    Код: Выделить всё

    docker commit
    - антипаттерн. Состояние - только в том/bind, образ остаётся неизменным.
Мини-лаба: повторить руками
  • Создай том:

    Код: Выделить всё

    docker volume create labvol
    и посмотри его Mountpoint через

    Код: Выделить всё

    docker volume inspect labvol
    .
  • Запиши в него файл одноразовым контейнером:

    Код: Выделить всё

    docker run --rm -v labvol:/data alpine sh -c 'echo hello > /data/test.txt'
    .
  • Убедись, что данные пережили контейнер: подними новый и прочитай:

    Код: Выделить всё

    docker run --rm -v labvol:/data alpine cat /data/test.txt
    . Должно вывести hello.
  • Сделай бэкап тома в tar.gz приёмом со служебным alpine-контейнером, удали том через

    Код: Выделить всё

    docker volume rm labvol
    , создай заново и восстанови из архива. Проверь, что test.txt на месте.
  • Спровоцируй Permission denied: примонтируй bind mount в каталог, которым владеет чужой UID, и запусти запись под

    Код: Выделить всё

    -u 1000:1000
    . Затем почини через , а не через 777.
Контрольные вопросы
  • Почему данные исчезают после

    Код: Выделить всё

    docker rm
    , если не использовать том, и при чём тут writable-слой OverlayFS?
  • Чем отличается поведение и , когда исходного каталога bind mount не существует?
  • Где физически лежат named volume на Linux и почему этот путь не виден напрямую на Docker Desktop для Mac?
  • Откуда берётся Permission denied на bind mount и почему

    Код: Выделить всё

    chmod 777
    - неправильный ответ?
Итог

Контейнер эфемерен, состояние выносим наружу. Named volume - для продовых данных, ими управляет Docker и сам разруливает владельцев. Bind mount - для кода и конфигов при разработке, прямой проброс с хоста, но права и существование каталога на тебе. tmpfs - для временного и секретного в памяти. Для bind предпочитай явный , для уборки - проверяй дважды, права чини через UID, а не через 777. И всегда знай, где лежит твой docker том и как его забэкапить - до того, как он понадобится.
👍2 ❤️1 🔥2 😄 🤔1
✔ Лучший ответ сформирован автоматически — Spyce
Marina_DevOps писал(а):-v с несуществующим путем на хосте молча создаст пустой каталог (от root), а --mount с type=bind в той же ситуации упадет с ошибкой так вот откуда у меня на vps пустые папки от рута рядом с проектом, которые rm без sudo не берет. неделю гадал, уже думал кто-то по ssh лазит. перехожу на --mount, спасибо
Перейти к ответу →
Аватара пользователя
Spyce
Сообщения: 2
Зарегистрирован: 16 май 2026, 17:13

Re: Тома и хранение данных: volumes и bind mounts

Сообщение Spyce »

✔ Лучший ответ — сформирован автоматически
Marina_DevOps писал(а):-v с несуществующим путем на хосте молча создаст пустой каталог (от root), а --mount с type=bind в той же ситуации упадет с ошибкой
так вот откуда у меня на vps пустые папки от рута рядом с проектом, которые rm без sudo не берет. неделю гадал, уже думал кто-то по ssh лазит. перехожу на --mount, спасибо
👍1 ❤️ 🔥 😄 🤔
Аватара пользователя
haskellkun
Сообщения: 1
Зарегистрирован: 12 май 2026, 20:40

Re: Тома и хранение данных: volumes и bind mounts

Сообщение haskellkun »

а как тома бэкапить? хочу раз в сутки складывать копию pgdata на отдельный диск. правильно понимаю, что надо поднимать временный контейнер с двумя маунтами и делать tar внутри него? docker volume export в доках не нашел, может плохо искал
👍 ❤️1 🔥1 😄 🤔
Аватара пользователя
asdadad
Сообщения: 1
Зарегистрирован: 30 май 2026, 22:08

Re: Тома и хранение данных: volumes и bind mounts

Сообщение asdadad »

про uid в точку. у меня grafana с bind mount не стартовала, оказалось у нее uid 472, а каталог был от рута. причем с именованным томом та же grafana заводится без танцев, видимо как раз из-за копирования файлов из образа при первом монтировании. жаль раньше этого урока не было
👍1 ❤️ 🔥 😄 🤔
Аватара пользователя
juveleo
Сообщения: 2
Зарегистрирован: 16 май 2026, 18:50

Re: Тома и хранение данных: volumes и bind mounts

Сообщение juveleo »

наконец дошло, почему nginx у меня отдавал 403 вместо сайта. я монтировал каталог, который еще не создал, docker сделал его пустым, и nginx было нечего отдавать. два вечера убил на конфиги nginx, а дело было в одной строчке с -v
👍1 ❤️ 🔥 😄 🤔1
Ответить
← Предыдущая глава
Пишем свой Dockerfile
Следующая глава →
Сети в Docker: связываем контейнеры между собой

Все главы курса «Docker: контейнеризация от основ до продакшена»

Поделиться темой: ✈ Telegram VK
Похожие запросы: как сделать бэкап docker volume и восстановить данныеkubernetes pvc и storageclass как подключить хранилищеdocker volume и bind mount в чем разница и где хранятся данныенастройка dns сервера bind

Вернуться в «Docker с нуля»

Кто сейчас на конференции

Сейчас этот форум просматривают: нет зарегистрированных пользователей и 1 гость