Docker ENTRYPOINT против CMD — Ваш контейнер обманул вас

Обновлено

Вы объединили ENTRYPOINT и CMD в вашем Dockerfile, контейнер запустил неправильное приложение, и теперь вы здесь. Ниже полный разбор — все возможные комбинации, ловушка с использованием оболочки и формата exec, а также паттерны, которые действительно работают.

Docker ENTRYPOINT против CMD — Ваш контейнер лжёт вам 1
Реклама · УДАЛИТЬ?

Ошибка происходит в 2 часа ночи. Вы запускаете контейнер, и вместо API-сервера вы получаете командную строку. Или ничего вообще. Или ваш процесс запускается в обёртке, которая «ест» sh который ест SIGTERM как конфеты — поэтому грациозный выход занимает 10 секунд ожидания Docker, прежде чем он отказывается и отправляет SIGKILL.

Причина, почти каждый раз: вы ошиблись ENTRYPOINT и CMD. Или объединили их так, что Docker безусловно принимает — просто не так, как вы ожидаете.

CMD: По умолчанию, который можно заменить

CMD устанавливает то, что запускается при старте контейнера — но это лишь предложение, а не правило. Любое значение после имени образа полностью заменяется:

FROM ubuntu
CMD ["echo", "hello from CMD"]
$ docker run myimage
hello from CMD

$ docker run myimage echo goodbye
goodbye

Это echo goodbye не приписывал — он заменял. Ваша вся CMD исчезла. Это предусмотрено: CMD является стандартным поведением, а не принудительным. Любое аргументы во время выполнения имеют приоритет.

ENTRYPOINT: Часть, которая всегда запускается

ENTRYPOINT устанавливает исполняемый файл, который запускается независимо от того, что происходит. Аргументы во время выполнения не заменяют его — они передаются ему вместо:

FROM ubuntu
ENTRYPOINT ["echo"]
CMD ["hello"]
$ docker run myimage
hello

$ docker run myimage goodbye
goodbye

$ docker run --entrypoint cat myimage /etc/hostname
mycontainer-abc123

Когда оба установлены, ENTRYPOINT является исполняемым файлом, а CMD становится его стандартными аргументами. Можно переопределить CMD свободно. Можно переопределить ENTRYPOINT только в том случае, если вы явно передаёте --entrypoint.

Все комбинации ENTRYPOINT и CMD, объяснённые

В документации Docker включена эта таблица, но не уделяется внимание тем строкам, которые могут разрушить ваш день:

ENTRYPOINTCMDЧто на самом деле запускается
Управляет передачей между сайтамиУправляет передачей между сайтамиОшибка — контейнер требует команду из какого-то источника
Управляет передачей между сайтами["cmd", "arg"] формат execcmd arg
Управляет передачей между сайтамиcmd arg формат shell/bin/sh -c "cmd arg"
["entry"] формат execУправляет передачей между сайтамиentry
["entry"] формат exec["arg1", "arg2"] формат execentry arg1 arg2
["entry"] формат execcmd arg формат shellentry /bin/sh -c "cmd arg" — почти наверняка неправильно
entry формат shell["arg1"] формат exec/bin/sh -c "entry"CMD тихо игнорируется
entry формат shellcmd arg формат shell/bin/sh -c "entry"CMD тихо игнорируется

Строки, отмеченные как «CMD тихо игнорируется», отвечают за значительную часть сессий отладки Docker. Формат shell ENTRYPOINT не объединяется с CMD — он полностью игнорируется. Docker не предупреждает вас об этом.

Формат shell против формата exec: ловушка обработки сигналов

Оба инструкции принимают два формата, и выбор имеет большее значение, чем большинство учебников по Dockerfile.

Формат exec (синтаксис массива):

ENTRYPOINT ["nginx", "-g", "daemon off;"]

Ваша программа запускается напрямую. Она становится PID 1. Когда Docker отправляет SIGTERM для остановки контейнера, ваш процесс получает сигнал. Грациозный выход работает. Логи очищаются. Соединения закрываются чисто.

Формат shell (простая строка):

ENTRYPOINT nginx -g "daemon off;"

