Design wechseln

Docker-Build beschleunigen: Cache nutzen für 10x schnellere Builds – Praxisleitfaden

Easton editorial illustration: registry transfer crane

Tippfehler korrigiert, erneut docker build – und npm install läuft schon wieder. 10 Minuten vorbei, 20 Social-Media-Posts gescrollt, zwei Kaffee getrunken – der Fortschrittsbalken dreht sich noch.

Wer containerisiert entwickelt, kennt dieses Gefühl.

Docker-Cache-Mechanismus verstehen, Build-Zeit von 10 Minuten auf 30 Sekunden drücken.

30 Sek.
Build-Zeit
Von 10 Minuten auf 30 Sekunden – 20-fach schneller

Drei sofort nutzbare Tipps: .dockerignore konfigurieren, Layer-Cache verstehen, Dockerfile-Reihenfolge optimieren. Am Ende BuildKit-Cache-Mounts. Build zu langsam? Dann weiterlesen.

Warum ist Ihr Docker-Build so langsam?

Der Build-Context ist zu groß

Ein häufiger Stolperstein: der Build-Context.

Bei docker build . packt Docker zuerst alle Dateien im .-Verzeichnis und sendet sie an den Docker-Daemon – node_modules, .git, große Testdatensätze inklusive.

Extremfall: 800 MB Build-Context bei einem Frontend-Projekt. Übertragung allein 2–3 Minuten – im Image oft weniger als 10 MB Quellcode nötig.

Wie ein Buch per Kurier und das Regal gleich mit.

Kaskade bei Cache-Invalidierung

Zweites Problem: Layer-Cache nicht verstanden.

Docker-Images sind geschichtet. Jede Anweisung – FROM, RUN, COPY – erzeugt eine Layer. Docker prüft Cache pro Layer; bei gleicher Anweisung und unveränderten Dateien wird wiederverwendet.

Aber: Sobald eine Layer invalidiert ist, müssen alle folgenden neu gebaut werden – Dominosteine.

Typisches Dockerfile:

FROM node:18
COPY . /app
WORKDIR /app
RUN npm install

Sieht harmlos aus – ist es nicht.

COPY . /app kopiert alles. Jede Dateiänderung – selbst ein Tippfehler in der README – invalidiert die Layer. Danach läuft npm install erneut.

Deshalb: eine Zeile Code geändert, ganzer Abhängigkeitsbaum neu installiert.

Falsche Anweisungsreihenfolge

Dritter Fehler: Reihenfolge der Anweisungen.

Docker prüft von oben nach unten; bei Invalidierung stoppt der Cache. Selten geänderte Anweisungen nach oben, häufig geänderte nach unten.

Viele Dockerfiles machen das Gegenteil: zuerst Code (häufig), dann Abhängigkeiten (selten). Jede Code-Änderung macht den Abhängigkeits-Cache wertlos.

Kurz: nicht klar, was sich oft ändert und was stabil bleibt.

Tipp 1 – .dockerignore für kleineren Build-Context

Zuerst die einfachste, schnell spürbare Optimierung: .dockerignore.

Was ist das?

Wie .gitignore: sagt Docker, welche Dateien nicht in den Build-Context gehören.

Anlegen: .dockerignore im Projektroot (neben dem Dockerfile), Regeln eintragen – fertig.

Konfiguration für Node.js

Praxis-Konfiguration:

# Abhängigkeitsverzeichnisse
**/node_modules/
**/npm-debug.log
**/.npm

# Git
.git/
.gitignore
.gitattributes

# Tests und Dokumentation
**/test/
**/tests/
**/docs/
**/*.md
!README.md

# IDE und Editor
.vscode/
.idea/
*.swp
*.swo
.DS_Store

# Umgebungsvariablen und Konfiguration
.env
.env.*
*.local

# Build-Artefakte
dist/
build/
coverage/

Wichtig:

  1. node_modules unbedingt ausschließen – oft hunderte MB, im Image wird neu installiert.
  2. **/ für verschachtelte Verzeichnisse – z. B. ./node_modules/ und ./packages/lib/node_modules/.
  3. Verzeichnisse mit Schrägstrichnode_modules/ = Verzeichnis, node_modules = Dateiname. Docker unterscheidet strikt.

Wie stark der Effekt ist

Next.js-Projekt im Test:

  • Vorher: Build-Context 520 MB, Übertragung 2 Min. 15 Sek.
  • Nachher: Build-Context 4,8 MB, Übertragung 3 Sek.

3 Sekunden – 2 Minuten gespart.

Zusätzlich schlankeres Image: ohne .git und node_modules von 1,2 GB auf 680 MB.

