Mi a különbség a Docker `CMD` és `ENTRYPOINT` között?

A modern szoftverfejlesztésben a Docker az alkalmazások konténerizálásának sarokkövévé vált. Lehetővé teszi a fejlesztők számára, hogy az alkalmazásokat és függőségeiket izolált, hordozható egységekbe csomagolják, garantálva a konzisztens működést a különböző környezetekben. Ennek a rugalmas ökoszisztémának a középpontjában a Dockerfile áll, amely egyfajta „receptkönyvként” szolgál a Docker image-ek elkészítéséhez. Ez a fájl tartalmazza azokat az utasításokat, amelyek alapján a Docker felépíti a konténerfuttatáshoz szükséges képfájlt.

A Dockerfile számos utasítást kínál, de kettő közülük különösen fontos, és gyakran okoz félreértést a kezdő és néha a tapasztaltabb Docker felhasználók körében is: a CMD és az ENTRYPOINT. Bár mindkettő alapvetően a konténer indulásakor végrehajtódó parancsot vagy alkalmazást határozza meg, működésükben, viselkedésükben és felülírhatóságukban jelentős különbségek rejlenek. E különbségek megértése kulcsfontosságú a robusztus, hatékony és könnyen karbantartható Docker image-ek építéséhez.

Ebben a cikkben részletesen megvizsgáljuk a CMD és az ENTRYPOINT utasításokat, feltárva azok célját, szintaxisát, egymással való interakciójukat és a legjobb gyakorlatokat, amelyek segítenek eldönteni, mikor melyiket használd. Célunk, hogy egy átfogó útmutatót nyújtsunk, amely eloszlatja a félreértéseket, és képessé tesz arra, hogy tudatosabban tervezd meg a Docker image-eidet.

Mi is az a Docker Röviden?

Mielőtt mélyebbre ásnánk, érdemes röviden felidézni, miért is olyan népszerű a Docker. A Docker egy nyílt forráskódú platform, amely lehetővé teszi az alkalmazások és azok környezetének „konténerekbe” csomagolását. Egy konténer egy könnyűsúlyú, önálló, futtatható szoftvercsomag, amely mindent tartalmaz, ami egy alkalmazás futtatásához szükséges: kódot, futtatókörnyezetet, rendszertámogató eszközöket, könyvtárakat és beállításokat. A konténerek előnye a gyors indítás, a platformfüggetlenség és az izoláció, ami nagyban leegyszerűsíti a fejlesztési, tesztelési és üzemeltetési folyamatokat.

A Konténer Indításának Alappillérei: CMD és ENTRYPOINT

Amikor egy Docker image-ből elindítunk egy konténert, a Dockernek tudnia kell, milyen parancsot vagy programot kell futtatnia a konténer fő folyamataként. Ezt a célt szolgálja a CMD és az ENTRYPOINT utasítás. Bár mindkettő a konténer indulásakor végrehajtódó parancsot definiálja, a fő különbség abban rejlik, hogyan kezelik az argumentumokat, hogyan írhatók felül, és milyen szerepet töltenek be az image tervezésében.

A CMD Utasítás Részletesen

A CMD utasítás az egyik leggyakrabban használt elem egy Dockerfile-ban. Fő célja, hogy alapértelmezett parancsot vagy argumentumokat szolgáltasson egy futtatható képhez. Ez a parancs akkor hajtódik végre, ha a docker run parancsnak nem adunk meg explicit parancsot vagy argumentumokat. Ha megadunk, akkor a CMD utasításban szereplő parancs felülíródik.

Formátumok:

  1. Exec form (ajánlott): CMD ["futtatható", "paraméter1", "paraméter2"]
    Ez a preferált formátum, mivel a parancs közvetlenül hajtódik végre, shell nélkül. A parancs lesz a konténer PID 1-es processze, ami kulcsfontosságú a megfelelő jelkezelés (pl. SIGTERM a leállításkor) biztosításához. Például: CMD ["nginx", "-g", "daemon off;"]
  2. Shell form: CMD parancs paraméter1 paraméter2
    Ez a forma a parancsot egy shellben (/bin/sh -c) futtatja. Bár kényelmes lehet a shell funkciók (pl. környezeti változók, pipeline-ok) használata miatt, problémákat okozhat a jelkezelésben, mivel a shell lesz a PID 1, és nem adja tovább a jeleket az alkalmazásnak. Például: CMD echo "Hello Docker!"
  3. ENTRYPOINT-hoz tartozó paraméterek: CMD ["paraméter1", "paraméter2"]
    Ebben az esetben a CMD csupán argumentumokat szolgáltat az ENTRYPOINT utasításnak. Erről később részletesebben is szó lesz.

