~/guides/docker-compose-pre-start-init-containers

Docker Compose

pre_start в Docker Compose 5.3: нативные init-контейнеры без костылей с service_completed_successfully

Новая секция pre_start (Compose 5.3.0, июль 2026) заменяет паттерн с отдельным one-shot сервисом для миграций и подготовки томов перед стартом основного контейнера.

Второго июля 2026 года вышел Docker Compose v5.3.0, и в нём появилась вещь, которую сообщество просило годами: полноценные init-контейнеры прямо в спецификации файла, без обходных путей. Раньше, чтобы запустить миграцию базы перед стартом приложения, приходилось городить отдельный сервис с restart: "no" и указывать на него depends_on с условием service_completed_successfully. Теперь это одна секция pre_start внутри самого сервиса.

Что вообще решает эта фича

Init-контейнеры — это короткоживущие контейнеры, которые выполняются последовательно перед стартом основного контейнера сервиса. Если хоть один шаг завершится с ненулевым кодом, сервис не запустится вообще. Задачи, для которых это годится: миграции базы данных, починка прав на volume, генерация конфигов на лету, любая упорядоченная цепочка подготовительных шагов.

Для статических файлов и секретов это не нужно — для них уже есть нативные configs и secrets. Для фоновых задач со своим жизненным циклом вроде бэкапов по расписанию это тоже не подходит — pre_start работает только один раз перед стартом, а не постоянно.

Требования к версии

Функциональность доступна начиная с Docker Compose 5.3.0. Проверить свою версию:

docker compose version

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

Базовый пример: миграция перед стартом приложения

services:
  app:
    image: myapp:latest
    depends_on:
      db:
        condition: service_healthy
    pre_start:
      - command: ["./manage.py", "migrate"]

  db:
    image: postgres:18
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U $${POSTGRES_USER} -d $${POSTGRES_DB}"]
      interval: 10s
      retries: 5
      start_period: 30s
      timeout: 10s

Контейнер app создаётся, но не стартует, пока эфемерный контейнер с командой миграции не завершится с кодом 0. Если миграция упадёт — приложение не запустится вообще, и ошибка будет видна прямо в выводе docker compose up.

Ключевые правила поведения pre_start

Правило Как это работает
Отдельный эфемерный контейнер на каждый шаг Каждый пункт списка — свой собственный короткоживущий контейнер
Образ по умолчанию Наследуется от родительского сервиса, если не указан явно через image
Сеть Шаг подключается к той же сети, что и сервис, включая доступ к зависимостям из depends_on
Volume Общие точки монтирования сервиса доступны и внутри pre_start шага
Условие продолжения Каждый шаг обязан завершиться с кодом 0, иначе сервис не стартует
Повторный запуск Шаг пропускается при повторном docker compose up, если он уже успешно отработал и определение не менялось

Починка прав на volume перед стартом non-root сервиса

Именованные volume создаются с правами root. Если сервис работает под непривилегированным пользователем, права нужно поправить до монтирования — раньше это делали отдельным сервисом-костылём, теперь можно прямо внутри pre_start с другим образом:

services:
  app:
    image: myapp:latest
    user: "1000:1000"
    volumes:
      - data:/data
    pre_start:
      - image: busybox
        user: root
        command: sh -c 'chown -R 1000:1000 /data'

volumes:
  data:

Обратите внимание: шаг pre_start использует другой образ (busybox) и работает от root, хотя сам сервис — от пользователя 1000.

Цепочка из нескольких шагов подряд

Шаги pre_start выполняются строго в объявленном порядке, каждый следующий стартует только после успешного завершения предыдущего:

services:
  app:
    image: myapp:latest
    depends_on:
      db:
        condition: service_healthy
    pre_start:
      - command: ["./manage.py", "migrate"]
      - command: ["./manage.py", "loaddata", "fixtures.json"]

  db:
    image: postgres:18

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

Как это соотносится со старым паттерном one-shot сервиса

Критерий Старый паттерн (отдельный сервис) pre_start
Виден ли в docker compose ps после завершения Да, как exited-сервис Нет, это не отдельный сервис
Нужно ли дублировать image Да, если образ тот же самый Нет, наследуется автоматически
Цепочка из нескольких шагов Требует несколько сервисов и связей depends_on между ними Просто список внутри одной секции
Уместность для общей зависимости нескольких сервисов Хорошо подходит Не годится — pre_start привязан к одному сервису

Старый паттерн всё ещё имеет смысл, если подготовительная работа — общая для нескольких сервисов или должна быть адресуема отдельно, а не быть шагом ровно одного сервиса.

Ограничения, о которых стоит знать заранее

  • pre_start выполняется один раз для сервиса целиком, а не один раз на каждую реплику — per_replica: true пока не поддерживается.
  • Точки монтирования, общие между репликами (именованные volume, bind mounts), доступны из pre_start. А вот tmpfs или анонимные volume конкретного инстанса общим запуском не адресуются.
  • Масштабирование сервиса (docker compose up --scale) само по себе не перезапускает pre_start — шаг выполняется заново только при изменении определения, прошлом сбое или явном --force-recreate.

Практический совет по внедрению

Если в проекте уже есть паттерн с отдельным сервисом-миграцией и service_completed_successfully, переписывать всё разом не обязательно — обе конструкции могут сосуществовать в одном файле. Разумный подход — переносить на pre_start именно те задачи, которые логически привязаны к одному конкретному сервису, а общие для нескольких сервисов задачи оставить как есть.

Если пишете такой блок вручную и хочется на берегу проверить структуру всего файла на синтаксические ошибки перед первым docker compose up, Docker Compose Validator покажет базовые проблемы раньше, чем это сделает сам Docker при реальном запуске.

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

pre_start требует Docker Compose 5.3.0 или новее — на старых версиях фича либо не распознаётся, либо игнорируется без явной ошибки, стоит явно проверить версию перед миграцией продовых конфигов.

Шаги внутри pre_start не транзакционны: сбой второго шага не откатывает первый, поэтому инициализационные скрипты стоит писать идемпотентными.

pre_start заменяет старый паттерн one-shot сервиса именно для задач, привязанных к одному конкретному сервису — для общих для нескольких сервисов подготовительных задач старый подход с отдельным сервисом остаётся уместным.