Симптом неприятный: после изменения миграции локальная база ведёт себя по-старому, а на новом ноутбуке сценарий не повторяется. Кажется, что Docker игнорирует compose-файл, поэтому хочется удалить все контейнеры и выполнить глобальный prune. Цена такого действия выше самой ошибки: можно потерять нужные локальные данные, не понять, какой слой был виноват, и оставить в README разрушительную команду без границ.
Чистый запуск нужен не как ритуал, а как контрольный эксперимент. Он отвечает на один вопрос: ошибка воспроизводится на конфигурации без выбранного старого состояния или исчезает вместе с конкретным volume, образом либо bind mount. Для этого сначала фиксирую проект, сервисы и данные, которые разрешено удалить, затем собираю итоговый Compose YAML, запускаю стек и проверяю один известный маршрут. В январе 2020 это означает отдельный CLI docker-compose и Compose-файл 3.7; другой синтаксис не подменяет исторический пример.
Почему повторный up не равен чистой среде
Контейнер можно пересоздать, а named volume останется. В PostgreSQL это обычно означает, что каталог данных с таблицами, пользователями и историей миграций пережил контейнер. Bind mount ведёт себя иначе: он каждый раз показывает текущие файлы рабочей копии. Образ содержит зафиксированные при сборке слои, но уже созданный контейнер может продолжать работать на старом образе, пока его не пересоздали. Эти три типа состояния нельзя удалять одной командой «на всякий случай».
Сначала полезно назвать ожидаемую чистоту. Нужна пустая база? Тогда артефакт — отсутствие старых таблиц до миграции и понятный лог их применения. Нужен чистый frontend build? Тогда артефакт — версия образа или пересозданный контейнер, а не удалённый volume PostgreSQL. Нужны свежие исходники? Тогда проверяется bind mount и рабочая копия. Такая формулировка защищает от ложного успеха: после удаления всех данных приложение может подняться, но исходная ошибка в неправильной строке DATABASE_URL останется нерасследованной.
| Слой | Какой след может остаться | Безопасная проверка | Разрешённое действие |
|---|---|---|---|
| Контейнер web | старый процесс или старый command | сверить образ, command и время создания через docker-compose ps и inspect | пересоздать только сервис web после фиксации конфигурации |
| Named volume postgres_data | схема, данные, журнал миграций PostgreSQL | назвать volume и ожидаемый признак пустой базы | удалить только volume данного локального проекта после явного подтверждения |
| Bind mount рабочей копии | не старый снимок, а текущие файлы хоста | сопоставить путь mount с working_dir и изменить контрольный файл | исправить путь или рабочую копию; удаление volume не поможет |
| Образ web | слои зависимостей и код, скопированный при build | проверить, был ли образ пересобран после изменения Dockerfile или lock-файла | пересобрать конкретный образ, не стирая данные базы |
В таблице намеренно нет команды глобальной очистки Docker. Она слишком широка для локального расследования: может затронуть образы, volumes и сети других проектов. Локальный эксперимент должен иметь имя Compose-проекта и ограниченный набор сервисов. Если человек не может назвать volume, который удаляет, операция ещё не готова к запуску.
Сначала смотрим фактический проект, потом удаляем состояние
Перед разрушительной веткой запускаю docker-compose config --services и docker-compose config. Первая команда помогает увидеть, какой набор сервисов объявлен. Вторая показывает, не подставилась ли неожиданная переменная и действительно ли база пишет в postgres_data. Затем сохраняю короткий лог docker-compose logs --tail=50 web db и состояние docker-compose ps. Это снимок до эксперимента: без него невозможно отличить «состояние было старым» от «стек вообще не поднимался».
В Compose-файле ниже named volume назван явно. Это важно для разговора о чистом старте: команда обсуждает не абстрактный «docker cache», а конкретный каталог данных PostgreSQL. Bind mount для исходников оставлен отдельно и не исчезает при очистке volume. Если проект использует реальные локальные данные, до такой операции нужен экспорт или иной согласованный способ восстановления; учебный пароль и база в примере не являются разрешением удалять чужую рабочую базу.
version: '3.7'
services:
web:
build: .
command: npm run dev
ports:
- '3000:3000'
environment:
DATABASE_URL: postgres://app:app@db:5432/app
volumes:
- .:/srv/app
db:
image: postgres:12.1-alpine
environment:
POSTGRES_DB: app
POSTGRES_USER: app
POSTGRES_PASSWORD: app
volumes:
- postgres_data:/var/lib/postgresql/data
volumes:
postgres_data:
Историческая команда docker-compose down -v удаляет ресурсы, связанные с данным Compose-проектом, включая объявленные named volumes, когда флаг -v действительно передан. Это полезно только после проверки имени проекта и списка сервисов. Она не заменяет понимание того, что именно удаляет, и не должна записываться в README без слова «локально» и без предупреждения о данных. В этом материале сохранён синтаксис эпохи Compose v1, чтобы команда и формат файла не расходились.
Один контрольный маршрут вместо «вроде заработало»
После очистки не достаточно увидеть зелёный статус Up. Выбираю один маршрут, который точно зависит от обновлённого слоя. Для базы это может быть запуск миграции с ожидаемым логом и запрос к таблице. Для frontend — один URL, в ответе которого видна версия сборки или изменённая строка. Для переменной — безопасная диагностическая строка, подтверждающая, что приложение прочитало нужный режим. Маршрут должен быть коротким и повторяемым до и после эксперимента.
Если после удаления postgres_data ошибка исчезла, вывод тоже ограничен: старое состояние базы было частью условия. Это ещё не доказывает, что миграция написана правильно или что production переживёт обновление. Если ошибка осталась, volume исключён, и следующий кандидат — адрес сервиса, переменная, образ или код. Такая развилка экономит время: вместо нового полного переустановления команда переносит внимание только на слой, который ещё способен объяснить симптом.
Чистота должна быть обратимой и документированной
В локальной команде полезно держать два разных маршрута: обычный docker-compose up -d для продолжения работы и явно разрушительный clean-start для тестовой базы. У разрушительного маршрута должны быть входные условия: проект локальный, данные не нужны или сохранены, имя Compose-проекта проверено, список volumes известен. После него должны быть выходные условия: база создана заново, миграции прошли, выбранный endpoint отвечает. Без этих границ команда учится стирать состояние, а не отлаживать конфигурацию.
Образ тоже не надо смешивать с volume. Если Dockerfile или lock-файл изменились, можно пересобрать только web и затем проверить, использует ли новый контейнер этот образ. Если изменились исходники под bind mount, сборка может вообще не быть причиной. Если поменялась база, named volume может быть именно тем, что надо сохранить для обычной работы. Хороший сценарий чистого запуска показывает эти различия прямо в имени команды и в ожидаемом результате.
Маршрут чистого эксперимента
- Назвать симптом и слой, который должен быть чистым: образ, контейнер, bind mount или named volume; не начинать с удаления всего Docker.
- Зафиксировать имя локального Compose-проекта, вывод
docker-compose config --services, итоговый config, ps и короткие логи до изменения. - Проверить, какие данные затрагивает выбранный volume; сохранить нужный локальный дамп либо остановиться, если безопасность данных не доказана.
- Выполнить ограниченное действие: пересобрать web, пересоздать один контейнер или для учебной базы осознанно выполнить
docker-compose down -vв нужном проекте. - Запустить тот же стек и пройти один контрольный маршрут: миграция, безопасный запрос или endpoint с заранее описанным результатом.
- Записать вывод: какой слой исключён или подтверждён, какие данные были удалены и какой следующий эксперимент нужен, если симптом остался.
Итог: чистый запуск — это доказательство, а не кнопка
В Docker локальное состояние живёт в нескольких местах. Контейнеры, образы, bind mounts и named volumes переживают разные операции. Поэтому чистый запуск ценен только тогда, когда заранее определено, какой след удаляется и какая проверка должна измениться. Такая дисциплина оставляет данные в безопасности и превращает странный локальный дефект в последовательность наблюдений.
Эта статья не запускала команды с Docker daemon и не удаляла volumes рабочего проекта. Примеры показывают структуру безопасного эксперимента, а не подтверждённый прогон production-среды. Перед использованием в конкретном репозитории нужно подставить реальные сервисы, путь данных, команду миграции и правило резервного копирования. Если эти четыре вещи не названы, чистый запуск ещё нельзя считать безопасным.
Проверяемые источники
- Docker Engine 19.03 release notes — историческая ветка Engine, актуальная в начале 2020 года; она задаёт границу примеров, а не современный набор возможностей Docker
- Docker Compose FAQ: различие Compose v1 и v2 — документация фиксирует, что проекты Compose v1 обычно использовали верхнее поле version с форматами 2.x и 3.x; в статье поэтому намеренно используется команда docker-compose и version 3.7
- Docker Compose: legacy file versions — справочник по историческим форматам Compose 2.x и 3.x, с которыми работал отдельный бинарник docker-compose
- Docker: manage volumes — назначение именованных volumes и отличие управляемого Docker хранилища от файлов рабочей копии
- Docker: bind mounts — граница между путём на машине Docker daemon и путём внутри контейнера, а также риск скрыть содержимое целевой директории монтированием
- Docker Compose: down — современное описание удаления контейнеров, сетей и опциональных volumes; историческая команда в статье сохранена как docker-compose down -v
- Docker Compose: startup and shutdown order — порядок запуска сервисов не равен готовности приложения принимать соединения; готовность нужно проверять отдельным наблюдением