Layer 8Ehrlicher Tech-Support

Docker: Build-Cache greift nie – falsche Reihenfolge im Dockerfile

Jeder Docker-Build installiert alle Pakete neu? Dann steht COPY . . zu früh. So ordnest du die Anweisungen, damit der Build-Cache endlich greift.

Aufwand:
ca. 20 Min.
Voraussetzung:
Docker, Editor, Terminal

Das Symptom

Ein Tippfehler im Quellcode, ein neuer Build – und Docker installiert seelenruhig wieder sämtliche Abhängigkeiten. Jedes Mal. Aus einem Build, der zehn Sekunden dauern könnte, werden vier Minuten, in der CI-Pipeline entsprechend mehr. Im Build-Log taucht CACHED nur bei den ersten ein, zwei Zeilen auf, danach läuft alles wieder von vorn:

 => [2/5] WORKDIR /app                                       CACHED
 => [3/5] COPY . /app
 => [4/5] RUN npm ci                                         92.4s

Die „Layer 8“-Wahrheit

Der Cache ist nicht defekt, er wird bei jedem Build sauber und regelkonform weggeworfen – von dir. Ein Image besteht aus Layern, und die Docker-Dokumentation beschreibt die entscheidende Eigenschaft in einem Satz: Ändert sich ein Layer, sind alle Layer betroffen, die danach kommen. Ein COPY . . weit oben im Dockerfile zieht damit jede noch so kleine Codeänderung in einen Layer, auf dem alles Weitere aufbaut. Das nachfolgende npm ci oder pip install hat gar keine Chance, jemals aus dem Cache zu kommen – seine Grundlage ist bei jedem Build eine andere.

Die Reihenfolge im Dockerfile ist deshalb keine Stilfrage, sondern die eigentliche Konfiguration des Cache-Verhaltens. Die Dokumentation nennt die Regel unumwunden: Teure Schritte gehören nahe an den Anfang, häufig wechselnde ans Ende. Übersetzt in den Alltag heißt das: zuerst nur package.json und package-lock.json kopieren, dann installieren, und erst ganz zum Schluss den Quellcode, der sich mehrmals täglich ändert. Danach überlebt der Installations-Layer hunderte Commits, weil die Manifeste eben nicht bei jeder Codezeile mitwandern.

Dazu gehört die zweite, gern vergessene Hälfte: eine .dockerignore. Ohne sie schaufelt COPY . . auch .git, lokale node_modules und Logdateien in den Layer – Ballast, der den Kontext aufbläht und den Cache aus Gründen entwertet, die mit deiner Anwendung nichts zu tun haben. Beides zusammen, richtige Reihenfolge plus Ignore-Datei, verwandelt den minutenlangen Neubau in ein paar Sekunden. Es ist eine der wenigen Optimierungen, die nichts kostet außer fünf Minuten Umsortieren.

Die echte, verifizierte Lösung

1000 % faktenbasiert. Jeder Befehl ist real, jeder Pfad existiert. Schritt für Schritt:

  1. 1.

    Im Build-Log nachsehen, wo der Cache abreißt

    Nicht raten, ablesen. Bei einem zweiten Build markiert Docker jeden wiederverwendeten Schritt als CACHED. Der erste Schritt ohne diese Markierung ist die Bruchstelle – und wenn das direkt das COPY der Quellen ist, hast du die Diagnose bereits. Die ausführliche Ausgabe zeigt jeden Schritt einzeln.

    docker build --progress=plain -t meineapp .
    
  2. 2.

    Das Dockerfile umsortieren: erst das Manifest, dann installieren, dann der Rest

    Das ist der eigentliche Eingriff und meist eine Sache von drei Zeilen. Zuerst nur die Dateien der Paketverwaltung kopieren, dann die Abhängigkeiten installieren, erst danach den Quellcode. Solange sich die Manifestdateien nicht ändern, bleibt der teure Installationsschritt im Cache – egal wie oft du am Code schraubst.

    FROM node:lts-alpine
    WORKDIR /app
    
    # 1. Nur die Manifeste - ändern sich selten
    COPY package.json package-lock.json ./
    
    # 2. Teurer Schritt, bleibt dadurch im Cache
    RUN npm ci
    
    # 3. Der Quellcode - ändert sich ständig, deshalb ganz unten
    COPY . .
    CMD ["node", "server.js"]
    
  3. 3.

    Dasselbe Muster für Python, PHP oder Go anwenden

    Die Sprache ist austauschbar, das Prinzip nicht: Es gibt immer eine Handvoll Dateien, die die Abhängigkeiten beschreiben, und sehr viele, die den Code enthalten. Kopiere die erste Gruppe allein, installiere, kopiere dann die zweite. Bei Python sind das requirements.txt oder pyproject.toml, bei PHP composer.json und composer.lock, bei Go go.mod und go.sum.

    FROM python:3.13-slim
    WORKDIR /app
    
    COPY requirements.txt ./
    RUN pip install --no-cache-dir -r requirements.txt
    
    COPY . .
    CMD ["python", "app.py"]
    
  4. 4.

    Eine .dockerignore-Datei anlegen

    Ohne sie wandern .git, lokale Abhängigkeitsordner, Logs und Build-Artefakte in den Build-Kontext und damit in den COPY-Layer. Jede Änderung daran – auch eine völlig belanglose – kann den Cache ab dieser Stelle entwerten. Die Datei gehört ins Wurzelverzeichnis des Build-Kontexts und funktioniert wie eine .gitignore.

    .git
    node_modules
    __pycache__
    *.log
    dist
    
  5. 5.

    Den Erfolg messen, nicht vermuten

    Einmal bauen, eine einzelne Quelldatei ändern, erneut bauen. Beim zweiten Durchlauf müssen alle Schritte bis einschließlich der Installation als CACHED durchlaufen, und nur das COPY der Quellen sowie alles danach neu ausgeführt werden. Passiert das nicht, steht noch eine häufig wechselnde Anweisung zu weit oben.

    time docker build -t meineapp .
    touch src/index.js
    time docker build -t meineapp .
    
  6. 6.

    Aufräumen, wenn der Cache über die Jahre wuchert

    Ein greifender Cache belegt Platz – das ist der Preis und normalerweise ein guter. Wird er zu groß, entfernt der Builder-Aufräumbefehl die ungenutzten Einträge. Der nächste Build dauert dann einmalig wieder lange, danach greift der Cache wie zuvor.

    docker builder prune
    