Felülírási viselkedés:

A CMD utasítás rendkívül könnyen felülírható. Ha a docker run parancsnak adunk meg egy parancsot, az felülírja a Dockerfile-ban definiált CMD-t. Például:

  • Dockerfile: CMD ["echo", "Hello from Dockerfile!"]
  • docker run myimage: Output: Hello from Dockerfile!
  • docker run myimage echo "Hello from command line!": Output: Hello from command line! (a CMD felülíródott)

Fontos megjegyezni, hogy egy Dockerfile-ban csak az utolsó CMD utasítás érvényesül. Ha több CMD sort is megadunk, a korábbiak figyelmen kívül maradnak.

Mikor használd a CMD-t?

  • Alapértelmezett parancsokhoz: Amikor szeretnél egy alapértelmezett viselkedést biztosítani az image-hez, de meg akarod adni a felhasználóknak a rugalmasságot, hogy könnyen felülírják azt.
  • Argumentumok szolgáltatásához ENTRYPOINT-nak: Gyakori minta, hogy a CMD az ENTRYPOINT által futtatott fő program alapértelmezett paramétereit biztosítja.

Az ENTRYPOINT Utasítás Részletesen

Az ENTRYPOINT utasítás célja, hogy a konténer fő végrehajtható parancsát vagy alkalmazását definiálja. Ez a parancs mindig lefut, függetlenül attól, hogy a docker run kapott-e parancsot. Az ENTRYPOINT-nak megadott parancsokat a docker run argumentumai és a CMD utasításban szereplő paraméterek egészítik ki.

Formátumok:

  1. Exec form (erősen ajánlott): ENTRYPOINT ["futtatható", "paraméter1", "paraméter2"]
    Ez a preferált és leggyakrabban használt forma. A parancs közvetlenül fut, a konténer PID 1-es processze lesz, biztosítva a megfelelő jelkezelést. Például: ENTRYPOINT ["java", "-jar", "app.jar"]
  2. Shell form: ENTRYPOINT parancs paraméter1 paraméter2
    Ez a forma a parancsot egy shellben (/bin/sh -c) futtatja. Ahogy a CMD esetében, ez is problémákat okozhat a jelkezelésben. Általában kerülendő, kivéve, ha kifejezetten szükséged van a shell funkciókra, és tisztában vagy a mellékhatásokkal. Például: ENTRYPOINT echo "Starting..." && myapp

Felülírási viselkedés:

Az ENTRYPOINT utasítás nehezebben felülírható, mint a CMD. Csak explicit módon a --entrypoint flaggel írható felül a docker run parancsban. Például:

  • Dockerfile: ENTRYPOINT ["/usr/bin/ping"]
  • docker run myimage google.com: Output: PING google.com (172.217.16.174) 56(84) bytes of data. (a google.com argumentumként adódik át a ping-nek)
  • docker run --entrypoint /bin/bash myimage: Futtatja a /bin/bash-t a ping helyett, és interaktív shellt nyit a konténerben.

Ahogy a CMD esetében, itt is csak az utolsó ENTRYPOINT utasítás érvényesül egy Dockerfile-ban.

Interakció a CMD-vel:

Ez az, ahol a két utasítás közötti szinergia a leginkább megnyilvánul. Ha az ENTRYPOINT exec formában van megadva, akkor a CMD (szintén exec formában megadva) az ENTRYPOINT parancs argumentumaiként szolgál.

  • Dockerfile:
    ENTRYPOINT ["nginx"]
    CMD ["-g", "daemon off;"]
  • docker run myimage: A Docker a nginx -g daemon off; parancsot futtatja.
  • docker run myimage -v: A Docker a nginx -v parancsot futtatja. (A -v felülírta a CMD-t, de az ENTRYPOINT megmaradt.)

