Вопрос 5 · Раздел 14

В чём разница между CMD и ENTRYPOINT?

Обе инструкции определяют, что будет запущено при старте контейнера, но у них разные задачи:

Версии по языкам: English Russian Ukrainian

🟢 Junior Level

Простое объяснение

Обе инструкции определяют, что будет запущено при старте контейнера, но у них разные задачи:

  • ENTRYPOINT — это основная программа контейнера (например, java)
  • CMD — это аргументы по умолчанию для этой программы (например, -jar app.jar)

RUN – выполняется при СБОРКЕ образа. CMD/ENTRYPOINT – при ЗАПУСКЕ контейнера.

Аналогия

Представьте вызов функции в программировании:

  • ENTRYPOINT — это имя функции
  • CMD — это параметры по умолчанию
java -jar app.jar
^^^^^ ^^^^^^^^^^^^
 |        |
ENTRYPOINT  CMD

Как они работают вместе

ENTRYPOINT ["java", "-jar", "/app.jar"]
CMD ["--server.port=8080"]

При запуске docker run myapp выполнится: java -jar /app.jar --server.port=8080

При запуске docker run myapp --server.port=9090 выполнится: java -jar /app.jar --server.port=9090

Две формы записи

Exec form (рекомендуется) — в виде JSON-массива:

ENTRYPOINT ["java", "-jar", "/app.jar"]

Shell form — как обычная строка:

ENTRYPOINT java -jar /app.jar

Всегда используйте Exec form — она правильно обрабатывает сигналы завершения.

Что запомнить

  • ENTRYPOINT — фиксированная команда, CMD — параметры по умолчанию
  • Их можно использовать вместе
  • CMD легко переопределить при запуске, ENTRYPOINT — нет
  • Всегда используйте Exec form ["cmd", "arg1", "arg2"]
  • В Dockerfile может быть только одна CMD и одна ENTRYPOINT

🟡 Middle Level

Детальное поведение

ENTRYPOINT (Точка входа)

Определяет основную исполняемую программу контейнера:

  • Стабильность: её сложно переопределить случайно (требуется флаг --entrypoint)
  • Назначение: превращает контейнер в «исполняемый бинарник»
  • Аргументы: все аргументы, переданные в docker run, дописываются в конец команды ENTRYPOINT

CMD (Команда по умолчанию)

Определяет команду по умолчанию или параметры для ENTRYPOINT:

  • Гибкость: очень легко переопределить. Любые аргументы в конце docker run полностью заменяют CMD
  • Назначение: предоставить стандартное поведение, которое пользователь может легко изменить

Три сценария использования

Сценарий 1: Только CMD

CMD ["java", "-jar", "/app.jar"]

docker run myappjava -jar /app.jar docker run myapp echo helloecho hello (CMD полностью заменён)

Сценарий 2: Только ENTRYPOINT

ENTRYPOINT ["java", "-jar", "/app.jar"]

docker run myappjava -jar /app.jar docker run myapp --debugjava -jar /app.jar --debug

Сценарий 3: ENTRYPOINT + CMD (рекомендуется)

ENTRYPOINT ["java", "-jar", "/app.jar"]
CMD ["--server.port=8080"]

docker run myappjava -jar /app.jar --server.port=8080 docker run myapp --server.port=9090java -jar /app.jar --server.port=9090

Сценарий 3 (ENTRYPOINT + CMD) – рекомендуемый. Сценарий 1 – для простых скриптов. Сценарий 2 – когда контейнер должен быть «исполняемым файлом».

Типичные ошибки

Ошибка Последствие Как избежать
Shell form ENTRYPOINT java -jar Сигналы не доходят до приложения Exec form ["java", "-jar"]
Несколько CMD в Dockerfile Учитывается только последняя Одна CMD на файл
CMD без ENTRYPOINT CMD легко случайно заменить Используйте пару ENTRYPOINT + CMD
ENTRYPOINT без CMD Разработчик не может переопределить для отладки Добавьте CMD с параметрами по умолчанию
Путаница с переопределением Контейнер запускает не то Понимайте, что docker run myapp arg заменяет CMD

Переопределение при запуске

# Переопределить CMD — просто передайте аргументы
docker run myapp --custom-arg

# Переопределить ENTRYPOINT — используйте флаг
docker run --entrypoint /bin/bash myapp

