Named volumes: lifecycle, drivers, backup
Named volume — это persistence-механизм Docker по умолчанию для production-сценариев. В отличие от bind mount, где ты сам отвечаешь за host path, named volume полностью управляется Docker’ом: создаётся, монтируется, удаляется через CLI или compose. Этот урок — про lifecycle, физическое хранение, volume drivers и backup. После него ты будешь спокойно держать Postgres data в named volume и переносить его между машинами.
df и du: сколько диска и где именно
Lifecycle: create, ls, inspect, rm
# Создать.
docker volume create pgdata
# pgdata
# Создать с metadata.
docker volume create \
--driver local \
--label env=dev \
--label app=postgres \
pgdata-dev
# Все volumes.
docker volume ls
# DRIVER VOLUME NAME
# local pgdata
# local pgdata-dev
# Подробности конкретного.
docker volume inspect pgdata
# [
# {
# "CreatedAt": "2026-05-15T10:23:11Z",
# "Driver": "local",
# "Labels": {},
# "Mountpoint": "/var/lib/docker/volumes/pgdata/_data",
# "Name": "pgdata",
# "Options": {},
# "Scope": "local"
# }
# ]
# Удалить (только если ни один контейнер не использует).
docker volume rm pgdata-dev
# Удалить все unused volumes (опасно, спросит подтверждения).
docker volume prune
docker volume prune удаляет все volumes, которые не примонтированы ни к одному контейнеру (даже остановленному). Если ты выключил Postgres-контейнер и сделал prune — Postgres data может уйти. Перед prune всегда сделай docker volume ls и пойми, что удаляется.
Lifecycle взаимодействия с контейнером
Volume и контейнер связаны через mount при docker run -v:
docker run -d --name pg -v pgdata:/var/lib/postgresql/data postgres:17
# pgdata создался автоматически, потому что не существовал
docker rm -f pg
# Контейнер удалён, volume — нет.
docker volume ls
# local pgdata
docker run -d --name pg2 -v pgdata:/var/lib/postgresql/data postgres:17
# Новый контейнер видит ту же базу.
Volume переживает удаление контейнера. Это и есть его смысл. Удалить volume вместе с контейнером — отдельный явный жест:
docker rm -fv pg # -v убирает анонимные volumes контейнера, но не именованные
docker volume rm pgdata # явное удаление именованного volume
Где физически живёт volume
Default driver — local. На Linux:
sudo ls /var/lib/docker/volumes/pgdata/_data/
# base/
# global/
# pg_wal/
# postgresql.conf
# ...
Внутри _data — реальная файловая система Postgres. Можно даже скопировать содержимое отсюда (если хост-Postgres имеет ту же major-версию) и подложить в другой volume — Docker этого не знает, ему всё равно.
На macOS Docker Desktop держит /var/lib/docker/ внутри Linux VM (com.docker.virtualization.framework), и доступа из Finder нет. То же с OrbStack и Rancher Desktop. Чтобы заглянуть внутрь:
# OrbStack: orb shell в alpine.
docker run --rm -it -v pgdata:/data alpine ls -la /data
# Любая платформа: подцепить busybox.
docker run --rm -v pgdata:/d busybox ls -la /d
Volume — это просто папка на хосте с дополнительной маркировкой. Если ты потерял доступ к Docker daemon, но /var/lib/docker/volumes/ цел, можно восстановить данные через простой cp -r в любую другую FS.
Volume drivers
Default — local. Это просто папка на хосте. Но Docker позволяет подключать другие драйверы:
| Driver | Где живут данные | Когда полезен |
|---|---|---|
local | /var/lib/docker/volumes/ | Single-host, default |
local + NFS | NFS-сервер | Несколько хостов читают одну папку |
nfs | Plugin для NFS-only | Чистый NFS без хитростей |
glusterfs | GlusterFS-кластер | Распределённое хранилище для swarm |
s3fs | Через плагин, S3-bucket | Архив в S3, не для горячих БД |
| Облачные | AWS EBS / Azure Disk / GCE PD | k8s-стиль persistence через CSI |
Local-NFS вариант через стандартный driver:
docker volume create \
--driver local \
--opt type=nfs \
--opt o=addr=10.0.0.5,rw \
--opt device=:/exports/data \
shared-data
Этот volume — по сути NFS-mount внутри Docker. Контейнеры на разных хостах могут монтировать shared-data и видеть одни данные.
Для junior DE на одной dev-машине local — достаточный выбор. Кастомные драйверы — это уже зона compose-стенда с несколькими нодами или k8s.
Backup и restore
Стандартный паттерн: одноразовый контейнер монтирует исходный volume и host-папку для tar-архива, копирует данные.
# Backup: pgdata -> ./backups/pgdata-2026-05-15.tar.gz
docker run --rm \
-v pgdata:/source:ro \
-v $(pwd)/backups:/backup \
alpine \
tar czf /backup/pgdata-2026-05-15.tar.gz -C /source .
Что произошло:
- Контейнер alpine монтирует
pgdataв/source(read-only). - Хост-папка
./backupsпримонтирована в/backupкак bind. tar czf ... -C /source .упаковывает содержимое volume в архив, который лежит на хосте.
Restore — обратно:
docker volume create pgdata-restored
docker run --rm \
-v pgdata-restored:/target \
-v $(pwd)/backups:/backup \
alpine \
tar xzf /backup/pgdata-2026-05-15.tar.gz -C /target
Для Postgres простой tar data directory работает только если Postgres-контейнер остановлен в момент бэкапа. На запущенной БД tar поймает inconsistent state — pg_wal не дописан, control file в полпути. Для горячего бэкапа используй pg_dump или pg_basebackup, а не tar volume. Бэкап volume — корректный вариант для остановленного сервиса или для data-only volumes (CSV, parquet, MinIO buckets).
Compose: volumes секция
В compose-файле named volumes объявляются в top-level секции volumes::
services:
postgres:
image: postgres:17
environment:
POSTGRES_PASSWORD: secret
volumes:
- pgdata:/var/lib/postgresql/data
- ./init:/docker-entrypoint-initdb.d:ro
minio:
image: minio/minio:RELEASE.2026-01-15T00-00-00Z
command: server /data
volumes:
- minio-data:/data
volumes:
pgdata:
minio-data:
driver: local
driver_opts:
type: none
o: bind
device: /mnt/big-disk/minio
Имена volumes в compose префиксуются именем проекта. Если проект называется etl (по имени папки), volume pgdata будет физически etl_pgdata. Это видно в docker volume ls.
Попробуй сам
# 1. Полный round-trip: volume -> данные -> backup -> restore.
docker volume create demo-data
docker run --rm -v demo-data:/d alpine sh -c \
'for i in $(seq 1 10); do echo "line $i" > /d/file-$i.txt; done; ls /d'
# 10 файлов в volume. Сделаем бэкап.
mkdir -p ./demo-backups
docker run --rm \
-v demo-data:/source:ro \
-v $(pwd)/demo-backups:/backup \
alpine tar czf /backup/demo.tar.gz -C /source .
ls -la ./demo-backups
# demo.tar.gz весит ~500 байт.
# Уничтожим оригинал.
docker volume rm demo-data
docker volume ls | grep demo-data
# (пусто)
# Restore в новый volume.
docker volume create demo-data-restored
docker run --rm \
-v demo-data-restored:/target \
-v $(pwd)/demo-backups:/backup \
alpine tar xzf /backup/demo.tar.gz -C /target
docker run --rm -v demo-data-restored:/d alpine ls /d
# Те же 10 файлов.
# Cleanup.
docker volume rm demo-data-restored
rm -rf ./demo-backups
# 2. Inspect и labels.
docker volume create --label team=etl --label tier=db etl-pgdata
docker volume inspect etl-pgdata
# Видишь labels в выводе.
docker volume ls --filter label=team=etl
# Только volumes с этим лейблом.
docker volume rm etl-pgdata
Production-практика: помечай volumes labels (team=, env=, app=). Это позволяет фильтровать в docker volume ls --filter и делать селективный prune --filter. В compose: volumes: pgdata: { labels: { team: etl } }.
В следующем уроке — реальный пример с Postgres: что произойдёт при первом запуске, как мигрировать с major-версии на следующую.