В чём разница между CMD и ENTRYPOINT?
Обе инструкции определяют, что будет запущено при старте контейнера, но у них разные задачи:
🟢 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 myapp → java -jar /app.jar
docker run myapp echo hello → echo hello (CMD полностью заменён)
Сценарий 2: Только ENTRYPOINT
ENTRYPOINT ["java", "-jar", "/app.jar"]
docker run myapp → java -jar /app.jar
docker run myapp --debug → java -jar /app.jar --debug
Сценарий 3: 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
Сценарий 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 — транзакции прерываются, соединения не закрываются.
Решения:
- Exec form (рекомендуется):
ENTRYPOINT ["java", "-jar", "/app.jar"]Приложение — PID 1, получает сигналы напрямую.
- Tini (init system):
ENTRYPOINT ["tini", "--", "java", "-jar", "/app.jar"]Tini — минимальный init, корректно forwarding сигналы и reap’ит zombie-процессы.
- 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 и сигналы не дойдут до приложения.
Антипаттерны
- Shell form без обработки сигналов:
ENTRYPOINT java -jar /app.jar— graceful shutdown не работает. - Несколько CMD/ENTRYPOINT: учитывается только последняя.
- ENTRYPOINT без CMD в образах для разработчиков: невозможно переопределить для отладки.
- Entrypoint-скрипт без
exec: скрипт остаётся PID 1, сигналы теряются. - Секреты в 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 arg?» —argзаменяет 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 при обновлении