Häufige Fragen (FAQ)

?Warum wirft eine winzige Codeänderung den ganzen Paket-Layer weg?

Weil Docker-Layer aufeinander aufbauen und nicht unabhängig voneinander sind. Die Docker-Dokumentation formuliert es knapp: Ändert sich ein Layer, sind alle Layer betroffen, die danach kommen. Steht COPY . . vor dem Installieren der Abhängigkeiten, dann ändert jede geänderte Quelldatei diesen Layer – und alles danach, also auch das Installieren, muss neu laufen. Der Cache ist nicht kaputt, er wird nur systematisch entwertet. Die Anweisung, die sich am häufigsten ändert, steht schlicht an der falschen Stelle.

?In welcher Reihenfolge gehören die Anweisungen dann?

Teuer und selten geändert nach oben, billig und ständig geändert nach unten. Die Docker-Dokumentation gibt genau diese Faustregel aus: Weil eine Änderung alle folgenden Schritte neu baut, sollen teure Schritte nahe an den Anfang des Dockerfiles und häufig wechselnde Schritte ans Ende. Für eine Anwendung heißt das konkret: erst die Dateien der Paketverwaltung kopieren, dann die Abhängigkeiten installieren, ganz zum Schluss den Quellcode, der sich am häufigsten ändert. Dann bleibt der Installations-Layer über hunderte Code-Commits hinweg im Cache.

?Wozu noch eine .dockerignore-Datei, wenn die Reihenfolge stimmt?

Weil der Build-Kontext alles ist, was der Client an den Builder schickt – bei einem unbedachten COPY . . landet der ganze Ordner in dem Layer, inklusive .git, lokaler node_modules, Log- und Build-Verzeichnissen. Die Docker-Dokumentation empfiehlt dafür eine .dockerignore-Datei im Wurzelverzeichnis des Build-Kontexts; sie funktioniert ähnlich wie eine .gitignore und schließt Dateien und Verzeichnisse aus dem Kontext aus. Der Effekt ist doppelt: Der Kontext wird kleiner, und lokale Nebensächlichkeiten können den Cache nicht mehr durch bloße Anwesenheit invalidieren.

Belege

Diese Seite ist gegen die folgenden Primärquellen geprüft – Hersteller, Standard oder Gesetzestext. Wer es nicht glaubt, muss es nicht: Da steht, wo es nachzulesen ist.

Zuletzt geprüft am . Dokumentation altert – ein Datum daneben ist der einzige ehrliche Umgang damit.

Auch zu finden unter

Verwandte Fehler

SWLästigCannot connect to the Docker daemon

Docker: Cannot connect to the Docker daemon

docker-Befehle scheitern mit 'Cannot connect to the Docker daemon'? Meist läuft der Dienst nicht oder Rechte fehlen. So läuft es wieder.

SWLästigRestarting (1)

Docker-Container startet endlos neu

Der Container hängt in einer Neustartschleife. Der Hauptprozess beendet sich – die Logs des abgestürzten Laufs verraten, warum.

SWLästigexec format error

Docker: exec format error

Der Container startet nicht und meldet „exec format error“? Fast immer passt die Prozessorarchitektur des Images nicht zum Host — ARM gegen x86.

SWLästigno space left on device

Docker: no space left on device beheben

Docker bricht mit 'no space left on device' ab, obwohl scheinbar Platz da ist? Alte Images, Container und Build-Cache fressen die Platte. So räumst du auf.

SWLästigpermission denied while trying to connect to the Docker daemon socket

Docker: permission denied am Docker-Socket

Docker läuft, aber ohne sudo kommt „permission denied“? Der Zugriff hängt an der Docker-Gruppe — und die hat sicherheitsrelevante Nebenwirkungen.