Перейти к содержимому
RootPoligon

Данные: слой, том, bind mount

Ситуация

Вы выпустили новую версию «Заметок»: собрали образ, удалили старый контейнер и запустили новый. Пользователи пишут: пропали все заметки. Образ тот же, приложение то же — куда делись данные?

Главная мысль

Всё, что контейнер записал «к себе», лежит в его слое для изменений и удаляется вместе с контейнером (docker rm). Остановка и повторный запуск слой не трогают — а пересоздание удаляет. Данные, которые должны жить дольше контейнера, хранят вне его — в томе или в папке хоста, подключённых в нужный путь.

Где данныеПереживут stop/startПереживут rm и новый контейнерКто управляет
Слой контейнераданетDocker, вместе с контейнером
Именованный томдадаDocker, отдельно от контейнеров
Bind mount (папка хоста)дадавы, обычными правами файлов

Новые слова

  • Слой контейнера — записываемый слой поверх образа (глава 1).
  • Том (volume) — хранилище, которым управляет Docker; у тома есть имя, он подключается в путь внутри контейнера: -v rpd-notes-data:/data.
  • Bind mount — подключение существующей папки или файла хоста: -v "$PWD/data:/data". Если слева абсолютный путь — это bind mount, если имя — том.

Разбор

Образ rpd-notes:1 (глава 3), папка ~/docker-course/rpd-notes. Учебных контейнеров прошлых глав быть не должно: docker ps -a --filter name=rpd- — пусто. Если остались rpd-a, rpd-b из практики главы 4, удалите их: docker rm -f rpd-a rpd-b — иначе порт 8081 будет занят.

Слой контейнера: stop не страшен, rm — да

cd ~/docker-course/rpd-notes
docker run -d --name rpd-app -p 127.0.0.1:8080:8000 rpd-notes:1
sleep 1
curl -sS -d 'Позвонить в сервис' http://127.0.0.1:8080/notes
docker stop rpd-app
docker start rpd-app
sleep 1
curl -sS http://127.0.0.1:8080/notes
Сохранено. Заметок: 1
Позвонить в сервис

curl -d отправляет POST-запрос с текстом. После stop/start заметка на месте. Теперь — как при «обновлении»:

docker rm -f rpd-app
docker run -d --name rpd-app -p 127.0.0.1:8080:8000 rpd-notes:1
sleep 1
curl -sS http://127.0.0.1:8080/
Заметки
Заметок: 0

Новый контейнер — новый слой. Старый слой удалён вместе со старым контейнером. Это и случилось в ситуации.

Именованный том

docker rm -f rpd-app
docker volume create rpd-notes-data
docker run -d --name rpd-app -p 127.0.0.1:8080:8000 -v rpd-notes-data:/data rpd-notes:1
sleep 1
curl -sS -d 'Купить молоко' http://127.0.0.1:8080/notes
docker rm -f rpd-app
docker run -d --name rpd-app -p 127.0.0.1:8080:8000 -v rpd-notes-data:/data rpd-notes:1
sleep 1
curl -sS http://127.0.0.1:8080/notes
Сохранено. Заметок: 1
Купить молоко

Контейнер пересоздан — заметка на месте: она в томе, а том не принадлежит контейнеру.

docker volume ls --filter name=rpd-
docker exec rpd-app ls -ln /data
DRIVER    VOLUME NAME
local     rpd-notes-data
total 4
-rw-r--r-- 1 10001 10001 29 Oct 16 11:57 notes.json

Файл принадлежит пользователю 10001, от которого работает приложение. Почему у него есть право писать в том? Новый пустой том при первом подключении Docker заполняет содержимым образа в этом пути — вместе с владельцем. В Dockerfile папка /data отдана 10001 (RUN … chown 10001:10001 /data), и том получил того же владельца.

Bind mount: папка хоста как есть

mkdir -p data
ls -ld data
docker run -d --name rpd-bind -p 127.0.0.1:8081:8000 -v "$PWD/data:/data" rpd-notes:1
sleep 1
docker ps -a --filter name=rpd-bind --format '{{.Names}} {{.Status}}'
docker logs rpd-bind
drwxr-xr-x 2 root root 4096 Oct 16 14:57 data
rpd-bind Exited (1) Less than a second ago
notes: не могу писать в /data: Permission denied

$PWD подставляет текущую папку: Docker требует абсолютный путь. Папку создал root, а приложение — пользователь 10001. Docker не меняет права папки хоста — она подключается как есть. Минимальное исправление — отдать папку тому пользователю, который в неё пишет:

docker rm rpd-bind
chown 10001:10001 data
docker run -d --name rpd-bind -p 127.0.0.1:8081:8000 -v "$PWD/data:/data" rpd-notes:1
sleep 1
curl -sS -d 'Заметка в папке хоста' http://127.0.0.1:8081/notes
cat data/notes.json
Сохранено. Заметок: 1
["Заметка в папке хоста"]

Файл появился прямо в папке проекта на хосте. Это удобно для файлов, которые вы правите сами (настройки, код при разработке), и для резервной копии. Для данных приложения обычно лучше том: Docker сам выставит права, и данные не лежат среди файлов проекта. Права 777 на папку — не исправление: писать туда сможет любой.

