8 lines
16 KiB
JSON
8 lines
16 KiB
JSON
{
|
||
"index": 286,
|
||
"slug": "editorial-2020-01-field-docker-local",
|
||
"title": "Чистый запуск Docker: как найти старое состояние и не потерять данные",
|
||
"excerpt": "После изменения Dockerfile или миграции локальный стек продолжает показывать старый результат. Разбираем, где живёт состояние, как проверить каждый слой и когда чистый запуск действительно безопасен.",
|
||
"contentHtml": "<p>Симптом знакомый: вы меняете миграцию или Dockerfile, выполняете <code>docker-compose up</code>, а приложение продолжает видеть старую схему или старую версию файлов. На новом ноутбуке тот же репозиторий ведёт себя иначе. Цена ошибки — не только потерянный час. Команда может удалить нужные локальные данные, исправить не тот слой и закрепить в README опасную команду вроде глобального <code>prune</code>.</p>\n<p>Тезис простой: чистый запуск Docker — это контрольный эксперимент, а не уборка всего daemon. Сначала нужно доказать, где осталось старое состояние. Потом удалить или пересоздать только этот слой. Контейнер, образ, bind mount и named volume живут по разным правилам. Повторный <code>up</code> не делает их одинаково свежими.</p>\n<h2>Где остаётся старое состояние</h2>\n<p>Контейнер хранит собственный записываемый слой и параметры запуска. Если контейнер пересоздали, этот слой исчезает. Образ хранит слои, созданные при сборке. Изменение Dockerfile или lock-файла не меняет уже собранный образ само по себе. Bind mount показывает контейнеру выбранный путь с хоста. Он обычно даёт текущие файлы рабочей копии, но только по правильному пути. Named volume хранит данные независимо от жизненного цикла контейнера. Для PostgreSQL там остаются таблицы, роли и история миграций.</p>\n<p>Эти слои дают разные симптомы. Старый код после изменения Dockerfile указывает на старый образ или контейнер. Старые таблицы после пересоздания контейнера указывают на volume. Файл есть на хосте, но процесс читает старую копию, если mount направлен в другой каталог. Поэтому команда <code>docker-compose down -v</code> может устранить один симптом и одновременно уничтожить полезные данные, не доказав причину.</p>\n<div class=\"table-scroll\"><table><caption>Симптом → причина → проверка → действие</caption><thead><tr><th scope=\"col\">Симптом</th><th scope=\"col\">Причина</th><th scope=\"col\">Проверка</th><th scope=\"col\">Действие</th></tr></thead><tbody><tr><td>Старая схема базы после нового контейнера</td><td>Named volume пережил контейнер</td><td>Сверить volume в <code>docker-compose config</code> и mounts через <code>docker inspect</code></td><td>Сохранить данные или удалить только volume локального проекта</td></tr><tr><td>Новый Dockerfile не меняет версию приложения</td><td>Используется старый образ или контейнер</td><td>Проверить время сборки, image ID и command в <code>docker-compose ps</code> и inspect</td><td>Пересобрать и пересоздать только нужный сервис</td></tr><tr><td>Контейнер не видит изменённый файл</td><td>Неверный путь bind mount или рабочий каталог</td><td>Сопоставить путь на хосте, destination mount, <code>working_dir</code> и команду</td><td>Исправить путь; volume базы здесь не поможет</td></tr><tr><td>После очистки ошибка осталась</td><td>Причина в переменной, адресе сервиса или коде</td><td>Повторить тот же запрос и сравнить безопасный лог конфигурации</td><td>Вернуть сохранённые данные и перейти к следующей гипотезе</td></tr></tbody></table></div>\n<h2>Сначала зафиксируйте факты</h2>\n<p>До разрушительной команды нужен снимок проекта. Назовите Compose-проект, сервис, который даёт симптом, и данные, которые нельзя потерять. Затем соберите итоговую конфигурацию. В ней видны подставленные переменные, сервисы, volumes, пути и команды. Это важнее исходного YAML: Compose мог получить значение из окружения или файла <code>.env</code>.</p>\n<pre><code>docker-compose config --services\ndocker-compose config\ndocker-compose ps\ndocker-compose logs --tail=50 web db</code></pre>\n<p>Сохраните вывод до изменения. Если проблема связана с базой, дополнительно проверьте имя volume и его привязку к контейнеру. Если проблема связана с кодом, проверьте image ID, дату сборки и mount. Не печатайте пароль базы в общий лог. Учебные значения в примере ниже нужны только для локального стенда и не задают правило хранения секретов.</p>\n<h2>Минимальный пример Compose</h2>\n<p>В этом учебном фрагменте исходники приходят через bind mount, а PostgreSQL пишет данные в named volume. Сервисы разделены: очистка базы не должна автоматически означать удаление исходников или образа.</p>\n<pre><code>version: '3.7'\n\nservices:\n web:\n build: .\n command: php -S 0.0.0.0:8080 -t public\n ports:\n - '8080:8080'\n environment:\n DATABASE_URL: postgres://app:app@db:5432/app\n volumes:\n - .:/srv/app\n\n db:\n image: postgres:12.1-alpine\n environment:\n POSTGRES_DB: app\n POSTGRES_USER: app\n POSTGRES_PASSWORD: app\n volumes:\n - postgres_data:/var/lib/postgresql/data\n\nvolumes:\n postgres_data:</code></pre>\n<p>Строка <code>.:/srv/app</code> не копирует исходники в образ. Она связывает текущий каталог хоста с путём внутри контейнера. Если процесс работает из <code>/app</code>, а mount направлен в <code>/srv/app</code>, приложение может читать другой набор файлов. Строка <code>postgres_data:/var/lib/postgresql/data</code> делает состояние базы постоянным между пересозданиями контейнера. Поэтому обычный <code>up</code> возвращает ту же схему, пока volume существует.</p>\n<figure><img src=\"/assets/editorial/2020/docker-local-clean-start-2020.svg\" alt=\"Схема чистого локального запуска Docker: сначала проверяются итоговая конфигурация и логи, затем выбирается конкретный слой для пересоздания или удаления, после чего выполняется контрольный маршрут\" loading=\"lazy\" /><figcaption>Чистый запуск отделяет образ, контейнер, bind mount и named volume. Удаляется только подтверждённый источник старого состояния.</figcaption></figure>\n<h2>Выберите действие по гипотезе</h2>\n<p>Если изменился Dockerfile, lock-файл или команда запуска, начните с пересборки образа и пересоздания сервиса. Удалять volume базы для этого не нужно. Учебная проверка может выглядеть так:</p>\n<pre><code>docker-compose build --no-cache web\ndocker-compose up -d --force-recreate web\ndocker-compose exec web php -v\ndocker-compose logs --tail=50 web</code></pre>\n<p>Если изменились исходники под bind mount, сборка может вообще не быть причиной. Сверьте файл внутри контейнера с файлом на хосте. Если они различаются, проверьте путь, рабочий каталог и способ запуска. Не заменяйте bind mount удалением named volume: это два разных источника.</p>\n<p>Если проверка доказывает, что старые таблицы живут в <code>postgres_data</code>, перед удалением сохраните нужные данные. Для тестовой базы допустим отдельный разрушительный сценарий с известным Compose-проектом. Команда с <code>-v</code> должна быть ограничена этим проектом, а не заменяться <code>docker volume prune</code>. В рабочем проекте остановитесь, если не можете назвать volume и подтвердить, что его содержимое восстановимо.</p>\n<h2>Маршрут чистого эксперимента</h2>\n<ol><li>Сформулируйте симптом и слой, который должен измениться: образ, контейнер, bind mount или named volume.</li><li>Зафиксируйте имя Compose-проекта, список сервисов, итоговый config, состояние контейнеров и короткие логи.</li><li>Проверьте путь данных и сохраните нужный локальный дамп. Если безопасность данных не доказана, не удаляйте volume.</li><li>Выполните одно ограниченное действие: пересоберите образ, пересоздайте сервис или удалите только подтверждённый volume тестовой базы.</li><li>Запустите тот же стек и пройдите один контрольный маршрут: миграцию, запрос к известной таблице или URL с заранее описанным ответом.</li><li>Сравните результат с исходным симптомом. Запишите, какой слой подтвердился или исключился, и только потом выбирайте следующую гипотезу.</li></ol>\n<h2>Отрицательный путь: очистка не помогла</h2>\n<p>Представим, что volume удалён, база создана заново, а приложение всё ещё получает ошибку подключения. Из этого следует только одно: старый volume больше не объясняет симптом. Не нужно повторять очистку и расширять радиус удаления. Проверьте значение <code>DATABASE_URL</code>, имя сервиса <code>db</code>, готовность PostgreSQL и код миграции. Внутри контейнера <code>localhost</code> означает сам контейнер, а не соседний сервис. Для Compose-сети адресом базы служит имя сервиса.</p>\n<p>Если после пересборки и пересоздания сервис всё ещё запускает старую версию, проверьте, не перекрывает ли образ bind mount, не используется ли другой Compose-файл и не запущен ли контейнер вне текущего проекта. Успешный статус <code>Up</code> не доказывает готовность приложения. Нужен ответ контрольного маршрута и лог, который подтверждает именно проверяемое условие.</p>\n<h2>Ограничения и критерий готовности</h2>\n<p>Чистый локальный запуск не проверяет production-обновление базы, резервное копирование или совместимость версий Docker. Он не доказывает, что миграция безопасна для существующих данных. Синтаксис <code>docker-compose</code> сохранён в командах как исторический вариант для исходного контекста; в современных установках используется <code>docker compose</code>. Перед копированием примера проверьте версию CLI и формат проекта.</p>\n<p>Сценарий готов к использованию, если выполнены четыре условия: команда называет удаляемый слой; вывод до операции сохранён; данные либо не нужны, либо восстановимы; контрольный маршрут даёт заранее определённый результат после запуска. Если хотя бы одно условие не выполнено, это не чистый эксперимент, а рискованное удаление состояния.</p>\n<h2>Проверяемые источники</h2>\n<ul><li><a href=\"https://docs.docker.com/engine/storage/\" target=\"_blank\" rel=\"noopener noreferrer\">Docker Engine: Storage</a> — различия writable layer, volume и bind mount.</li><li><a href=\"https://docs.docker.com/reference/cli/docker/compose/down/\" target=\"_blank\" rel=\"noopener noreferrer\">Docker CLI: docker compose down</a> — область действия удаления контейнеров, сетей и volumes.</li><li><a href=\"https://docs.docker.com/reference/compose-file/volumes/\" target=\"_blank\" rel=\"noopener noreferrer\">Compose file reference: volumes</a> — объявление и подключение persistent volumes.</li></ul>"
|
||
}
|