1. Перезапуск vs сброс базы
В Docker‑мире очень легко попасть в ловушку интуиции из “обычного” приложения: мы видим контейнер PostgreSQL и думаем, что “контейнер = база”. А Docker такой: “ха‑ха, нет”. В реальности база данных как сервис — это два слоя: процесс Postgres (живёт в контейнере) и его данные (обычно живут в томе/volume). И для нашей текущей истории “данные” — это не только строки в таблицах, но и сама схема, flyway_schema_history, и то seed‑состояние, с которым потом стартует приложение. Поэтому команда, которая убирает контейнер, может вообще не тронуть состояние, на которое Flyway будет смотреть при следующем запуске.
Давайте зафиксируем базовую мысль в одной фразе: контейнер — это упаковка процесса, а данные обычно вынесены в отдельное хранилище, которое переживает контейнер. На практике для PostgreSQL в Compose это почти всегда named volume.
Чтобы было проще представить, держите аналогию (из серии “всё в мире — это кухня”). Контейнер PostgreSQL — это как кастрюля на плите: вы можете снять её с плиты (остановить контейнер), можете вообще выкинуть кастрюлю и купить новую (удалить контейнер и создать заново). Но суп (данные) вы обычно переливаете в контейнер‑контейнер (volume), который стоит в холодильнике. Суп живёт дольше, чем конкретная кастрюля.
Мини‑схема того, что мы хотим понимать:
flowchart TD
%% Контейнер — это процесс, volume — это данные (и они живут отдельно)
A[compose up] --> B[Создан контейнер postgres]
B --> C[Контейнер монтирует named volume]
C --> D[Данные пишутся в volume]
E[docker compose down] --> F[Контейнер удалён]
D -->|обычно остаётся| G[Volume остаётся]
H[docker compose down -v] --> I[Контейнер удалён]
I --> J[Volume удалён]
Если вы эту картинку держите в голове, половина “магии” Compose исчезает.
2. Данные Postgres в compose.yaml
Сейчас мы аккуратно посмотрим на то, как именно в нашем учебном проекте Container-Ready Catalog Service PostgreSQL хранит данные. Это важно именно руками увидеть: не “в теории Docker”, а в нашем репозитории. Потому что команда docker compose down работает не с вашей идеей о базе, а с конкретной конфигурацией — и она описана в YAML.
Типичный фрагмент compose.yaml для PostgreSQL (минимально и по делу) выглядит примерно так:
services:
postgres:
image: postgres:18
environment:
# Имя базы, которая будет создана при первом старте контейнера
POSTGRES_DB: catalog
# Пользователь, который будет создан при первом старте контейнера
POSTGRES_USER: catalog
# Пароль для этого пользователя (для локалки ок, в проде так не делаем)
POSTGRES_PASSWORD: catalog
volumes:
# Ключевой момент: данные Postgres пишутся в /var/lib/postgresql/data,
# но физически хранятся в named volume, который переживает контейнер.
- postgres-data:/var/lib/postgresql/data
volumes:
# Объявляем named volume (Docker сам создаст его при необходимости)
postgres-data:
Обратите внимание: /var/lib/postgresql/data — это “дом” Postgres внутри контейнера, туда он пишет файлы данных. Но в Compose мы не оставляем эти файлы внутри writable layer контейнера, а монтируем туда named volume postgres-data. Значит, физическое хранение данных уходит “наружу” контейнера.
Тут часто возникает вопрос: “А как понять, что этот volume реально существует и живёт своей жизнью?” Самый прямой способ — посмотреть список томов в Docker:
# Показать все Docker volumes, которые сейчас существуют на машине
docker volume ls
# DRIVER VOLUME NAME
# local docker-java-catalog-service_postgres-data
Важный нюанс: имя тома в Docker часто получается не ровно postgres-data, а с префиксом Compose‑проекта. Compose по умолчанию берёт имя проекта из имени директории (поэтому у вас может быть docker-java-catalog-service_postgres-data, catalog_postgres-data и так далее). Это нормально: так Docker защищает вас от случайного конфликта томов между разными проектами.
Ещё один трезвый факт: если вы удалили контейнер Postgres, том может продолжать существовать. Он не “приклеен” к контейнеру навечно; это отдельный объект в Docker.
3. Что делает docker compose down
Команда docker compose down обычно воспринимается как “ну всё, выключить стенд”. И это почти правда — но с одним большим исключением, которое и создаёт большую часть боли. down убирает контейнеры вашего Compose‑проекта, убирает сеть, которую Compose создал для них, и в целом “разбирает стенд”. Но named volumes по умолчанию он не удаляет, потому что volumes воспринимаются как данные, которые могут быть вам ещё нужны.
Представьте, что вы делаете так:
# Поднимаем стек (контейнеры + сеть + подключение volume)
docker compose up -d
# Разбираем стек (контейнеры + сеть), но volume оставляем
docker compose down
# Поднимаем стек снова — контейнеры будут новые, а volume будет старый
docker compose up -d
С точки зрения контейнеров, это будет “пересоздать”. С точки зрения named volume, это будет “использовать старое хранилище данных”. То есть вы получили новый контейнер PostgreSQL (новый процесс), но подключили к нему старую директорию данных.
И это прекрасно видно по поведению Flyway. Когда база “чистая”, Flyway при старте приложения применяет миграции. Когда база “не чистая” (то есть schema history уже есть), Flyway обычно быстро сообщает, что всё уже сделано. Условный пример логов при повторном старте после обычного down может выглядеть так:
# Пример: база НЕ чистая, потому что flyway_schema_history уже есть в volume
app-1 | Flyway Community Edition ...
app-1 | Schema history table "public"."flyway_schema_history" already exists
app-1 | No migrations necessary
app-1 | Started CatalogApplication
Это полезный сигнал: вы думали, что “перезапустили всё с нуля”, а на самом деле вы запустились на уже существующей схеме и данных.
Ещё одна частая путаница: новички ожидают, что down удалит вообще всё, включая образы. Нет. down не удаляет image, который вы собрали для app, и не удаляет postgres:18, который вы скачали. Он не занимается “генеральной уборкой”, он просто разбирает конкретный стек контейнеров.
Иногда стоит на секунду сравнить down с stop. docker compose stop обычно останавливает контейнеры, но оставляет их как объекты (их можно потом запустить обратно). docker compose down идёт дальше: он контейнеры удаляет, чтобы следующий up создал новые. Но повторюсь, volume при этом остаётся, и именно из‑за этого “новый контейнер” не означает “новая пустая база”.
4. Что делает docker compose down -v
Теперь к команде, которая внешне отличается всего одной буквой, а по смыслу — как “закрыть вкладку” и “снести Windows”. Флаг -v у docker compose down говорит Compose: “пожалуйста, вместе с контейнерами удали и volumes, объявленные в файле”.
То есть вот так:
# Разбираем стек и одновременно удаляем named volumes из compose.yaml
docker compose down -v
# Поднимаем снова — Postgres получает новый пустой volume
docker compose up -d
Этот сценарий означает: контейнеры будут пересозданы, сеть будет пересоздана, и PostgreSQL получит полностью новый, пустой volume, потому что старый вы удалили. Для базы это “я только что родилась”.
И вот тут снова отлично виден Flyway. После down -v и нового up приложение при старте снова будет применять миграции “с нуля”, потому что schema history таблицы в чистой базе ещё нет:
# Пример: база чистая, Flyway создаёт таблицу истории и накатывает миграции заново
app-1 | Flyway Community Edition ...
app-1 | Creating schema history table "public"."flyway_schema_history"
app-1 | Successfully applied 2 migrations
app-1 | Started CatalogApplication
В этой лекции важно не впасть в крайность: down -v — не “правильнее”, чем down. Это просто другой инструмент.
down -v — это сброс локальной базы до чистого состояния. Он полезен, когда вам нужно воспроизводимо вернуться к baseline, а не просто перезапустить процессы. Это как “начать уровень заново”, а не “поставить игру на паузу и продолжить”.
И да, это действительно удаляет данные. В этом месте курс обычно теряет одного студента, который радостно сделал down -v в проекте, где “ну я тут недельку аккуратно руками правил базу” — и внезапно понял, что “ручная правка” была не лучшей стратегией хранения знаний. Docker не злой — он просто честный.
5. Как выбирать down или down -v
Сейчас мы превратим две команды в понятное инженерное решение: какую применять и почему. Важно не заучить правило “делай всегда -v”, а понять контекст. Иначе вы будете либо постоянно терять данные, либо постоянно таскать за собой “грязное” состояние базы, которое мешает воспроизводимости.
Самый частый сценарий для docker compose down — когда вам нужно просто “пересобрать стенд” после изменения конфигурации, образа приложения или просто чтобы убрать подвисшие контейнеры. Например, вы поменяли SPRING_PROFILES_ACTIVE, поправили порт, подправили healthcheck, обновили образ app и хотите, чтобы Compose пересоздал контейнеры. При этом данные БД вам не мешают, а даже полезны: вы не хотите каждый раз терять схему, history table Flyway и рабочие/seed‑записи.
А вот docker compose down -v нужен как осознанный “reset до чистого состояния”. Но здесь важно не перепутать цель: -v не лечит саму SQL‑ошибку и не является обязательным шагом после каждого падения Flyway. Для нашего PostgreSQL baseline обычный путь после фикса migration‑файла — заново поднять стек на том же volume: схема, history table и данные остаются, а неприменённая миграция просто пробуется ещё раз. -v нужен для другой задачи — когда вы хотите сознательно стереть локальное состояние, прогнать всю историю миграций с нуля, проверить seed baseline или убрать накопившийся drift.
Чтобы мозгу было проще, можно держать такую табличку:
| Команда | Контейнеры | Сеть Compose | Named volume PostgreSQL | Ожидаемый эффект |
|---|---|---|---|---|
| docker compose down | удаляются | удаляется | остаётся | “перезапуск стенда без потери БД” |
| docker compose down -v | удаляются | удаляется | удаляется | “полный reset БД до чистого состояния” |
Ещё один практический момент: down -v — это не “починка” проблемы как таковой. Если у вас поломана миграция (SQL неверный), удаление volume не делает SQL правильным. Оно лишь возвращает вас в известную точку, чтобы после исправления миграции вы могли убедиться, что проект действительно поднимается “с нуля” на чистой базе. Источник истины всё равно остаётся в репозитории.
Проверка, что база старая
Когда человек видит, что приложение стартует странно, первая реакция обычно “ну я сейчас пару раз перезапущу”. Это нормальная человеческая реакция, но она плохо дружит с контейнерами. Тут очень важно заменять гадание наблюдением: проверяем, остался ли volume, остались ли контейнеры, как Compose назвал проект, и какое состояние у Postgres.
Начать можно с самого простого: посмотреть, что вообще сейчас запущено и что было создано:
# -a показывает и остановленные контейнеры (это полезно при разборе падений)
docker compose ps -a
# NAME STATUS
# catalog-postgres-1 Exited (0)
# catalog-app-1 Exited (1)
Дальше смотрим volumes. Если вы сделали down, контейнеров в ps уже не будет, но volume, скорее всего, останется:
# Volume остаётся, даже если контейнеры уже удалены
docker volume ls
# local docker-java-catalog-service_postgres-data
Если вы сделали down -v, и после этого docker volume ls больше не показывает ваш том, значит база действительно сброшена.
Иногда полезно уточнить “а какой именно том соответствует моему проекту”. Для этого можно посмотреть, какие volumes объявлены у проекта, прямо в compose.yaml, и помнить, что Docker будет хранить их с префиксом Compose‑проекта. Если у вас в компьютере несколько учебных репозиториев, то “postgres-data” может превратиться в несколько разных Docker‑томов с разными префиксами — и это нормально.
И наконец, самый прикладной сигнал — это логи. В рамках этого курса вы уже умеете читать логи app и postgres вместе. Тут та же мысль: если после перезапуска Flyway не применяет миграции, то база не чистая; если применяет, то база новая. Даже не нужно быть DBA, достаточно быть внимательным к нескольким строчкам лога.
6. Типичные ошибки при reset локальной БД в Compose
Ошибка №1: думать, что docker compose down “очищает базу”.
Обычно это проявляется так: вы “выключили стенд”, подняли снова, а миграции не применяются и данные “почему‑то” остались. На самом деле ничего мистического не произошло: вы просто оставили named volume, и Postgres поднялся на старом состоянии. Это ожидаемо и правильно.
Ошибка №2: использовать docker compose down -v как первую реакцию на любую проблему.
Иногда студент встречает первую ошибку старта и сразу стирает volume. Да, это “помогает”, потому что вы возвращаетесь к чистому состоянию. Но это превращается в привычку, которая маскирует реальные проблемы конфигурации и усложняет жизнь: вы каждый раз теряете данные и заново ждёте миграции, даже когда это не нужно.
Ошибка №3: пытаться “лечить” broken migration только reset’ом.
Если миграция падает из‑за ошибки в SQL, удаление volume даёт вам чистую базу, но при следующем запуске вы снова упадёте на том же месте. Reset — это часть recovery‑workflow, но не замена исправления миграции. Источник истины — Vx__*.sql в репозитории, а не ваша локальная база.
Ошибка №4: путать “контейнер PostgreSQL” и “данные PostgreSQL”.
Это выглядит так: человек удалил контейнер и уверен, что “удалил базу”. Но данные уехали в volume, и при следующем up они вернутся. Чтобы не ловить это как сюрприз, держите в голове связку: контейнер — процесс, volume — данные. Удалили только процесс — данные остались.
Ошибка №5: не проверять результат reset’а по наблюдаемым признакам.
Иногда делают down, потом up, потом смотрят только на факт “контейнеры запущены” и успокаиваются. Но сейчас этого уже недостаточно. Лучше проверить две вещи: что пишет Flyway (применял ли миграции) и что показывает API/health. Если вы ожидаете чистую базу, а Flyway пишет “No migrations necessary”, значит “чистая” — это была просто мечта, а не реальность.
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