Docker запускает это как /bin/sh -c "nginx -g daemon off;". Оболочка становится PID 1. Когда SIGTERM появляется, sh получает его — и sh не передаёт сигналы дочерним процессам. Ваш контейнер зависает на 10 секунд, получает SIGKILLи погибает без очистки. Каждый раз.

Используйте формат exec. Всегда. Для обоих ENTRYPOINT и CMD.

Три рабочих шаблона

Шаблон 1: Фиксированный исполняемый файл, с возможностью замены параметров по умолчанию

Правильный шаблон для большинства производственных контейнеров. Исполняемый файл фиксирован; флаги могут заменяться во время выполнения:

ENTRYPOINT ["/app/server"]
CMD ["--port", "8080", "--env", "production"]
# Use defaults
docker run myimage

# Override at deploy time
docker run myimage --port 9090 --env staging

Шаблон 2: Обёртка скрипта с exec

Когда вам нужно выполнить инициализацию перед основным процессом (миграции, ввод секретов, обработка сигналов), используйте обёртку скрипта. Критическая строка — exec "$@" в конце — она заменяет процесс оболочки на вашу команду CMD, поэтому ваш исполняемый файл становится PID 1:

#!/bin/sh
set -e

echo "Running migrations..."
/app/migrate

# Hand off to CMD — exec replaces shell, so /app/server becomes PID 1
exec "$@"
COPY entrypoint.sh /entrypoint.sh
RUN chmod +x /entrypoint.sh
ENTRYPOINT ["/entrypoint.sh"]
CMD ["/app/server", "--port", "8080"]

Если вы пропустите exec "$@", оболочка остаётся PID 1 и вы возвращаетесь к проблемам обработки сигналов.

Шаблон 3: Только CMD, без ENTRYPOINT

Подходит для образов разработки или контейнеров инструментов, где вы хотите запускать произвольные команды в одинаковой среде:

FROM python:3.12-slim
WORKDIR /app
COPY . .
RUN pip install -r requirements.txt
CMD ["python", "app.py"]

Для производства, шаблон 1 или 2 является более безопасным — вы не хотите, чтобы неправильно настроенный скрипт развертывания случайно запускал docker run myimage bash и заменял ваш сервер сессией оболочки.

Что имеет отношение к docker exec

ничего. docker exec запускает команду в уже работающем контейнере. Он полностью обходит ENTRYPOINT . Вам не нужно думать о ENTRYPOINT при запуске docker exec mycontainer bash для просмотра — вы взаимодействуете с живой средой контейнера, а не с конфигурацией запуска.

Смущение обычно возникает у людей, которые отладка с помощью docker exec и убеждаются, что всё работает, а затем удивляются, почему docker run поведение отличается. Это совершенно разные пути выполнения кода.

Проверка перед отправкой

  • Оба ENTRYPOINT и CMD используют формат exec (синтаксис массива, а не простые строки)
  • Если у вас есть обёртка скрипта, она заканчивается exec "$@"
  • Вы проверили docker run myimage и docker run myimage --your-flag чтобы убедиться, что оба пути работают
  • docker stop mycontainer заканчивается за 2 секунды (не за 10 — если это 10, у вас есть проблема с сигналами)

Если вы хотите получить автоматическую обратную связь до того, как ваш Dockerfile будет отправлен в реестр, IO Tools’ Dockerfile Linter обнаруживает использование формата shell, отсутствие exec в скриптах entrypoint и другие паттерны, которые вызывают тихое поведение во время выполнения.

Хотите убрать рекламу? Откажитесь от рекламы сегодня

Установите наши расширения

Добавьте инструменты ввода-вывода в свой любимый браузер для мгновенного доступа и более быстрого поиска

в Расширение Chrome в Расширение края в Расширение Firefox в Расширение Opera

Табло результатов прибыло!

Табло результатов — это интересный способ следить за вашими играми, все данные хранятся в вашем браузере. Скоро появятся новые функции!

Реклама · УДАЛИТЬ?
Реклама · УДАЛИТЬ?
Реклама · УДАЛИТЬ?

новости с техническими моментами

Примите участие

Помогите нам продолжать предоставлять ценные бесплатные инструменты

Купи мне кофе
Реклама · УДАЛИТЬ?