Запропонувати правкуПокращити цю статтюДопрацюйте відповідь до «У чому різниця між CMD і ENTRYPOINT?». Ваші зміни проходять модерацію перед публікацією.Потрібне підтвердженняКонтентЩо ви змінюєте🇺🇸EN🇺🇦UAПереглядЗаголовок (UA)Коротка відповідь (UA)**`CMD` і `ENTRYPOINT`** обидва визначають що крутиться при старті container. `CMD` повністю перевизначається через `docker run`. `ENTRYPOINT` фіксований; `CMD` стає його дефолтними аргументами. ```dockerfile ENTRYPOINT ["echo"] CMD ["hello"] # docker run img -> echo hello # docker run img bye -> echo bye (CMD замінено, ENTRYPOINT лишився) # docker run --entrypoint /bin/sh img # -> /bin/sh (ENTRYPOINT явно перевизначено) ``` **Головне:** `ENTRYPOINT` коли image це один інструмент (як CLI). `CMD` сам коли image це сервіс без аргументів. Зв'язка `ENTRYPOINT + CMD` дає фіксовану команду з замінними дефолтами.Показується над повною відповіддю для швидкого нагадування.Відповідь (UA)Зображення**`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` | тільки `ENTRYPOINT` | `ENTRYPOINT + CMD` | |---|---|---|---| | `docker run img` запускає | CMD | ENTRYPOINT | ENTRYPOINT + CMD | | `docker run img foo bar` запускає | `foo bar` | ENTRYPOINT + `foo bar` | ENTRYPOINT + `foo bar` | | `--entrypoint X img foo bar` запускає | `X foo bar` | `X foo bar` | `X foo bar` | | Підходить для | сервісів без args | image-як-CLI | CLI з дефолтними 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 і отримує сигнали правильно.Для рев’юераПримітка для модератора (необов’язково)Бачить лише модератор. Прискорює рев’ю.