aslain.dev
0%
01 Hizmetler 02 Hakkımda 03 Projeler 04 Stack 05 Blog 06 İletişim
← Tüm makaleler Tools & DevOps

Een Dockerfile schrijven: efficiënte images met lagen en cache

Een goed geschreven Dockerfile is het hart van het recept dat je app overal op dezelfde manier laat draaien. Toch produceren de meeste Dockerfiles images die groter zijn dan nodig en bouwen ze alles opnieuw bij de kleinste wijziging. In deze gids lopen we stap voor stap door Dockers model van lagen (layers) en cache, en bouwen we images die kleiner, sneller en reproduceerbaar zijn.

Het lagenmodel: elke instructie is een laag

Een image bestaat uit gestapelde, alleen-lezen lagen. Elke instructie in een Dockerfile — FROM, RUN, COPY, ADD — produceert een nieuwe laag. Docker cachet deze lagen: als een instructie en de invoer ervan niet zijn veranderd, berekent Docker de laag niet opnieuw, maar hergebruikt de gecachte versie.

Het cruciale punt: als een laag verandert, wordt de cache van elke laag eronder ook ongeldig. Dingen die vaak veranderen achteraan in de Dockerfile zetten, en dingen die zelden veranderen vooraan, heeft dus direct invloed op de buildtijd. Volgorde is het halve werk.

Laten we beginnen met een eenvoudige Dockerfile

Een typisch maar gebrekkig voorbeeld voor een Node.js-app ziet er zo uit:

FROM node:20
WORKDIR /app
COPY . .
RUN npm install
CMD ["node", "server.js"]

Dit werkt, maar er is een probleem: omdat COPY . . het hele project kopieert, maakt het wijzigen van één regel broncode de cache van de RUN npm install-laag eronder ongeldig, zodat afhankelijkheden elke keer opnieuw worden geïnstalleerd. Terwijl als package.json niet is veranderd, de afhankelijkheden niet hoeven te veranderen.

De cache correct gebruiken: scheid afhankelijkheden

De oplossing is om eerst alleen de afhankelijkheidsmanifesten te kopiëren en te installeren, en de applicatiecode als laatste te laten:

FROM node:20
WORKDIR /app

# Eerst alleen de afhankelijkheidsbestanden
COPY package.json package-lock.json ./
RUN npm ci

# Daarna de applicatiecode
COPY . .

CMD ["node", "server.js"]

Nu draait alleen de laatste COPY . .-laag opnieuw wanneer de broncode verandert; de npm ci-laag komt uit de cache. Zolang package-lock.json niet is veranderd, worden afhankelijkheden niet opnieuw geïnstalleerd. Deze kleine herordening kan de buildtijd van minuten naar seconden brengen.

npm ci gebruiken in plaats van npm install is ook een bewuste keuze: ci volgt het lock-bestand exact, is deterministisch en betrouwbaarder voor CI-omgevingen.

Houd rommel buiten met .dockerignore

COPY . . kopieert alles — node_modules, .git, logbestanden, je lokale .env... Dit blaast de image op en verprutst onnodig de cache. Voeg een .dockerignore-bestand toe in de projectroot:

node_modules
.git
.gitignore
Dockerfile
*.log
.env
dist
coverage

Dit bestand werkt zoals .gitignore en somt paden op die nooit in de buildcontext (de bestanden die naar de Docker-daemon worden gestuurd) terecht mogen komen. Een kleinere context betekent snellere builds en een schonere image.

Multi-stage builds: gooi de buildtools eruit

De tools die nodig zijn om een app te bouwen (compilers, dev-afhankelijkheden) zijn niet nodig om hem te draaien. Een multi-stage build laat je in één fase bouwen en alleen het resultaat naar een kleine eindimage meenemen:

FROM node:20 AS build
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci
COPY . .
RUN npm run build

# Een dunne eindfase die alleen de productieoutput meeneemt
FROM node:20-slim AS runtime
WORKDIR /app
ENV NODE_ENV=production
COPY package.json package-lock.json ./
RUN npm ci --omit=dev
COPY --from=build /app/dist ./dist
CMD ["node", "dist/server.js"]

Hier kopieert --from=build alleen de gecompileerde dist-map naar de tweede fase. De eindimage bevat geen buildtools of dev-afhankelijkheden, wat de imagegrootte vaak halveert of meer. Het kiezen van een dunne base-image zoals node:20-slim verkleint ook de grootte en het aanvalsoppervlak.

Praktische regels en veelgemaakte fouten

  • Combineer RUN-commando's: elke RUN is een laag. Koppel pakketinstallaties met && en ruim op in dezelfde laag; bijvoorbeeld op een Debian-basis apt-get update && apt-get install -y curl && rm -rf /var/lib/apt/lists/*.
  • Zet versies vast: FROM node:20 is beter dan FROM node:latest; gebruik een specifieke versie voor reproduceerbaarheid.
  • Schrijf CMD en ENTRYPOINT in exec-vorm: CMD ["node", "server.js"] stuurt signalen (SIGTERM) correct door vergeleken met de shell-vorm, wat een nette afsluiting mogelijk maakt.
  • Draai niet als root: definieer in productie een USER zodat de container als een gebruiker zonder rechten draait.
  • Gebruik WORKDIR: verkies WORKDIR boven cd; het is leesbaar en blijft behouden tussen lagen.

De image bouwen en inspecteren

Zodra de Dockerfile klaar is, is de image bouwen en de grootte controleren eenvoudig:

docker build -t mijn-app:1.0 .
docker images mijn-app
docker history mijn-app:1.0

docker history toont, laag voor laag, welke instructie hoeveel ruimte inneemt; het is de meest praktische tool om te vinden wat je image opblaast. Met BuildKit (standaard in modern Docker) toont de build-uitvoer ook welke stappen CACHED waren.

Veelgestelde vragen

Moet ik een Dockerfile of Compose gebruiken?

Ze doen verschillende dingen. Een Dockerfile definieert hoe één image wordt gebouwd; docker compose definieert en draait meerdere containers samen (bijvoorbeeld een app plus een database). Compose verwijst meestal via build: naar je Dockerfile — de een vervangt dus de ander niet, ze worden samen gebruikt.

Wat is het verschil tussen COPY en ADD?

Beide kopiëren bestanden, maar ADD heeft twee extra mogelijkheden: downloaden van externe URL's en automatisch uitpakken van lokale tar-archieven. Omdat dat gedrag je kan verrassen, geef altijd de voorkeur aan COPY voor gewone bestandskopieën, en gebruik ADD alleen voor speciale behoeften zoals het uitpakken van een tarball.

Waarom is mijn image nog steeds zo groot?

De meest voorkomende oorzaken: een niet-dunne base-image (probeer slim of alpine), geen multi-stage build gebruiken, niet opruimen binnen dezelfde laag, en een ontbrekende .dockerignore. Inspecteer de lagen met docker history en richt je op de grootste.

Een efficiënte, kleine en snelle image nodig? Ik kan je bestaande Dockerfiles optimaliseren voor lagen en cache en je CI/CD-pipelines versnellen — neem contact op.

Devamı için