Docker volumes кажутся простыми ровно до того момента, пока не понадобятся бэкап, перенос на другой сервер или «чистка диска» после пары месяцев разработки. Ниже — рабочие схемы, которые реально помогают в проде: как делать бэкап/восстановление volume без привязки к внутренностям Docker, как выбирать между volume и bind-mount, чем опасен docker prune и почему «странные» ошибки почти всегда упираются в permissions, uid и gid.
Volume и bind-mount: что выбрать в реальной жизни
У Docker есть два самых популярных способа подмонтировать данные в контейнер:
- Docker volume — именованное хранилище, которым управляет Docker. Обычно лежит в
/var/lib/docker/volumes(если не менялиdata-root). - Bind mount — «проброс» конкретной директории хоста в контейнер (например,
/srv/app/uploads→/var/www/uploads).
Bind mount vs volume: практическое сравнение
Docker volume удобен, когда вы хотите абстрагироваться от путей на хосте: переносите проект между серверами, крутите несколько окружений, используете CI и хотите меньше ручного сопровождения. Volume проще бэкапить стандартными приёмами через временный контейнер, и он меньше привязан к структуре каталогов хоста.
Bind mount полезен, когда данные должны быть явно в предсказуемом месте на хосте: для прямого доступа утилитами хоста, для интеграции с существующими бэкапами, для логов в каталог под logrotate, для разработки (чтобы изменения в коде сразу были видны в контейнере без пересборки).
Для продакшена часто выигрывает гибрид: база и критичные stateful-данные держим в volume, а конфиги/сертификаты/артефакты — в bind mount в каталогах вроде /srv/project/.
Как быстро понять, что именно используется
Посмотреть монтирования конкретного контейнера:
docker inspect --format '{{ json .Mounts }}' my_container
Список volumes:
docker volume ls
Где физически лежат данные volume (поле Mountpoint):
docker volume inspect my_volume
Backup volume: надёжный бэкап без магии
Задача бэкапа volume — упаковать содержимое в архив и сохранить в понятное место (например, /srv/backups). Самый переносимый способ: запустить временный контейнер, примонтировать volume и каталог для бэкапа, выполнить tar.
Бэкап volume в tar.gz (универсальный вариант)
Пример: volume pgdata, бэкапы кладём в /srv/backups/docker:
mkdir -p /srv/backups/docker
docker run --rm -v pgdata:/data -v /srv/backups/docker:/backup alpine sh -c 'cd /data && tar -czf /backup/pgdata.tar.gz .'
Плюсы подхода:
- не зависит от ОС и утилит внутри «боевого» контейнера;
- не требует знать реальный путь
Mountpointна хосте; - архив легко переносится и восстанавливается на другом сервере.
Важно про права: permissions, uid/gid
Данные в volume часто принадлежат не root (например, Postgres с uid=999). Если бэкап/restore сделаны без учёта владельцев, после миграции ловите permission denied.
Практика простая:
- архивируйте/распаковывайте внутри временного контейнера, который запускается от root (так
tarкорректно восстановит владельцев и режимы); - если после восстановления права всё равно «не те», делайте явный
chownпо нужномуuid/gid.
Консистентность: бэкапить «на горячую» или останавливать контейнер?
Для статических данных (uploads, артефакты) бэкап на лету обычно нормален. Для СУБД и любых stateful-сервисов выбирайте один из вариантов:
- логический дамп (pg_dump, mysqldump) — консистентно, но может быть дольше;
- остановка контейнера на время архивации — самый простой вариант, но с простоем;
- механизмы hot backup самой СУБД — лучший вариант для продакшена.
Если вы строите регулярные бэкапы PostgreSQL «по-взрослому», пригодится отдельная схема с PITR/WAL: PITR для PostgreSQL: WAL, восстановление и типовые ошибки.

