Тома и bind mount
Файлы внутри контейнера временные. Удалили контейнер — исчезли и файлы. Для баз данных и для файлов с данными нужны тома.
Потеря данных: смотрим сами
Заголовок раздела «Потеря данных: смотрим сами»Запустим Postgres без тома, создадим таблицу и удалим контейнер.
docker run -d --name pg1 -e POSTGRES_PASSWORD=secret -e POSTGRES_DB=shop postgres:17sleep 5docker exec pg1 psql -U postgres -d shop -c "CREATE TABLE t(x int); INSERT INTO t VALUES (1),(2),(3);"CREATE TABLEINSERT 0 3Запишите имя тома первого контейнера. Оно понадобится для уборки в конце:
docker inspect pg1 --format '{{range .Mounts}}{{.Name}}{{end}}'Ответ — длинная строка из букв и цифр. Это имя тома.
Удалим контейнер и запустим новый с теми же настройками:
docker rm -f pg1docker run -d --name pg2 -e POSTGRES_PASSWORD=secret -e POSTGRES_DB=shop postgres:17sleep 5docker exec pg2 psql -U postgres -d shop -c "SELECT count(*) FROM t;"ERROR: relation "t" does not existLINE 1: SELECT count(*) FROM t; ^Таблицы нет. База новая и пустая.
Почему так? Образ postgres сам создаёт анонимный том для папки /var/lib/postgresql/data. Это видно в docker inspect:
docker inspect pg2 --format '{{range .Mounts}}{{.Type}} {{.Name}} -> {{.Destination}}{{end}}'volume 60339739465d7ecb35d3abd... -> /var/lib/postgresql/dataСтарые данные остались в первом анонимном томе. Но у нового контейнера свой новый том. Вернуться к старому без имени сложно. Для работы нужен том с именем.
Уберите за собой:
docker rm -fv pg2Флаг -v удаляет вместе с контейнером его анонимный том. Том первого контейнера pg1 остался. Его имя вы записали в начале урока. Удалите его по имени: docker volume rm <имя>. Не трогайте другие тома: на вашем компьютере могут быть данные других проектов.
Два вида монтирования
Заголовок раздела «Два вида монтирования»| Именованный том (volume) | Bind mount | |
|---|---|---|
| Что это | Папка, которой управляет Docker | Папка с вашего компьютера |
| Запись | -v pgdata:/var/lib/postgresql/data | -v "$PWD/data:/data" |
| Где лежит | Внутри Docker. Путь вам не нужен | В вашей папке. Вы видите файлы |
| Для чего | Данные баз, состояние программ | Исходные файлы, код, конфиги |
Простое правило:
- База данных — именованный том.
- Ваши файлы, которые вы открываете и правите — bind mount.
Формат всегда один: -v ОТКУДА:КУДА. Если слева имя без слэша, это том. Если слева путь, это bind mount.
Именованный том для Postgres
Заголовок раздела «Именованный том для Postgres»docker volume create pgdataЗапустите Postgres с этим томом:
docker run -d --name pg \ -e POSTGRES_PASSWORD=secret \ -e POSTGRES_DB=shop \ -v pgdata:/var/lib/postgresql/data \ postgres:17Создайте таблицу, удалите контейнер и запустите новый с тем же томом:
sleep 5docker exec pg psql -U postgres -d shop -c "CREATE TABLE t(x int); INSERT INTO t VALUES (1),(2),(3);"docker rm -f pgdocker run -d --name pg \ -e POSTGRES_PASSWORD=secret \ -e POSTGRES_DB=shop \ -v pgdata:/var/lib/postgresql/data \ postgres:17sleep 5docker exec pg psql -U postgres -d shop -c "SELECT count(*) FROM t;" count------- 3(1 row)Данные пережили удаление контейнера. Логи нового контейнера скажут: Database directory appears to contain a database; Skipping initialization. Значит, Postgres нашёл старую базу.
Переменные POSTGRES_PASSWORD и POSTGRES_DB работают только при первом старте с пустым томом. Если том уже с базой, они игнорируются.
Команды для томов
Заголовок раздела «Команды для томов»docker volume ls # список томовdocker volume inspect pgdata # подробностиdocker volume rm pgdata # удалить том (после удаления контейнера)docker volume ls --filter name=pgdataDRIVER VOLUME NAMElocal pgdataТом нельзя удалить, пока его использует контейнер. Удаление тома стирает данные навсегда.
Bind mount: папка с вашего компьютера
Заголовок раздела «Bind mount: папка с вашего компьютера»Покажем на nginx. Создайте папку с одной страницей:
mkdir -p siteecho "<h1>Привет, Docker</h1>" > site/index.htmlПодключите её в контейнер:
docker run -d --name web -p 8080:80 -v "$PWD/site:/usr/share/nginx/html:ro" nginx:1.29curl -s localhost:8080<h1>Привет, Docker</h1>Теперь измените файл на своём компьютере. Контейнер перезапускать не надо:
echo "<h1>Версия 2</h1>" > site/index.htmlcurl -s localhost:8080<h1>Версия 2</h1>Контейнер видит те же файлы, что и вы. Так удобно разрабатывать: правите код в редакторе, контейнер сразу работает с новой версией.
Если порт 8080 занят, возьмите другой, например -p 8090:80.
Только чтение: :ro
Заголовок раздела «Только чтение: :ro»Суффикс :ro значит read-only. Контейнер может читать, но не может писать:
docker exec web sh -c 'echo x > /usr/share/nginx/html/new.html'sh: 1: cannot create /usr/share/nginx/html/new.html: Read-only file systemПодключайте исходные данные с :ro. Так ошибка в программе не испортит ваш CSV.
Путь слева в bind mount должен быть абсолютным. Поэтому пишем "$PWD/site", а не ./site. Кавычки нужны, если в пути есть пробелы.
Удалите контейнер: docker rm -f web.
Резервная копия тома
Заголовок раздела «Резервная копия тома»Том можно сохранить в архив с помощью временного контейнера. Он читает том и пишет архив в вашу папку:
docker stop pgdocker run --rm -v pgdata:/src:ro -v "$PWD:/backup" alpine:3.22 \ tar czf /backup/pgdata.tar.gz -C /src .docker start pgЭто копия файлов базы. Делайте её, только когда Postgres остановлен. Поэтому первая команда — docker stop. Для работающей базы лучше pg_dump: docker exec pg pg_dump -U postgres shop > shop.sql.
Практика
Заголовок раздела «Практика»Работайте в папке ~/docker-practice. Если файлов ещё нет, скачайте их в уроке Установка.
Задание 1
Заголовок раздела «Задание 1»Сначала удалите контейнер и том из примеров выше: docker rm -f pg && docker volume rm pgdata.
Запустите Postgres pg с томом pgdata. Создайте таблицу t с тремя строками. Удалите контейнер. Запустите новый с тем же томом и посчитайте строки.
Проверка: count равен 3.
Решение
Команды — в разделе «Именованный том для Postgres» выше. Главное: оба раза один и тот же флаг -v pgdata:/var/lib/postgresql/data.
Задание 2
Заголовок раздела «Задание 2»Не запуская новый контейнер, покажите, что том pgdata существует. Потом попробуйте удалить его, пока контейнер pg ещё жив. Что ответит Docker?
Проверка: ошибка вида volume is in use.
Решение
docker volume ls --filter name=pgdatadocker volume rm pgdataТом занят контейнером pg. Сначала docker rm -f pg, потом docker volume rm pgdata.
Задание 3
Заголовок раздела «Задание 3»Запустите app/report.py без своего образа. Возьмите python:3.12-slim и подключите папку курса через bind mount. Данные — data/orders.csv.
Проверка: в ответе Заказов: 500 и выручка: organic 23715.82, ads 23082.74, email 15459.67.
Решение
docker run --rm -v "$PWD:/work" -w /work \ -e ORDERS_PATH=/work/data/orders.csv \ python:3.12-slim python app/report.py-v "$PWD:/work"подключает всю папку курса.-w /workделает её рабочей папкой в контейнере.-e ORDERS_PATH=...говорит скрипту, где искать CSV.
Так запускают скрипт без своего образа. Это удобно для проверки на скорую руку.
Задание 4
Заголовок раздела «Задание 4»Подключите data с флагом :ro и попробуйте записать в неё файл из контейнера.
Проверка: ошибка Read-only file system.
Решение
docker run --rm -v "$PWD/data:/data:ro" alpine:3.22 sh -c 'echo x > /data/test.txt'Задание 5
Заголовок раздела «Задание 5»Удалите всё, что создали: контейнеры pg, web и том pgdata.
Проверка: docker volume ls --filter name=pgdata не показывает строк, кроме заголовка.
Решение
docker rm -f pg webdocker volume rm pgdataЕсли контейнера web уже нет, Docker просто скажет об этом. Это не страшно.
Дальше: Свой образ: Dockerfile.