Skip to main content

У чому різниця між CMD і ENTRYPOINT?

CMD і ENTRYPOINT це дві Dockerfile-інструкції, що визначають, що крутиться при старті container. Різниця проявляється у момент, коли користувач передає аргументи у docker run.

Теорія

TL;DR

  • CMD ["prog", "arg"] = дефолтна команда, повністю замінна. docker run img otherCmd повністю її замінює.
  • ENTRYPOINT ["prog"] = фіксована перша частина команди. Все, що йде після docker run img ... стає його аргументами (або CMD, якщо нічого не передаєш).
  • Зв'язка ENTRYPOINT + CMD це стандартний патерн для CLI-image: фіксований entrypoint, дефолтні args, які можна перевизначити.
  • Бери exec-форму (["prog", "arg"], JSON-масив). Shell-форма (prog arg) загортає все у /bin/sh -c, що ламає обробку сигналів.
  • --entrypoint у docker run перевизначає сам ENTRYPOINT; CMD перевизначається просто аргументами після імені image.

Швидкий приклад

dockerfile
FROM alpine:3.21 ENTRYPOINT ["echo"] CMD ["hello"]
bash
$ docker build -t demo . $ docker run --rm demo hello # ENTRYPOINT (echo) + CMD (hello) $ docker run --rm demo bye bye # ENTRYPOINT (echo) + новий CMD (bye) $ docker run --rm --entrypoint /bin/sh demo -c 'ls /' bin lib usr # ↑ ENTRYPOINT явно перевизначено; CMD стає argv для /bin/sh

Три запуски, три поведінки. Розпил ENTRYPOINT + CMD саме і дає таку гнучкість.

Чотири поширених патерни

Патерн 1: тільки CMD — сервіс без аргументів

dockerfile
FROM nginx:1.27-alpine CMD ["nginx", "-g", "daemon off;"]
bash
$ docker run myimg -> nginx -g "daemon off;" $ docker run myimg sh -> sh (CMD повністю замінено)

Бери це, коли image крутить один фіксований сервіс і користувач не має потреби міняти команду. Більшість сервіс-image.

Патерн 2: тільки ENTRYPOINT — image це CLI-інструмент

dockerfile
FROM alpine:3.21 RUN apk add --no-cache curl ENTRYPOINT ["curl"]
bash
$ docker run myimg https://example.com # -> curl https://example.com $ docker run myimg --help # -> curl --help

Image стає командою. Користувачі передають curl-флаги прямо у docker run.

Патерн 3: ENTRYPOINT + CMD — фіксована команда з дефолтними args

dockerfile
FROM alpine:3.21 RUN apk add --no-cache curl ENTRYPOINT ["curl"] CMD ["--help"]
bash
$ docker run myimg -> curl --help (дефолт) $ docker run myimg https://example.com # -> curl https://example.com (override)

Канонічний патерн «image як CLI з корисною дефолтною поведінкою».

Патерн 4: shell-форма (уникай у проді)

dockerfile
CMD nginx -g "daemon off;" # shell-форма

Docker тихо обгортає це як /bin/sh -c 'nginx -g "daemon off;"'. Реальний PID 1 у container це /bin/sh, не nginx. SIGTERM іде на sh, не на твій застосунок. Результат: docker stop чекає увесь grace-період і потім SIGKILL.

Завжди бери exec-форму (["prog", "arg"]) у прод-image.

Таблиця порівняння

Аспекттільки CMDтільки ENTRYPOINTENTRYPOINT + CMD
docker run img запускаєCMDENTRYPOINTENTRYPOINT + CMD
docker run img foo bar запускаєfoo barENTRYPOINT + foo barENTRYPOINT + foo bar
--entrypoint X img foo bar запускаєX foo barX foo barX foo bar
Підходить длясервісів без argsimage-як-CLICLI з дефолтними args

Типові помилки

Shell-форма і дивуватися, чому docker stop повільний

dockerfile
# НЕПРАВИЛЬНО: shell-форма, sh це PID 1, сигнали не доходять до nginx CMD nginx -g "daemon off;" # ПРАВИЛЬНО: exec-форма, nginx це PID 1, SIGTERM доходить CMD ["nginx", "-g", "daemon off;"]

З shell-формою docker stop шле SIGTERM на /bin/sh, який його ігнорує. Через 10 секунд Docker шле SIGKILL. Твій застосунок помирає жорстко, без graceful cleanup.

Спроба розгорнути env-змінні у exec-формі

dockerfile
# НЕПРАВИЛЬНО: exec-форма НЕ розгортає $VAR CMD ["echo", "$HOME"] # виведе літерал $HOME # ПРАВИЛЬНО 1: shell-форма (приймай trade-off PID 1 або --init) CMD echo $HOME # ПРАВИЛЬНО 2: явно виклич shell у exec-формі CMD ["/bin/sh", "-c", "echo $HOME"]

Exec-форма базується на execve() і не робить shell-парсингу. Якщо треба розгортання env-змінних, маєш сам викликати shell.

Два рядки CMD або ENTRYPOINT

Працює лише останній. Попередні тихо ігноруються.

dockerfile
CMD ["echo", "first"] CMD ["echo", "second"] # Container виконає: echo second

