Eine Schritt-für-Schritt-Anleitung zum Erstellen von mehrstufigen Docker-Bildern
Was sind Multi-Stage Docker Builds?
Mehrstufige Docker-Builds sind eine Funktion, die in Docker 17.05 eingeführt wurde und die es Ihnen ermöglicht, mehrere -Anweisungen innerhalb einer einzelnen Dockerdatei zu verwenden. Jede -Anweisung beginnt eine neue Phase, die ein eigenes Basisbild, Abhängigkeiten und Befehle haben kann. Artefakte können selektiv von einer Stufe zur anderen kopiert werden, während das endgültige Bild nur das behält, was für den Betrieb der Anwendung unbedingt erforderlich ist. Dieser Ansatz eliminiert die Notwendigkeit, Dockerfiles manuell zu verketten oder sich auf komplexe Build-Skripte zu verlassen, und es reduziert die Bildgröße drastisch, indem Compiler, Entwicklungsbibliotheken und Zwischendateien ausgeschlossen werden.
Die Kernidee ist, die Build-Umgebung von der Laufzeitumgebung zu trennen. In einem typischen einstufigen Build installiert ein Entwickler alle Build-Tools (z. B. Compiler, Paketmanager, Test-Frameworks) in demselben Bild, das für die Produktion verwendet wird. Das bläht das Bild und vergrößert die Angriffsfläche. Mehrstufige Builds lösen dies, indem sie ein dickes, funktionsreiches Bild für die Kompilierung verwenden und dann nur die resultierenden Artefakte in ein minimales Laufzeitbild wie oder kopieren.
Vorteile von Multi-Stage Builds
Die Vorteile der Einführung von mehrstufigen Builds gehen über die einfache Größenreduzierung hinaus und haben tiefgreifende Auswirkungen auf Sicherheit, Wartbarkeit und Bereitstellungsgeschwindigkeit.
1. Reduzierte Bildgröße
Durch das Verwerfen von Build-Zeit-Abhängigkeiten verkleinern mehrstufige Builds oft Bilder um 50% bis 90%. Zum Beispiel kann eine Node.js-Anwendung, die mit dem vollständigen -Bild (über 300 MB) erstellt wurde, auf unter 20 MB reduziert werden, indem nur der gebaute -Ordner in eine -Basis kopiert wird. Diese Speichereinsparungen führen direkt zu schnelleren Pull-Zeiten, weniger Netzwerkbandbreite und niedrigeren Registrierungskosten.
2. Verbesserte Sicherheit
Jedes installierte Paket oder Tool in einem Container-Image ist eine potenzielle Sicherheitslücke. Mehrstufige Builds ermöglichen es Ihnen, Compiler, Debugger und Entwicklungsbibliotheken vom endgültigen Bild auszuschließen, wodurch die Angriffsfläche erheblich reduziert wird. Sie können sogar Bilder wie oder für die Laufzeitphase verwenden, die nur das absolute Minimum enthalten, um die Anwendungsbinär auszuführen.
3. Rationalisierter Bauprozess
Alle Build-Schritte werden in einem einzigen Dockerfile definiert, wodurch der Prozess in sich geschlossen und leicht zu versionieren ist. CI/CD-Pipelines profitieren von einem einzigen Einstiegspunkt: dem Dockerfile. Es besteht keine Notwendigkeit, separate Build-Scripts oder manuelle Bereinigungsschritte zu pflegen.
4. Verbesserte Reproduzierbarkeit und Kohärenz
Da der gesamte Build in einem Dockerfile erfasst wird, kann jeder Entwickler oder jedes System die gleichen Schichten reproduzieren. Die Verwendung spezifischer Versions-Tags für Basisbilder garantiert weiterhin konsistente Builds in allen Umgebungen.
Erstellen eines Multi-Stage-Dockerfiles: Schritt für Schritt
Diese Lösung umfasst die Erstellung einer produktionsbereiten mehrstufigen Dockerfile für eine Node.js- und React-Anwendung.
1. Planen Sie Ihre Etappen
Vor dem Schreiben von Code sollten Sie die benötigten Stufen abbilden.
- Builder stage – installiert alle Build-Tools, installiert Abhängigkeiten und führt den Build-Befehl aus.
- Runtime stage – verwendet ein minimales Basisbild, kopiert nur die gebauten Artefakte aus der Builder-Phase und definiert das Laufzeitverhalten.
Bei komplexen Projekten können Sie Zwischenstufen für Tests, statische Analysen oder die Komprimierung von Assets hinzufügen.
2. Schreibe die Builder Stage
Beginnen Sie mit einem Basisbild, das die erforderliche Toolchain enthält. Verwenden Sie benannte Stufen mit , um sie später zu verweisen.
FROM node:14-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
COPY . .
RUN npm run build
Schlüsselpunkte:
- Verwenden Sie für eine deterministische, schnellere Abhängigkeitsinstallation.
- Halten Sie Befehle als separate Schichten, wenn möglich, um Caching zu nutzen.
- Installieren Sie Build-Tools (wie TypeScript, Webpack) nur in dieser Phase.
3. Hinzufügen einer Zwischentestphase (optional)
Um die Codequalität zu erzwingen, fügen Sie eine Stufe hinzu, die Tests ausführt. Diese Stufe kann das Builder-Image wiederverwenden oder zusätzliche Werkzeuge installieren. Da es sich nicht um die letzte Stufe handelt, sind Testfehler im endgültigen Bild nicht vorhanden.
FROM builder AS test
RUN npm run test
Sie können diese Phase in Ihrer CI-Pipeline mit ausführen, um Fehler frühzeitig zu erkennen, ohne das gesamte endgültige Bild zu erstellen.
4. Laufzeitphase definieren
Für eine React-Anwendung kann das Laufzeit-Image ein Nginx-Server sein. Für eine Backend-API kann es ein distroless Basis-Image oder ein minimal Alpine mit der Node.js Laufzeit sein. Kopieren Sie nur die wesentlichen Artefakte mit .
FROM nginx:alpine
COPY --from=builder /app/build /usr/share/nginx/html
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]
Wenn Sie die Node.js-Laufzeit benötigen, vermeiden Sie das Kopieren von aus dem Builder; stattdessen installieren Sie Produktionsabhängigkeiten in der Laufzeitphase neu:
FROM node:14-alpine AS runtime
WORKDIR /app
COPY --from=builder /app/dist ./dist
RUN npm ci --only=production
EXPOSE 3000
CMD ["node", "dist/server.js"]
5. Erstellen und Testen des Bildes
Erstellen Sie das endgültige Bild mit dem Standardbefehl:
docker build -t myapp:latest .
Um die Größe zu überprüfen, führen Sie aus und vergleichen Sie sie mit einem einstufigen Build. Führen Sie den Container aus und bestätigen Sie, dass die Anwendung korrekt reagiert:
docker run -d -p 8080:80 myapp:latest
curl http://localhost:8080
Best Practices für Multi-Stage Builds
- Verwende spezifische Basis-Bild-Tags – vermeide , um Überraschungen zu vermeiden.
- – Kopieren und vor dem Rest des Quellcodes, so dass die Schicht nur dann ungültig wird, wenn sich Abhängigkeiten ändern.
- Leverage buildKit – aktivieren Sie BuildKit mit für schnellere Builds, Inline-Caching und bessere Parallelität.
- Erstellen Sie mehrere Endphasen für verschiedene Umgebungen – zum Beispiel eine Entwicklungsphase mit Debugging-Tools und eine Produktionsphase mit einem gehärteten Basisbild.
- Verwende für Entwicklungs-Builds – in der Entwicklung kannst du bei anhalten, um Live-Reload und Quellkarten zu erhalten, und dann mit für die Produktion neu erstellen.
- Behalte Geheimnisse aus Bildern – verwende das Flag von Docker BuildKit , wenn du während des Builds Anmeldeinformationen übergeben musst; füge sie niemals in das endgültige Bild ein.
Gemeinsame Muster und Anwendungsfälle
Kompilierte Sprachen (Go, Rust, C++)
Für statische Binärdateien kann die Laufzeitstufe (leeres Basisbild) verwenden, wobei nur die Binärdatei und möglicherweise eine Konfigurationsdatei kopiert werden.
FROM golang:1.20-alpine AS builder
WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 go build -o myapp .
FROM scratch
COPY --from=builder /app/myapp /myapp
ENTRYPOINT ["/myapp"]
Python-Anwendungen
Verwenden Sie eine Builder-Stufe mit , um Abhängigkeiten zu installieren und alle C-Erweiterungen zu kompilieren, und kopieren Sie dann nur die installierten Pakete in eine Laufzeit-Stufe:
FROM python:3.11-slim AS builder
WORKDIR /app
COPY requirements.txt .
RUN pip install --user -r requirements.txt
COPY . .
FROM python:3.11-slim
COPY --from=builder /root/.local /root/.local
COPY --from=builder /app /app
ENV PATH=/root/.local/bin:$PATH
CMD ["python", "app.py"]
Frontend mit API Server
Erstellen Sie sowohl Frontend als auch Backend in einem Dockerfile. Verwenden Sie separate Builder-Stufen für jedes und kopieren Sie dann beide Artefakte in ein einziges Laufzeitbild:
FROM node:14-alpine AS frontend-builder
WORKDIR /app
COPY frontend/package*.json ./
RUN npm ci
COPY frontend/ .
RUN npm run build
FROM node:14-alpine AS api-builder
WORKDIR /app
COPY api/package*.json ./
RUN npm ci
COPY api/ .
RUN npm run build
FROM node:14-alpine
WORKDIR /app
COPY --from=frontend-builder /app/build ./public
COPY --from=api-builder /app/dist ./dist
RUN npm ci --only=production
EXPOSE 3000
CMD ["node", "dist/server.js"]
Fehlerbehebung bei Multi-Stage Builds
- Layer Caching funktioniert nicht – stellen Sie sicher, befiehlt die Reihenfolge der Abhängigkeiten vor dem Quellcode.
- Artefakt nicht gefunden – verifizieren Sie die Pfade in der -Anweisung. Die Builder-Stufe muss an dem angegebenen Ort Ausgabe erzeugen.
- Secret Leak – Kopiere niemals ganze Verzeichnisse, die oder enthalten könnten.
- Große endgültige Bilder trotz mehrstufiger – prüfen Sie, ob Sie versehentlich oder die gesamte Quelle kopieren.
Schlussfolgerung
Mehrstufige Docker-Builds sind ein Eckpfeiler der modernen Containerisierung. Sie ermöglichen es Ihnen, schlanke, sichere Bilder zu versenden, während der Build-Prozess einfach und in einem einzigen Dockerfile dokumentiert bleibt. Durch die Trennung von Build- und Laufzeitproblemen können Sie die Bildgröße drastisch reduzieren, die Sicherheit verbessern und CI/CD-Pipelines optimieren. Die hier gezeigten Techniken gelten für fast jeden Stack - egal ob Sie Node.js, Go, Python, Java oder Frontend-Anwendungen erstellen. Beginnen Sie mit der Konvertierung Ihrer vorhandenen Dockerfiles in mehrstufige Builds, und Sie werden sofort Verbesserungen bei der Build-Geschwindigkeit, der Bereitstellungseffizienz und den gesamten Infrastrukturkosten sehen.
Weitere Einzelheiten finden Sie in der offiziellen Docker-Dokumentation für mehrstufige Builds und dem Dockerfile Best Practice Guide Reale Beispiele sind auch in der Docker Library Dokumentation verfügbar.