Häufige Fallstricke

Fallstrick 1: .dockerignore gilt nur im Root des Build-Contexts. Bei docker build -f subfolder/Dockerfile . liegt die Datei im Projektroot, nicht in subfolder.

Fallstrick 2: node_modules ohne Schrägstrich kann scheitern – besser node_modules/.

Fallstrick 3: .git vergessen – oft hunderte MB, im Image nie gebraucht.

Tipp 2 – Docker Layer-Cache verstehen und nutzen

.dockerignore beschleunigt die Übertragung – der Kern bleibt das Cache-Verhalten.

Wie Layer-Cache funktioniert

Docker-Images sind wie eine Torte: jede Schicht = Ergebnis einer Dockerfile-Anweisung.

FROM node:18          # Layer 1
RUN apt-get update    # Layer 2
COPY package.json .   # Layer 3
RUN npm install       # Layer 4
COPY . .              # Layer 5

Beim Build prüft Docker Layer für Layer:

  1. Layer 1: FROM – lokales node:18-Image? Cache.
  2. Layer 2: RUN – gleicher Befehlstext? Cache.
  3. Layer 3: COPY – Checksum von package.json unverändert? Cache.
  4. Layer 4–5: analog.

Sobald eine Layer invalidiert ist, folgen alle neu – Dominokette. Änderung in Layer 3 → npm install und Code-COPY laufen erneut.

Cache-Effekt erkennen

Build-Ausgabe:

Step 3/5 : COPY package.json .
 ---> Using cache
 ---> 3a8f29e7c5b1

Using cache = Cache aktiv. Fehlt es, wird neu gebaut.

Mit docker history <IMAGE-ID> sehen Sie Layer-Historie, SIZE und Erstellungszeit.

Warum COPY anders ist als RUN

RUN hängt am Befehlstext – RUN npm install unverändert → Cache möglich.

COPY/ADD prüfen Dateiinhalt per Checksum. Gleicher Name, anderer Inhalt → Cache weg.

Sinnvoll – geänderte Dateien können Folgeschritte beeinflussen.

Deshalb ist COPY . . riskant: jede Projektdatei-Änderung invalidiert die Layer.

Tipp 3 – Dockerfile-Reihenfolge optimieren

Cache verstanden – wie schreibt man das Dockerfile?

Goldene Regel: von stabil zu volatil

Selten geänderte Anweisungen oben, häufig geänderte unten.

Docker prüft von oben. Stabile obere Layer bleiben gültig, wenn unten nur Code wechselt.

Reihenfolge:

  1. Basis-Image – fast nie
  2. System-Abhängigkeiten – selten
  3. Projekt-Abhängigkeiten – manchmal
  4. Quellcode – täglich

So maximieren Sie Cache-Nutzung.

Fehler: Code zuerst, dann Abhängigkeiten

Typischer Anfängerfehler:

FROM node:18
WORKDIR /app

# Fehler: gesamtes Projekt kopieren
COPY . .

# Abhängigkeiten installieren
RUN npm install

CMD ["npm", "start"]

Code geändert → COPY . . invalidiert → npm install neu. Eine JS-Zeile, hunderte npm-Pakete, wieder 10 Minuten.

Richtig: Abhängigkeiten zuerst, dann Code

Optimiert:

FROM node:18
WORKDIR /app

# Schritt 1: nur Abhängigkeitsdateien
COPY package.json package-lock.json ./

# Schritt 2: installieren (Layer wird gecacht)
RUN npm ci --only=production

# Schritt 3: Quellcode
COPY . .

CMD ["npm", "start"]

Vorteile:

  1. Unverändertes package.jsonnpm ci aus Cache.
  2. Nur Code geändert → nur COPY . . neu, Abhängigkeits-Layer bleibt.
  3. Zweiter Build ohne npm-Install – deutlich schneller.

Praxis: nachfolgende Builds von 7–8 Minuten auf etwa 30 Sekunden.

Gleiches Prinzip für andere Sprachen

Python:

FROM python:3.11
WORKDIR /app

COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .

Go:

FROM golang:1.21
WORKDIR /app

COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN go build -o main .

Kern: Abhängigkeitsdateien und Quellcode getrennt kopieren, Install-Schritt cachen.

Fortgeschritten: feingranulares COPY

Bei komplexer Struktur:

COPY .eslintrc.json .prettierrc ./
COPY package*.json ./
RUN npm install
COPY ./lib ./lib
COPY ./src ./src

Selten, aber in Monorepos nützlich.

Fortgeschritten – BuildKit-Cache-Mounts

Basis-Optimierungen erledigt – BuildKit-Cache-Mounts.

