From bd12b02fc61e5a717206160f5f029fa910870784 Mon Sep 17 00:00:00 2001 From: "E.Gavrilov" Date: Thu, 3 Sep 2026 22:39:52 +0300 Subject: [PATCH] =?UTF-8?q?EDITORIAL-286:=20=D1=83=D0=BB=D1=83=D1=87=D1=88?= =?UTF-8?q?=D0=B8=D1=82=D1=8C=20=D1=81=D1=82=D0=B0=D1=82=D1=8C=D1=8E=20?= =?UTF-8?q?=D0=BE=20=D0=BB=D0=BE=D0=BA=D0=B0=D0=BB=D1=8C=D0=BD=D0=BE=D0=BC?= =?UTF-8?q?=20Docker?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- editorial/agent-rewrites/286.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/editorial/agent-rewrites/286.json b/editorial/agent-rewrites/286.json index 9f7ae36..b8bc170 100644 --- a/editorial/agent-rewrites/286.json +++ b/editorial/agent-rewrites/286.json @@ -1,7 +1,7 @@ { "index": 286, "slug": "editorial-2020-01-field-docker-local", - "title": "Чистый запуск Docker: как найти старое состояние и не потерять данные", - "excerpt": "После изменения Dockerfile или миграции локальный стек продолжает показывать старый результат. Разбираем, где живёт состояние, как проверить каждый слой и когда чистый запуск действительно безопасен.", - "contentHtml": "

Симптом знакомый: вы меняете миграцию или Dockerfile, выполняете docker-compose up, а приложение продолжает видеть старую схему или старую версию файлов. На новом ноутбуке тот же репозиторий ведёт себя иначе. Цена ошибки — не только потерянный час. Команда может удалить нужные локальные данные, исправить не тот слой и закрепить в README опасную команду вроде глобального prune.

\n

Тезис простой: чистый запуск Docker — это контрольный эксперимент, а не уборка всего daemon. Сначала нужно доказать, где осталось старое состояние. Потом удалить или пересоздать только этот слой. Контейнер, образ, bind mount и named volume живут по разным правилам. Повторный up не делает их одинаково свежими.

\n

Где остаётся старое состояние

\n

Контейнер хранит собственный записываемый слой и параметры запуска. Если контейнер пересоздали, этот слой исчезает. Образ хранит слои, созданные при сборке. Изменение Dockerfile или lock-файла не меняет уже собранный образ само по себе. Bind mount показывает контейнеру выбранный путь с хоста. Он обычно даёт текущие файлы рабочей копии, но только по правильному пути. Named volume хранит данные независимо от жизненного цикла контейнера. Для PostgreSQL там остаются таблицы, роли и история миграций.

\n

Эти слои дают разные симптомы. Старый код после изменения Dockerfile указывает на старый образ или контейнер. Старые таблицы после пересоздания контейнера указывают на volume. Файл есть на хосте, но процесс читает старую копию, если mount направлен в другой каталог. Поэтому команда docker-compose down -v может устранить один симптом и одновременно уничтожить полезные данные, не доказав причину.

\n
Симптом → причина → проверка → действие
СимптомПричинаПроверкаДействие
Старая схема базы после нового контейнераNamed volume пережил контейнерСверить volume в docker-compose config и mounts через docker inspectСохранить данные или удалить только volume локального проекта
Новый Dockerfile не меняет версию приложенияИспользуется старый образ или контейнерПроверить время сборки, image ID и command в docker-compose ps и inspectПересобрать и пересоздать только нужный сервис
Контейнер не видит изменённый файлНеверный путь bind mount или рабочий каталогСопоставить путь на хосте, destination mount, working_dir и командуИсправить путь; volume базы здесь не поможет
После очистки ошибка осталасьПричина в переменной, адресе сервиса или кодеПовторить тот же запрос и сравнить безопасный лог конфигурацииВернуть сохранённые данные и перейти к следующей гипотезе
\n

Сначала зафиксируйте факты

\n

До разрушительной команды нужен снимок проекта. Назовите Compose-проект, сервис, который даёт симптом, и данные, которые нельзя потерять. Затем соберите итоговую конфигурацию. В ней видны подставленные переменные, сервисы, volumes, пути и команды. Это важнее исходного YAML: Compose мог получить значение из окружения или файла .env.

\n
docker-compose config --services\ndocker-compose config\ndocker-compose ps\ndocker-compose logs --tail=50 web db
\n

Сохраните вывод до изменения. Если проблема связана с базой, дополнительно проверьте имя volume и его привязку к контейнеру. Если проблема связана с кодом, проверьте image ID, дату сборки и mount. Не печатайте пароль базы в общий лог. Учебные значения в примере ниже нужны только для локального стенда и не задают правило хранения секретов.

\n

Минимальный пример Compose

\n

В этом учебном фрагменте исходники приходят через bind mount, а PostgreSQL пишет данные в named volume. Сервисы разделены: очистка базы не должна автоматически означать удаление исходников или образа.

\n
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:
\n

Строка .:/srv/app не копирует исходники в образ. Она связывает текущий каталог хоста с путём внутри контейнера. Если процесс работает из /app, а mount направлен в /srv/app, приложение может читать другой набор файлов. Строка postgres_data:/var/lib/postgresql/data делает состояние базы постоянным между пересозданиями контейнера. Поэтому обычный up возвращает ту же схему, пока volume существует.

\n
\"Схема
Чистый запуск отделяет образ, контейнер, bind mount и named volume. Удаляется только подтверждённый источник старого состояния.
\n

Выберите действие по гипотезе

\n

Если изменился Dockerfile, lock-файл или команда запуска, начните с пересборки образа и пересоздания сервиса. Удалять volume базы для этого не нужно. Учебная проверка может выглядеть так:

\n
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
\n

Если изменились исходники под bind mount, сборка может вообще не быть причиной. Сверьте файл внутри контейнера с файлом на хосте. Если они различаются, проверьте путь, рабочий каталог и способ запуска. Не заменяйте bind mount удалением named volume: это два разных источника.

\n

Если проверка доказывает, что старые таблицы живут в postgres_data, перед удалением сохраните нужные данные. Для тестовой базы допустим отдельный разрушительный сценарий с известным Compose-проектом. Команда с -v должна быть ограничена этим проектом, а не заменяться docker volume prune. В рабочем проекте остановитесь, если не можете назвать volume и подтвердить, что его содержимое восстановимо.

\n

Маршрут чистого эксперимента

\n
  1. Сформулируйте симптом и слой, который должен измениться: образ, контейнер, bind mount или named volume.
  2. Зафиксируйте имя Compose-проекта, список сервисов, итоговый config, состояние контейнеров и короткие логи.
  3. Проверьте путь данных и сохраните нужный локальный дамп. Если безопасность данных не доказана, не удаляйте volume.
  4. Выполните одно ограниченное действие: пересоберите образ, пересоздайте сервис или удалите только подтверждённый volume тестовой базы.
  5. Запустите тот же стек и пройдите один контрольный маршрут: миграцию, запрос к известной таблице или URL с заранее описанным ответом.
  6. Сравните результат с исходным симптомом. Запишите, какой слой подтвердился или исключился, и только потом выбирайте следующую гипотезу.
\n

Отрицательный путь: очистка не помогла

\n

Представим, что volume удалён, база создана заново, а приложение всё ещё получает ошибку подключения. Из этого следует только одно: старый volume больше не объясняет симптом. Не нужно повторять очистку и расширять радиус удаления. Проверьте значение DATABASE_URL, имя сервиса db, готовность PostgreSQL и код миграции. Внутри контейнера localhost означает сам контейнер, а не соседний сервис. Для Compose-сети адресом базы служит имя сервиса.

\n

Если после пересборки и пересоздания сервис всё ещё запускает старую версию, проверьте, не перекрывает ли образ bind mount, не используется ли другой Compose-файл и не запущен ли контейнер вне текущего проекта. Успешный статус Up не доказывает готовность приложения. Нужен ответ контрольного маршрута и лог, который подтверждает именно проверяемое условие.

\n

Ограничения и критерий готовности

\n

Чистый локальный запуск не проверяет production-обновление базы, резервное копирование или совместимость версий Docker. Он не доказывает, что миграция безопасна для существующих данных. Синтаксис docker-compose сохранён в командах как исторический вариант для исходного контекста; в современных установках используется docker compose. Перед копированием примера проверьте версию CLI и формат проекта.

\n

Сценарий готов к использованию, если выполнены четыре условия: команда называет удаляемый слой; вывод до операции сохранён; данные либо не нужны, либо восстановимы; контрольный маршрут даёт заранее определённый результат после запуска. Если хотя бы одно условие не выполнено, это не чистый эксперимент, а рискованное удаление состояния.

\n

Проверяемые источники

\n" + "title": "Старый PHP в Docker: как воспроизводимо поднять локальный проект", + "excerpt": "Проект требует версию PHP, которой нет на компьютере разработчика. Разбираем, как описать окружение в Docker, проверить границы между образом, контейнером и данными и не превратить чистый запуск в потерю локальной базы.", + "contentHtml": "

Симптом появляется ещё до первого изменения в коде: проект требует старую версию PHP, а в системе разработчика установлена другая. На одном компьютере команда запускается, на другом падает при старте или видит другой набор расширений. Установка нужного PHP поверх рабочей среды кажется быстрым решением, но она меняет систему хоста и не фиксирует версии для следующего участника.

\n

В такой ситуации Docker полезен не как кнопка «запустить всё», а как описание границы проекта. Образ фиксирует базовое окружение и набор установленных пакетов, контейнер запускает процесс, bind mount (прямое подключение пути с хоста) отдаёт текущий код, а named volume хранит данные отдельно от контейнера. Сначала нужно разделить эти слои. Только после этого становится понятно, что пересобирать и что нельзя удалять.

\n

Что именно должно быть одинаковым

\n

Для локального PHP-проекта важна не только строка с версией интерпретатора. На результат влияют расширения PHP, зависимости Composer, веб-сервер, переменные окружения и состояние базы. Если один из этих элементов берётся с хоста, разработчик получает не тот проект, который описан в репозитории.

\n

Контейнер не делает файлы проекта частью образа автоматически. В типичной схеме исходники подключаются через bind mount, а база пишет в named volume. Поэтому пересоздание контейнера может убрать его записываемый слой, но не очистить базу. И наоборот: удаление volume не исправит старый код, который процесс получает через неверный mount.

\n
Граница локального окружения и способ её проверить
СлойЧто в нём живётНаблюдаемый симптомПроверка
ОбразPHP, расширения, системные пакеты и команда сборкиВ контейнере не та версия PHP или нет расширенияdocker compose images и docker compose exec web php -v
КонтейнерЗапущенный процесс, его параметры и временные измененияПерезапуск возвращает прежнее состояние процессаdocker compose ps, docker inspect и логи
Bind mountФайлы выбранного каталога на хостеПроцесс видит не тот файл, который открыт в редактореСопоставить host path, destination и working_dir
Named volumeДанные базы или другой persistent storageНовая миграция не применяется к «новой» базеПроверить volume в итоговом Compose config и mounts
\n

Минимальная воспроизводимая схема

\n

Ниже учебный фрагмент для проекта, которому нужен PHP 7.4 и PostgreSQL. Версии, имена каталогов и команду запуска нужно заменить на значения конкретного репозитория. Пароль в примере демонстрационный: секреты не следует хранить в Compose-файле или отправлять в общий лог.

\n
services:\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:
\n

Строка .:/srv/app подключает текущий каталог к контейнеру. Если команда запускает PHP из /app, а mount установлен в /srv/app, процесс может работать не с теми файлами. Строка postgres_data:/var/lib/postgresql/data отделяет данные PostgreSQL от жизненного цикла контейнера. При следующем up Compose использует существующий volume, если его не удалили вручную.

\n

Dockerfile должен описывать именно требования приложения, а не случайные пакеты с машины разработчика:

\n
FROM php:7.4-cli\n\nWORKDIR /srv/app\nCOPY --from=composer:2 /usr/bin/composer /usr/bin/composer\nCOPY composer.json composer.lock ./\nRUN composer install --no-interaction --prefer-dist\nCOPY . .\n\nCMD [\"php\", \"-S\", \"0.0.0.0:8080\", \"-t\", \"public\"]
\n

Это не универсальный production-образ. В реальном проекте понадобятся его расширения PHP, системные библиотеки и способ запуска, зафиксированные в исходном Dockerfile. Смысл примера в другом: версия интерпретатора и порядок установки зависимостей находятся в описании окружения, а не в личной настройке ноутбука.

\n
\"Схема
Перед чистым запуском сначала фиксируем конфигурацию, затем меняем только подтверждённый слой и повторяем тот же контрольный маршрут.
\n

Сначала получить итоговую конфигурацию

\n

Исходный YAML не всегда равен тому, что получает Compose. Значения могут прийти из shell, файла .env, другого Compose-файла или флага --env-file. Поэтому диагностику начинаем с итоговой модели, а не с догадок о том, какой файл был прочитан.

\n
docker compose config --services\ndocker compose config\ndocker compose ps\ndocker compose logs --tail=50 web db
\n

В выводе проверяем имя проекта, образ или build context, список mount, рабочий каталог, команду и адрес базы. Если нужно выяснить источник подстановок, команда docker compose config --environment показывает окружение, которое Compose использует для interpolation (подстановки переменных). Такой вывод может содержать секреты, поэтому его не копируют в тикет без редактирования.

\n

После этого проверяем версию PHP внутри контейнера. Команда на хосте и команда в контейнере отвечают на разные вопросы:

\n
php -v\ndocker compose exec web php -v\ndocker compose exec web php -m | sort\ndocker compose exec web composer check-platform-reqs
\n

Первая строка показывает локальную систему и может быть полезна для сравнения. Вторая и третья показывают фактический интерпретатор и расширения процесса приложения. Если composer check-platform-reqs не проходит, сначала сверяем Dockerfile и lock-файл. Обновлять зависимости «до последних» для исправления расхождения не нужно: это меняет задачу и может скрыть исходную причину.

\n

Симптом → гипотеза → ограниченное действие

\n

Один и тот же внешний симптом не доказывает один и тот же слой. Таблица помогает выбрать действие с минимальным радиусом разрушения.

\n
Диагностическая матрица для локального проекта
СимптомГипотезаПроверкаДействие
PHP внутри контейнера старше или новее ожиданияИспользуется старый image или изменился Dockerfile без rebuildСверить build context, image ID и php -vdocker compose build web, затем пересоздать только web
Изменение PHP-файла не видноНеверный bind mount или рабочий каталогСравнить файл на хосте и путь внутри контейнераИсправить mount или working_dir, volume базы не трогать
Миграция видит старые таблицыNamed volume пережил контейнерСверить volume и mount базы через config и inspectСделать дамп; удалить volume только известного тестового проекта
После пересборки нет соединения с БДНеверны URL, имя сервиса или готовность PostgreSQLПроверить итоговую переменную, логи и имя dbИсправить конфигурацию; не расширять очистку
\n

Что пересобирать, а что сохранять

\n

Изменение Dockerfile или composer.lock относится к образу. Начинаем с пересборки и пересоздания сервиса:

\n
docker compose build web\ndocker compose up -d --force-recreate web\ndocker compose exec web php -v\ndocker compose logs --tail=50 web
\n

Если команда после rebuild всё ещё показывает старый PHP, проверяем, какой Compose-файл и проект использованы, не перекрыт ли путь bind mount и не запущен ли контейнер из другого каталога. Статус Up доказывает только, что процесс не завершился сразу. Он не доказывает нужную версию PHP, готовность базы или корректный ответ приложения.

\n

Изменение исходника под bind mount обычно не требует пересборки: сначала проверяем, что контейнер видит тот же файл. Исключение — проект, который копирует код в образ и не подключает его через mount. Тогда проверяем Dockerfile и действительно выполняем rebuild. Не переносим привычку одного режима в другой.

\n

Удаление данных — отдельный сценарий. Перед ним сохраняем дамп и записываем точное имя Compose-проекта и volume:

\n
docker compose exec -T db pg_dump -U app app > backup.sql\ndocker compose down\ndocker volume ls\n# После проверки имени тестового volume:\ndocker volume rm project_postgres_data\ndocker compose up -d db
\n

Команда docker compose down -v удаляет named volumes, объявленные в Compose-файле, и подключённые anonymous volumes. Это не «обновление PHP» и не безопасная команда по умолчанию. В рабочем проекте остановитесь, если не можете доказать, что volume принадлежит именно этому локальному стенду и что данные восстановимы. Глобальный docker volume prune для такого расследования не подходит: он расширяет область удаления за пределы одной гипотезы.

\n

Контрольный маршрут после изменения

\n

После действия повторяем не только команду запуска, но и тот же признак, по которому заметили проблему. Так эксперимент остаётся воспроизводимым.

\n
  1. Запишите исходный симптом: например, проект падает на отсутствующем расширении или миграция видит старую таблицу.
  2. Сохраните итоговый config, список сервисов, короткие логи и идентификатор проверяемого образа.
  3. Назовите один слой, который должен измениться: image, container, bind mount или named volume.
  4. Выполните одно ограниченное действие и не удаляйте соседние слои «на всякий случай».
  5. Проверьте версию PHP, расширения, состояние базы и один заранее выбранный URL или CLI-маршрут.
  6. Сравните результат с исходным симптомом и запишите, какая гипотеза подтвердилась или исключилась.
\n

Если чистый запуск не помог

\n

Представим, что контейнер пересоздан, а приложение всё ещё получает ошибку подключения. Это исключает только временное состояние старого контейнера. Следом проверяем DATABASE_URL, имя сервиса db, готовность PostgreSQL и код миграции.

\n

В Compose-сети localhost внутри контейнера означает этот же контейнер. Для обращения к соседнему сервису используется его имя из Compose, в нашем примере — db. Если приложение запускается на хосте, а база в контейнере, адрес будет другим; нельзя переносить URL из одного режима запуска в другой без проверки.

\n

Если приложение видит старый PHP после пересборки, сравниваем image ID, время сборки и фактическую команду. Если оно видит старый файл, сравниваем mount и рабочий каталог. Если ошибка появилась после удаления volume, восстанавливаем дамп и возвращаемся к конфигурации миграций. Повторное расширение очистки без новой проверки не добавляет доказательств.

\n

Границы решения

\n

Docker решает расхождение локального окружения, но не делает миграцию безопасной и не заменяет резервное копирование. Пример не проверяет production-обновление базы, права доступа, сетевую задержку или совместимость приложения с новой версией Docker. Эти условия нужно проверять отдельно.

\n

Для старого проекта команда docker-compose с дефисом могла быть исходной CLI. В актуальной документации используется docker compose; перед копированием команд проверьте установленную версию и доступный синтаксис. Важно сохранить смысл операции: сначала увидеть итоговую конфигурацию, затем изменить выбранный слой и повторить контрольный маршрут.

\n

Локальная схема готова к командному использованию, если новый разработчик получает одинаковую версию PHP из Dockerfile, видит описанные в Compose mount и сервисы, может проверить конфигурацию одной последовательностью команд, а очистка данных требует явного подтверждения. Если команда запуска зависит от ручной настройки хоста или неизвестного volume, окружение ещё не воспроизводимо.

\n

Проверяемые источники

" }