Mikor használd az ENTRYPOINT-ot?

  • Konténer fő programjának meghatározására: Amikor a konténer egy adott alkalmazás (pl. webszerver, adatbázis) futtatására készült, és mindig az az alkalmazás kell, hogy induljon el.
  • Segédprogram-konténerek (Utility Containers): Olyan image-ek esetén, amelyek egyetlen, jól definiált eszközt (pl. curl, kubectl) biztosítanak. Az ENTRYPOINT itt a segédprogramot definiálja, a CMD pedig az alapértelmezett paramétereit (pl. --help).
  • Wrapper scriptek: Amikor a konténer indítása előtt valamilyen inicializációs logikát (pl. konfigurációs fájlok generálása, adatbázis migráció) kell végrehajtani. Ekkor az ENTRYPOINT egy shell scriptre mutat, amely elvégzi a feladatokat, majd az exec "$@" parancs segítségével elindítja a tényleges alkalmazást, átadva neki a CMD-ben vagy a docker run-ban megadott argumentumokat.

A Szimbiózis: CMD és ENTRYPOINT Együtt

A leghatékonyabb Docker image-ek gyakran mindkét utasítást kombinálják az exec formátumok használatával. Ez a megközelítés lehetővé teszi a konténer funkciójának világos és rugalmas meghatározását:

  • Az ENTRYPOINT definiálja a konténer fő végrehajtható programját, azt az „alkalmazást”, amelyet a konténer képvisel. Ez általában egy olyan parancs, amelynek argumentumokat lehet átadni.
  • A CMD alapértelmezett argumentumokat biztosít az ENTRYPOINT számára. Ezek az argumentumok könnyen felülírhatók a docker run parancsban, így a felhasználó testreszabhatja a konténer indítási viselkedését anélkül, hogy az ENTRYPOINT-ot kellene módosítania.

Példa egy Nginx konténerre:

FROM nginx:latest
ENTRYPOINT ["nginx"]
CMD ["-g", "daemon off;"]
  • docker run my-nginx-image: Elindítja az Nginx-et a nginx -g daemon off; paranccsal.
  • docker run my-nginx-image -v: A CMD felülíródik, és a nginx -v parancs fut le, kiírva az Nginx verzióját.

Ha az ENTRYPOINT shell formában van megadva, akkor a CMD-ben szereplő utasítás egyszerűen hozzáadódik az ENTRYPOINT parancs végéhez, mint annak része. Ez ritkán a kívánt viselkedés, mivel nehezen kezelhető és félreértésekhez vezethet.

Főbb Különbségek Összefoglalása

Az alábbi táblázatban összefoglaljuk a legfontosabb különbségeket:

Jellemző CMD ENTRYPOINT
Célja Alapértelmezett parancs vagy argumentumok. A konténer fő végrehajtható parancsa.
Felülírás módja Könnyen felülírható a docker run <image> <parancs>-szal. Nehezebben felülírható, explicit módon a docker run --entrypoint <parancs> <image>-szal.
Interakció a docker run argumentumokkal Ha a docker run parancsot kap, a CMD felülíródik. Ha a docker run argumentumokat kap, azok az ENTRYPOINT parancs argumentumaiként adódnak át.
Interakció a másik utasítással Ha van ENTRYPOINT exec formában, akkor a CMD az ENTRYPOINT argumentumaiként szolgál. Az ENTRYPOINT mindig lefut, és a CMD adja az alapértelmezett argumentumait (ha ENTRYPOINT exec formában van).
Használati eset Alapértelmezett, könnyen módosítható viselkedés. A konténer lényegi funkciója, futtatható program, segédprogramok.

Mikor Melyiket Válaszd? A Best Practices

A helyes választás nagyban függ a Docker image céljától és a kívánt rugalmasságtól:

Használd az ENTRYPOINT-ot, ha:

  • A konténer egy adott alkalmazást (pl. webszerver, adatbázis, speciális CLI eszköz) futtat, és ez az alkalmazás a konténer fő funkciója. Például: ENTRYPOINT ["nginx"] vagy ENTRYPOINT ["java", "-jar", "myapp.jar"].
  • Egy segédprogramot (utility container) szeretnél létrehozni. Például egy curl image:
    FROM alpine/git
    ENTRYPOINT ["git"]
    CMD ["--help"]
    

    Ekkor docker run my-git-image clone <repo> parancsot futtathatsz, és a git clone <repo> hajtódik végre.

  • Egy wrapper scriptet szeretnél futtatni a tényleges alkalmazás előtt. Ez a script végezhet konfigurációt, jogosultságbeállítást, vagy ellenőrzéseket, mielőtt az exec "$@" paranccsal átadja a vezérlést a CMD-nek vagy a docker run argumentumainak.
    FROM busybox
    COPY entrypoint.sh /usr/local/bin/entrypoint.sh
    RUN chmod +x /usr/local/bin/entrypoint.sh
    ENTRYPOINT ["/usr/local/bin/entrypoint.sh"]
    CMD ["echo", "Default command!"]
    

    Az entrypoint.sh tartalma:

    #!/bin/sh
    echo "Running custom entrypoint..."
    # Some setup logic here
    exec "$@"
    

    Így a script lefut, majd a CMD vagy a `docker run` argumentumai végrehajtódnak.

