← Vissza a bloghoz Alapok

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 a RUN apt-get install utasításokat mindig ugyanabba a RUN sorba tedd (&&-vel összefűzve). Ha külön rétegekbe rakod, a cache-elt update ré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.27 vagy node:20-alpine. A tag mozgó hivatkozás — ugyanaz a tag idővel más-más image-re mutathat (a latest kü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:

  1. Kevesebb lemezhasználat: tíz Node.js-alapú image is csak egyszer tárolja a közös alaprétegeket.
  2. 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. A Already 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 és docker inspect segí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.