Was ist BuildKit?

Seit Docker 18.09: neuer Build-Engine, schneller, stärkere Cache-Funktionen.

Aktivieren:

# temporär
export DOCKER_BUILDKIT=1
docker build .

# oder direkt
DOCKER_BUILDKIT=1 docker build .

Ab Docker 19.03+ oft standardmäßig an. Mit docker version prüfen.

Was sind Cache-Mounts?

Layer-Cache-Problem: bei Invalidierung muss der ganze Schritt neu laufen.

Neue Abhängigkeit in package.jsonnpm install-Layer weg → alle Pakete neu laden, auch bekannte.

Cache-Mounts: Paketmanager-Download-Cache bleibt, auch wenn die Layer invalidiert ist – persistenter Cache-Ordner über Builds hinweg.

Anwendung

Node.js:

FROM node:18
WORKDIR /app

COPY package*.json ./

RUN --mount=type=cache,target=/root/.npm \
    npm ci --only=production

COPY . .
CMD ["npm", "start"]

--mount=type=cache,target=/root/.npm:

  • type=cache – Cache-Mount
  • target=/root/.npm – npm-Cache-Verzeichnis

Bei geändertem package.json liest npm vorhandenen Cache aus /root/.npm, lädt nur Neue/Geänderte.

Andere Paketmanager

Yarn:

RUN --mount=type=cache,target=/root/.yarn \
    yarn install --frozen-lockfile

pip (Python):

RUN --mount=type=cache,target=/root/.cache/pip \
    pip install -r requirements.txt

apt (Systempakete):

RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \
    apt-get update && apt-get install -y gcc

Bei apt sharing=locked – apt braucht exklusiven Cache, vermeidet Konflikte bei parallelen Builds.

Wirkung

Projekt mit 200+ Abhängigkeiten:

  • Layer-Cache invalid, Cache-Mount aktiv: Install von 8 Min. auf 1 Min. 30 Sek.
  • Kaltstart ohne Cache: weiterhin ~8 Min.

Cache-Mounts sind die zweite Verteidigungslinie. Layer-Cache intakt = am schnellsten; sonst Mount als Puffer.

Hinweise

  1. Begrenzte Aufbewahrung: BuildKit räumt Cache älter als 2 Tage und größer als 512 MB auf. In CI/CD ggf. Strategie anpassen.

  2. Nicht überall nötig: Wenige Abhängigkeiten – Unterschied oft gering.

  3. Pfade prüfen: Cache-Verzeichnis je Paketmanager unterschiedlich – Dokumentation konsultieren.

Fazit

Kern in drei Schritten:

Sofort: .dockerignore im Projektroot – node_modules, .git, Tests ausschließen. Unter 5 Minuten, Build-Context 90 %+ kleiner.

Heute: Dockerfile-Reihenfolge – Abhängigkeitsdateien COPY, RUN install, dann Quellcode. Nachfolgende Builds von 10 Minuten auf 30 Sekunden.

Bei Bedarf: BuildKit-Cache-Mounts bei vielen, häufig wechselnden Abhängigkeiten – Rettung bei Layer-Cache-Ausfall.

Mein Projekt nach diesen drei Schritten: 10 Minuten → 30 Sekunden, 1,2 GB → 680 MB – exzellentes Aufwand-Nutzen-Verhältnis.

Build zu langsam? Diesen Leitfaden ausprobieren – im Kommentar gern melden, wie viel schneller es wurde.

Docker-Build-Beschleunigung – vollständiger Optimierungsablauf

Build-Zeit von 10 Minuten auf 30 Sekunden – Layer-Cache, .dockerignore und Dockerfile-Optimierung

Estimated time: PT30M

  1. 1

    Step 1: Ursachen langsamer Builds: Build-Context und Layer-Cache

    Build-Context:
  2. 2

    Step 2: Tipp 1: .dockerignore für kleineren Build-Context

    Sofort: .dockerignore im Projektroot, node_modules, .git, Tests ausschließen – unter 5 Minuten, Context 90 %+ kleiner.
  3. 3

    Step 3: Tipp 2: Dockerfile-Reihenfolge optimieren

    Heute: Abhängigkeitsdateien zuerst COPY, dann RUN install, zuletzt Quellcode – nachfolgende Builds von 10 Minuten auf 30 Sekunden.
  4. 4

    Step 4: Tipp 3: BuildKit-Cache-Mounts

    Bei vielen Abhängigkeiten: BuildKit-Cache-Mounts – Rettung bei Layer-Cache-Ausfall.

FAQ

