Docker image-ek és rétegek: hogyan épül fel egy image?
Az image-ek rétegekből (layer) állnak, és ez a kulcs a gyors buildhez és a kis mérethez. Megnézzük a réteges felépítést, a build cache-t és a tageket.
Amikor először látsz egy Docker buildet futni, és sorra megjelennek a Step 1/8, Step 2/8 üzenetek, joggal merül fel a kérdés: mi történik valójában? Hogyan lesz egy néhány soros Dockerfile-ból egy futtatható image, és miért gyorsul fel a második build annyira? A válasz egyetlen szóban rejlik: rétegek. A Docker image-ek rétegekből (layer) épülnek fel, és ennek megértése a kulcsa a gyors buildeknek és a kis méretű image-eknek egyaránt. Vágjunk is bele.
Mi az a réteges felépítés?
A Docker image valójában nem egyetlen monolitikus fájl, hanem egymásra épülő, csak olvasható rétegek halmaza. Minden réteg a filerendszer egy-egy módosítását tárolja: hozzáadott fájlokat, törléseket, jogosultság-változásokat. A Docker ezeket egy úgynevezett union filesystem segítségével vetíti egymásra, így a végeredmény egyetlen, egységes filerendszernek látszik a konténer számára.
A lényeg: a rétegek csak olvashatók. Amikor elindítasz egy konténert, a Docker a meglévő rétegek tetejére tesz egy vékony, írható réteget. Minden, amit a futó konténer ír, ebbe a felső rétegbe kerül — az alatta lévő image-rétegek érintetlenek maradnak. Ezért indíthatsz ugyanabból az image-ből száz konténert anélkül, hogy százszor lemásolnád a teljes filerendszert.
💡 Tipp: Gondolj a rétegekre úgy, mint egy fénymásolatok egymásra helyezett, áttetsző fóliáira. Mindegyik fólia hozzátesz valamit a képhez, és felülről nézve egyetlen összképet látsz — pontosan így működik a union filesystem.
Minden Dockerfile-utasítás egy réteg
Az image rétegeit a Dockerfile utasításai határozzák meg. A buildelést végző utasítások — FROM, RUN, COPY, ADD — egy-egy új réteget hoznak létre. Nézzünk egy egyszerű példát:
FROM node:20-alpine
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci --omit=dev
COPY . .
CMD ["node", "server.js"]
Itt a FROM, a két COPY és a RUN mind külön réteget eredményez. (A metaadat-jellegű utasítások, mint a WORKDIR, CMD, ENV vagy LABEL, nem hoznak létre tényleges filerendszer-réteget, csak a konfigurációt módosítják.)
Ha új Dockerfile-t írnál, a Dockerfile írása lépésről lépésre cikk minden utasítást részletesen bemutat. A konténerek alapjaihoz pedig a Mi az a Docker? cikk ad gyors áttekintést.
A build cache: miért gyors a második build?
Itt jön a réteges felépítés egyik legnagyobb előnye. A Docker minden rétegre cache-t tart. Buildeléskor utasításról utasításra halad, és ha egy adott utasítás bemenete (a parancs maga és a hozzá tartozó fájlok) nem változott az előző buildhez képest, akkor a Docker újrahasználja a cache-elt réteget ahelyett, hogy újra végrehajtaná.
A bökkenő: amint egy réteg megváltozik, az alatta lévő összes rétegnek is újra kell épülnie. A cache ugyanis fentről lefelé, sorban érvényesül — egy módosítás a cache-t az adott ponttól lefelé érvényteleníti.
Helyes sorrend a cache kihasználásához
Ebből következik az egyik legfontosabb optimalizációs szabály: a ritkán változó dolgokat tedd előre, a gyakran változókat hátra. Nézd meg újra a fenti Dockerfile-t. Miért külön COPY a package.json-nek és a forráskódnak?
COPY package.json package-lock.json ./
RUN npm ci --omit=dev
COPY . .
Mert a függőségeid (package.json) ritkán változnak, a forráskódod viszont gyakran. Így ha csak egy sort módosítasz a kódban, a npm ci réteg cache-ből jön — nem kell újratelepíteni az összes csomagot. Ha viszont mindent egyetlen COPY . .-vel másolnál a RUN npm ci elé, minden apró kódváltozás újraindítaná a teljes függőség-telepítést.
⚠️ Figyelem: A
RUN apt-get updateés aRUN apt-get installutasításokat mindig ugyanabba aRUNsorba tedd (&&-vel összefűzve). Ha külön rétegekbe rakod, a cache-eltupdateréteg miatt elavult csomaglistából telepíthetsz.
Tagek és digestek: hogyan azonosítjuk az image-eket?
Az image-eket kétféleképpen hivatkozhatod:
- Tag: ember által olvasható címke, pl.
nginx:1.27vagynode:20-alpine. A tag mozgó hivatkozás — ugyanaz a tag idővel más-más image-re mutathat (alatestkülönösen). - Digest: egy SHA256 hash, amely az image tartalmának egyértelmű, megváltoztathatatlan ujjlenyomata, pl.
nginx@sha256:abc123.... Ugyanaz a digest mindig pontosan ugyanazt az image-et jelenti.
Ha reprodukálható buildeket akarsz (pl. production környezetben), érdemes digestre hivatkozni:
FROM node:20-alpine@sha256:1f2e3d...
A tagek és a latest buktatóiról, valamint a registry-k működéséről bővebben olvashatsz A Docker Hub használata cikkben.
Hasznos parancsok a rétegek vizsgálatához
Az image rétegeinek megtekintéséhez a docker history parancsot használhatod:
docker history node:20-alpine
Ez kilistázza az image rétegeit, méretüket és a hozzájuk tartozó utasítást — kiváló eszköz annak felderítésére, melyik lépés hizlalja az image-et.
A helyi image-ek listázása:
docker images
A kimenetben láthatod a repository nevét, a taget, az image ID-t (a digest rövid formáját) és a méretet. Egy konkrét image részletes vizsgálatához:
docker inspect node:20-alpine
Rétegek megosztása image-ek között
A réteges felépítés egy gyakran alábecsült előnye a megosztás. Mivel a rétegeket a tartalmuk hash-e azonosítja, két különböző image, amely ugyanarra a FROM node:20-alpine alaprétegre épül, fizikailag ugyanazokat a réteg-fájlokat osztja meg a lemezen. A Docker nem tárolja kétszer ugyanazt.
Ennek két konkrét haszna van:
- Kevesebb lemezhasználat: tíz Node.js-alapú image is csak egyszer tárolja a közös alaprétegeket.
- Gyorsabb letöltés: amikor
docker pull-lal húzol le egy image-et, a Docker csak azokat a rétegeket tölti le, amelyek még nincsenek meg helyben. AAlready existsüzenet pontosan ezt jelzi.
Ezért is éri meg, ha a projektjeidben konzisztens, közös alap-image-eket használsz — a megosztott rétegek miatt a tárhely és a sávszélesség is jobban kihasználható.
Összefoglalás
A Docker image-ek réteges felépítése nem csupán technikai érdekesség, hanem a hatékony konténerezés alapja:
- Az image csak olvasható rétegek halmaza, amit egy union filesystem vetít egységes egésszé; a konténer egy írható réteget kap a tetejére.
- Minden filerendszert módosító Dockerfile-utasítás (
FROM,RUN,COPY,ADD) egy réteget hoz létre. - A build cache rétegenként működik; a ritkán változó utasításokat tedd előre, hogy a gyakori kódváltozások ne építsék újra a függőségeket.
- A tag mozgó hivatkozás, a digest egyértelmű ujjlenyomat — reprodukálhatósághoz használj digestet.
- A
docker history,docker imagesésdocker inspectsegít a rétegek vizsgálatában, a közös rétegek pedig megosztódnak az image-ek között.
Most, hogy érted a rétegeket, próbáld ki: futtass egy docker history-t egy kedvenc image-eden, és nézd meg, melyik lépés mekkora! Ha az alapoktól indulnál, kezdd a Kezdő lépések oldallal, vagy mélyülj el a működésben a Hogyan működik cikkel.