Speicherwachstum eigener Registries kontrollieren
docker system prune raeumt nur lokal auf, das Wachstum einer eigenen Docker Registry mit Tausenden gepushten Images bleibt davon unberuehrt. Registry-seitige Garbage Collection und durchdachte Retention-Policies sind der eigentliche Hebel gegen ausufernden Speicherverbrauch.
Inhaltsverzeichnis
- 1. Warum docker system prune hier nicht hilft
- 2. Wie eine Registry Images intern speichert
- 3. Garbage Collection bei Docker Distribution im Detail
- 4. GC-Performance und Laufzeit bei grossen Registries
- 5. Retention-Policies fuer alte Tags einrichten
- 6. Lifecycle-Policies bei Cloud-Registries
- 7. Der Sonderfall ungetaggter Manifeste
- 8. Speicherwachstum ueberwachen
- 9. Best Practices fuer nachhaltigen Registry-Betrieb
- 10. Zusammenfassung
- 11. FAQ
1. Warum docker system prune hier nicht hilft
Ein weit verbreitetes Missverstaendnis ist, docker system prune oder docker image prune wuerden auch den Speicherplatz auf dem Registry-Server reduzieren. Tatsaechlich wirken diese Befehle ausschliesslich lokal auf dem Rechner oder Runner, auf dem sie ausgefuehrt werden, und raeumen dort ungenutzte Images, gestoppte Container und verwaiste Layer auf. Der Speicher einer selbst betriebenen Registry, etwa auf Basis von Docker Distribution, Harbor oder GitLab Container Registry, bleibt davon vollstaendig unberuehrt, denn die Registry ist ein eigenstaendiger Dienst mit eigenem Speicher-Backend, meist einem Objektspeicher wie S3 oder einem lokalen Dateisystem.
In einer aktiven CI/CD-Pipeline mit taeglich mehreren Builds, jeweils versehen mit eindeutigen Git-SHA- oder Datums-Tags wie im vorherigen Artikel beschrieben, wird eine Registry ohne eigene Aufraeum-Strategie kontinuierlich groesser, selbst wenn die allermeisten dieser Images nach kurzer Zeit nie wieder abgerufen werden. Ohne Gegenmassnahme fuehrt das langfristig zu stark steigenden Speicherkosten, laengeren Backup-Zeiten und im Extremfall zu vollgelaufenen Storage-Volumes, die den Registry-Betrieb komplett zum Erliegen bringen koennen.
2. Wie eine Registry Images intern speichert
Eine Docker Registry speichert Images nicht als monolithische Dateien, sondern als Menge von Content-adressierten Blobs, die den einzelnen Layern und der Manifest-Datei entsprechen, plus einer Zuordnungstabelle, die Tags auf Manifest-Digests abbildet. Mehrere Tags oder sogar mehrere unterschiedliche Images koennen sich denselben Blob teilen, etwa eine gemeinsame Basis-Layer wie php:8.4-fpm-alpine, wodurch die Registry effizient nur einmal Speicherplatz fuer identische Inhalte belegt, unabhaengig davon, wie viele Tags letztlich darauf verweisen.
Genau diese Struktur macht das Loeschen kompliziert: Ein einfaches Entfernen eines Tags loescht in der Regel nur die Zuordnung im Manifest-Index, nicht aber die zugrunde liegenden Blobs, denn die Registry kann zum Zeitpunkt des Tag-Loeschens nicht ohne Weiteres wissen, ob ein anderer Tag oder ein anderes Image denselben Blob noch referenziert. Genau hier setzt die eigentliche Garbage Collection an, die in einem separaten Durchlauf alle noch referenzierten Blobs ermittelt und ausschliesslich die tatsaechlich verwaisten Blobs physisch von der Platte entfernt.
3. Garbage Collection bei Docker Distribution im Detail
Die freie Referenzimplementierung Docker Distribution, auf der viele selbst gehostete Registries basieren, bringt einen eingebauten Garbage-Collector mit, der ueber den Befehl registry garbage-collect innerhalb des Registry-Containers aufgerufen wird. Der Ablauf gliedert sich in zwei Phasen: Zunaechst wird ein Mark-Durchlauf ausgefuehrt, der ausgehend von allen noch existierenden Tags rekursiv alle referenzierten Manifest- und Layer-Digests markiert, danach ein Sweep-Durchlauf, der alle nicht markierten Blobs physisch loescht.
Wichtig ist, dass die Registry waehrend des Garbage-Collection-Laufs im schreibgeschuetzten Modus betrieben werden sollte, sofern nicht die neuere Version mit --dry-run und Delete-Marker-Unterstuetzung verwendet wird, da sonst parallel gepushte Images faelschlicherweise als verwaist markiert und geloescht werden koennten. In produktiven Setups wird der GC-Lauf daher meist in einem Wartungsfenster oder mit der neueren Delete-Marker-Strategie ausgefuehrt, die ein gefahrloses Loeschen auch bei laufendem Registry-Betrieb erlaubt.
# Garbage Collection bei Docker Distribution (klassischer Ablauf)
docker exec registry bin/registry garbage-collect \
--dry-run /etc/docker/registry/config.yml
# Nach Pruefung des Dry-Run tatsaechlich loeschen
docker exec registry bin/registry garbage-collect \
/etc/docker/registry/config.yml
4. GC-Performance und Laufzeit bei grossen Registries
Bei einer kleinen internen Registry mit wenigen hundert Images dauert ein Garbage-Collection-Lauf meist nur Sekunden bis wenige Minuten, doch bei grossen Registries mit Zehntausenden Repositories und Millionen Blobs kann der Mark-Durchlauf, der jedes Manifest einzeln aufloesen und rekursiv durchlaufen muss, mehrere Stunden in Anspruch nehmen. Wird die GC in dieser Zeit im schreibgeschuetzten Modus betrieben, bedeutet das faktisch einen mehrstuendigen Ausfall aller Push-Operationen, was fuer aktive CI/CD-Landschaften kaum akzeptabel ist.
Als Gegenmassnahme setzen groessere Deployments meist auf die Delete-Marker-Strategie, die Loeschungen zunaechst nur als Markierung vormerkt und den eigentlichen physischen Sweep in einem separaten, klar terminierten Wartungsfenster ausfuehrt, sowie auf ein Objektspeicher-Backend wie S3 statt eines lokalen Dateisystems, da Objektspeicher-APIs den parallelen, batch-weisen Zugriff auf Millionen Blobs deutlich effizienter unterstuetzen als ein einzelnes lokales Dateisystem mit entsprechend vielen Inodes.
5. Retention-Policies fuer alte Tags einrichten
Garbage Collection allein loescht nur bereits entfernte Tags physisch, sie entscheidet aber nicht, welche Tags ueberhaupt geloescht werden sollen. Diese Aufgabe uebernimmt eine Retention-Policy, die Regeln definiert wie: behalte die letzten zehn Tags pro Repository, loesche Tags aelter als neunzig Tage ausser den letzten fuenf SemVer-Releases, oder behalte alle Tags, die mit einem geschuetzten Praefix wie release- beginnen. Ohne eine solche Policy waechst die Menge an Tags unbegrenzt weiter, selbst wenn die zugrunde liegenden Blobs durch Deduplizierung teilweise geteilt werden.
In Registries wie Harbor oder GitLab Container Registry lassen sich Retention-Policies deklarativ ueber die Weboberflaeche oder eine YAML-Konfiguration definieren und laufen automatisch nach einem festen Zeitplan, meist taeglich oder woechentlich. Bei der reinen Docker-Distribution-Registry ohne Zusatzsoftware muss diese Logik hingegen selbst per Skript implementiert werden, das ueber die Registry-HTTP-API alle Tags eines Repositories abfragt, die Regeln anwendet und ueberzaehlige Tags gezielt loescht, bevor im Anschluss der eigentliche Garbage-Collection-Lauf gestartet wird.
# Harbor Tag-Retention-Regel (Beispiel-Konfiguration)
rules:
- repoMatches: "mironsoft/app"
tagMatches: "sha-*"
retention: "keep the most recently pushed 20 tags"
- repoMatches: "mironsoft/app"
tagMatches: "release-*"
retention: "keep forever"
6. Lifecycle-Policies bei Cloud-Registries
Cloud-Registries wie AWS ECR, Google Artifact Registry oder Azure Container Registry bringen eigene Lifecycle-Policy-Mechanismen mit, die konzeptionell dieselbe Aufgabe erfuellen wie die Retention-Policies bei Harbor, jedoch anbieterspezifisch konfiguriert werden. Bei ECR etwa definiert man eine Lifecycle-Policy als JSON-Regelwerk mit Prioritaeten, das zum Beispiel untagged Images nach einer bestimmten Anzahl Tage automatisch entfernt und die Anzahl gespeicherter Images pro Tag-Praefix begrenzt.
Der entscheidende Vorteil dieser verwalteten Loesungen ist, dass die eigentliche Garbage Collection, also das physische Freigeben des Speichers nach der Loeschregel-Anwendung, vom Cloud-Anbieter automatisch und ohne manuellen Wartungsaufwand durchgefuehrt wird. Bei selbst gehosteten Registries hingegen muss der Betreiber sowohl die Retention-Policy als auch den eigentlichen Garbage-Collection-Lauf selbst orchestrieren, meist per Cron-Job oder als periodischer CI-Pipeline-Schritt ausserhalb der eigentlichen Anwendungs-Pipelines.
# AWS ECR Lifecycle-Policy anwenden
aws ecr put-lifecycle-policy \
--repository-name mironsoft/app \
--lifecycle-policy-text '{
"rules": [{
"rulePriority": 1,
"description": "Untagged Images nach 7 Tagen entfernen",
"selection": {"tagStatus": "untagged", "countType": "sinceImagePushed", "countUnit": "days", "countNumber": 7},
"action": {"type": "expire"}
}]
}'
7. Der Sonderfall ungetaggter Manifeste
Ein haeufig uebersehenes Detail ist, dass beim Ueberschreiben eines Tags, etwa wenn app:buildcache bei jedem CI-Lauf neu gepusht wird wie im Artikel zu BuildKit Remote Cache beschrieben, das vorherige Manifest nicht automatisch geloescht wird, sondern als sogenanntes ungetaggtes oder dangling Manifest in der Registry verbleibt, bis es explizit entfernt oder von der Garbage Collection als verwaist erkannt wird. Bei haeufig wechselnden Cache-Tags oder CI-Tags kann sich dadurch eine erhebliche Menge ungetaggter, aber noch referenzierter oder erst nach GC tatsaechlich entfernbarer Manifeste ansammeln.
Fuer diesen Fall bieten die meisten Registry-APIs einen Endpunkt, um alle Manifest-Digests eines Repositories aufzulisten und mit der Menge der aktuell vergebenen Tags abzugleichen, sodass ungetaggte Manifeste gezielt identifiziert und vor dem naechsten Garbage-Collection-Lauf explizit per DELETE-Request entfernt werden koennen. Diese explizite Bereinigung beschleunigt den nachfolgenden GC-Lauf, da weniger Manifeste im Mark-Durchlauf betrachtet werden muessen, und macht das Speicherwachstum insgesamt vorhersagbarer.
8. Speicherwachstum ueberwachen
Ohne Monitoring bleibt Speicherwachstum meist unbemerkt, bis das zugrunde liegende Volume tatsaechlich vollgelaufen ist und Pushes fehlschlagen, was in einer aktiven CI/CD-Pipeline zu einem kompletten Ausfall aller Deploys fuehren kann. Sinnvoll ist daher, den belegten Speicherplatz des Registry-Backends regelmaessig zu erfassen, etwa ueber CloudWatch-Metriken bei ECR, den Storage-Endpunkt bei Harbor, oder ein eigenes Skript, das die Groesse des zugrunde liegenden S3-Buckets oder Dateisystems misst und als Metrik an Prometheus oder ein aequivalentes System exportiert.
Zusaetzlich zum reinen Speicherverbrauch lohnt sich ein Blick auf die Anzahl der Tags pro Repository und deren Zuwachsrate ueber die Zeit, denn ein ploetzlicher Anstieg kann auf eine fehlerhafte CI-Konfiguration hindeuten, etwa eine Pipeline, die versehentlich bei jedem Lauf einen neuen, eindeutigen Tag statt eines wiederverwendeten Cache-Tags erzeugt. Ein Alert bei ueberschrittenem Schwellenwert fuer Speicherverbrauch oder Tag-Anzahl verschafft dem Betriebsteam ausreichend Vorlaufzeit, um Retention-Policies nachzujustieren, bevor es kritisch wird.
9. Best Practices fuer nachhaltigen Registry-Betrieb
In der Praxis bewaehrt sich eine Kombination aus mehreren Massnahmen: eine klare Tagging-Strategie wie im vorherigen Artikel beschrieben, die zwischen dauerhaft zu behaltenden Release-Tags und kurzlebigen CI-Tags unterscheidet, eine automatisierte Retention-Policy, die kurzlebige Tags nach einer definierten Frist entfernt, ein regelmaessiger, idealerweise woechentlicher Garbage-Collection-Lauf im Wartungsfenster oder mit Delete-Marker-Unterstuetzung im laufenden Betrieb, sowie kontinuierliches Monitoring des Speicherverbrauchs mit Alarmierung bei Anomalien.
Wer diese Bausteine von Anfang an einplant, statt sie erst nach einem vollgelaufenen Storage-Volume nachzuruesten, vermeidet nicht nur unnoetige Betriebskosten, sondern auch akute Ausfaelle der Registry, die im schlimmsten Fall die gesamte Deployment-Faehigkeit eines Teams blockieren koennen. Die folgende Tabelle vergleicht die wichtigsten Aufraeum-Mechanismen und ihre jeweilige Rolle.
| Mechanismus | Was wird entfernt | Ausloeser | Typisches Werkzeug |
|---|---|---|---|
| docker system prune | Lokale, ungenutzte Images/Container | Manuell auf Host/Runner | Docker CLI |
| Retention-Policy | Ueberzaehlige oder veraltete Tags | Zeitplan oder Regelwerk | Harbor, ECR, GitLab Registry |
| Garbage Collection | Physisch verwaiste Blobs | Nach Tag-Loeschung, periodisch | registry garbage-collect, Cloud-GC |
| Manuelle Manifest-Bereinigung | Ungetaggte, dangling Manifeste | Ad-hoc oder vor GC-Lauf | Registry-HTTP-API, eigenes Skript |
Mironsoft
Container-Infrastruktur, CI-Pipelines und Deployment-Automatisierung
Docker-Setups, die im Team und in Produktion tragfähig bleiben?
Wir prüfen bestehende Dockerfiles und Compose-Stacks auf Sicherheitslücken, aufgeblähte Images und fragile Build-Pipelines und bauen daraus eine Container-Infrastruktur, die schnell baut, sicher läuft und im Team nachvollziehbar bleibt.
Dockerfile-Review
Multi-Stage-Builds, Layer-Caching und Image-Größe systematisch optimieren.
Security-Audit
Container-Isolation, Secrets-Handling und Image-Scanning gegen echte Angriffsflächen absichern.
CI/CD-Integration
Build-Pipelines, Registries und Deployment-Strategien für reproduzierbare Releases aufbauen.
10. Zusammenfassung
Registry Garbage Collection: Das Wichtigste auf einen Blick
Kernproblem
docker system prune wirkt nur lokal, das Speicherwachstum der Registry selbst bleibt davon unberuehrt.
Zwei Schritte noetig
Erst Retention-Policy entscheidet, welche Tags weg sollen, dann Garbage Collection gibt den Speicher physisch frei.
Cloud vs. selbst gehostet
Cloud-Registries automatisieren GC, selbst gehostete Registries brauchen eigene Cron-Jobs dafuer.
Monitoring wichtig
Speicherverbrauch und Tag-Anzahl ueberwachen, um Fehlkonfigurationen fruehzeitig zu erkennen.