Warum ist der Docker-Build so langsam?
Build-Context-Problem:
• Bei docker build . packt Docker zuerst alle Dateien im .-Verzeichnis und sendet sie an den Docker-Daemon
• Dazu gehören node_modules, .git und Testdaten
• Bei Frontend-Projekten kann der Build-Context 800 MB groß sein – allein die Übertragung dauert 2–3 Minuten
• Im Image werden oft weniger als 10 MB Quellcode benötigt
• Wie ein Buch per Kurier schicken und dabei das ganze Regal mitpacken

Kaskade bei Cache-Invalidierung:
• Docker-Images sind geschichtet – jede Dockerfile-Anweisung (FROM, RUN, COPY) erzeugt eine Layer
• Beim Build prüft Docker jede Layer auf Cache; bei identischer Anweisung und unveränderten Dateien wird der Cache genutzt
• Sobald eine Layer invalidiert ist, müssen alle folgenden neu gebaut werden – wie Dominosteine

Typischer Fehler: FROM node:18, COPY . /app, WORKDIR /app, RUN npm install – bei jeder Code-Änderung läuft npm install erneut.
Wie konfiguriert man .dockerignore, um den Build-Context zu verkleinern?
Sofort umsetzen: .dockerignore im Projektroot anlegen und node_modules, .git, Testdateien ausschließen – dauert unter 5 Minuten, Build-Context schrumpft um 90 %+.

Beispiel .dockerignore:
• node_modules (Abhängigkeiten)
• .git (Git-Historie)
• *.log (Logdateien)
• .env (Umgebungsvariablen)
• dist (Build-Artefakte)
• test (Testdateien)
• *.md (Dokumentation)

Ergebnis: Build-Context von 800 MB auf unter 10 MB, Übertragung von 2–3 Minuten auf wenige Sekunden.
Wie optimiert man die Reihenfolge der Dockerfile-Anweisungen?
Heute anpassen: Abhängigkeitsdateien zuerst COPY, dann RUN install, zuletzt Quellcode – nachfolgende Builds von 10 Minuten auf 30 Sekunden.

Prinzip:
• Selten geänderte Anweisungen nach oben (FROM, System- und App-Abhängigkeiten)
• Häufig geänderte nach unten (Quellcode COPY)

Vorher:
• FROM node:18
• COPY . /app
• WORKDIR /app
• RUN npm install
• Jede Code-Änderung triggert npm install neu

Nachher:
• FROM node:18
• WORKDIR /app
• COPY package*.json ./
• RUN npm install
• COPY . .
• npm install nur bei package.json-Änderung, Code-Änderungen invalidieren die Abhängigkeits-Layer nicht
Wie nutzt man BuildKit-Cache-Mounts?
Bei vielen, häufig aktualisierten Abhängigkeiten: BuildKit-Cache-Mounts – Rettung, wenn Layer-Cache ausfällt.

BuildKit-Cache-Mount:
• --mount=type=cache für Cache-Verzeichnisse
• npm-Cache auf dem Host persistieren
• Beim nächsten Build direkt nutzen – oft 10x schneller

Beispiele:
• npm: RUN --mount=type=cache,target=/root/.npm npm install
• yarn: RUN --mount=type=cache,target=/root/.yarn yarn install --frozen-lockfile
• pip: RUN --mount=type=cache,target=/root/.cache/pip pip install -r requirements.txt
• apt: RUN --mount=type=cache,target=/var/cache/apt,sharing=locked apt-get update && apt-get install -y gcc

Bei apt sharing=locked setzen – apt braucht exklusiven Cache-Zugriff, vermeidet Konflikte bei parallelen Builds.
Welche Wirkung hat die Docker-Build-Optimierung?
Ergebnisse:
• Build-Zeit von 10 Minuten auf 30 Sekunden (20-fach)
• Image-Größe von 1,2 GB auf 680 MB
• Build-Context von 800 MB auf unter 10 MB
• Übertragung von 2–3 Minuten auf Sekunden

In meinem Projekt nach diesen drei Schritten: 10 Minuten → 30 Sekunden, 1,2 GB → 680 MB – eines der besten Aufwand-Nutzen-Verhältnisse.

Kern in drei Schritten:
• Sofort: .dockerignore im Projektroot
• Heute: Dockerfile-Reihenfolge anpassen
• Bei Bedarf: BuildKit-Cache-Mounts bei vielen Abhängigkeiten

7 Min. Lesezeit · Veröffentlicht am: 17. Dez. 2025 · Aktualisiert am: 14. Juli 2026

Ähnliche Beiträge

Kommentare

Melde dich mit GitHub an, um einen Kommentar zu hinterlassen

Easton BlogEaston Blog