Shell form vs Exec form: критический нюанс

Exec form — команда запускается напрямую как процесс с PID 1. Правильно обрабатывает сигналы завершения. Важно для Kubernetes и Docker Stop.

Shell form — команда запускается как подпроцесс /bin/sh -c. Сигналы ОС приходят в оболочку sh, а не в приложение. Приложение может быть «убито» жёстко без завершения транзакций.

Что запомнить

  • Используйте Exec form для обеих инструкций
  • ENTRYPOINT для неизменной части команды
  • CMD для параметров, которые могут быть изменены
  • Только одна CMD и одна ENTRYPOINT учитываются (последние в файле)
  • Без proper signal handling приложение не сможет корректно завершиться

Когда НЕ использовать ENTRYPOINT

Не используйте ENTRYPOINT, если образ предназначен для разных команд (например, образ curl/wget для различных запросов). В этом случае CMD гибче.


🔴 Senior Level

Архитектурный дизайн ENTRYPOINT и CMD

Различие между ENTRYPOINT и CMD — это не просто синтаксис, это паттерн проектирования для создания переиспользуемых контейнерных образов.

Паттерн «Executable Container»

ENTRYPOINT превращает контейнер в исполняемый файл:

ENTRYPOINT ["java"]
CMD ["-jar", "/app.jar"]

Контейнер ведёт себя как бинарник:

docker run myapp -version          # java -version
docker run myapp -jar other.jar    # java -jar other.jar

Проблема PID 1 и обработка сигналов

В Linux процесс с PID 1 имеет особое поведение:

  • Не получает сигналы по умолчанию (игнорирует SIGTERM)
  • Отвечает за «усыновление» осиротевших процессов
  • Не завершается автоматически при unhandled signals

PID 1 – главный процесс в контейнере. Если он не умеет завершаться корректно (обрабатывать SIGTERM), приложение будет «убито» жёстко через SIGKILL.

Shell form проблема:

ENTRYPOINT java -jar /app.jar

Процессы в контейнере:

PID 1: /bin/sh -c "java -jar /app.jar"
PID 7: java -jar /app.jar

SIGTERM приходит в /bin/sh, который не знает, что делать с java. Результат: docker stop ждёт 10 секунд и посылает SIGKILL. Приложение не выполняет graceful shutdown — транзакции прерываются, соединения не закрываются.

Решения:

  1. Exec form (рекомендуется):
    ENTRYPOINT ["java", "-jar", "/app.jar"]
    

    Приложение — PID 1, получает сигналы напрямую.

  2. Tini (init system):
    ENTRYPOINT ["tini", "--", "java", "-jar", "/app.jar"]
    

    Tini — минимальный init, корректно forwarding сигналы и reap’ит zombie-процессы.

  3. Docker –init флаг:
    docker run --init myapp
    

Взаимодействие с Kubernetes

В Kubernetes ENTRYPOINT и CMD маппятся на поля контейнера:

Docker Kubernetes
ENTRYPOINT command
CMD args
containers:
- name: app
  image: myapp
  command: ["java", "-jar"]     # переопределяет ENTRYPOINT
  args: ["--server.port=9090"]   # переопределяет CMD

Важно: если в Kubernetes указаны command и args, они полностью заменяют Docker ENTRYPOINT и CMD.

Trade-offs

Подход Плюс Минус
Только CMD Гибкость для разработчика Легко случайно заменить
Только ENTRYPOINT Стабильность Сложно переопределить для отладки
ENTRYPOINT + CMD Лучшее из обоих Нужно понимать взаимодействие
Entrypoint-скрипт Гибкая инициализация Ещё один слой, нужно поддерживать
Tini Правильные сигналы + zombie reap Дополнительная зависимость

Edge Cases

  • Override в Kubernetes: command в K8s манифесте полностью заменяет ENTRYPOINT. Если разработчик ожидает, что entrypoint-скрипт выполнит миграции, а K8s переопределил command — миграции не запустятся.
  • Наследование ENTRYPOINT: если базовый образ имеет ENTRYPOINT, а дочерний Dockerfile — нет, наследуется ENTRYPOINT родителя. Это может привести к неожиданному поведению.
  • JSON array parsing: ENTRYPOINT ["java", "-jar", "/app.jar"] — это JSON. Ошибка в кавычках или запятых приведёт к падению сборки.
  • CMD как shell form + ENTRYPOINT как exec form: ENTRYPOINT ["java"] + CMD -jar app.jar — CMD выполнится как /bin/sh -c "java -jar app.jar", ENTRYPOINT будет проигнорирован.

