Почему данные пропадают:
У каждого контейнера поверх слоев образа (про них была глава 3) есть свой тонкий слой для записи. Все, что процесс пишет на диск, попадает туда. Удалили контейнер, удалился и этот слой. Легко проверить:
Код: Выделить всё
docker run --name scratch alpine:3.22 sh -c "echo hello > /data.txt"
docker rm scratch
docker run --rm --name scratch alpine:3.22 cat /data.txtС postgres результат будет похожий, но механика другая, и ее стоит понимать:
Код: Выделить всё
docker run -d --name db -e POSTGRES_PASSWORD=secret postgres:16
docker exec db pg_isready -U postgres
docker exec db psql -U postgres -c "CREATE DATABASE shop;"
docker rm -f db
docker run -d --name db -e POSTGRES_PASSWORD=secret postgres:16
docker exec db pg_isready -U postgres
docker exec db psql -U postgres -c "\l"Volumes:
Том (volume) это хранилище, которым управляет сам Docker. На Linux при обычной rootful-установке тома лежат в /var/lib/docker/volumes, в rootless-режиме в ~/.local/share/docker/volumes, но лезть туда руками не нужно, работаем через CLI.
Код: Выделить всё
docker volume create pgdata
docker run -d --name db \
-e POSTGRES_PASSWORD=secret \
-v pgdata:/var/lib/postgresql/data \
postgres:16Полезные команды: docker volume ls (список), docker volume inspect pgdata (покажет Mountpoint, то есть путь на хосте, драйвер, метки и Scope; каких-либо сведений о контейнерах там нет, кто использует том, смотрите через docker ps -a --filter volume=pgdata или docker system df -v), docker volume rm pgdata (удалить; команда откажет, если том привязан хотя бы к одному контейнеру, даже остановленному, сначала придется удалить сам контейнер). Кстати, docker volume create можно и не вызывать: если тома с таким именем нет, Docker создаст его при запуске сам.
Bind mounts:
Bind mount пробрасывает в контейнер конкретный каталог или файл с хоста. Классический сценарий: разработка, когда код редактируете на хосте, а исполняется он в контейнере.
Код: Выделить всё
docker run -d --name web \
-p 8080:80 \
-v "$(pwd)/site:/usr/share/nginx/html:ro" \
nginx:1.27Отличить просто: если в -v слева имя без слешей, это том. Если путь (начинается с / или ./), это bind mount.
Есть и второй синтаксис, --mount. Он многословнее, но честнее:
Код: Выделить всё
docker run -d --name db \
--mount type=volume,source=pgdata,target=/var/lib/postgresql/data \
postgres:16Третий тип монтирования, tmpfs. Данные живут только в оперативной памяти и исчезают при остановке контейнера, на диск не попадают вообще:
Код: Выделить всё
docker run -d --name web \
--mount type=tmpfs,target=/tmp/cache,tmpfs-size=64m \
nginx:1.27Что когда использовать: данные сервисов (базы, очереди, загруженные пользователями файлы) храните в томах. Bind mounts оставьте для исходников при разработке и для подсовывания конфигов. tmpfs для временного и секретного.
Бэкап и восстановление томов:
На Linux с rootful-установкой том это обычный каталог /var/lib/docker/volumes/pgdata/_data, и скопировать его можно напрямую. Но такой способ не переносим: на Docker Desktop хост вообще виртуалка и до этого пути еще надо добраться, в rootless путь другой, а у нелокальных драйверов каталога на хосте может не быть вовсе. Поэтому классический паттерн бэкапа, который работает везде одинаково: одноразовый контейнер, которому даем и том, и каталог хоста.
Код: Выделить всё
docker run --rm \
-v pgdata:/data:ro \
-v "$(pwd):/backup" \
alpine:3.22 tar czf /backup/pgdata.tar.gz -C /data .Код: Выделить всё
docker run --rm \
-v pgdata:/data \
-v "$(pwd):/backup" \
alpine:3.22 tar xzf /backup/pgdata.tar.gz -C /dataТипичные грабли:
Первое. Если примонтировать пустой каталог хоста поверх пути, где в образе уже что-то лежало, содержимое образа "исчезнет", вы увидите пустой хостовый каталог. С именованными томами поведение другое: при первом монтировании пустого тома Docker копирует в него файлы из образа. Наглядный пример nginx: примонтируйте новый том в /usr/share/nginx/html, и внутри окажется дефолтная страница из образа. А вот postgres тут не показатель: каталог /var/lib/postgresql/data в самом образе пуст, копировать нечего, поэтому initdb инициализирует кластер с нуля что с новым именованным томом, что с пустым bind mount.
Второе. Права. Процесс в контейнере работает под своим UID (у postgres это 999), и если каталог на хосте принадлежит root без прав на запись для остальных, получите permission denied. Лечится chown на хосте под нужный UID. Отдельная история на Fedora, RHEL и прочих системах с включенным SELinux: там bind mount дает permission denied даже при правильных UID и правах, потому что у файлов нет подходящей SELinux-метки. Добавьте к монтированию суффикс :z (общая метка, каталог можно шарить между контейнерами) или :Z (приватная метка под один контейнер), например -v "$(pwd)/site:/usr/share/nginx/html:ro,z". Только не вешайте :z и :Z на системные каталоги вроде /home или /var, relabel перепишет метки и поломает систему.
Третье. docker volume prune. По умолчанию команда сносит только анонимные неиспользуемые тома, но с флагом -a удалит и именованные, если они не подключены к контейнерам. Контейнер с базой удален, значит его том с точки зрения Docker уже не используется. Перед prune -a всегда смотрите docker volume ls.
Четвертое. Анонимные тома. Инструкция VOLUME в Dockerfile (как у того же postgres) или -v /data без имени слева создает том со случайным именем-хэшем. Они копятся незаметно и едят диск, наш первый эксперимент в этой главе как раз оставил после себя такой. docker rm -v имя_контейнера удаляет их вместе с контейнером.
И про скорость: на macOS и Windows bind mounts медленнее, чем на Linux, потому что файлы гоняются между хостом и виртуальной машиной Docker Desktop. На macOS дефолтный нынче VirtioFS заметно сократил разрыв по сравнению со старым gRPC-FUSE, но на каталогах из десятков тысяч мелких файлов вроде node_modules разница все еще ощутима, такие каталоги лучше держать в томе. На Windows с WSL2 главное правило: держите код внутри файловой системы WSL, bind mount с диска C: в контейнер работает медленно.
Итог:
Вы научились отделять данные от контейнера: тома для боевых данных, bind mounts для разработки, tmpfs для временного, --mount там, где важна предсказуемость, плюс бэкап тома через одноразовый контейнер с tar. В главе 6 свяжем контейнеры по сети, а в главе 8 увидите, как все это, включая тома, описывается декларативно в docker-compose.yml.