Данные: слой, том, 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
Практика: обновление без потери данных
- Создайте том
rpd-notes-dataи запуститеrpd-appизrpd-notes:1с этим томом и портом127.0.0.1:8080. Сохраните две заметки. - «Выпустите новую версию»: пересоздайте контейнер
rpd-appс тем же томом, но с новым заголовком-e GREETING='Заметки, версия 2'. - Проверьте, что заголовок новый, а обе заметки на месте.
- Ответьте: какая из команд —
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", в кавычках — путь может содержать пробелы.
Проверка понимания
- Почему заметки пережили
docker stopиdocker startдаже без тома? - Вы подключили к новому контейнеру том по имени, которого не было. Что произойдёт?
Ответы — в 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.
Проверка понимания
stopиstartработают с тем же контейнером, а его слой для изменений удаляется только вместе с контейнером (docker rm).- Docker молча создаст новый пустой том с этим именем (а для нашего образа — заполнит его содержимым
/dataиз образа). Приложение запустится «без данных», ошибки не будет. Проверка —docker volume ls --filter name=rpd-иdocker inspect -f '{{json .Mounts}}' rpd-app(проверено на опечаткеrpd-notse-data).
Прочитали и разобрались? Отметьте урок — прогресс сохранится в этом браузере.