Restore volume: как восстановить из архива
«Docker restore» для volumes — это не отдельная команда, а процедура: создать (или очистить) volume и распаковать туда архив.
Восстановление в новый volume
Создаём volume:
docker volume create pgdata
Распаковываем архив:
docker run --rm -v pgdata:/data -v /srv/backups/docker:/backup alpine sh -c 'cd /data && tar -xzf /backup/pgdata.tar.gz'
Если восстанавливаете в существующий volume, сначала остановите все контейнеры, которые его используют. Иначе получите гонки и частично перезаписанное состояние.
Как аккуратно очистить volume перед restore
Вариант 1 (самый простой): удалить и создать заново:
docker stop my_db
docker volume rm pgdata
docker volume create pgdata
Вариант 2: очистить содержимое (когда удалять volume нельзя из-за зависимостей/оркестрации):
docker stop my_db
docker run --rm -v pgdata:/data alpine sh -c 'rm -rf /data/* /data/.[!.]* /data/..?*'
Быстрая проверка после восстановления
- есть ожидаемые файлы;
- владельцы и режимы файлов выглядят адекватно;
- контейнер стартует без ошибок доступа.
Посмотреть права внутри volume:
docker run --rm -v pgdata:/data alpine sh -c 'ls -la /data | head'
Permissions и uid/gid: почему после переноса всё ломается
Классика: volume восстановили, контейнер стартует и падает с «permission denied» или «could not open file». В большинстве случаев причина одна: несовпадение владельцев/прав с тем, от какого пользователя реально запускается процесс в контейнере.
Почему имена пользователей не важны
В Linux права завязаны на числа: uid и gid. Имя пользователя (например, postgres) — это только отображение через /etc/passwd внутри конкретной системы/образа. Volume хранит именно числовые идентификаторы, поэтому «postgres» в разных образах может иметь разные uid.
Как узнать uid/gid процесса в контейнере
Если контейнер живой:
docker exec my_container id
Если контейнер не стартует, проверьте образ через временный запуск:
docker run --rm --entrypoint sh my_image -c 'id'
Как быстро починить права после restore
Два рабочих пути:
- chown внутри временного контейнера с примонтированным volume (универсально);
- chown на хосте по пути
Mountpoint(быстро, но привязываетесь к внутренностям Docker и рискуете ошибиться путём).
Пример: назначить владельцем uid=999, gid=999:
docker run --rm -v pgdata:/data alpine sh -c 'chown -R 999:999 /data'
Если сервис ожидает строгие режимы каталогов (например, 0700), добавьте настройку прав отдельно:
docker run --rm -v pgdata:/data alpine sh -c 'chmod 0700 /data'
docker prune: как чистить диск и не удалить нужное
docker prune — набор команд для удаления неиспользуемых объектов: контейнеров, образов, сетей, build cache и volumes. Подвох в том, что «неиспользуемый» для Docker означает «не привязан ни к одному контейнеру». Если вы удалили контейнер, но данные хотели сохранить, volume быстро становится «осиротевшим» и попадает под удаление.
Какие prune-команды бывают
docker container prune— остановленные контейнеры.docker image prune— неиспользуемые образы.docker network prune— неиспользуемые сети.docker volume prune— неиспользуемые volumes (самое рискованное).docker system prune— комплексная чистка (опасно без инвентаризации).
Перед volume prune: инвентаризация
Список volumes:
docker volume ls
Какие контейнеры используют конкретный volume:
docker ps -a --filter volume=pgdata
Если команда ничего не выводит, volume не привязан ни к одному контейнеру и будет кандидатом на удаление.
Почему docker system prune «внезапно» ломает окружение
- удалились образы, и при рестарте/деплое их пришлось заново тянуть (время, трафик);
- удалились volumes, которые на момент чистки «временно не были подключены» (вы удалили контейнер для пересоздания);
- удалился build cache, и сборки стали заметно дольше.
Перед чисткой полезно понять, что именно съедает место:
docker system df
Если сомневаетесь, нужен volume или нет, сначала сделайте tar-бэкап. Ошибка при удалении почти всегда дороже лишнего архива.
Рабочие схемы бэкапа для продакшена
Ниже — несколько шаблонов, которые обычно переживают рост проекта и миграции.
Шаблон 1: volume → tar → ротация на хосте
Хорош для небольших проектов: прозрачно, переносимо, минимум зависимостей. Автоматизацию (cron/systemd timer) и ротацию делаете сами.
Пример (каждая команда — отдельной строкой):
mkdir -p /srv/backups/docker
TS=$(date +%F_%H%M%S)
docker run --rm -v pgdata:/data -v /srv/backups/docker:/backup alpine sh -c "cd /data && tar -czf /backup/pgdata_${TS}.tar.gz ."
Если вы хотите хранить бэкапы в S3-совместимом хранилище и делать ротацию «по уму», пригодится обзор практичных инструментов: S3-бэкапы: restic/borg и типовые схемы хранения.
Шаблон 2: данные в bind mount → бэкап средствами хоста
Если у вас уже есть системный бэкап, bind mount упрощает интеграцию: данные лежат в /srv или /var и попадают в существующие задания. Минус — нужно дисциплинированно поддерживать права и политики безопасности (SELinux/AppArmor), если они включены.
Шаблон 3: гибрид (часто лучший)
Volume для того, что «живёт в Docker» (БД, очереди, состояние), а bind mount — для того, что нужно видеть/редактировать на хосте (конфиги, сертификаты, кастомные скрипты, каталоги обмена). Если проект живёт на одном узле, обычно достаточно хорошего VDS, где вы контролируете диски, бэкапы и расписания.
Чек-лист миграции на другой сервер
Зафиксируйте список volumes:
docker volume ls.Снимите бэкап каждого критичного volume в архив.
Сохраните compose-файлы/манифесты и переменные окружения (в отдельное защищённое место).
На новом сервере создайте volumes и выполните restore.
Проверьте
permissions: владельцев, режимы, фактическиеuid/gidпроцесса в контейнере.Только после проверки запускайте сервисы и переключайте трафик.

Частые ошибки и быстрые диагнозы
«Volume пустой, хотя данные должны быть»
Обычно вы смонтировали не тот volume или перепутали путь в контейнере. Проверьте:
docker volume inspect my_volume
docker inspect my_container --format '{{ json .Mounts }}'
После restore приложение создаёт новые файлы, но не читает старые
Почти всегда это uid/gid. Сравните владельцев файлов в volume и пользователя процесса в контейнере, затем сделайте chown.
«docker volume prune удалил нужное»
Docker удалил «неиспользуемые» volumes (не привязанные к контейнерам). На будущее: перед пересозданием контейнеров держите правило «сначала бэкап». А агрессивные чистки на проде запускайте только после инвентаризации.
Итоги
Volumes — правильный способ хранить состояние контейнеров, но относиться к ним нужно как к данным: делать регулярный бэкап, понимать процедуру restore, аккуратно применять prune и всегда держать в голове permissions, uid и gid. Один раз выстроите понятный процесс бэкапа/восстановления и дисциплину чистки — и Docker перестанет «съедать диск» и «ломаться после миграции».


