Images versionieren, aufraeumen, absichern
Die in GitLab eingebaute Container Registry spart einen externen Registry-Dienst, verlangt aber eine bewusste Tag-Strategie, automatische Cleanup-Policies gegen ausufernden Speicherverbrauch und klar definierte Zugriffsrechte pro Projekt und Gruppe.
Inhaltsverzeichnis
- 1. Ein Registry-Dienst, der bereits im Projekt steckt
- 2. Eine Tag-Strategie, die Nachvollziehbarkeit sichert
- 3. Cleanup-Policies: automatisches Aufraeumen statt manueller Pflege
- 4. Manuelles und geplantes Aufraeumen fuer Sonderfaelle
- 5. Zugriffsrechte auf Projektebene steuern
- 6. Zugriffsrechte auf Gruppenebene und ueber Projektgrenzen hinweg
- 7. Speicherverbrauch beobachten, bevor er zum Problem wird
- 8. Image-Groesse optimieren, bevor Cleanup ueberhaupt noetig wird
- 9. Haeufige Fehler beim Betrieb der Container Registry
- 10. Zusammenfassung
- 11. FAQ
1. Ein Registry-Dienst, der bereits im Projekt steckt
Jedes GitLab-Projekt bringt automatisch eine eigene Container Registry mit, sobald die Funktion in den Projekteinstellungen aktiviert ist. Das erspart Teams den Betrieb eines separaten Registry-Dienstes wie einer selbst gehosteten Harbor-Instanz oder eines externen Cloud-Registry-Abos, samt der dazugehoerigen Authentifizierung, Netzwerkkonfiguration und Wartung. Der CI-Runner ist ueber die vordefinierte Variable CI_REGISTRY bereits mit den noetigen Zugangsdaten ausgestattet, sodass ein Image direkt aus der Pipeline heraus gebaut und gepusht werden kann, ohne zusaetzliche Secrets manuell zu hinterlegen.
Diese enge Integration ist zugleich der groesste Vorteil und die groesste Gefahr: Weil das Pushen eines Images so reibungslos funktioniert, sammeln sich ueber Monate hinweg oft hunderte oder tausende Tags an, von denen die allermeisten nie wieder benoetigt werden. Ohne eine bewusste Strategie fuer Versionierung und Aufraeumen waechst die Registry unkontrolliert, was sowohl Speicherkosten als auch die Uebersichtlichkeit beim Suchen nach einem konkreten Image erheblich beeintraechtigt.
2. Eine Tag-Strategie, die Nachvollziehbarkeit sichert
Der haeufigste Anfaengerfehler ist, jedes Image nur mit latest zu taggen. Das funktioniert kurzfristig, macht es aber unmoeglich nachzuvollziehen, welcher Code-Stand tatsaechlich in einem laufenden Container steckt, sobald mehrfach neu gepusht wurde. Bewaehrt hat sich stattdessen eine Kombination aus mehreren Tags pro Build: ein unveraenderlicher Tag basierend auf der Commit-SHA fuer exakte Nachvollziehbarkeit, ein semantischer Versions-Tag fuer Releases und optional ein latest-Tag ausschliesslich fuer den jeweils neuesten Stand des Hauptbranches.
Wichtig ist, die Commit-SHA-Tags als die eigentliche Wahrheit zu behandeln und semantische oder latest-Tags lediglich als zusaetzliche, bewegliche Zeiger darauf zu verstehen. So laesst sich im Ernstfall exakt nachvollziehen, welches Image zu welchem Commit gehoert, was insbesondere bei Rollbacks unverzichtbar ist, wenn schnell auf einen bestimmten, bekannt funktionierenden Stand zurueckgewechselt werden muss.
# .gitlab-ci.yml
build-image:
stage: build
image: docker:27
services:
- docker:27-dind
script:
- docker login -u "$CI_REGISTRY_USER" -p "$CI_REGISTRY_PASSWORD" "$CI_REGISTRY"
- docker build
-t "$CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA"
-t "$CI_REGISTRY_IMAGE:$CI_COMMIT_REF_SLUG"
.
- docker push "$CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA"
- docker push "$CI_REGISTRY_IMAGE:$CI_COMMIT_REF_SLUG"
- |
if [ "$CI_COMMIT_TAG" ]; then
docker tag "$CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA" "$CI_REGISTRY_IMAGE:$CI_COMMIT_TAG"
docker push "$CI_REGISTRY_IMAGE:$CI_COMMIT_TAG"
docker tag "$CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA" "$CI_REGISTRY_IMAGE:latest"
docker push "$CI_REGISTRY_IMAGE:latest"
fi
3. Cleanup-Policies: automatisches Aufraeumen statt manueller Pflege
GitLab bietet unter Settings > Packages and registries > Clean up image tags eine eingebaute Cleanup Policy, die regelmaessig laeuft (der genaue Zeitpunkt wird von GitLab intern geplant, nicht vom Nutzer festgelegt) und Tags anhand konfigurierbarer Regeln entfernt. Die zentralen Stellschrauben sind ein Regex-Muster fuer Tags, die aufbewahrt werden sollen, ein zweites Regex-Muster fuer Tags, die explizit von der Loeschung ausgenommen sind, sowie eine Regel, wie viele der neuesten Tags pro Muster in jedem Fall erhalten bleiben.
Eine sinnvolle Grundkonfiguration behaelt alle Tags, die wie eine semantische Version aussehen (^v?\d+\.\d+\.\d+$), unbegrenzt, waehrend Commit-SHA-Tags nur die letzten zwanzig bis dreissig Exemplare behalten und alle uebrigen, ungetaggten oder aelteren Tags nach einer definierten Frist geloescht werden. Diese Kombination stellt sicher, dass Release-Versionen dauerhaft nachvollziehbar bleiben, waehrend der Speicherverbrauch durch die deutlich zahlreicheren Zwischenbuild-Tags begrenzt bleibt.
# Cleanup-Policy per API konfigurieren
curl --request PUT --header "PRIVATE-TOKEN: <token>" \
"https://gitlab.example.com/api/v4/projects/123" \
--data-urlencode "container_expiration_policy_attributes[enabled]=true" \
--data-urlencode "container_expiration_policy_attributes[cadence]=1d" \
--data-urlencode "container_expiration_policy_attributes[keep_n]=25" \
--data-urlencode "container_expiration_policy_attributes[older_than]=30d" \
--data-urlencode "container_expiration_policy_attributes[name_regex_keep]=^v?[0-9]+\.[0-9]+\.[0-9]+$" \
--data-urlencode "container_expiration_policy_attributes[name_regex]=.*"
4. Manuelles und geplantes Aufraeumen fuer Sonderfaelle
Die automatische Cleanup-Policy deckt den Regelfall ab, aber manche Situationen erfordern gezieltes manuelles Eingreifen, etwa wenn ein grosser Batch fehlerhafter Images aus einer defekten Pipeline-Konfiguration entfernt werden muss, bevor die naechste planmaessige Bereinigung laeuft. Ueber die Container Registry API lassen sich Tags gezielt per Skript loeschen, was sich gut in ein einmaliges Aufraeum-Skript oder einen dedizierten, manuell ausgeloesten CI-Job packen laesst.
Fuer wiederkehrende Sonderfaelle, etwa das woechentliche Entfernen aller Images von geloeschten Feature-Branches, bietet sich ein geplanter Pipeline-Job (Scheduled Pipeline) an, der ueber die API zunaechst alle noch existierenden Branches abfragt und anschliessend alle Registry-Tags loescht, die keinem aktiven Branch mehr zugeordnet werden koennen. Das ergaenzt die allgemeine Cleanup-Policy um eine gezielte, branchbezogene Bereinigungslogik.
# Gezieltes Loeschen eines einzelnen Tags per API
curl --request DELETE --header "PRIVATE-TOKEN: <token>" \
"https://gitlab.example.com/api/v4/projects/123/registry/repositories/456/tags/feature-alter-branch"
# Bulk-Loeschung mit Namensfilter (asynchron, gibt eine Job-ID zurueck)
curl --request DELETE --header "PRIVATE-TOKEN: <token>" \
"https://gitlab.example.com/api/v4/projects/123/registry/repositories/456/tags" \
--data-urlencode "name_regex_delete=^feature-.*$" \
--data-urlencode "keep_n=0"
5. Zugriffsrechte auf Projektebene steuern
Standardmaessig folgt der Zugriff auf die Container Registry der allgemeinen Sichtbarkeit des Projekts: Bei einem privaten Projekt benoetigen Nutzer mindestens Reporter-Zugriff, um Images zu pullen, und mindestens Developer-Zugriff, um zu pushen. Diese Kopplung ist fuer die meisten Teams sinnvoll, laesst sich aber unter Settings > General > Visibility, project features, permissions feiner steuern, etwa um die Registry unabhaengig von der allgemeinen Projektsichtbarkeit ausschliesslich fuer Projektmitglieder freizugeben.
Fuer CI/CD-Pipelines, die auf Images aus einem anderen Projekt zugreifen muessen (etwa ein zentrales Basis-Image-Repository), bietet sich ein Deploy Token mit dem Scope read_registry an, statt persoenliche Zugangsdaten in der Pipeline-Konfiguration zu hinterlegen. Deploy Tokens sind projektgebunden, laufzeitbeschraenkt konfigurierbar und koennen unabhaengig vom Konto einer einzelnen Person widerrufen werden, was ein deutlich saubereres Sicherheitsmodell als geteilte persoenliche Tokens ergibt.
6. Zugriffsrechte auf Gruppenebene und ueber Projektgrenzen hinweg
Fuer Organisationen mit mehreren zusammenhaengenden Projekten, etwa ein Basis-Image-Projekt und mehrere darauf aufbauende Anwendungsprojekte, lohnt sich eine gruppenweite Betrachtung. GitLab erlaubt es, ueber Group Deploy Tokens Zugriff auf die Registries aller Projekte innerhalb einer Gruppe zu gewaehren, was die Verwaltung einzelner Projekt-Tokens erheblich reduziert, wenn ein CI-Job aus Projekt B regelmaessig ein Basis-Image aus Projekt A ziehen muss.
Zusaetzlich unterstuetzt GitLab die CI_DEPENDENCY_PROXY-Variablen fuer den Dependency Proxy, der externe Registries wie Docker Hub gruppenweit cached und damit sowohl Rate-Limit-Probleme mit oeffentlichen Registries reduziert als auch eine zentrale Kontrolle darueber ermoeglicht, welche externen Basis-Images ueberhaupt in der Organisation verwendet werden duerfen. Diese Kombination aus eigener Registry und Dependency Proxy deckt die meisten Container-bezogenen Zugriffsszenarien ab, ohne einen externen Dienst zu benoetigen.
# Basis-Image ueber den Dependency Proxy statt direkt von Docker Hub ziehen
docker login -u "$CI_DEPENDENCY_PROXY_USER" \
-p "$CI_DEPENDENCY_PROXY_PASSWORD" \
"$CI_DEPENDENCY_PROXY_SERVER"
docker pull "$CI_DEPENDENCY_PROXY_GROUP_IMAGE_PREFIX/library/node:20-alpine"
7. Speicherverbrauch beobachten, bevor er zum Problem wird
Die Container Registry zaehlt bei GitLab.com direkt zum Speicherkontingent des Namespace, und auch bei selbst gehosteten Instanzen wirkt sich eine ungebremst wachsende Registry auf den Speicherplatz des Object-Storage-Backends aus. Unter Settings > Usage Quotas > Storage laesst sich der aktuelle Verbrauch der Registry getrennt von Repository- und Artefakt-Speicher einsehen, was fruehzeitig zeigt, ob die Cleanup-Policy tatsaechlich greift oder ob sich trotz aktivierter Regeln Tags unbemerkt ansammeln, etwa weil ein Regex-Muster versehentlich zu viele Tags von der Loeschung ausschliesst.
Ein regelmaessiger Blick auf diese Kennzahl, kombiniert mit einer kurzen Stichprobe der tatsaechlich vorhandenen Tags ueber die Registry-Oberflaeche, deckt Konfigurationsfehler meist innerhalb weniger Wochen auf, statt dass der Speicherverbrauch erst nach Monaten unkontrollierten Wachstums auffaellt. Besonders bei mehreren parallel aktiven Projekten mit eigener Registry lohnt sich ein monatlicher Sammel-Check aller Namespaces, um Ausreisser fruehzeitig zu identifizieren.
8. Image-Groesse optimieren, bevor Cleanup ueberhaupt noetig wird
Die effektivste Massnahme gegen eine ausufernde Registry setzt vor dem Aufraeumen an: kleinere Images verursachen weniger Speicherverbrauch pro Tag und werden schneller gepusht und gepullt. Multi-Stage-Builds trennen die Build-Umgebung mit Compiler, Composer-Cache und Dev-Abhaengigkeiten strikt vom schlanken Laufzeit-Image, sodass am Ende nur die tatsaechlich benoetigten Artefakte im finalen Layer landen. Fuer ein PHP-Projekt bedeutet das typischerweise, composer install --no-dev erst in der letzten Stage auszufuehren und den Vendor-Ordner aus einer vorherigen Build-Stage gezielt zu kopieren.
Zusaetzlich lohnt sich ein bewusster Umgang mit der Layer-Reihenfolge im Dockerfile: Selten aendernde Schichten wie das Betriebssystem-Basisimage und System-Pakete gehoeren an den Anfang, haeufig aendernder Anwendungscode ans Ende, damit der Docker-Build-Cache moeglichst viele Schichten wiederverwenden kann. Das reduziert nicht nur die Build-Zeit in der Pipeline, sondern haelt auch die Anzahl unterschiedlicher, tatsaechlich gespeicherter Layer-Kombinationen in der Registry kleiner.
9. Haeufige Fehler beim Betrieb der Container Registry
Ein wiederkehrender Fehler ist ein zu aggressives Regex-Muster in der Cleanup-Policy, das versehentlich auch semantische Versions-Tags erfasst und damit Release-Images loescht, die eigentlich dauerhaft erhalten bleiben sollten. Vor der produktiven Aktivierung einer neuen Cleanup-Regel empfiehlt sich daher ein Testlauf gegen eine kleine Stichprobe von Tags oder zumindest eine sorgfaeltige manuelle Pruefung des Regex-Musters gegen die tatsaechlich vorhandene Tag-Liste.
Ein zweiter haeufiger Fehler ist, persoenliche Zugangstoken statt Deploy Tokens fuer automatisierte Zugriffe zu verwenden. Verlaesst die zustaendige Person das Team oder wird ihr Konto deaktiviert, faellt der Zugriff fuer alle darauf angewiesenen Pipelines unerwartet aus. Die folgende Tabelle fasst die zentralen Mechanismen fuer Versionierung, Aufraeumen und Zugriffsschutz zusammen.
| Mechanismus | Zweck | Empfohlene Konfiguration | Ort in GitLab |
|---|---|---|---|
| Commit-SHA-Tag | Exakte Nachvollziehbarkeit jedes Builds | Immer zusaetzlich zu anderen Tags setzen | CI-Job (docker build/push) |
| Cleanup Policy | Automatisches Entfernen alter Tags | Semantische Versionen behalten, SHA-Tags begrenzen | Settings > Packages and registries |
| Deploy Token (read_registry) | Pipeline-Zugriff ohne persoenliches Konto | Fuer alle automatisierten Pulls verwenden | Settings > Repository > Deploy tokens |
| Group Deploy Token | Zugriff auf mehrere Projekt-Registries | Bei zentralem Basis-Image-Projekt | Gruppe > Settings > Deploy tokens |
| Dependency Proxy | Caching externer Images, Rate-Limit-Schutz | Fuer alle Docker-Hub-Basisimages | Gruppe > Packages > Dependency Proxy |
Mironsoft
CI/CD-Pipelines, Zero-Downtime-Deployments und Release-Automatisierung
Deployments, die ohne Ausfallzeit und ohne Nervenkitzel laufen?
Wir prüfen bestehende GitLab-Pipelines auf fragile Deployment-Schritte und fehlende Absicherung und bauen daraus einen Release-Prozess mit Zero-Downtime-Deployments, automatisierten Checks und einem Rollback, dem ihr im Ernstfall vertrauen könnt.
Pipeline-Review
Bestehende .gitlab-ci.yml auf Fragilität, fehlende Stages und Sicherheitslücken prüfen.
Zero-Downtime-Deployment
Symlink-Releases, Health-Checks und Rollback-Strategien für Magento-Shops aufbauen.
CI/CD-Automatisierung
Tests, Security-Scans und Deployments zu einer zuverlässigen Pipeline verbinden.
10. Zusammenfassung
Container Registry: Das Wichtigste auf einen Blick
Tag-Strategie
Commit-SHA als Wahrheit, semantische Versionen und latest als bewegliche Zeiger.
Cleanup Policy
Automatisches Aufraeumen mit Regex-Ausnahmen fuer Release-Versionen.
Deploy Tokens
Projektgebundener, widerrufbarer Zugriff statt persoenlicher Zugangsdaten.
Dependency Proxy
Externe Basis-Images gruppenweit cachen statt direkt von Docker Hub ziehen.