Docker Compose: command vs entrypoint, ano ang kaibahan?
Alamin kung paano pinapatakbo ng ENTRYPOINT ang program at nagbibigay ng arguments ang command. Kasama ang 4 override combinations at bakit binubura ng entrypoint ang CMD.
Docker Compose command at entrypoint, sa iisang panuntunan
Sa Docker Compose, itinatalaga ng entrypoint: ang program na tatakbo, at itinatalaga ng command: ang mga argumentong ipapasa sa program na iyon. Ang proseso ng container ay ang entrypoint list na sinusundan ng command list. Lahat ng iba pang behavior sa page na ito ay nagmumula sa isang pangungusap na iyon.
Ang dalawang key na ito ay tumutugma sa dalawang Dockerfile instruction. Pinapalitan ng entrypoint: ang ENTRYPOINT ng image. Pinapalitan ng command: ang CMD ng image. Hindi magkahiwalay ang epekto ng mga ito. Dito kadalasang nagkakaroon ng problema: kapag itinakda ang entrypoint:, itinatapon din nito ang CMD ng image. Direktang sinasabi ito sa Compose specification. Kung non-null ang entrypoint, binabalewala ng Compose ang anumang default command mula sa image.
Basahin kung ano ang dine-declare na ng image
Bago mag-override ng anuman, tingnan muna kung ano ang kasama sa image.
docker image inspect --format '{{json .Config.Entrypoint}}' postgres:16
docker image inspect --format '{{json .Config.Cmd}}' postgres:16Makikita mo ang ["docker-entrypoint.sh"] at ["postgres"], kaya tumatakbo ang container gamit ang docker-entrypoint.sh postgres. Ginagawa ng script na iyon ang data directory sa unang boot, binabasa ang mga variable na POSTGRES_*, ibinababa ang privileges sa user na postgres, at pagkatapos ay ine-exec ang mga argument na ipinasa rito. Ang buong desisyon ay nakabatay sa kung aling bahagi ang gusto mong baguhin. Para magpasa ng flag sa database, palitan ang command:. Kapag pinalitan mo ang entrypoint:, hindi tatakbo ang alinman sa setup na iyon.
Apat na kombinasyon, ipinapakita sa isang maliit na image
Bumuo ng image na ang tanging gawain ay i-print ang listahan ng mga argument na ginamit sa pagsisimula nito.
FROM alpine:3.20
ENTRYPOINT ["/bin/echo", "ep"]
CMD ["cmd"]docker build -t argdemo .services:
demo:
image: argdemoPatakbuhin ang docker compose up pagkatapos ng bawat pag-edit at basahin ang iisang linyang nilo-log nito.
- Walang key na naka-set. Ang proseso ay
/bin/echo ep cmdat ipinapakita ng log angep cmd. command: ["cmd2"]lamang. Ang proseso ay/bin/echo ep cmd2. Hindi nagbabago ang entrypoint at ang mga argument lamang ang nabago.entrypoint: ["/bin/echo", "ep2"]lamang. Ang proseso ay/bin/echo ep2at ipinapakita ng log angep2. Wala na angcmdmula sa image, at walang babala tungkol dito.- Parehong naka-set ang dalawang key. Ang proseso ay
/bin/echo ep2 cmd2. Ito lamang ang sitwasyong makokontrol mo ang buong listahan ng mga argument.
Bakit nililinis ng pagtatakda ng entrypoint ang image CMD
Ang CMD ng isang image ay isinusulat bilang default argument list para sa ENTRYPOINT ng image na iyon. Kapag pinalitan ang entrypoint, napupunta ang mga argumentong iyon sa program na hindi na tumatakbo. Kaya inaalis ng Compose ang mga ito sa halip na bumuo ng command line na hindi naman nilayon ng author ng image. Ganito rin ang pagkilos ng docker run --entrypoint, kaya Docker behaviour ito at hindi kakaibang asal ng Compose.
Konkreto ang resulta. Idinedeklara ng nginx:1.27 ang ENTRYPOINT ["/docker-entrypoint.sh"] at CMD ["nginx", "-g", "daemon off;"]. Itakda ang entrypoint: /custom-init.sh, at magsisimula ang script na walang laman ang argument list. Ang script na nagtatapos sa karaniwang exec "$@" ay wala nang mae-exec. Kaya walang ginagawa ang exec, umaabot ang script sa huling linya nito, at lumalabas ang container na may code 0 at walang error message saanman. Ibalik mismo ang mga argument:
services:
web:
image: nginx:1.27
entrypoint: /custom-init.sh
command: ["nginx", "-g", "daemon off;"]Tandaan ang panuntunan: sa tuwing itinatakda mo ang entrypoint:, pagpasiyahan din sa parehong edit kung ano ang dapat na command:.
Exec form at shell form, at kung paano naiiba ang Compose
Tumatanggap ang Dockerfile ng dalawang syntax. Ang CMD ["nginx", "-g", "daemon off;"] ay exec form: direktang tumatakbo ang binary at walang shell na kasangkot. Ang CMD nginx -g "daemon off;" ay shell form: nire-rewrite ito ng Docker bilang /bin/sh -c 'nginx -g "daemon off;"', kaya unang tumatakbo ang shell at nagiging child process nito ang program mo.
Hindi kinokopya ng Compose ang rule na ito, at ikinagugulat ito ng maraming user. Hinahati sa mga argument ang string sa command: at direktang ine-execute ito, nang walang /bin/sh -c wrapper. Malinaw ito sa Compose reference: hindi tumatakbo ang field na command sa loob ng SHELL context na tinukoy sa image, kaya kailangan mong ikaw mismo ang mag-invoke ng shell kung kailangan mo ng mga feature nito.
Iyon ang dahilan kung bakit literal na text na hello $HOSTNAME ang pini-print ng command: echo "hello $$HOSTNAME". Walang shell na nakabasa sa string, kaya walang nag-expand dito. Mag-request ng shell kapag kailangan mo nito:
services:
demo:
image: alpine:3.20
command: /bin/sh -c 'echo "hello $$HOSTNAME"'Signal, PID 1, at malinis na docker compose down
docker compose stop at docker compose down ay nagpapadala ng SIGTERM sa PID 1 sa loob ng bawat container, naghihintay ng stop_grace_period, at pagkatapos ay nagpapadala ng SIGKILL. Ang default na grace period ay 10 segundo.
Espesyal ang PID 1 sa Linux. Hindi ipinapatupad ng kernel ang default action ng isang signal para sa PID 1. Dahil dito, ang process na walang inilagay na SIGTERM handler ay binabalewala lang ang SIGTERM kapag tumatakbo ito bilang PID 1. Nananatili ito sa buong grace period at pagkatapos ay sapilitang tine-terminate. Maaari nitong putulin ang anumang bukas na connection o transaction na hindi pa na-commit.
Mas malamang itong mangyari kapag may shell sa harap ng iyong program, dahil ang shell ang PID 1 at karamihan sa mga shell ay hindi nagpapasa ng signal sa child process. May ilang shell na pinapalitan ang sarili ng huling command sa isang -c string. Kaya kung minsan, ang iyong program ang umaabot sa PID 1. Depende ito sa shell at sa eksaktong string, kaya huwag manghula. Basahin ito:
docker compose exec -T web cat /proc/1/cmdline | tr '\0' ' '; echoKung /bin/sh -c ... ang ipinapakita bilang PID 1 sa halip na ang iyong program, may dalawang paraan para ayusin ito. Gamitin ang exec form sa image, o panatilihin ang shell at ipasa rito ang process gamit ang exec:
services:
web:
image: myapp:1.4
command: /bin/sh -c 'exec myapp --config /etc/myapp.toml'Pinapalitan ng exec ang shell process ng iyong program sa halip na mag-fork ng child process. Dahil dito, namamana ng iyong program ang PID 1 at natatanggap nito ang signal.
May mga program na nag-spawn ng mga child process pero hindi nire-reap ang mga ito. Nag-iiwan ito ng zombie processes, dahil ang PID 1 din ang reaper. May switch ang Compose para rito:
services:
web:
image: myapp:1.4
init: true
stop_grace_period: 30sAng init: true ay nagpapatakbo ng maliit na init process bilang PID 1. Ipinapasa nito ang mga signal sa iyong process at nire-reap ang mga child process. Nagbibigay ang stop_grace_period ng mas mahabang oras para sa talagang mabagal na shutdown. Kung ibang signal ang inaasahan ng iyong program, binabago ng stop_signal: SIGQUIT ang ipinapadala ng Compose. Basahin kung ano ang hinihingi na ng image gamit ang docker image inspect --format '{{.Config.StopSignal}}' nginx:1.27.
Kung palaging umaabot ng 10 segundo bawat service ang isang stack kapag docker compose down, ipinapakita nitong walang humahawak sa SIGTERM. Ayusin muna ito bago sisihin ang tooling, at tingnan ang pagkakaiba ng docker compose down at stop para malaman kung ano ang inaalis ng bawat subcommand.
Lumilitaw muli ang pagkakaiba ng exec at shell sa isa pang bahagi. Direktang pinapatakbo ng healthcheck na nakasulat bilang test: ["CMD", "curl", "-f", "http://localhost/"] ang binary, samantalang dumaraan sa shell ang test: ["CMD-SHELL", "curl -f http://localhost/ || exit 1"] upang magkaroon ng kahulugan ang ||. Sinasaklaw ng Pagsulat ng Compose healthcheck na tapat na nagfa-fail ang iba pang detalye ng field na iyon.
Pagdaragdag ng flag sa official image
Ito ang pangunahing kailangan ng karamihan ng mambabasa. Gusto mo ng isang dagdag na flag sa postgres, at hindi mo dapat maapektuhan ang initialization script.
services:
db:
image: postgres:16
environment:
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
volumes:
- pgdata:/var/lib/postgresql/data
command: postgres -c max_connections=200 -c shared_buffers=256MB
volumes:
pgdata:command: lamang ang binago, kaya tumatakbo pa rin ang docker-entrypoint.sh at ine-exec pa rin nito ang ibinigay mo. Suriin ang resulta sa halip na mag-assume:
docker compose up -d db
docker compose exec -T db psql -U postgres -c 'show max_connections;'Dapat ipakita ng output ang 200. Kung 100 pa rin ang ipinapakita nito, patakbuhin ang docker compose config at kumpirmahin na nasa merged output ang inaasahan mong command. Pinapalitan ng Compose nang buo ang command kapag nagme-merge ito ng mga override file; hindi nito idinadagdag ang laman dito. Kaya tahimik na nananalo ang pangalawang file na nagse-set din ng command:.
Ang ${POSTGRES_PASSWORD} sa itaas ay ine-expand ng Compose sa host gamit ang iyong .env file, bago pa malikha ang container. Sinasaklaw ng Mga env file at secret sa Compose kung saan ligtas ilagay ang value na iyon.
Pagpapatakbo ng one-off migration gamit ang docker compose run
docker compose run gumagawa ng bagong container mula sa parehong service definition at pinapalitan ang command ng anumang ita-type mo pagkatapos ng service name. Tumatakbo pa rin ang entrypoint ng image, kaya eksaktong kapareho ng long-running container ang paghahanda sa container.
docker compose run --rm app python manage.py migrate- Tinatanggal ng
--rmang container kapag natapos ang command. Kung wala ito, bawat run ay nag-iiwan ng stopped container na makikita sadocker compose ps -a. - Hindi pino-publish ang mga port. Hindi pinapansin ng
runcontainer angports:ng service maliban kung idaragdag mo ang--service-ports, kaya hindi ito sasalungat sa service na kasalukuyang tumatakbo. - Nauunang magsimula ang mga dependency. Lahat ng nasa
depends_onay magsisimula bago ang iyong command, at nilalampasan iyon ng--no-deps. - Nakakakuha ang container ng generated name gaya ng
myproject-app-run-9f2c1a, kaya hindi ito sumasalungat sa service container.
Para palitan din ang entrypoint, may flag para rito:
docker compose run --rm --entrypoint /bin/sh app -c 'python manage.py migrate'Ang nagiging argument list ay /bin/sh -c 'python manage.py migrate', dahil command pa rin ang mga salitang nasa pagkatapos ng service name. Ang docker compose exec ang kabilang tool at iba ang paraan ng pagtatrabaho nito: nagpapatakbo ito ng process sa loob ng container na tumatakbo na, at ganap nitong binabalewala ang entrypoint: at command:. Gamitin ang run para sa task na nangangailangan ng bagong container, at exec para tingnan ang loob ng live container. Inililista ng cheat sheet para sa mga command ng Compose ang iba pang subcommand nang magkakatabi.
Bakit agad lumalabas ang container?
Magsimula sa exit code dahil mabilis nitong nililimitahan ang posibleng sanhi.
docker compose ps -a
docker compose logs appExit code 0 at walang output. Tumakbo at natapos ang command. Ang pinakakaraniwang sanhi ay isang entrypoint: override na isinama ang image's CMD, kaya tumakbo ang entrypoint na walang laman ang argument list at walang maipasa sa susunod na proseso.
Error na nagtatapos sa permission denied. Walang executable bit ang script sa loob ng image. Karaniwan itong nangyayari dahil hindi kailanman itinakda ang bit sa file sa repository. Itakda ito sa build time gamit ang COPY --chmod=0755 entrypoint.sh /entrypoint.sh.
Error na nagtatapos sa no such file or directory para sa file na malinaw mong nakikita sa image. May Windows line endings ang script. Kaya binabasa ang unang linya nito bilang #!/bin/sh na may kasamang carriage return byte. Hinahanap tuloy ng kernel ang interpreter na may byte na iyon sa pangalan nito, ngunit wala itong makita. Patakbuhin ang dos2unix entrypoint.sh, pagkatapos ay idagdag ang * text eol=lf sa .gitattributes upang hindi na ito maulit.
executable file not found in $PATH. Wala sa image ang binary na nakapangalan sa command:, o nagsulat ka ng shell built-in gaya ng cd kung saan isang totoong program lamang ang maaaring gamitin.
Pagpasok sa shell ng image na nagfa-fail ang entrypoint
Kapag namamatay ang entrypoint bago mo ma-inspect ang anuman, palitan ito:
docker compose run --rm --entrypoint /bin/sh appKung nagbalik ito ng executable file not found in $PATH, walang shell ang image. Kadalasang walang shell ang Distroless at scratch-based images. Mababasa mo pa rin ang filesystem mula sa labas nang hindi sinisimulan ang entrypoint:
docker create --name probe myapp:1.4
docker export probe | tar -tv | head -40
docker rm probeKapag kailangan mong manatiling tumatakbo ang container para paulit-ulit mo itong ma-attach, patakbuhin ito gamit ang prosesong hindi kailanman nag-e-exit. Ilagay ito sa override file na hindi mo ika-commit:
services:
app:
entrypoint: ["tail", "-f", "/dev/null"]
command: []Hindi mahigpit na kailangan ang command: [], dahil na-clear na ng pag-set sa entrypoint: ang CMD ng image, pero ipinapakita nito ang intent para sa susunod na magbasa ng file. I-start ito at pumasok dito:
docker compose -f compose.yaml -f compose.debug.yaml up -d app
docker compose exec app /bin/shNgayon, patakbuhin nang manu-mano ang totoong entrypoint at i-monitor kung saan ito humihinto. Sa ganitong paraan, makikita ang error message sa terminal mo sa halip na sa container na namatay makalipas ang kalahating segundo. Kung binubuo mo pa lang ang una mong stack, saklaw ng unang Compose stack sa isang VPS ang file layout na ipinapalagay ng lahat ng nasa itaas.
FAQ
Bakit agad nag-e-exit ang container ko pagkatapos ng docker compose up?
Suriin ang docker compose ps -a para makita ang exit code. Ang Exit 0 na walang output ay karaniwang nangangahulugang itinakda mo ang entrypoint: sa service. Dahil dito, na-clear din ang CMD ng image, kaya tumakbo ang entrypoint na walang laman na argument list at natapos agad. Ibalik ang mga argument gamit ang command:. Ang error na nagtatapos sa permission denied ay nangangahulugang walang executable bit ang entrypoint script. Ang error na nagtatapos sa no such file or directory para sa file na umiiral ay nangangahulugang Windows line endings ang gamit ng script. Dahil dito, interpreter ang tinutukoy ng shebang line na wala sa system.
Tinatanggal ba ng pagtatakda ng entrypoint sa Compose ang CMD ng image?
Oo. Kung non-null ang entrypoint, binabalewala ng Compose ang anumang default command na idineklara ng image. Dokumentado ang behavior na ito at tumutugma ito sa docker run --entrypoint. Ang dahilan ay ang CMD ng image ay isinusulat bilang mga argument para sa ENTRYPOINT ng image. Kapag pinalitan mo ang entrypoint, wala nang pag-aari ang mga lumang argument. Itakda ang command: sa parehong service kung kailangan pa rin ng mga argument ang bagong entrypoint.
Pinapadaan ba sa shell ang string sa Compose command?
Hindi. Hindi tulad ng Dockerfile CMD, hinahati ang string sa Compose command: bilang mga argument at direktang ine-execute, nang walang /bin/sh -c wrapper. Kaya hindi kailanman ine-expand ng shell sa loob ng container ang $VARIABLE. Direktang tawagin ang shell kapag kailangan mo ito, gaya ng nasa command: /bin/sh -c 'echo "hello $$HOSTNAME"'. Ini-escape ng dobleng $$ ang dollar sign, kaya ipinapasa ito ng Compose sa container sa halip na i-expand sa host.
Bakit umaabot ng sampung segundo ang docker compose down para sa isang container?
Ipinapadala ng Compose ang SIGTERM sa PID 1, naghihintay ng stop_grace_period (10 segundo bilang default), at pagkatapos ay ipinapadala ang SIGKILL. Hindi ipinapatupad ng kernel ang mga default signal action sa PID 1. Kaya binabalewala ng program na walang SIGTERM handler ang signal at palaging hinihintay ang buong panahon. Alamin kung ano talaga ang PID 1 gamit ang docker compose exec -T app cat /proc/1/cmdline | tr '\0' ' '. Kung shell ito, ilipat ang image sa exec form o isulat ang exec sa loob ng shell string. Kung nagse-spawn ang process ng mga child na hindi nito nire-reap, itakda ang init: true sa service.