Használd a CMD-t, ha:

  • Alapértelmezett argumentumokat szeretnél biztosítani az ENTRYPOINT-hoz. Ez a leggyakoribb és ajánlott használati mód.
    FROM debian
    ENTRYPOINT ["ping"]
    CMD ["google.com"]
    

    Ekkor docker run my-ping-image a ping google.com parancsot futtatja. A docker run my-ping-image bing.com pedig a ping bing.com-ot.

  • Egy image-et hozol létre, amelynek nincs fix ENTRYPOINT-ja, és csak egy könnyen felülírható alapértelmezett parancsot szeretnél biztosítani. Ez ritkábban fordul elő, de hasznos lehet egyszerű, egyszeri futtatásra szánt konténerek esetén.
    FROM alpine
    CMD ["echo", "Hello from simple container!"]
    

    docker run my-simple-image kiírja az üdvözlést. docker run my-simple-image ls -l pedig a konténer gyökérkönyvtárának tartalmát listázza ki.

Haladó Megfontolások: Shell vs. Exec Form – A Rinek a Felszín Alatt

A CMD és az ENTRYPOINT utasításoknál említettük a „shell form” és „exec form” megkülönböztetését. Ez nem csupán szintaktikai különbség, hanem mélyrehatóan befolyásolja a konténer folyamatkezelését és viselkedését.

Exec form (ajánlott):

  • Szintaxis: ["parancs", "paraméter1", "paraméter2"]
  • Működés: A Docker közvetlenül futtatja a megadott parancsot. Ez a parancs lesz a konténer PID 1-es processze.
  • Előnyök:
    • Megfelelő jelkezelés: A PID 1-es processz megkapja a Docker által küldött leállítási jeleket (pl. SIGTERM), így az alkalmazásnak lehetősége van a „graceful shutdown”-ra (tisztességes leállásra), mielőtt a Docker erővel leállítaná (SIGKILL). Ez kulcsfontosságú az adatvesztés elkerüléséhez és az alkalmazás stabilitásához.
    • Nincs felesleges shell processz: Nincs extra shell overhead, ami minimálisan csökkentheti az erőforrás-felhasználást és a komplexitást.
  • Hátrányok:
    • Nincsenek shell funkciók: Nem használhatók shell-specifikus funkciók (pl. környezeti változók közvetlen helyettesítése, wildcard-ok, pipeline-ok, összetett redirekciók). Pl. CMD ["echo", "$MY_VAR"] nem helyettesíti a változót, hanem szó szerint kiírja a $MY_VAR-t.

Shell form:

  • Szintaxis: parancs paraméter1 paraméter2
  • Működés: A Docker a parancsot egy shellben futtatja (pl. /bin/sh -c "parancs paraméter1 paraméter2"). A shell lesz a konténer PID 1-es processze.
  • Előnyök:
    • Shell funkciók: Használhatók shell-specifikus funkciók (pl. CMD echo "Hello $USER").
  • Hátrányok:
    • Rossz jelkezelés: Mivel a shell a PID 1, és a shell indítja el az alkalmazást mint gyermekprocesszt, a Docker által küldött leállítási jeleket (SIGTERM) a shell kapja meg, és gyakran nem adja tovább az alkalmazásnak. Ez azt jelenti, hogy az alkalmazás nem tudja magát rendesen leállítani, és végül erőszakosan lesz leállítva (SIGKILL), ami adatvesztéshez vezethet.
    • Extra processz: Egy felesleges shell processz fut, ami minimális erőforrást fogyaszt.

Az exec trükk:

Ha szükséged van shell funkciókra (pl. környezeti változók beállítása egy scriptben), de mégis szeretnéd a megfelelő jelkezelést, használhatod az exec parancsot egy shell scripten belül, amelyet az ENTRYPOINT futtat:

