Docker ENTRYPOINT против CMD — Ваш контейнер обманул вас
Вы объединили ENTRYPOINT и CMD в вашем Dockerfile, контейнер запустил неправильное приложение, и теперь вы здесь. Ниже полный разбор — все возможные комбинации, ловушка с использованием оболочки и формата exec, а также паттерны, которые действительно работают.
Ошибка происходит в 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 включена эта таблица, но не уделяется внимание тем строкам, которые могут разрушить ваш день:
| ENTRYPOINT | CMD | Что на самом деле запускается |
|---|---|---|
| Управляет передачей между сайтами | Управляет передачей между сайтами | Ошибка — контейнер требует команду из какого-то источника |
| Управляет передачей между сайтами | ["cmd", "arg"] формат exec | cmd arg |
| Управляет передачей между сайтами | cmd arg формат shell | /bin/sh -c "cmd arg" |
["entry"] формат exec | Управляет передачей между сайтами | entry |
["entry"] формат exec | ["arg1", "arg2"] формат exec | entry arg1 arg2 ✓ |
["entry"] формат exec | cmd arg формат shell | entry /bin/sh -c "cmd arg" — почти наверняка неправильно |
entry формат shell | ["arg1"] формат exec | /bin/sh -c "entry" — CMD тихо игнорируется |
entry формат shell | cmd 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 и другие паттерны, которые вызывают тихое поведение во время выполнения.
Установите наши расширения
Добавьте инструменты ввода-вывода в свой любимый браузер для мгновенного доступа и более быстрого поиска
恵 Табло результатов прибыло!
Табло результатов — это интересный способ следить за вашими играми, все данные хранятся в вашем браузере. Скоро появятся новые функции!
Подписаться на новости
все Новые поступления
всеОбновлять: Наш последний инструмент был добавлен 6 Июня, 2026
