~/guides/docker-compose-pre-start-idempotency

Docker Compose

Идемпотентность шагов pre_start в Docker Compose: почему нетранзакционность — не баг, а осознанный компромисс

Как спроектировать шаги pre_start так, чтобы повторный запуск после частичного сбоя не создавал дубли и не ломал состояние.

Секция pre_start в Docker Compose 5.3 не даёт транзакционности между шагами: если второй шаг из трёх упадёт, первый не откатится, а при повторном docker compose up все шаги запустятся заново с нуля — включая уже успешно отработавший первый.

Конкретный пример проблемы

services:
  app:
    image: myapp:latest
    pre_start:
      - command: ["./manage.py", "seed-admin-user"]
      - command: ["./manage.py", "load-initial-data"]

Если seed-admin-user создаёт запись через обычный INSERT без защиты от дубля, а второй шаг после этого падает, повторный запуск после исправления причины сбоя второго шага снова выполнит первый шаг — и получит ошибку уникальности или, того хуже, тихий дубликат записи, если уникальность не защищена на уровне базы.

Что значит идемпотентность здесь конкретно

Повторный запуск той же команды должен приводить к тому же результату, что и один запуск — без накопления побочных эффектов. Это не абстрактный принцип хорошего тона, а прямое следствие того, как pre_start реально устроен.

Операция Неидемпотентный вариант Идемпотентный вариант
Вставка записи INSERT INTO users VALUES (…) INSERT … ON CONFLICT DO NOTHING
Создание таблицы CREATE TABLE logs (…) CREATE TABLE IF NOT EXISTS logs (…)
Создание директории mkdir /data/cache mkdir -p /data/cache
Применение миграций Ручной SQL-скрипт без трекинга Alembic/Prisma Migrate — уже идемпотентны по дизайну

Почему готовые инструменты миграций обычно безопасны

Большинство современных инструментов миграций спроектированы идемпотентными изначально — они трекают, какие миграции уже применены, и пропускают их при повторном запуске. Риск в основном в самописных shell-командах и разовых SQL-скриптах внутри pre_start, а не в стандартных инструментах экосистемы.

Почему это редко замечают заранее

Неидемпотентный шаг молча работает нормально месяцами, пока однажды не случится частичный сбой соседнего шага в цепочке — и тогда повторный запуск создаёт проблему, которую тяжело диагностировать постфактум, потому что сама последовательность шагов выглядела рабочей всё это время.

Как проверить свои шаги заранее

Compose pre_start Idempotency Risk Analyzer ищет в командах эвристические признаки неидемпотентного поведения — не как гарантированный вердикт, а как список мест, которые стоит перепроверить перед боевым использованием.

Итоговый чеклист

pre_start не даёт транзакционности между шагами по дизайну — сбой второго шага не откатывает первый, а повторный запуск выполняет все шаги заново.

Готовые инструменты миграций обычно уже идемпотентны сами по себе, риск сосредоточен в самописных SQL-скриптах и shell-командах.

Неидемпотентный шаг обычно молча работает до первого частичного сбоя — стоит проверять идемпотентность заранее, а не полагаться на то, что “оно же работало”.