FROM alpine
COPY my_script.sh /usr/local/bin/my_script.sh
RUN chmod +x /usr/local/bin/my_script.sh
ENTRYPOINT ["/usr/local/bin/my_script.sh"]
CMD ["my_app_default_arg"]

A my_script.sh tartalma:

#!/bin/sh
# Shell magic here, e.g., variable substitution
echo "Setting up environment..."
export MY_VAR="some_value"

# Finally, execute the main application
# The 'exec' command replaces the current shell process with the application,
# making the application the new PID 1.
exec my_app --arg1 "$MY_VAR" "$@"

Ez a módszer ötvözi a shell kényelmét a megfelelő PID 1 viselkedéssel.

Gyakorlati Példák és Szcenáriók

1. Webszerver (Nginx)

FROM nginx:alpine
# Az ENTRYPOINT a fő alkalmazást definiálja, ami mindig az Nginx.
ENTRYPOINT ["nginx"]
# A CMD az Nginx alapértelmezett paramétereit adja meg.
# Ez felülírható, ha a felhasználó más paraméterekkel szeretné indítani az Nginx-et.
CMD ["-g", "daemon off;"]

Futtatás:

  • docker run my-nginx-image: Elindítja az Nginx-et a nginx -g daemon off; paranccsal.
  • docker run my-nginx-image -v: A CMD felülíródik, és a nginx -v fut le (kiírja az Nginx verzióját).

2. Adatbázis Kliens (Postgres psql)

FROM postgres:16-alpine
# Az ENTRYPOINT a psql klienst definiálja.
ENTRYPOINT ["psql"]
# A CMD az alapértelmezett segítő üzenetet adja.
CMD ["--help"]

Futtatás:

  • docker run my-psql-client: Futtatja a psql --help parancsot.
  • docker run my-psql-client -h db-server -U admin: Futtatja a psql -h db-server -U admin parancsot, kapcsolódva az adatbázishoz.

3. Saját Fejlesztésű Java Alkalmazás

FROM openjdk:17-jdk-slim
WORKDIR /app
COPY target/my-app.jar my-app.jar
# Az ENTRYPOINT definiálja a fő végrehajtható programot.
ENTRYPOINT ["java", "-jar", "my-app.jar"]
# A CMD az alapértelmezett argumentumokat adja meg az alkalmazásnak.
CMD ["--server.port=8080", "--spring.profiles.active=prod"]

Futtatás:

  • docker run my-java-app: Futtatja a java -jar my-app.jar --server.port=8080 --spring.profiles.active=prod parancsot.
  • docker run my-java-app --server.port=9000: Futtatja a java -jar my-app.jar --server.port=9000 parancsot.

Összefoglalás

A CMD és az ENTRYPOINT utasítások közötti különbségek megértése alapvető fontosságú a hatékony és robusztus Docker image-ek építéséhez. Bár mindkettő a konténer indítási parancsát határozza meg, különböző szerepet töltenek be:

  • Az CMD alapértelmezett parancsot vagy argumentumokat szolgáltat, amelyek könnyen felülírhatók a docker run parancsban. Ideális az image alapértelmezett viselkedésének meghatározására, amely szükség esetén módosítható.
  • Az ENTRYPOINT a konténer fő végrehajtható programját definiálja, amely mindig lefut. Argumentumokat vár, amelyeket a CMD vagy a docker run parancsban megadott paraméterek biztosítanak. Ez a választás akkor optimális, ha a konténer egy adott alkalmazás céljára épült, és ezt az alkalmazást kell mindig futtatnia.

A legtöbb esetben az ENTRYPOINT exec formában történő használata, kiegészítve a CMD-ben megadott alapértelmezett argumentumokkal, a legrugalmasabb és leginkább ajánlott minta. Ez biztosítja a konténer kiszámítható működését, a megfelelő jelkezelést és a könnyű testreszabhatóságot a felhasználók számára.

Ne feledd, a Docker konténerek lényege a konzisztencia és a hordozhatóság. A CMD és ENTRYPOINT utasítások tudatos használatával olyan image-eket hozhatsz létre, amelyek világosan kommunikálják a szándékukat, és megbízhatóan működnek a különböző környezetekben. Kísérletezz bátran, olvasd el a Docker dokumentációját, és hamarosan profin fogod kezelni ezeket az alapvető utasításokat!

Leave a Reply

Az e-mail címet nem tesszük közzé. A kötelező mezőket * karakterrel jelöltük