Что делает инструкция CMD в Dockerfile?

Ответ

Инструкция CMD в Dockerfile задаёт команду и её аргументы по умолчанию, которые будут выполнены при запуске контейнера, если пользователь не укажет свою команду в docker run. В одном Dockerfile может быть только одна эффективная инструкция CMD (используется последняя).

Форматы записи:

  1. Exec-форма (рекомендуется): CMD ["executable", "param1", "param2"]

    • Команда выполняется напрямую, без оболочки (/bin/sh).
    • Переменные окружения не интерполируются, но это более чистый сигнальный механизм (PID 1).
      CMD ["nginx", "-g", "daemon off;"]
  2. Shell-форма: CMD command param1 param2

    • Выполняется через /bin/sh -c.
    • Поддерживает подстановку переменных окружения, но хуже обрабатывает сигналы остановки.
      CMD nginx -g 'daemon off;'

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

  • Если определён ENTRYPOINT, то CMD задаёт аргументы по умолчанию для него.
  • Аргументы CMD можно полностью переопределить при запуске контейнера.

Практический пример из моего опыта:

FROM python:3.11-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
# ENTRYPOINT задаёт интерпретатор, CMD — скрипт по умолчанию
ENTRYPOINT ["python"]
CMD ["app.py"]

Запуск контейнера:

docker run my-app                 # Выполнит: python app.py
docker run my-app test_runner.py  # Выполнит: python test_runner.py (CMD переопределён)

Ответ 18+ 🔞

Смотри, объясню тебе про эту самую CMD в Dockerfile, чтобы ты не охуел от всей этой документации. Это, по сути, команда по умолчанию, которую контейнер будет выполнять, когда ты его запустишь, если сам ничего не придумаешь. В одном файле может быть только одна рабочая CMD — последняя, остальные, понятное дело, накрываются медным тазом.

Как это записывают, блядь:

  1. Exec-форма (её все умные дяди советуют): CMD ["исполняемый_файл", "параметр1", "параметр2"]

    • Тут всё честно, без посредников. Команда запускается напрямую, без всякого /bin/sh.
    • Переменные окружения внутри не раскрываются, но зато сигналы типа SIGTERM доходят как надо, что для PID 1 — святое дело.
      CMD ["nginx", "-g", "daemon off;"]
  2. Shell-форма: CMD команда параметр1 параметр2

    • А вот это уже через оболочку, типа /bin/sh -c.
    • Плюс — переменные окружения работают. Минус — с обработкой сигналов для остановки контейнера начинается пиздец и бардак, потому что PID 1 теперь у этой самой оболочки.
      CMD nginx -g 'daemon off;'

А если ещё и ENTRYPOINT есть? Вот тут начинается магия, ёпта. Если ты определил ENTRYPOINT, то CMD становится не полноценной командой, а просто набором аргументов по умолчанию для этого самого ENTRYPOINT. И самое главное — эти аргументы из CMD ты можешь запросто перебить, когда будешь контейнер запускать. Удобно, как блядь.

Пример из жизни, чтобы совсем всё стало ясно: Допустим, делаем мы простенький питонячий сервис.

FROM python:3.11-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
# ENTRYPOINT — это как будто фиксированный инструмент (python)
ENTRYPOINT ["python"]
# А CMD — это дефолтный скрипт, который мы этим инструментом запустим
CMD ["app.py"]

Теперь смотри, как это работает на практике:

# Просто запускаем — выполнится то, что по умолчанию: python app.py
docker run my-app

# А тут мы говорим: "Нахуй твой app.py, запускай test_runner.py"
# И выполнится: python test_runner.py. CMD переопределён, ENTRYPOINT остался.
docker run my-app test_runner.py

Вот и вся философия. ENTRYPOINT — это как будто молоток, а CMD — гвоздь, который ты по умолчанию собираешься забивать. Но если захочешь, можешь взять и шуруп вместо гвоздя, молоток-то останется тем же.