Паттерн «Entry Point Script»

Для сложных приложений используют entrypoint-скрипт:

COPY docker-entrypoint.sh /usr/local/bin/
RUN chmod +x /usr/local/bin/docker-entrypoint.sh
ENTRYPOINT ["docker-entrypoint.sh"]
CMD ["java", "-jar", "/app.jar"]
#!/bin/sh
# docker-entrypoint.sh

# Выполняем миграции
if [ "$RUN_MIGRATIONS" = "true" ]; then
    java -jar /app.jar --migrate
    exit 0
fi

# Запускаем приложение (exec заменяет процесс скрипта)
exec "$@"

exec "$@" заменяет процесс shell-скрипта на целевую команду, сохраняя PID 1 и сигналы. Без exec скрипт останется PID 1 и сигналы не дойдут до приложения.

Антипаттерны

  1. Shell form без обработки сигналов: ENTRYPOINT java -jar /app.jar — graceful shutdown не работает.
  2. Несколько CMD/ENTRYPOINT: учитывается только последняя.
  3. ENTRYPOINT без CMD в образах для разработчиков: невозможно переопределить для отладки.
  4. Entrypoint-скрипт без exec: скрипт остаётся PID 1, сигналы теряются.
  5. Секреты в CMD/ENTRYPOINT: CMD ["java", "-jar", "app.jar", "--db-password=secret"] — видно в docker inspect и ps.

Performance

Подход Время shutdown Graceful shutdown Zombie reap
Shell form 10s (SIGKILL timeout) Нет Нет
Exec form < 1s (SIGTERM) Да Нет
Exec form + tini < 1s Да Да
Docker –init < 1s Да Да

Резюме

  • Используйте Exec form для обеих инструкций — критично для signal handling.
  • ENTRYPOINT + CMD вместе создают гибкий и переиспользуемый образ.
  • Понимайте маппинг на Kubernetes command/args.
  • Проблема PID 1 реальна: используйте exec form или tini.
  • Entrypoint-скрипты с exec "$@" — стандарт для сложных сценариев инициализации.
  • В Dockerfile может быть только одна инструкция CMD и одна ENTRYPOINT (учитывается последняя).

🎯 Шпаргалка для интервью

Обязательно знать:

  • ENTRYPOINT — фиксированная команда (PID 1), CMD — параметры по умолчанию
  • ENTRYPOINT + CMD вместе — рекомендуемый паттерн («executable container»)
  • Exec form ["cmd", "arg"] обязателен для корректной обработки сигналов
  • Docker CMD легко переопределить (docker run image args), ENTRYPOINT — через --entrypoint
  • В K8s: Docker ENTRYPOINT → command, CMD → args (полная замена)
  • Проблема PID 1: процесс без обработчика сигналов не делает graceful shutdown
  • Решение PID 1: exec form, tini, или docker run --init

Частые уточняющие вопросы:

  • «Что происходит при docker run myapp argarg заменяет CMD, дописывается к ENTRYPOINT
  • «Почему shell form опасна в K8s?» — Сигналы приходят в /bin/sh, не в приложение; graceful shutdown не работает
  • «Что делает exec "$@" в entrypoint-скрипте?» — Заменяет процесс скрипта на целевую команду, сохраняя PID 1
  • «Можно ли иметь несколько CMD в Dockerfile?» — Да, но учитывается только последняя

Красные флаги (НЕ говорить):

  • «CMD и ENTRYPOINT — одно и то же» (разная семантика и переопределяемость)
  • «Использую shell form для удобства» (graceful shutdown сломан)
  • «В K8s command и args дополняют Docker ENTRYPOINT/CMD» (полностью заменяют!)
  • «PID 1 не имеет значения в контейнере» (критичен для signal handling)

Связанные темы:

  • [[Какие основные инструкции используются в Dockerfile]] — все инструкции Dockerfile
  • [[Что такое Pod в Kubernetes]] — контейнеры в K8s (signal handling важен)
  • [[Как организовать rolling update в Kubernetes]] — graceful shutdown при обновлении