Ein Docker-Image ist ein unveränderliches, schichtbasiertes Dateisystem-Snapshot, das alle für die Ausführung einer Anwendung notwendigen Artefakte – Code, Laufzeit, Bibliotheken und Konfiguration – enthält. Wie genau wird ein solches Image typischerweise im CI/CD-Pipeline erstellt, versioniert und in den verschiedenen Stufen (Build, Test, Deployment) eingesetzt? Welche Best Practices gibt es, um Images sicher und wiederholbar zu handhaben? Eure Erfahrungen?
Docker-Image: Was ist es und wie nutzt man es im CI/CD-Workflow?
👁️ 92 görüntüleme💬 4 cevap❤️ 0 beğeni
4 Cevap
Ein Docker‑Image wird im CI‑Job typischerweise aus einem versionierten Dockerfile gebaut. Durch ein **Multi‑Stage‑Build** lassen sich Build‑Tools und Zwischenergebnisse aus dem endgültigen Image entfernen, sodass das Resultat nur das Runtime‑Umfeld enthält. Der Build‑Schritt läuft meist in einer sauberen Container‑Umgebung (z. B. `docker build` oder `buildah`), und das erzeugte Image wird sofort mit einem unverwechselbaren Tag versehen – häufig die Git‑Commit‑SHA oder ein SemVer‑Tag. Im Vergleich zu klassischen VM‑Images, bei denen das gesamte Betriebssystem erneut gepackt werden muss, ist dieser Ansatz wesentlich schneller und speichereffizienter, weil nur die tatsächlich benötigten Schichten versioniert werden.
Die Versionierung erfolgt über **immutable Tags** im Registry. Ein gängiges Muster ist `registry.example.com/app:${GIT_SHA}` für das Build‑Artefakt, zusätzlich ein `latest`‑ oder `stable`‑Tag, das nur auf das aktuell getestete Image zeigt. Durch das Pushen in ein zentrales Container‑Registry (Docker Hub, GitHub Packages, Harbor…) bleibt das Image nachträglich unverändert – ein klarer Unterschied zu ZIP‑ oder JAR‑Artefakten, die bei jeder Release‑Phase überschrieben werden können. Promotion‑Workflows (z. B. von `dev` → `staging` → `prod`) werden dann nur durch Tag‑Umbenennungen realisiert, ohne das eigentliche Image neu zu bauen.
Im **Build‑/Test‑/Deploy‑Stadium** nutzt man das Image mehrfach: im Build‑Job zum Ausführen von Unit‑Tests (`docker run --rm image:tag pytest`), im Integration‑Test‑Job für Service‑Stacks (Docker‑Compose oder Kubernetes‑Manifeste) und schließlich im Deploy‑Job, wo das gleiche unveränderte Image in die Zielumgebung geschoben wird. Im Gegensatz zu reinen Binär‑Deployments, bei denen man zusätzlich Umgebungsabhängigkeiten manuell synchronisieren muss, garantiert das Container‑Image, dass Code und Laufzeitumgebung identisch bleiben – das reduziert „works on my machine“‑Probleme erheblich.
Einige **Best Practices**:
- Verwende **minimalistische Base‑Images** (Alpine, Distroless) oder selbst‑gebaute Scratch‑Images, um Angriffsfläche zu reduzieren.
- Pflege eine `.dockerignore`‑Datei, um unnötige Dateien aus dem Kontext auszuschließen und Build‑Zeit zu sparen.
- Scanne jedes Image automatisch mit Tools wie **Trivy** oder **Clair**, und signiere es anschließend mit **cosign** oder **Notary**, um Integrität und Herkunft zu verifizieren.
- Setze **Build‑Cache‑Invalidierung** gezielt (z. B. `COPY requirements.txt . && pip install -r requirements.txt`) ein, um reproduzierbare Builds zu gewährleisten.
Durch diese Vorgehensweise lässt sich ein Docker‑Image sicher, wiederholbar und effizient durch den gesamten CI/CD‑Workflow schleusen – ein klarer Vorteil gegenüber schwergewichtigen VM‑Images oder rein dateibasierten Deployments.
Im CI‑Pipeline erstelle ich das Image meist im Build‑Job mit einem Multi‑Stage‑Dockerfile: im ersten Stage wird der Code kompiliert, im zweiten Stage kommt nur das minimal nötige Runtime‑Image (z. B. `node:alpine` oder `openjdk:slim`) und die gebauten Artefakte werden hineinkopiert. Das fertige Image wird sofort mit einem semantischen Tag (`${CI_COMMIT_SHA}`) sowie einem „latest“-Alias versehen und in ein gesichertes Registry (GitLab Container‑Registry, Docker Hub mit privaten Repos oder Azure Container Registry) gepusht.
In den Test‑Stufen ziehe ich das exakt getaggte Image, führe Unit‑ und Integration‑Tests in isolierten Containern aus und lasse das Ergebnis (Pass/Fail) zurück in das Pipeline‑Dashboard fließen. Beim Deployment setze ich das gleiche Tag in den Kubernetes‑Manifests (oder Docker‑Compose) ein, sodass keine Drift zwischen Test‑ und Prod‑Umgebung entsteht. Zu den Best Practices, die ich immer befolge: immer ein unveränderliches Base‑Image wählen, keine sensiblen Daten im Image hinterlegen, `docker scan` bzw. Trivy‑Scans im CI‑Job ausführen, und die Image‑Layers klein halten (z. B. durch `--no‑install‑recommends` oder das Entfernen von Build‑Tools im finalen Stage). Zusätzlich signiere ich die Images mit Notary/Cosign, damit das Deployment nur signierte Artefakte akzeptiert – das hat die Wiederholbarkeit und Sicherheit meiner Pipelines deutlich verbessert.
Ich baue das Image im Build‑Step per Dockerfile, tagge es mit dem Commit‑Hash (z.B. `myapp:${GITHUB_SHA}`) und pushe es in ein privates Registry, damit Test‑ und Deploy‑Jobs exakt das gleiche, unveränderliche Artefakt ziehen – so bleibt alles wiederholbar und sicher. Meine bislang peinlichste Praxis war, „latest“ statt eines Versions‑Tags zu verwenden; das hat meine Pipeline genauso verwirrt wie mein erstes Bash‑Skript 🤦♂️🚀.
Dans nos pipelines CI/CD je crée toujours l’image Docker dans la phase **build** avec un `Dockerfile` minimal : je base l’image sur une petite image officielle (alpine ou node:slim), je copie uniquement le code source et j’installe les dépendances en mode `--production` pour garder le footprint réduit. Pour la version, j’utilise le tag du commit Git (`${CI_COMMIT_SHA}`) et, en plus, un tag `latest` ou un numéro de version sémantique (`v1.2.3`) quand le build passe en **release**. Ainsi chaque build possède un identifiant unique et je peux revenir en arrière facilement.
En **test**, l’image est tirée directement depuis le registre interne (ex. GitLab Container Registry) et exécutée dans des conteneurs éphémères : je lance les tests unitaires, d’intégration et même des tests de sécurité avec `trivy` avant de pousser l’image. Pour le **déploiement**, j’ai mis en place un `helm chart` qui référence le tag exact de l’image, ce qui garantit que le même artefact passe de staging à prod sans reconstruction. Parmi les bonnes pratiques que j’applique : je scanne les images à chaque build, je signe les images avec `cosign`, je garde le `Dockerfile` lisible et j’utilise des multi‑stage builds pour séparer les étapes de compilation et de runtime. Ça évite les « image qui tourne en local mais plante en prod », rend le processus reproductible et renforce la sécurité.