Docker Compose run или exec: где выполнять одноразовую команду

Приложение в Compose ещё не запущено, но нужно выполнить проверку конфигурации или подготовительную команду в его окружении. Разработчик выбирает docker compose exec app ... и получает сообщение, что сервис не работает. После запуска через run команда выполняется, но HTTP-порт недоступен. Это ожидаемое поведение двух разных операций.

Exec добавляет процесс в уже запущенный контейнер. Run создаёт новый одноразовый контейнер на основе конфигурации сервиса. Up запускает сам сервис с его обычной командой. Когда эти роли разделены, гораздо проще понять, где искать результат и почему команда закончилась.

Начать с безопасного локального примера

Создайте пустой каталог и сохраните в нём compose.yaml с одним сервисом. Он не использует базу, секреты или тома:

services:
  app:
    image: alpine:3.22
    command: ["sleep", "3600"]

Обычная команда сервиса — sleep на час. Она нужна, чтобы контейнер продолжал работать, пока мы изучаем exec. Для примера потребуется доступный Docker Engine и возможность загрузить образ. Все следующие команды выполняются из этого каталога.

docker compose run --rm app sh -c 'echo one-off; hostname'
docker compose ps --all
docker compose up -d app
docker compose exec app sh -c 'echo running-service; hostname'
docker compose ps --all

Первая команда создаёт отдельный контейнер, выводит его hostname и завершает оболочку. Флаг --rm удаляет этот одноразовый контейнер после выхода. Долгоживущий сервис при этом не появляется: обычная команда sleep была заменена командой sh.

После up -d app существует работающий экземпляр сервиса. Exec запускает оболочку внутри него и выводит его hostname. Завершение этой оболочки не завершает sleep: основной процесс сервиса продолжает работать. В ps остаётся запущенный контейнер app.

После проверки остановите и удалите контейнер учебного сервиса. Следующую команду выполняйте именно из созданной в начале пустой учебной папки с приведённым compose.yaml:

docker compose down

Она завершит оставшийся sleep и удалит контейнер и сеть этого учебного проекта. Флаг -v здесь не нужен. Не переносите команду в каталог другого проекта: там она остановит уже его сервисы.

Почему exec не подходит для ещё не запущенного сервиса

Exec не собирает окружение с нуля и не заменяет основной процесс. Ему нужен существующий работающий контейнер. Поэтому подготовительные команды часто выполняют через run, а диагностику уже работающего экземпляра — через exec.

При этом новый контейнер run получает настройки сервиса, включая подключённые тома и сеть. В реальном проекте одноразовая команда может обратиться к той же базе, что и приложение. Само слово «одноразовая» не означает изоляцию данных. Перед миграцией или импортом проверьте выбранный Compose-проект, адрес базы и то, что именно собирается менять команда.

У примера нет томов, поэтому результат в файловой системе одноразового контейнера исчезнет после его удаления. Если задача должна создать файл для хоста или следующего контейнера, заранее определите место хранения. Это часть сценария команды, а не побочное свойство run.

Почему run не открыл веб-приложение

По умолчанию run не публикует порты, описанные у сервиса. Это позволяет выполнять вспомогательные команды без конфликта с уже запущенным экземпляром. Если одноразовому контейнеру действительно нужны сервисные порты, у run есть --service-ports; обычно для обычного веб-запуска проще использовать up.

Есть ещё одна деталь: команда после имени сервиса заменяет его command, но не обязательно ENTRYPOINT образа. Если в образе стоит собственный entrypoint-скрипт, он может обработать аргументы раньше ожидаемой команды. Проверьте Dockerfile и итоговую конфигурацию, прежде чем объяснять неожиданный вывод поведением shell.

Для выполнения в скрипте учитывайте терминал: Compose exec по умолчанию выделяет TTY, а -T отключает его. Это важно для перенаправления бинарного вывода и некоторых автоматических сценариев. Выбирайте флаг под формат команды, а не добавляйте интерактивный режим автоматически.

От знакомства к рабочему проекту

Сначала решите задачу выбора run или exec для инициализации. Затем пройдите практику эксплуатации Compose: там есть ситуации с завершившимся init, портами, healthcheck и сохранностью данных.

В курсе Docker и Docker Compose вы соберёте приложение с PostgreSQL, настроите сеть и тома и разберёте типичные поломки запуска. Откройте программу, чтобы перейти от отдельных команд к проекту, который можно передать коллеге.

Продолжить на практике

  • Docker и Docker Compose: контейнеризация приложений ↗

    Упакуйте приложение в Docker и запустите его с PostgreSQL через Compose. Образы, сети, тома, диагностика поломок, обновление версий и восстановление данных — с практикой и итоговым проектом.