Файл можно подключить и только для чтения: -v "$PWD/nginx/default.conf:/etc/nginx/conf.d/default.conf:ro" — так в главе 6 подключим настройки nginx.

Что удаляет данные

docker volume rm rpd-notes-data
Error response from daemon: remove rpd-notes-data: volume is in use - [9a47bcbf0aba…]

Пока том подключён к контейнеру (даже остановленному), Docker его не удалит. После docker rm -f rpd-app команда docker volume rm rpd-notes-data удалит том без подтверждения и без возврата. Поэтому:

  • удаляйте тома только по имени и только свои (--filter name=rpd-);
  • не используйте команды, которые чистят «всё неиспользуемое» (docker volume prune, docker system prune --volumes): на общей машине они удалят и чужие данные.

Уборка после разбора (учебные данные больше не нужны):

docker rm -f rpd-app rpd-bind
docker volume rm rpd-notes-data

Практика: обновление без потери данных

  1. Создайте том rpd-notes-data и запустите rpd-app из rpd-notes:1 с этим томом и портом 127.0.0.1:8080. Сохраните две заметки.
  2. «Выпустите новую версию»: пересоздайте контейнер rpd-app с тем же томом, но с новым заголовком -e GREETING='Заметки, версия 2'.
  3. Проверьте, что заголовок новый, а обе заметки на месте.
  4. Ответьте: какая из команд — docker stop rpd-app, docker rm -f rpd-app, docker volume rm rpd-notes-data — удалит заметки?

Признаки успеха: curl -sS http://127.0.0.1:8080/ показывает Заметки, версия 2 и Заметок: 2; curl -sS http://127.0.0.1:8080/notes — обе заметки. В конце удалите учебные контейнер и том (docker rm -f rpd-app, затем docker volume rm rpd-notes-data): в главе 6 их создаст Compose.

Подсказка 1

Все команды — из разбора: docker volume create, docker run … -v том:/data …, curl -d.

Подсказка 2

Переменные окружения задаются при создании контейнера. Чтобы сменить GREETING, контейнер нужно пересоздать — docker rm -f rpd-app и снова docker run с -e.

Подсказка 3

docker run -d --name rpd-app -p 127.0.0.1:8080:8000 -v rpd-notes-data:/data -e GREETING='Заметки, версия 2' rpd-notes:1.

Решение — в answers/05-data.md.

Если результат другой

  • Заметок: 0 после пересоздания. Забыли -v rpd-notes-data:/data или опечатались в имени тома — Docker молча создал новый пустой том. Проверьте docker volume ls --filter name=rpd- и docker inspect -f '{{json .Mounts}}' rpd-app.
  • Контейнер с bind mount сразу Exited (1), в журнале Permission denied. Владелец папки хоста не тот, от кого работает приложение. chown на пользователя приложения, а не chmod 777.
  • Папка проекта пустая, а приложение пишет «куда-то». Вы написали -v data:/data без $PWD/. Слева не путь, а имя — Docker создал том с именем data (docker volume ls его покажет). Для bind mount нужен абсолютный путь: -v "$PWD/data:/data", в кавычках — путь может содержать пробелы.

Проверка понимания

  1. Почему заметки пережили docker stop и docker start даже без тома?
  2. Вы подключили к новому контейнеру том по имени, которого не было. Что произойдёт?

Ответы — в answers/05-data.md.

Дальше: 6. Compose: два сервиса вместе.

Разбор и проверка понимания

Лист ученика: 05-data.md.

Практика

docker volume create rpd-notes-data
docker run -d --name rpd-app -p 127.0.0.1:8080:8000 -v rpd-notes-data:/data rpd-notes:1
sleep 1
curl -sS -d 'Первая' http://127.0.0.1:8080/notes
curl -sS -d 'Вторая' http://127.0.0.1:8080/notes
Сохранено. Заметок: 1
Сохранено. Заметок: 2

«Новая версия» — пересоздать контейнер с тем же томом:

docker rm -f rpd-app
docker run -d --name rpd-app -p 127.0.0.1:8080:8000 -v rpd-notes-data:/data -e GREETING='Заметки, версия 2' rpd-notes:1
sleep 1
curl -sS http://127.0.0.1:8080/
curl -sS http://127.0.0.1:8080/notes
Заметки, версия 2
Заметок: 2
Первая
Вторая

Какая команда удалит заметки: только docker volume rm rpd-notes-data. docker stop не трогает даже слой контейнера; docker rm -f rpd-app удаляет контейнер и его слой, но заметки — в томе.

Воспроизведено: Docker Engine 29.8.2, образ-заменитель python:3.12-slim.

Проверка понимания

  1. stop и start работают с тем же контейнером, а его слой для изменений удаляется только вместе с контейнером (docker rm).
  2. Docker молча создаст новый пустой том с этим именем (а для нашего образа — заполнит его содержимым /data из образа). Приложение запустится «без данных», ошибки не будет. Проверка — docker volume ls --filter name=rpd- и docker inspect -f '{{json .Mounts}}' rpd-app (проверено на опечатке rpd-notse-data).

Прочитали и разобрались? Отметьте урок — прогресс сохранится в этом браузере.