Жодної помилки, жодного попередження. Легко проґавити у довгому Dockerfile.

Забути, що docker run img sh замінює CMD, не ENTRYPOINT

dockerfile
ENTRYPOINT ["my-app"] CMD ["--default-flag"]
bash
$ docker run img sh # запустить: my-app sh (НЕ shell) $ docker run --entrypoint sh img # реально shell

Якщо є ENTRYPOINT, ти не можеш просто кинутися у shell trailing-аргументом. Потрібен --entrypoint.

Реальне застосування

  • Сервіс-image (nginx, postgres, redis): тільки CMD. Дефолтна команда стартує daemon; просунуті юзери перевизначають через docker run img <custom-args>, якщо треба.
  • CLI-image (alpine/git, peter-evans/dockerhub-description, aws-cli): ENTRYPOINT встановлено на бінар. Image є CLI.
  • Гібридні утиліти (postgres:16 чий entrypoint крутить init-скрипти і exec'ить postgres): ENTRYPOINT ["docker-entrypoint.sh"] + CMD ["postgres"]. Entrypoint-скрипт це обгортка, що робить setup, а потім exec "$@", щоб запустити CMD.
  • CI/CD-friendly image: ENTRYPOINT ["my-tool"], тож джоби запускають docker run myimg <flags> без запам'ятовування імені бінаря.

Питання для поглиблення

Q: Яка різниця між exec-формою і shell-формою?


A: Exec-форма ["prog", "arg"] запускає бінар напряму через execve(), твоя програма це PID 1, сигнали до неї доходять, без додаткового shell-процесу. Shell-форма prog arg обгортає команду у /bin/sh -c '...', тому /bin/sh стає PID 1. Прод-image мають завжди використовувати exec-форму.

Q: Чому мій CMD не стартує container?


A: CMD запускається лише коли у docker run не передано команди. Якщо ти робиш docker run img bash, CMD замінено на bash. Також: CMD не запускається, якщо є ENTRYPOINT, що ігнорує свої аргументи. І рахується лише останній CMD у Dockerfile.

Q: Що робить патерн docker-entrypoint.sh?


A: Це shell-скрипт, поставлений як ENTRYPOINT. Всередині робить setup (валідація env-змінних, init директорій, міграції), потім exec "$@", щоб замінити себе реальним процесом застосунку. exec робить застосунок PID 1 (тож сигнали працюють). Використовується у postgres, mysql, redis і багатьох офіційних image.

Q: Чи можна перевизначити і ENTRYPOINT, і CMD одночасно?


A: Так: docker run --entrypoint /bin/sh myimg -c 'echo hi'. --entrypoint міняє entrypoint; все після імені image стає новими args (оригінальним CMD).

Q: (Senior) Коли б ти НЕ використав патерн ENTRYPOINT + CMD навіть для CLI-image?


A: Коли користувачам реально треба запускати непов'язані команди всередині того самого image, наприклад debug-image, де docker run img sh має просто працювати. З жорстким ENTRYPOINT для цього потрібен --entrypoint sh. Для вузько-фокусованих CLI entrypoint правильний; для general-purpose image лишити entrypoint порожнім (або [""]) тримає docker run img <будь-що> гнучким.

Приклади

Сервіс-image — тільки CMD

dockerfile
FROM nginx:1.27-alpine COPY nginx.conf /etc/nginx/nginx.conf COPY html/ /usr/share/nginx/html/ EXPOSE 80 CMD ["nginx", "-g", "daemon off;"]
bash
$ docker run -d -p 8080:80 mywebsite # використає CMD $ docker run -it mywebsite sh # CMD замінено на sh

Image крутить nginx за замовчуванням, але лишається придатним для дебагу.

CLI-image — ENTRYPOINT + CMD

dockerfile
FROM alpine:3.21 RUN apk add --no-cache curl ENTRYPOINT ["curl"] CMD ["--help"]
bash
$ docker run --rm mycurl -> curl --help $ docker run --rm mycurl https://api.github.com -> curl https://api.github.com $ docker run --rm --entrypoint sh mycurl -> shell, для дебагу

Image є curl. Дефолтна поведінка (показ help) робить перший запуск інформативним; передача args дає реальний інструмент.

Postgres-style entrypoint-скрипт

dockerfile
FROM postgres:16-alpine # Успадковує: # ENTRYPOINT ["docker-entrypoint.sh"] # CMD ["postgres"] # Entrypoint-скрипт (з base image) робить: # - валідацію POSTGRES_PASSWORD/POSTGRES_DB env-змінних # - запуск initdb, якщо data-dir порожня # - виконання /docker-entrypoint-initdb.d/* SQL-файлів при першому старті # - exec "$@" # стає: exec postgres
bash
$ docker run -e POSTGRES_PASSWORD=dev postgres:16-alpine # entrypoint крутить init-логіку, потім exec'ить postgres, що стає PID 1

Патерн: entrypoint = setup-обгортка, CMD = реальна команда, exec "$@" в кінці, тож реальна команда стає PID 1 і отримує сигнали правильно.

Коротка відповідь

Для співбесіди
Premium

Коротка відповідь допоможе вам впевнено відповідати на цю тему під час співбесіди.

Коментарі

Ще немає коментарів