Ответ
Процесс, который будет запущен внутри контейнера, определяется в Dockerfile с помощью инструкций ENTRYPOINT и CMD. Я всегда комбинирую их для создания гибких и безопасных образов.
Базовые инструкции и их взаимодействие:
ENTRYPOINTопределяет исполняемую команду, которую нельзя переопределить при запуске контейнера (если только не использовать--entrypoint).CMDзадает аргументы по умолчанию дляENTRYPOINT. Эти аргументы можно переопределить при запуске контейнера.
Предпочтительный паттерн (exec form):
FROM alpine:3.18
# Устанавливаем приложение
RUN apk add --no-cache nginx &&
mkdir -p /run/nginx
# Копируем конфигурацию
COPY nginx.conf /etc/nginx/nginx.conf
# Фиксируем исполняемый файл
ENTRYPOINT ["nginx"]
# Задаем аргументы по умолчанию
CMD ["-g", "daemon off;"]
При таком подходе контейнер по умолчанию запустит nginx -g "daemon off;", но можно легко изменить аргументы:
docker run my-nginx-image -g "daemon off;" -c /custom/nginx.conf
Почему exec form (["cmd", "arg1"]) лучше shell form (cmd arg1):
- Корректная обработка сигналов: Контейнер получит сигналы (SIGTERM) напрямую в PID 1, что позволит приложению корректно завершиться.
- Отсутствие оболочки: Экономит память и устраняет потенциальные проблемы с переменными окружения.
Для скриптов или сложной логики инициализации я использую скрипт-обертку в качестве ENTRYPOINT:
COPY docker-entrypoint.sh /usr/local/bin/
RUN chmod +x /usr/local/bin/docker-entrypoint.sh
ENTRYPOINT ["docker-entrypoint.sh"]
CMD ["app-start"]
Содержимое docker-entrypoint.sh:
#!/bin/sh
set -e
# Выполняем предварительную настройку (настройка переменных, проверка зависимостей)
echo "Initializing container..."
# Передаем управление основному процессу
exec "$@"
Важно: Для долгоживущих процессов (веб-серверы, базы данных) необходимо убедиться, что они работают в foreground-режиме, иначе Docker завершит контейнер сразу после запуска.