[{"data":1,"prerenderedAt":825},["ShallowReactive",2],{"/de-de/blog/using-gitlab-container-virtual-registry-with-docker-hardened-images":3,"navigation-de-de":39,"banner-de-de":463,"footer-de-de":473,"blog-post-authors-de-de-Tim Rizzi":709,"blog-related-posts-de-de-using-gitlab-container-virtual-registry-with-docker-hardened-images":723,"blog-promotions-de-de":763,"next-steps-de-de":815},{"id":4,"title":5,"authorSlugs":6,"authors":8,"body":10,"category":11,"categorySlug":11,"config":12,"content":16,"date":20,"description":17,"extension":25,"externalUrl":26,"featured":13,"heroImage":19,"isFeatured":13,"meta":27,"navigation":28,"path":29,"publishedDate":20,"rawbody":30,"seo":31,"slug":15,"stem":35,"tagSlugs":36,"tags":37,"template":14,"updatedDate":26,"__hash__":38},"blogPosts/de-de/blog/using-gitlab-container-virtual-registry-with-docker-hardened-images.yml","GitLab Container Virtual Registry mit Docker Hardened Images einrichten",[7],"tim-rizzi",[9],"Tim Rizzi","Wer im Plattformteam arbeitet, kennt solche Gespräche:\n\n*„Security sagt: Wir müssen gehärtete Base-Images verwenden.\"*\n\n*„Prima – wo trage ich jetzt die Credentials für noch eine weitere Registry ein?\"*\n\n*„Und wie stellen wir sicher, dass alle sie auch wirklich nutzen?\"*\n\nOder diese hier:\n\n*„Warum sind unsere Builds so langsam?\"*\n\n*„Wir pullen dasselbe 500-MB-Image in jedem einzelnen Job neu von Docker Hub.\"*\n\n*„Kann man die nicht irgendwo cachen?\"*\n\nIch arbeite bei GitLab an der [Container Virtual Registry](https://docs.gitlab.com/user/packages/virtual_registry/container/) – einem Pull-Through-Cache, der vor den vorgelagerten Registries sitzt: Docker Hub, dhi.io (Docker Hardened Images), MCR und Quay. Teams erhalten einen einzigen Endpunkt zum Pullen. Images werden beim ersten Abruf gecacht; alle nachfolgenden Pulls kommen aus dem Cache. Das Entwicklungsteam muss nicht wissen, aus welchem Upstream ein bestimmtes Image stammt.\n\nDieser Artikel zeigt die Einrichtung der Container Virtual Registry – mit Docker Hardened Images als konkretem Anwendungsfall, da diese Kombination für Teams mit Sicherheitsanforderungen besonders naheliegt.\n\n## Das Problem: Registry-Wildwuchs im Plattformteam\n\nDie Plattformteams, mit denen ich spreche, verwalten Container-Images über drei bis fünf Registries:\n\n- **Docker Hub** für die meisten Base-Images\n- **dhi.io** für Docker Hardened Images (sicherheitskritische Workloads)\n- **MCR** für .NET- und Azure-Tooling\n- **Quay.io** für das Red-Hat-Ökosystem\n- **Interne Registries** für proprietäre Images\n\nJede davon hat eigene Authentifizierungsmechanismen, unterschiedliche Netzwerklatenz und eine eigene Pfadstruktur für Images.\n\nCI/CD-Konfigurationen füllen sich mit registry-spezifischer Logik. Credential-Management wird zum eigenständigen Projekt. Und jeder Pipeline-Job lädt dieselben Base-Images erneut über das Netz – obwohl sie sich seit Wochen nicht geändert haben.\n\nContainer Virtual Registry konsolidiert das: eine Registry-URL, ein Authentifizierungsfluss über GitLab, gecachte Images aus GitLab-Infrastruktur statt wiederholter Internet-Traversierung.\n\n## Funktionsweise\n\nDas Modell ist geradlinig:\n\n```text\n\nPipeline ruft ab:\n  gitlab.com/virtual_registries/container/1000016/python:3.13\n\nVirtual Registry prüft:\n  1. Im Cache vorhanden? → Direkt zurückgeben\n  2. Nein? → Vom Upstream laden, cachen, zurückgeben\n\n\n```\n\nUpstreams werden in Prioritätsreihenfolge konfiguriert. Bei einem eingehenden Pull-Request durchsucht die Virtual Registry die Upstreams der Reihe nach, bis das Image gefunden wird. Das Ergebnis wird für einen konfigurierbaren Zeitraum gecacht – standardmäßig 24 Stunden.\n\n\n```text\n\n┌─────────────────────────────────────────────────────────┐ │                    CI/CD Pipeline                       │ │                          │                              │ │                          ▼                              │ │   gitlab.com/virtual_registries/container/\u003Cid>/image   │ └─────────────────────────────────────────────────────────┘\n                           │\n                           ▼\n┌─────────────────────────────────────────────────────────┐ │            Container Virtual Registry                   │ │                                                         │ │  Upstream 1: Docker Hub ────────────────┐               │ │  Upstream 2: dhi.io (Hardened) ────────┐│               │ │  Upstream 3: MCR ─────────────────────┐││               │ │  Upstream 4: Quay.io ────────────────┐│││               │ │                                      ││││               │ │                    ┌─────────────────┴┴┴┴──┐            │ │                    │        Cache          │            │ │                    │  (manifests + layers) │            │ │                    └───────────────────────┘            │ └─────────────────────────────────────────────────────────┘\n\n```\n\n## Was das konkret bringt – besonders mit Docker Hardened Images\n\n[Docker Hardened Images](https://docs.docker.com/dhi/) zeichnen sich durch minimale Angriffsfläche, nahezu keine bekannten CVEs, vollständige Software Bills of Materials (SBOMs) und SLSA-Provenance aus. Für Teams, die Base-Images für sicherheitskritische Workloads evaluieren, gehören sie auf die Shortlist.\n\nDer Wechsel zu dhi.io erzeugt jedoch dieselbe operative Reibung wie jede neue Registry:\n\n- **Credential-Verteilung**: Docker-Credentials müssen auf alle Systeme verteilt werden, die Images von dhi.io abrufen.\n- **CI/CD-Anpassungen**: Jede Pipeline muss für die Authentifizierung mit dhi.io aktualisiert werden.\n- **Akzeptanzproblem**: Ohne zentrale Steuerung greifen Teams weiterhin auf reguläre Images zurück.\n- **Fehlende Transparenz**: Ob Teams tatsächlich die gehärteten Varianten nutzen, ist kaum nachvollziehbar.\n\nDie Virtual Registry löst jeden dieser Punkte:\n\n**Einzelne Credential**: Teams authentifizieren sich bei GitLab. Die Virtual Registry übernimmt die Upstream-Authentifizierung. Docker-Credentials werden einmalig auf Registry-Ebene konfiguriert und gelten für alle Pulls.\n\n**Keine per-Team-CI/CD-Änderungen**: Pipelines auf die Virtual Registry zeigen lassen – fertig. Die Upstream-Konfiguration ist zentralisiert.\n\n**Schrittweise Einführung**: Da Images mit ihrem vollständigen Pfad gecacht werden, ist im Cache sichtbar, was tatsächlich abgerufen wird. Wird `library/python:3.11` statt der gehärteten Variante gepullt, ist das erkennbar.\n\n**Audit-Trail**: Der Cache zeigt exakt, welche Images aktiv genutzt werden – nachvollziehbar für Compliance-Zwecke und als Grundlage für das Verständnis der tatsächlichen Infrastruktur-Abhängigkeiten.\n\nWer das Konzept verstanden hat und die Einrichtung zu einem späteren Zeitpunkt in Angriff nimmt: Die wesentlichen Konzepte sind damit abgedeckt. Die technische Konfiguration folgt im nächsten Abschnitt.\n\n## Einrichtung\n\nDie folgende Einrichtung nutzt den Python-Client aus dem Demo-Projekt.\n\n### Virtual Registry erstellen\n\n```python\nfrom virtual_registry_client import VirtualRegistryClient\nclient = VirtualRegistryClient()\nregistry = client.create_virtual_registry(\n    group_id=\"785414\",  # ID der obersten Gruppe\n    name=\"platform-images\",\n    description=\"Cached container images for platform teams\"\n)\nprint(f\"Registry ID: {registry['id']}\") # Diese ID wird für die Pull-URL benötigt\n```\n\n### Docker Hub als Upstream hinzufügen\n\nFür offizielle Images wie Alpine, Python usw.:\n\n```python\n\ndocker_upstream = client.create_upstream(\n    registry_id=registry['id'],\n    url=\"https://registry-1.docker.io\",\n    name=\"Docker Hub\",\n    cache_validity_hours=24\n)\n\n```\n\n### Docker Hardened Images (dhi.io) hinzufügen\n\nDocker Hardened Images werden auf `dhi.io` gehostet – einer separaten Registry mit Authentifizierungspflicht:\n\n```python\n\ndhi_upstream = client.create_upstream(\n    registry_id=registry['id'],\n    url=\"https://dhi.io\",\n    name=\"Docker Hardened Images\",\n    username=\"your-docker-username\",\n    password=\"your-docker-access-token\",\n    cache_validity_hours=24\n)\n\n```\n\n### Weitere Upstreams hinzufügen\n\n```python\n\n# MCR für .NET-Teams client.create_upstream(\n    registry_id=registry['id'],\n    url=\"https://mcr.microsoft.com\",\n    name=\"Microsoft Container Registry\",\n    cache_validity_hours=48\n)\n# Quay für das Red-Hat-Ökosystem client.create_upstream(\n    registry_id=registry['id'],\n    url=\"https://quay.io\",\n    name=\"Quay.io\",\n    cache_validity_hours=24\n)\n\n```\n\n### CI/CD aktualisieren\n\nEine `.gitlab-ci.yml`, die über die Virtual Registry pullt:\n\n```yaml\n\nvariables:\n  VIRTUAL_REGISTRY_ID: \u003Cyour_virtual_registry_ID>\n\n  \nbuild:\n  image: docker:24\n  services:\n    - docker:24-dind\n  before_script:\n    # Authentifizierung bei GitLab – Upstream-Auth wird übernommen\n    - echo \"${CI_JOB_TOKEN}\" | docker login -u gitlab-ci-token --password-stdin gitlab.com\n  script:\n    # Alle Pulls laufen über die zentrale Virtual Registry\n    \n    # Offizielle Docker Hub Images (library/-Präfix erforderlich)\n    - docker pull gitlab.com/virtual_registries/container/${VIRTUAL_REGISTRY_ID}/library/alpine:latest\n    \n    # Docker Hardened Images von dhi.io (kein Präfix nötig)\n    - docker pull gitlab.com/virtual_registries/container/${VIRTUAL_REGISTRY_ID}/python:3.13\n    \n    # .NET von MCR\n    - docker pull gitlab.com/virtual_registries/container/${VIRTUAL_REGISTRY_ID}/dotnet/sdk:8.0\n\n\n```\n\n### Image-Pfadformate\n\nVerschiedene Registries verwenden unterschiedliche Pfadkonventionen:\n\n| Registry | Beispiel-Pull-URL |\n|----------|-------------------|\n| Docker Hub (offiziell) | `.../library/python:3.11-slim` |\n| Docker Hardened Images (dhi.io) | `.../python:3.13` |\n| MCR | `.../dotnet/sdk:8.0` |\n| Quay.io | `.../prometheus/prometheus:latest` |\n\n### Funktionsprüfung\n\nNach einigen Pulls lässt sich der Cache überprüfen:\n\n```python\n\nupstreams = client.list_registry_upstreams(registry['id']) for upstream in upstreams:\n    entries = client.list_cache_entries(upstream['id'])\n    print(f\"{upstream['name']}: {len(entries)} cached entries\")\n\n\n```\n\n## Messergebnisse\n\nTestergebnisse beim Pullen über die Virtual Registry:\n\n| Messgröße | Ohne Cache | Mit warmem Cache |\n|-----------|------------|-----------------|\n| Pull-Zeit (Alpine) | 10,3 s | 4,2 s |\n| Pull-Zeit (Python 3.13 DHI) | 11,6 s | ~4 s |\n| Netzwerk-Roundtrips zum Upstream | Jeder Pull | Nur Cache-Misses |\n\nDer erste Pull hat dieselbe Dauer – das Image muss vom Upstream geladen werden. Jeder weitere Pull innerhalb der Cache-Gültigkeitsdauer kommt direkt aus GitLab-Storage: kein Netzwerk-Hop zu Docker Hub, dhi.io, MCR oder einer anderen Registry.\n\nBei Teams mit vielen Pipeline-Jobs pro Tag summiert sich das zu einem messbaren Gewinn bei den Build-Laufzeiten.\n\n## Praktische Hinweise\n\n### Cache-Gültigkeit\n\nDer Standard sind 24 Stunden. Für sicherheitskritische Images, bei denen Patches schnell verfügbar sein sollen, empfiehlt sich ein kürzeres Intervall:\n\n```python\n\nclient.create_upstream(\n    registry_id=registry['id'],\n    url=\"https://dhi.io\",\n    name=\"Docker Hardened Images\",\n    username=\"your-username\",\n    password=\"your-token\",\n    cache_validity_hours=12\n)\n\n```\n\nFür stabile Images mit fixen Versions-Tags ist ein längeres Intervall problemlos.\n\n### Upstream-Priorität\n\nUpstreams werden der Reihe nach geprüft. Bei gleichnamigen Images in verschiedenen Registries gewinnt der erste passende Upstream.\n\n### Limits\n\n- Maximal 20 Virtual Registries pro Gruppe\n- Maximal 20 Upstreams pro Virtual Registry\n\n## Konfiguration über die Oberfläche\n\nVirtual Registries und Upstreams lassen sich auch direkt in der GitLab-Oberfläche einrichten – ohne API-Aufrufe. Unter **Einstellungen > Pakete und Registries > Virtual Registry** der jeweiligen Gruppe stehen folgende Optionen zur Verfügung:\n\n- Virtual Registries erstellen und verwalten\n- Upstreams hinzufügen, bearbeiten und neu anordnen\n- Cache anzeigen und verwalten\n- Überblick, welche Images abgerufen werden\n\n## Ausblick\n\nIn Entwicklung:\n\n- **Allow/Deny-Listen**: Regex-basierte Steuerung, welche Images aus welchen Upstreams abgerufen werden dürfen.\n\nContainer Virtual Registry befindet sich in der Beta-Phase. Die Funktion wird produktiv eingesetzt und wird weiterentwickelt – Feedback fließt direkt in die Priorisierung ein.\n\n## Feedback\n\nWer als Plattformteam mit Registry-Wildwuchs zu kämpfen hat: Ich möchte verstehen, wie die aktuelle Situation aussieht.\n\n- Wie viele Upstream-Registries werden verwaltet?\n- Wo liegt der größte Schmerzpunkt?\n- Würde ein solcher Ansatz helfen – und falls nicht: Was fehlt?\n\nErfahrungen und Rückmeldungen gerne im [Container Virtual Registry Feedback-Issue](https://gitlab.com/gitlab-org/gitlab/-/work_items/589630) teilen.\n\n## Weiterführende Ressourcen\n\n- [Neue GitLab-Metriken und Registry-Funktionen zur Optimierung von CI/CD-Pipelines](https://about.gitlab.com/de-de/blog/new-gitlab-metrics-and-registry-features-help-reduce-ci-cd-bottlenecks/)\n- [Container Virtual Registry – Dokumentation](https://docs.gitlab.com/user/packages/virtual_registry/container/)\n- [Container Virtual Registry – API](https://docs.gitlab.com/api/container_virtual_registries/)\n\n## Für deutsche Unternehmen könnte dies folgende Themen betreffen\n\nTeams, die sicherheitsgehärtete Base-Images mit vollständigen SBOMs und SLSA-Provenance einsetzen, haben möglicherweise auch Compliance-Überlegungen – beispielsweise in Bereichen wie Sicherheit der Software-Lieferkette, Nachvollziehbarkeit von Image-Abhängigkeiten und zentralem Audit-Trail.\n\nRegulatorische Frameworks wie NIS2 und der Cyber Resilience Act adressieren ähnliche Themen rund um Software-Lieferketten und SBOM-Transparenz. Für konkrete Compliance-Anforderungen empfiehlt sich Rücksprache mit entsprechender Fachberatung.","engineering",{"featured":13,"template":14,"slug":15},false,"BlogPost","using-gitlab-container-virtual-registry-with-docker-hardened-images",{"title":5,"description":17,"authors":18,"heroImage":19,"date":20,"body":10,"category":11,"tags":21},"Mehrere Registries hinter einem Endpunkt – GitLab Container Virtual Registry mit Docker Hardened Images, Caching und Audit-Trail.",[9],"https://res.cloudinary.com/about-gitlab-com/image/upload/v1772111172/mwhgbjawn62kymfwrhle.png","2026-03-12",[22,23,24],"tutorial","product","features","yml",null,{},true,"/de-de/blog/using-gitlab-container-virtual-registry-with-docker-hardened-images","seo:\n  config:\n    noIndex: false\n  title: Container Virtual Registry mit Docker Hardened Images\n  description: 'Mehrere Container-Registries zu einem Endpunkt konsolidieren: GitLab Container Virtual Registry mit Docker Hardened Images, Caching und Audit-Trail.'\ncontent:\n  title: GitLab Container Virtual Registry mit Docker Hardened Images einrichten\n  description: 'Mehrere Registries hinter einem Endpunkt – GitLab Container Virtual Registry mit Docker Hardened Images, Caching und Audit-Trail.'\n  authors:\n    - Tim Rizzi\n  heroImage: https://res.cloudinary.com/about-gitlab-com/image/upload/v1772111172/mwhgbjawn62kymfwrhle.png\n  date: '2026-03-12'\n  body: >-\n    Wer im Plattformteam arbeitet, kennt solche Gespräche:\n\n\n    *„Security sagt: Wir müssen gehärtete Base-Images verwenden.\"*\n\n\n    *„Prima – wo trage ich jetzt die Credentials für noch eine weitere Registry ein?\"*\n\n\n    *„Und wie stellen wir sicher, dass alle sie auch wirklich nutzen?\"*\n\n\n    Oder diese hier:\n\n\n    *„Warum sind unsere Builds so langsam?\"*\n\n\n    *„Wir pullen dasselbe 500-MB-Image in jedem einzelnen Job neu von Docker Hub.\"*\n\n\n    *„Kann man die nicht irgendwo cachen?\"*\n\n\n    Ich arbeite bei GitLab an der [Container Virtual Registry](https://docs.gitlab.com/user/packages/virtual_registry/container/) – einem Pull-Through-Cache, der vor den vorgelagerten Registries sitzt: Docker Hub, dhi.io (Docker Hardened Images), MCR und Quay. Teams erhalten einen einzigen Endpunkt zum Pullen. Images werden beim ersten Abruf gecacht; alle nachfolgenden Pulls kommen aus dem Cache. Das Entwicklungsteam muss nicht wissen, aus welchem Upstream ein bestimmtes Image stammt.\n\n\n    Dieser Artikel zeigt die Einrichtung der Container Virtual Registry – mit Docker Hardened Images als konkretem Anwendungsfall, da diese Kombination für Teams mit Sicherheitsanforderungen besonders naheliegt.\n\n\n    ## Das Problem: Registry-Wildwuchs im Plattformteam\n\n\n    Die Plattformteams, mit denen ich spreche, verwalten Container-Images über drei bis fünf Registries:\n\n\n    - **Docker Hub** für die meisten Base-Images\n\n    - **dhi.io** für Docker Hardened Images (sicherheitskritische Workloads)\n\n    - **MCR** für .NET- und Azure-Tooling\n\n    - **Quay.io** für das Red-Hat-Ökosystem\n\n    - **Interne Registries** für proprietäre Images\n\n\n    Jede davon hat eigene Authentifizierungsmechanismen, unterschiedliche Netzwerklatenz und eine eigene Pfadstruktur für Images.\n\n\n    CI/CD-Konfigurationen füllen sich mit registry-spezifischer Logik. Credential-Management wird zum eigenständigen Projekt. Und jeder Pipeline-Job lädt dieselben Base-Images erneut über das Netz – obwohl sie sich seit Wochen nicht geändert haben.\n\n\n    Container Virtual Registry konsolidiert das: eine Registry-URL, ein Authentifizierungsfluss über GitLab, gecachte Images aus GitLab-Infrastruktur statt wiederholter Internet-Traversierung.\n\n\n    ## Funktionsweise\n\n\n    Das Modell ist geradlinig:\n\n\n    ```text\n\n\n    Pipeline ruft ab:\n      gitlab.com/virtual_registries/container/1000016/python:3.13\n\n    Virtual Registry prüft:\n      1. Im Cache vorhanden? → Direkt zurückgeben\n      2. Nein? → Vom Upstream laden, cachen, zurückgeben\n\n\n    ```\n\n\n    Upstreams werden in Prioritätsreihenfolge konfiguriert. Bei einem eingehenden Pull-Request durchsucht die Virtual Registry die Upstreams der Reihe nach, bis das Image gefunden wird. Das Ergebnis wird für einen konfigurierbaren Zeitraum gecacht – standardmäßig 24 Stunden.\n\n\n\n    ```text\n\n\n    ┌─────────────────────────────────────────────────────────┐\n    │                    CI/CD Pipeline                       │\n    │                          │                              │\n    │                          ▼                              │\n    │   gitlab.com/virtual_registries/container/\u003Cid>/image   │\n    └─────────────────────────────────────────────────────────┘\n                               │\n                               ▼\n    ┌─────────────────────────────────────────────────────────┐\n    │            Container Virtual Registry                   │\n    │                                                         │\n    │  Upstream 1: Docker Hub ────────────────┐               │\n    │  Upstream 2: dhi.io (Hardened) ────────┐│               │\n    │  Upstream 3: MCR ─────────────────────┐││               │\n    │  Upstream 4: Quay.io ────────────────┐│││               │\n    │                                      ││││               │\n    │                    ┌─────────────────┴┴┴┴──┐            │\n    │                    │        Cache          │            │\n    │                    │  (manifests + layers) │            │\n    │                    └───────────────────────┘            │\n    └─────────────────────────────────────────────────────────┘\n\n\n    ```\n\n\n    ## Was das konkret bringt – besonders mit Docker Hardened Images\n\n\n    [Docker Hardened Images](https://docs.docker.com/dhi/) zeichnen sich durch minimale Angriffsfläche, nahezu keine bekannten CVEs, vollständige Software Bills of Materials (SBOMs) und SLSA-Provenance aus. Für Teams, die Base-Images für sicherheitskritische Workloads evaluieren, gehören sie auf die Shortlist.\n\n\n    Der Wechsel zu dhi.io erzeugt jedoch dieselbe operative Reibung wie jede neue Registry:\n\n\n    - **Credential-Verteilung**: Docker-Credentials müssen auf alle Systeme verteilt werden, die Images von dhi.io abrufen.\n\n    - **CI/CD-Anpassungen**: Jede Pipeline muss für die Authentifizierung mit dhi.io aktualisiert werden.\n\n    - **Akzeptanzproblem**: Ohne zentrale Steuerung greifen Teams weiterhin auf reguläre Images zurück.\n\n    - **Fehlende Transparenz**: Ob Teams tatsächlich die gehärteten Varianten nutzen, ist kaum nachvollziehbar.\n\n\n    Die Virtual Registry löst jeden dieser Punkte:\n\n\n    **Einzelne Credential**: Teams authentifizieren sich bei GitLab. Die Virtual Registry übernimmt die Upstream-Authentifizierung. Docker-Credentials werden einmalig auf Registry-Ebene konfiguriert und gelten für alle Pulls.\n\n\n    **Keine per-Team-CI/CD-Änderungen**: Pipelines auf die Virtual Registry zeigen lassen – fertig. Die Upstream-Konfiguration ist zentralisiert.\n\n\n    **Schrittweise Einführung**: Da Images mit ihrem vollständigen Pfad gecacht werden, ist im Cache sichtbar, was tatsächlich abgerufen wird. Wird `library/python:3.11` statt der gehärteten Variante gepullt, ist das erkennbar.\n\n\n    **Audit-Trail**: Der Cache zeigt exakt, welche Images aktiv genutzt werden – nachvollziehbar für Compliance-Zwecke und als Grundlage für das Verständnis der tatsächlichen Infrastruktur-Abhängigkeiten.\n\n\n    Wer das Konzept verstanden hat und die Einrichtung zu einem späteren Zeitpunkt in Angriff nimmt: Die wesentlichen Konzepte sind damit abgedeckt. Die technische Konfiguration folgt im nächsten Abschnitt.\n\n\n    ## Einrichtung\n\n\n    Die folgende Einrichtung nutzt den Python-Client aus dem Demo-Projekt.\n\n\n    ### Virtual Registry erstellen\n\n\n    ```python\n\n    from virtual_registry_client import VirtualRegistryClient\n\n    client = VirtualRegistryClient()\n\n    registry = client.create_virtual_registry(\n        group_id=\"785414\",  # ID der obersten Gruppe\n        name=\"platform-images\",\n        description=\"Cached container images for platform teams\"\n    )\n\n    print(f\"Registry ID: {registry['id']}\")\n    # Diese ID wird für die Pull-URL benötigt\n\n    ```\n\n\n    ### Docker Hub als Upstream hinzufügen\n\n\n    Für offizielle Images wie Alpine, Python usw.:\n\n\n    ```python\n\n\n    docker_upstream = client.create_upstream(\n        registry_id=registry['id'],\n        url=\"https://registry-1.docker.io\",\n        name=\"Docker Hub\",\n        cache_validity_hours=24\n    )\n\n\n    ```\n\n\n    ### Docker Hardened Images (dhi.io) hinzufügen\n\n\n    Docker Hardened Images werden auf `dhi.io` gehostet – einer separaten Registry mit Authentifizierungspflicht:\n\n\n    ```python\n\n\n    dhi_upstream = client.create_upstream(\n        registry_id=registry['id'],\n        url=\"https://dhi.io\",\n        name=\"Docker Hardened Images\",\n        username=\"your-docker-username\",\n        password=\"your-docker-access-token\",\n        cache_validity_hours=24\n    )\n\n\n    ```\n\n\n    ### Weitere Upstreams hinzufügen\n\n\n    ```python\n\n\n    # MCR für .NET-Teams\n    client.create_upstream(\n        registry_id=registry['id'],\n        url=\"https://mcr.microsoft.com\",\n        name=\"Microsoft Container Registry\",\n        cache_validity_hours=48\n    )\n\n    # Quay für das Red-Hat-Ökosystem\n    client.create_upstream(\n        registry_id=registry['id'],\n        url=\"https://quay.io\",\n        name=\"Quay.io\",\n        cache_validity_hours=24\n    )\n\n\n    ```\n\n\n    ### CI/CD aktualisieren\n\n\n    Eine `.gitlab-ci.yml`, die über die Virtual Registry pullt:\n\n\n    ```yaml\n\n\n    variables:\n      VIRTUAL_REGISTRY_ID: \u003Cyour_virtual_registry_ID>\n\n      \n    build:\n      image: docker:24\n      services:\n        - docker:24-dind\n      before_script:\n        # Authentifizierung bei GitLab – Upstream-Auth wird übernommen\n        - echo \"${CI_JOB_TOKEN}\" | docker login -u gitlab-ci-token --password-stdin gitlab.com\n      script:\n        # Alle Pulls laufen über die zentrale Virtual Registry\n        \n        # Offizielle Docker Hub Images (library/-Präfix erforderlich)\n        - docker pull gitlab.com/virtual_registries/container/${VIRTUAL_REGISTRY_ID}/library/alpine:latest\n        \n        # Docker Hardened Images von dhi.io (kein Präfix nötig)\n        - docker pull gitlab.com/virtual_registries/container/${VIRTUAL_REGISTRY_ID}/python:3.13\n        \n        # .NET von MCR\n        - docker pull gitlab.com/virtual_registries/container/${VIRTUAL_REGISTRY_ID}/dotnet/sdk:8.0\n\n\n    ```\n\n\n    ### Image-Pfadformate\n\n\n    Verschiedene Registries verwenden unterschiedliche Pfadkonventionen:\n\n\n    | Registry | Beispiel-Pull-URL |\n\n    |----------|-------------------|\n\n    | Docker Hub (offiziell) | `.../library/python:3.11-slim` |\n\n    | Docker Hardened Images (dhi.io) | `.../python:3.13` |\n\n    | MCR | `.../dotnet/sdk:8.0` |\n\n    | Quay.io | `.../prometheus/prometheus:latest` |\n\n\n    ### Funktionsprüfung\n\n\n    Nach einigen Pulls lässt sich der Cache überprüfen:\n\n\n    ```python\n\n\n    upstreams = client.list_registry_upstreams(registry['id'])\n    for upstream in upstreams:\n        entries = client.list_cache_entries(upstream['id'])\n        print(f\"{upstream['name']}: {len(entries)} cached entries\")\n\n\n    ```\n\n\n    ## Messergebnisse\n\n\n    Testergebnisse beim Pullen über die Virtual Registry:\n\n\n    | Messgröße | Ohne Cache | Mit warmem Cache |\n\n    |-----------|------------|-----------------|\n\n    | Pull-Zeit (Alpine) | 10,3 s | 4,2 s |\n\n    | Pull-Zeit (Python 3.13 DHI) | 11,6 s | ~4 s |\n\n    | Netzwerk-Roundtrips zum Upstream | Jeder Pull | Nur Cache-Misses |\n\n\n    Der erste Pull hat dieselbe Dauer – das Image muss vom Upstream geladen werden. Jeder weitere Pull innerhalb der Cache-Gültigkeitsdauer kommt direkt aus GitLab-Storage: kein Netzwerk-Hop zu Docker Hub, dhi.io, MCR oder einer anderen Registry.\n\n\n    Bei Teams mit vielen Pipeline-Jobs pro Tag summiert sich das zu einem messbaren Gewinn bei den Build-Laufzeiten.\n\n\n    ## Praktische Hinweise\n\n\n    ### Cache-Gültigkeit\n\n\n    Der Standard sind 24 Stunden. Für sicherheitskritische Images, bei denen Patches schnell verfügbar sein sollen, empfiehlt sich ein kürzeres Intervall:\n\n\n    ```python\n\n\n    client.create_upstream(\n        registry_id=registry['id'],\n        url=\"https://dhi.io\",\n        name=\"Docker Hardened Images\",\n        username=\"your-username\",\n        password=\"your-token\",\n        cache_validity_hours=12\n    )\n\n\n    ```\n\n\n    Für stabile Images mit fixen Versions-Tags ist ein längeres Intervall problemlos.\n\n\n    ### Upstream-Priorität\n\n\n    Upstreams werden der Reihe nach geprüft. Bei gleichnamigen Images in verschiedenen Registries gewinnt der erste passende Upstream.\n\n\n    ### Limits\n\n\n    - Maximal 20 Virtual Registries pro Gruppe\n\n    - Maximal 20 Upstreams pro Virtual Registry\n\n\n    ## Konfiguration über die Oberfläche\n\n\n    Virtual Registries und Upstreams lassen sich auch direkt in der GitLab-Oberfläche einrichten – ohne API-Aufrufe. Unter **Einstellungen > Pakete und Registries > Virtual Registry** der jeweiligen Gruppe stehen folgende Optionen zur Verfügung:\n\n\n    - Virtual Registries erstellen und verwalten\n\n    - Upstreams hinzufügen, bearbeiten und neu anordnen\n\n    - Cache anzeigen und verwalten\n\n    - Überblick, welche Images abgerufen werden\n\n\n    ## Ausblick\n\n\n    In Entwicklung:\n\n\n    - **Allow/Deny-Listen**: Regex-basierte Steuerung, welche Images aus welchen Upstreams abgerufen werden dürfen.\n\n\n    Container Virtual Registry befindet sich in der Beta-Phase. Die Funktion wird produktiv eingesetzt und wird weiterentwickelt – Feedback fließt direkt in die Priorisierung ein.\n\n\n    ## Feedback\n\n\n    Wer als Plattformteam mit Registry-Wildwuchs zu kämpfen hat: Ich möchte verstehen, wie die aktuelle Situation aussieht.\n\n\n    - Wie viele Upstream-Registries werden verwaltet?\n\n    - Wo liegt der größte Schmerzpunkt?\n\n    - Würde ein solcher Ansatz helfen – und falls nicht: Was fehlt?\n\n\n    Erfahrungen und Rückmeldungen gerne im [Container Virtual Registry Feedback-Issue](https://gitlab.com/gitlab-org/gitlab/-/work_items/589630) teilen.\n\n\n    ## Weiterführende Ressourcen\n\n\n    - [Neue GitLab-Metriken und Registry-Funktionen zur Optimierung von CI/CD-Pipelines](https://about.gitlab.com/de-de/blog/new-gitlab-metrics-and-registry-features-help-reduce-ci-cd-bottlenecks/)\n\n    - [Container Virtual Registry – Dokumentation](https://docs.gitlab.com/user/packages/virtual_registry/container/)\n\n    - [Container Virtual Registry – API](https://docs.gitlab.com/api/container_virtual_registries/)\n\n\n    ## Für deutsche Unternehmen könnte dies folgende Themen betreffen\n\n\n    Teams, die sicherheitsgehärtete Base-Images mit vollständigen SBOMs und SLSA-Provenance einsetzen, haben möglicherweise auch Compliance-Überlegungen – beispielsweise in Bereichen wie Sicherheit der Software-Lieferkette, Nachvollziehbarkeit von Image-Abhängigkeiten und zentralem Audit-Trail.\n\n\n    Regulatorische Frameworks wie NIS2 und der Cyber Resilience Act adressieren ähnliche Themen rund um Software-Lieferketten und SBOM-Transparenz. Für konkrete Compliance-Anforderungen empfiehlt sich Rücksprache mit entsprechender Fachberatung.\n  category: engineering\n  tags:\n    - tutorial\n    - product\n    - features\nconfig:\n  featured: false\n  template: BlogPost\n  slug: using-gitlab-container-virtual-registry-with-docker-hardened-images\n",{"config":32,"title":33,"description":34},{"noIndex":13},"Container Virtual Registry mit Docker Hardened Images","Mehrere Container-Registries zu einem Endpunkt konsolidieren: GitLab Container Virtual Registry mit Docker Hardened Images, Caching und Audit-Trail.","de-de/blog/using-gitlab-container-virtual-registry-with-docker-hardened-images",[22,23,24],[22,23,24],"rB2sO9HXKGsBvqKlCOAYQOhUqPhbvvVrZpkliB3tbn0",{"logo":40,"freeTrial":45,"sales":50,"login":55,"items":60,"search":381,"minimal":415,"duo":433,"switchNav":442,"pricingDeployment":453},{"config":41},{"href":42,"dataGaName":43,"dataGaLocation":44},"/de-de/","gitlab logo","header",{"text":46,"config":47},"Kostenlose Testversion anfordern",{"href":48,"dataGaName":49,"dataGaLocation":44},"https://gitlab.com/-/trial_registrations/new?glm_source=about.gitlab.com/de-de&glm_content=default-saas-trial/","free trial",{"text":51,"config":52},"Vertrieb kontaktieren",{"href":53,"dataGaName":54,"dataGaLocation":44},"/de-de/sales/","sales",{"text":56,"config":57},"Anmelden",{"href":58,"dataGaName":59,"dataGaLocation":44},"https://gitlab.com/users/sign_in/","sign in",[61,90,192,197,300,361],{"text":62,"config":63,"menu":65},"Plattform",{"dataNavLevelOne":64},"platform",{"type":66,"columns":67},"cards",[68,74,82],{"title":62,"description":69,"link":70},"Die intelligente Orchestrierungsplattform für DevSecOps",{"text":71,"config":72},"Die Plattform erkunden",{"href":73,"dataGaName":64,"dataGaLocation":44},"/de-de/platform/",{"title":75,"description":76,"link":77},"GitLab Duo Agent Platform","Agentische KI für den gesamten Software-Lebenszyklus",{"text":78,"config":79},"Lerne GitLab Duo kennen",{"href":80,"dataGaName":81,"dataGaLocation":44},"/de-de/gitlab-duo-agent-platform/","gitlab duo agent platform",{"title":83,"description":84,"link":85},"Warum GitLab?","Erfahre, warum sich Unternehmen für GitLab entscheiden",{"text":86,"config":87},"Mehr erfahren",{"href":88,"dataGaName":89,"dataGaLocation":44},"/de-de/why-gitlab/","why gitlab",{"text":91,"left":28,"config":92,"menu":94},"Produkt",{"dataNavLevelOne":93},"solutions",{"type":95,"link":96,"columns":100,"feature":171},"lists",{"text":97,"config":98},"Alle Lösungen anzeigen",{"href":99,"dataGaName":93,"dataGaLocation":44},"/de-de/solutions/",[101,126,149],{"title":102,"description":103,"link":104,"items":109},"Automatisierung","CI/CD und Automatisierung zur Beschleunigung der Bereitstellung",{"config":105},{"icon":106,"href":107,"dataGaName":108,"dataGaLocation":44},"AutomatedCodeAlt","/de-de/solutions/delivery-automation/","automated software delivery",[110,114,117,122],{"text":111,"config":112},"CI/CD",{"href":113,"dataGaLocation":44,"dataGaName":111},"/de-de/solutions/continuous-integration/",{"text":75,"config":115},{"href":80,"dataGaLocation":44,"dataGaName":116},"gitlab duo agent platform - product menu",{"text":118,"config":119},"Quellcodeverwaltung",{"href":120,"dataGaLocation":44,"dataGaName":121},"/de-de/solutions/source-code-management/","Source Code Management",{"text":123,"config":124},"Automatische Softwarebereitstellung",{"href":107,"dataGaLocation":44,"dataGaName":125},"Automated software delivery",{"title":127,"description":128,"link":129,"items":134},"Sicherheit","Entwickle Code schneller ohne Abstriche bei der Sicherheit",{"config":130},{"href":131,"dataGaName":132,"dataGaLocation":44,"icon":133},"/de-de/solutions/application-security-testing/","security and compliance","ShieldCheckLight",[135,139,144],{"text":136,"config":137},"Anwendungssicherheitstests",{"href":131,"dataGaName":138,"dataGaLocation":44},"Application security testing",{"text":140,"config":141},"Schutz der Software-Lieferkette",{"href":142,"dataGaLocation":44,"dataGaName":143},"/de-de/solutions/supply-chain/","Software supply chain security",{"text":145,"config":146},"Software-Compliance",{"href":147,"dataGaName":148,"dataGaLocation":44},"/de-de/solutions/software-compliance/","software compliance",{"title":150,"link":151,"items":156},"Auswertung",{"config":152},{"icon":153,"href":154,"dataGaName":155,"dataGaLocation":44},"DigitalTransformation","/de-de/solutions/visibility-measurement/","visibility and measurement",[157,161,166],{"text":158,"config":159},"Sichtbarkeit und Auswertung",{"href":154,"dataGaLocation":44,"dataGaName":160},"Visibility and Measurement",{"text":162,"config":163},"Wertstrommanagement",{"href":164,"dataGaLocation":44,"dataGaName":165},"/de-de/solutions/value-stream-management/","Value Stream Management",{"text":167,"config":168},"Analysen und Einblicke",{"href":169,"dataGaLocation":44,"dataGaName":170},"/de-de/solutions/analytics-and-insights/","Analytics and insights",{"title":172,"type":95,"items":173},"GitLab für",[174,180,186],{"text":175,"config":176},"Enterprise",{"icon":177,"href":178,"dataGaLocation":44,"dataGaName":179},"Building","/de-de/enterprise/","enterprise",{"text":181,"config":182},"Kleinunternehmen",{"icon":183,"href":184,"dataGaLocation":44,"dataGaName":185},"Work","/de-de/small-business/","small business",{"text":187,"config":188},"Öffentlicher Sektor",{"icon":189,"href":190,"dataGaLocation":44,"dataGaName":191},"Organization","/de-de/solutions/public-sector/","public sector",{"text":193,"config":194},"Preise",{"href":195,"dataGaName":196,"dataGaLocation":44,"dataNavLevelOne":196},"/de-de/pricing/","pricing",{"text":198,"config":199,"menu":201},"Ressourcen",{"dataNavLevelOne":200},"resources",{"type":95,"link":202,"columns":206,"feature":286},{"text":203,"config":204},"Alle Ressourcen anzeigen",{"href":205,"dataGaName":200,"dataGaLocation":44},"/de-de/resources/",[207,240,258],{"title":208,"items":209},"Erste Schritte",[210,215,220,225,230,235],{"text":211,"config":212},"Installieren",{"href":213,"dataGaName":214,"dataGaLocation":44},"/de-de/install/","install",{"text":216,"config":217},"Kurzanleitungen",{"href":218,"dataGaName":219,"dataGaLocation":44},"/de-de/get-started/","quick setup checklists",{"text":221,"config":222},"Lernen",{"href":223,"dataGaLocation":44,"dataGaName":224},"https://university.gitlab.com/","learn",{"text":226,"config":227},"Produktdokumentation",{"href":228,"dataGaName":229,"dataGaLocation":44},"https://docs.gitlab.com/","product documentation",{"text":231,"config":232},"Best-Practice-Videos",{"href":233,"dataGaName":234,"dataGaLocation":44},"/de-de/getting-started-videos/","best practice videos",{"text":236,"config":237},"Integrationen",{"href":238,"dataGaName":239,"dataGaLocation":44},"/de-de/integrations/","integrations",{"title":241,"items":242},"Entdecken",[243,248,253],{"text":244,"config":245},"Kundenerfolge",{"href":246,"dataGaName":247,"dataGaLocation":44},"/de-de/customers/","customer success stories",{"text":249,"config":250},"Blog",{"href":251,"dataGaName":252,"dataGaLocation":44},"/de-de/blog/","blog",{"text":254,"config":255},"Remote",{"href":256,"dataGaName":257,"dataGaLocation":44},"https://handbook.gitlab.com/handbook/company/culture/all-remote/","remote",{"title":259,"items":260},"Vernetzen",[261,266,271,276,281],{"text":262,"config":263},"GitLab Services",{"href":264,"dataGaName":265,"dataGaLocation":44},"/de-de/services/","services",{"text":267,"config":268},"Community",{"href":269,"dataGaName":270,"dataGaLocation":44},"/community/","community",{"text":272,"config":273},"Forum",{"href":274,"dataGaName":275,"dataGaLocation":44},"https://forum.gitlab.com/","forum",{"text":277,"config":278},"Veranstaltungen",{"href":279,"dataGaName":280,"dataGaLocation":44},"/events/","events",{"text":282,"config":283},"Partner",{"href":284,"dataGaName":285,"dataGaLocation":44},"/de-de/partners/","partners",{"config":287,"text":290,"image":291,"link":295},{"background":288,"textColor":289},"#2f2a6b","#fff","Perspektiven für die Softwareentwicklung der Zukunft",{"altText":292,"config":293},"The Source Promo-Karte",{"src":294},"https://res.cloudinary.com/about-gitlab-com/image/upload/v1758208064/dzl0dbift9xdizyelkk4.svg",{"text":296,"config":297},"Aktuelles",{"href":298,"dataGaName":299,"dataGaLocation":44},"/de-de/the-source/","the source",{"text":301,"config":302,"menu":304},"Unternehmen",{"dataNavLevelOne":303},"company",{"type":95,"columns":305},[306],{"items":307},[308,313,319,321,326,331,336,341,346,351,356],{"text":309,"config":310},"Über",{"href":311,"dataGaName":312,"dataGaLocation":44},"/de-de/company/","about",{"text":314,"config":315,"footerGa":318},"Karriere",{"href":316,"dataGaName":317,"dataGaLocation":44},"/jobs/","jobs",{"dataGaName":317},{"text":277,"config":320},{"href":279,"dataGaName":280,"dataGaLocation":44},{"text":322,"config":323},"Geschäftsführung",{"href":324,"dataGaName":325,"dataGaLocation":44},"/company/team/e-group/","leadership",{"text":327,"config":328},"Team",{"href":329,"dataGaName":330,"dataGaLocation":44},"/company/team/","team",{"text":332,"config":333},"Handbuch",{"href":334,"dataGaName":335,"dataGaLocation":44},"https://handbook.gitlab.com/","handbook",{"text":337,"config":338},"Investor Relations",{"href":339,"dataGaName":340,"dataGaLocation":44},"https://ir.gitlab.com/","investor relations",{"text":342,"config":343},"Trust Center",{"href":344,"dataGaName":345,"dataGaLocation":44},"/de-de/security/","trust center",{"text":347,"config":348},"AI Transparency Center",{"href":349,"dataGaName":350,"dataGaLocation":44},"/de-de/ai-transparency-center/","ai transparency center",{"text":352,"config":353},"Newsletter",{"href":354,"dataGaName":355,"dataGaLocation":44},"/company/contact/#contact-forms","newsletter",{"text":357,"config":358},"Presse",{"href":359,"dataGaName":360,"dataGaLocation":44},"/press/","press",{"text":362,"config":363,"menu":364},"Kontakt",{"dataNavLevelOne":303},{"type":95,"columns":365},[366],{"items":367},[368,371,376],{"text":51,"config":369},{"href":53,"dataGaName":370,"dataGaLocation":44},"talk to sales",{"text":372,"config":373},"Support-Portal",{"href":374,"dataGaName":375,"dataGaLocation":44},"https://support.gitlab.com","support portal",{"text":377,"config":378},"Kundenportal",{"href":379,"dataGaName":380,"dataGaLocation":44},"https://customers.gitlab.com/customers/sign_in/","customer portal",{"close":382,"login":383,"suggestions":390},"Schließen",{"text":384,"link":385},"Um Repositorys und Projekte zu durchsuchen, melde dich an bei",{"text":386,"config":387},"gitlab.com",{"href":58,"dataGaName":388,"dataGaLocation":389},"search login","search",{"text":391,"default":392},"Vorschläge",[393,395,400,402,407,412],{"text":75,"config":394},{"href":80,"dataGaName":75,"dataGaLocation":389},{"text":396,"config":397},"Codevorschläge (KI)",{"href":398,"dataGaName":399,"dataGaLocation":389},"/de-de/solutions/code-suggestions/","Code Suggestions (AI)",{"text":111,"config":401},{"href":113,"dataGaName":111,"dataGaLocation":389},{"text":403,"config":404},"GitLab auf AWS",{"href":405,"dataGaName":406,"dataGaLocation":389},"/de-de/partners/technology-partners/aws/","GitLab on AWS",{"text":408,"config":409},"GitLab auf Google Cloud",{"href":410,"dataGaName":411,"dataGaLocation":389},"/de-de/partners/technology-partners/google-cloud-platform/","GitLab on Google Cloud",{"text":83,"config":413},{"href":88,"dataGaName":414,"dataGaLocation":389},"Why GitLab?",{"freeTrial":416,"mobileIcon":421,"desktopIcon":426,"secondaryButton":429},{"text":417,"config":418},"Kostenlos testen",{"href":419,"dataGaName":49,"dataGaLocation":420},"https://gitlab.com/-/trials/new/","nav",{"altText":422,"config":423},"GitLab-Symbol",{"src":424,"dataGaName":425,"dataGaLocation":420},"https://res.cloudinary.com/about-gitlab-com/image/upload/v1758203874/jypbw1jx72aexsoohd7x.svg","gitlab icon",{"altText":422,"config":427},{"src":428,"dataGaName":425,"dataGaLocation":420},"https://res.cloudinary.com/about-gitlab-com/image/upload/v1758203875/gs4c8p8opsgvflgkswz9.svg",{"text":208,"config":430},{"href":431,"dataGaName":432,"dataGaLocation":420},"https://gitlab.com/-/trial_registrations/new?glm_source=about.gitlab.com/de-de/get-started/","get started",{"freeTrial":434,"mobileIcon":438,"desktopIcon":440},{"text":435,"config":436},"Mehr über GitLab Duo erfahren",{"href":80,"dataGaName":437,"dataGaLocation":420},"gitlab duo",{"altText":422,"config":439},{"src":424,"dataGaName":425,"dataGaLocation":420},{"altText":422,"config":441},{"src":428,"dataGaName":425,"dataGaLocation":420},{"button":443,"mobileIcon":448,"desktopIcon":450},{"text":444,"config":445},"/Option",{"href":446,"dataGaName":447,"dataGaLocation":420},"#contact","switch",{"altText":422,"config":449},{"src":424,"dataGaName":425,"dataGaLocation":420},{"altText":422,"config":451},{"src":452,"dataGaName":425,"dataGaLocation":420},"https://res.cloudinary.com/about-gitlab-com/image/upload/v1773335277/ohhpiuoxoldryzrnhfrh.png",{"freeTrial":454,"mobileIcon":459,"desktopIcon":461},{"text":455,"config":456},"Zurück zur Preisübersicht",{"href":195,"dataGaName":457,"dataGaLocation":420,"icon":458},"back to pricing","GoBack",{"altText":422,"config":460},{"src":424,"dataGaName":425,"dataGaLocation":420},{"altText":422,"config":462},{"src":428,"dataGaName":425,"dataGaLocation":420},{"title":464,"button":465,"config":470},"Sieh dir an, wie agentische KI die Softwarebereitstellung transformiert",{"text":466,"config":467},"Für GitLab Transcend am 10. Juni anmelden",{"href":468,"dataGaName":469,"dataGaLocation":44},"/de-de/releases/whats-new/#sign-up","transcend event",{"layout":471,"icon":472,"disabled":13},"release","AiStar",{"data":474},{"text":475,"source":476,"edit":482,"contribute":487,"config":492,"items":497,"minimal":700},"Git ist eine Marke von Software Freedom Conservancy und unsere Verwendung von „GitLab“ erfolgt unter Lizenz.",{"text":477,"config":478},"Quelltext der Seite anzeigen",{"href":479,"dataGaName":480,"dataGaLocation":481},"https://gitlab.com/gitlab-com/marketing/digital-experience/about-gitlab-com/","page source","footer",{"text":483,"config":484},"Diese Seite bearbeiten",{"href":485,"dataGaName":486,"dataGaLocation":481},"https://gitlab.com/gitlab-com/marketing/digital-experience/about-gitlab-com/-/blob/main/content/","web ide",{"text":488,"config":489},"Beteilige dich",{"href":490,"dataGaName":491,"dataGaLocation":481},"https://gitlab.com/gitlab-com/marketing/digital-experience/about-gitlab-com/-/blob/main/CONTRIBUTING.md/","please contribute",{"twitter":493,"facebook":494,"youtube":495,"linkedin":496},"https://x.com/gitlab","https://www.facebook.com/gitlab","https://www.youtube.com/channel/UCnMGQ8QHMAnVIsI3xJrihhg","https://www.linkedin.com/company/gitlab-com",[498,543,596,638,665],{"title":193,"links":499,"subMenu":514},[500,504,509],{"text":501,"config":502},"Tarife anzeigen",{"href":195,"dataGaName":503,"dataGaLocation":481},"view plans",{"text":505,"config":506},"Vorteile von Premium",{"href":507,"dataGaName":508,"dataGaLocation":481},"/de-de/pricing/premium/","why premium",{"text":510,"config":511},"Vorteile von Ultimate",{"href":512,"dataGaName":513,"dataGaLocation":481},"/de-de/pricing/ultimate/","why ultimate",[515],{"title":362,"links":516},[517,519,521,523,528,533,538],{"text":51,"config":518},{"href":53,"dataGaName":54,"dataGaLocation":481},{"text":372,"config":520},{"href":374,"dataGaName":375,"dataGaLocation":481},{"text":377,"config":522},{"href":379,"dataGaName":380,"dataGaLocation":481},{"text":524,"config":525},"Status",{"href":526,"dataGaName":527,"dataGaLocation":481},"https://status.gitlab.com/","status",{"text":529,"config":530},"Nutzungsbedingungen",{"href":531,"dataGaName":532,"dataGaLocation":481},"/terms/","terms of use",{"text":534,"config":535},"Datenschutzerklärung",{"href":536,"dataGaName":537,"dataGaLocation":481},"/de-de/privacy/","privacy statement",{"text":539,"config":540},"Cookie-Einstellungen",{"dataGaName":541,"dataGaLocation":481,"id":542,"isOneTrustButton":28},"cookie preferences","ot-sdk-btn",{"title":91,"links":544,"subMenu":553},[545,549],{"text":546,"config":547},"DevSecOps-Plattform",{"href":73,"dataGaName":548,"dataGaLocation":481},"devsecops platform",{"text":550,"config":551},"KI-unterstützte Entwicklung",{"href":80,"dataGaName":552,"dataGaLocation":481},"ai-assisted development",[554],{"title":555,"links":556},"Themen",[557,561,566,571,576,581,586,591],{"text":111,"config":558},{"href":559,"dataGaName":560,"dataGaLocation":481},"/de-de/topics/ci-cd/","cicd",{"text":562,"config":563},"GitOps",{"href":564,"dataGaName":565,"dataGaLocation":481},"/de-de/topics/gitops/","gitops",{"text":567,"config":568},"DevOps",{"href":569,"dataGaName":570,"dataGaLocation":481},"/de-de/topics/devops/","devops",{"text":572,"config":573},"Versionskontrolle",{"href":574,"dataGaName":575,"dataGaLocation":481},"/de-de/topics/version-control/","version control",{"text":577,"config":578},"DevSecOps",{"href":579,"dataGaName":580,"dataGaLocation":481},"/de-de/topics/devsecops/","devsecops",{"text":582,"config":583},"Cloud-nativ",{"href":584,"dataGaName":585,"dataGaLocation":481},"/de-de/topics/cloud-native/","cloud native",{"text":587,"config":588},"KI für das Programmieren",{"href":589,"dataGaName":590,"dataGaLocation":481},"/de-de/topics/devops/ai-for-coding/","ai for coding",{"text":592,"config":593},"Agentische KI",{"href":594,"dataGaName":595,"dataGaLocation":481},"/de-de/topics/agentic-ai/","agentic ai",{"title":597,"links":598},"Lösungen",[599,602,604,609,613,616,619,622,624,626,628,633],{"text":136,"config":600},{"href":131,"dataGaName":601,"dataGaLocation":481},"Application Security Testing",{"text":123,"config":603},{"href":107,"dataGaName":108,"dataGaLocation":481},{"text":605,"config":606},"Agile Entwicklung",{"href":607,"dataGaName":608,"dataGaLocation":481},"/de-de/solutions/agile-delivery/","agile delivery",{"text":610,"config":611},"SCM",{"href":120,"dataGaName":612,"dataGaLocation":481},"source code management",{"text":111,"config":614},{"href":113,"dataGaName":615,"dataGaLocation":481},"continuous integration & delivery",{"text":162,"config":617},{"href":164,"dataGaName":618,"dataGaLocation":481},"value stream management",{"text":562,"config":620},{"href":621,"dataGaName":565,"dataGaLocation":481},"/de-de/solutions/gitops/",{"text":175,"config":623},{"href":178,"dataGaName":179,"dataGaLocation":481},{"text":181,"config":625},{"href":184,"dataGaName":185,"dataGaLocation":481},{"text":187,"config":627},{"href":190,"dataGaName":191,"dataGaLocation":481},{"text":629,"config":630},"Bildungswesen",{"href":631,"dataGaName":632,"dataGaLocation":481},"/de-de/solutions/education/","education",{"text":634,"config":635},"Finanzdienstleistungen",{"href":636,"dataGaName":637,"dataGaLocation":481},"/de-de/solutions/finance/","financial services",{"title":198,"links":639},[640,642,644,646,649,651,653,655,657,659,661,663],{"text":211,"config":641},{"href":213,"dataGaName":214,"dataGaLocation":481},{"text":216,"config":643},{"href":218,"dataGaName":219,"dataGaLocation":481},{"text":221,"config":645},{"href":223,"dataGaName":224,"dataGaLocation":481},{"text":226,"config":647},{"href":228,"dataGaName":648,"dataGaLocation":481},"docs",{"text":249,"config":650},{"href":251,"dataGaName":252,"dataGaLocation":481},{"text":244,"config":652},{"href":246,"dataGaName":247,"dataGaLocation":481},{"text":254,"config":654},{"href":256,"dataGaName":257,"dataGaLocation":481},{"text":262,"config":656},{"href":264,"dataGaName":265,"dataGaLocation":481},{"text":267,"config":658},{"href":269,"dataGaName":270,"dataGaLocation":481},{"text":272,"config":660},{"href":274,"dataGaName":275,"dataGaLocation":481},{"text":277,"config":662},{"href":279,"dataGaName":280,"dataGaLocation":481},{"text":282,"config":664},{"href":284,"dataGaName":285,"dataGaLocation":481},{"title":301,"links":666},[667,669,671,673,675,677,679,684,689,691,693,695],{"text":309,"config":668},{"href":311,"dataGaName":303,"dataGaLocation":481},{"text":314,"config":670},{"href":316,"dataGaName":317,"dataGaLocation":481},{"text":322,"config":672},{"href":324,"dataGaName":325,"dataGaLocation":481},{"text":327,"config":674},{"href":329,"dataGaName":330,"dataGaLocation":481},{"text":332,"config":676},{"href":334,"dataGaName":335,"dataGaLocation":481},{"text":337,"config":678},{"href":339,"dataGaName":340,"dataGaLocation":481},{"text":680,"config":681},"Nachhaltigkeit",{"href":682,"dataGaName":683,"dataGaLocation":481},"/sustainability/","Sustainability",{"text":685,"config":686},"Vielfalt, Inklusion und Zugehörigkeit",{"href":687,"dataGaName":688,"dataGaLocation":481},"/de-de/diversity-inclusion-belonging/","Diversity, inclusion and belonging",{"text":342,"config":690},{"href":344,"dataGaName":345,"dataGaLocation":481},{"text":352,"config":692},{"href":354,"dataGaName":355,"dataGaLocation":481},{"text":357,"config":694},{"href":359,"dataGaName":360,"dataGaLocation":481},{"text":696,"config":697},"Transparenzerklärung zu moderner Sklaverei",{"href":698,"dataGaName":699,"dataGaLocation":481},"https://handbook.gitlab.com/handbook/legal/modern-slavery-act-transparency-statement/","modern slavery transparency statement",{"items":701},[702,704,707],{"text":529,"config":703},{"href":531,"dataGaName":532,"dataGaLocation":481},{"text":705,"config":706},"Cookies",{"dataGaName":541,"dataGaLocation":481,"id":542,"isOneTrustButton":28},{"text":534,"config":708},{"href":536,"dataGaName":537,"dataGaLocation":481},[710],{"id":711,"title":9,"body":26,"config":712,"content":714,"description":26,"extension":25,"meta":718,"navigation":28,"path":719,"seo":720,"stem":721,"__hash__":722},"blogAuthors/en-us/blog/authors/tim-rizzi.yml",{"template":713},"BlogAuthor",{"name":9,"config":715},{"headshot":716,"ctfId":717},"https://res.cloudinary.com/about-gitlab-com/image/upload/v1749661866/Blog/Author%20Headshots/trizzi-headshot.jpg","trizzi",{},"/en-us/blog/authors/tim-rizzi",{},"en-us/blog/authors/tim-rizzi","ADPqrpcnKveFJS0m_zFV0VLtb_h_txu59QVgz_YwKMc",[724,736,749],{"content":725,"config":734},{"title":726,"description":727,"authors":728,"heroImage":730,"date":731,"body":732,"category":11,"tags":733},"CI/CD-Observability im Unternehmensmaßstab aufbauen","Dieser Praxisleitfaden zu GitLab Pipeline Analytics hilft Self-Managed-Nutzern, mit Prometheus und Grafana operationale Insights zu gewinnen.",[729],"Paul Meresanu","https://res.cloudinary.com/about-gitlab-com/image/upload/v1774465167/n5hlvrsrheadeccyr1oz.png","2026-04-28","CI/CD-Optimierung beginnt mit Transparenz. Eine erfolgreiche DevOps-Plattform\nim Unternehmensmaßstab umfasst das Verständnis von Pipeline-Performance,\nJob-Ausführungsmustern und quantifizierbaren operationalen Insights – insbesondere\nfür Unternehmen, die GitLab Self-Managed betreiben.\n\nUm GitLab-Kund(inn)en dabei zu helfen, den vollen Nutzen ihrer Plattform\nauszuschöpfen, haben wir die GitLab CI/CD Observability-Lösung als Teil unseres\nPlatform Excellence-Programms entwickelt. Sie verwandelt rohe Pipeline-Metriken\nin handlungsrelevante operationale Erkenntnisse.\n\nEin führendes Finanzdienstleistungsunternehmen hat gemeinsam mit GitLabs Customer\nSuccess Architect Transparenz über seine GitLab Self-Managed-Deployments\ngewonnen. Gemeinsam haben wir eine containerisierte Observability-Lösung\nimplementiert, die den Open-Source-gitlab-ci-pipelines-exporter mit\nunternehmensgerechter Prometheus- und Grafana-Infrastruktur kombiniert.\n\nIn diesem Artikel werden die Herausforderungen beim Pipeline-Management im\nUnternehmensmaßstab erläutert – und wie GitLab CI/CD Observability diese mit\neiner praxisnahen End-to-End-Implementierung adressiert.\n\n\n## Die Herausforderung: CI/CD-Performance messen\n\nVor der Implementierung einer Observability-Lösung sollte die\nMessdimension klar definiert sein:\n\n* **Welche Metriken sind relevant?** Pipeline-Dauer, Job-Erfolgsraten,\n  Queue-Zeiten, Runner-Auslastung\n* **Wer braucht Transparenz?** Entwickler(innen), DevOps-Engineers,\n  Plattformteams, Führungsebene\n* **Welche Entscheidungen werden damit getroffen?** Infrastrukturinvestitionen,\n  Engpass-Behebung, Kapazitätsplanung\n\n\n## Lösungsarchitektur: Ein vollständiges Dashboard-Set für Observability\n\nNach dem Deployment stellt der Observability-Stack ein Set von\nGrafana-Dashboards bereit, das Echtzeit- und historische Transparenz über die\nCI/CD-Plattform bietet. Ein typisches Deployment umfasst:\n\n* **Pipeline Overview Dashboard:** Eine übergeordnete Ansicht mit Gesamtzahl\n  der Pipeline-Läufe, Erfolgs-/Fehlerquoten über die Zeit (als gestapelte\n  Balken- oder Zeitreihencharts) und Trends bei der durchschnittlichen\n  Pipeline-Dauer. Panels verwenden farbcodierte Statusindikatoren (Grün für\n  Erfolg, Rot für Fehler, Gelb für Abbruch), damit Plattformteams\n  Verschlechterungen auf einen Blick erkennen.\n* **Job Performance Dashboard:** Drill-down-Panels mit Verteilungen der\n  einzelnen Job-Dauern (Histogramm), den 10 langsamsten Jobs nach\n  Durchschnittsdauer und Job-Fehler-Heatmaps nach Projekt und Stage. Hier\n  identifizieren Teams konkrete Engpass-Jobs, die sich zu optimieren lohnen.\n* **Runner & Infrastructure Dashboard:** Kombiniert Node-Exporter-Host-Metriken\n  (CPU, Arbeitsspeicher, Disk) mit Pipeline-Queue-Zeit-Daten, um\n  Infrastruktur-Sättigung mit Pipeline-Wartezeiten zu korrelieren. Nützlich\n  für Kapazitätsplanungsentscheidungen wie die Skalierung von Runner-Pools oder\n  das Upgrade von Instanzgrößen.\n* **Deployment Frequency Dashboard:** Verfolgt Deployment-Anzahl und\n  -Dauer über die Zeit pro Umgebung, abgestimmt auf DORA-Metriken. Hilft\n  der Engineering-Führungsebene, Lieferdurchsatz und Environment-Drift\n  (Commits hinter main) zu bewerten.\n\nJedes Dashboard wird automatisch über Grafanas dateibasiertes Provisioning\nbereitgestellt, sodass es konsistent über alle Umgebungen hinweg deployed wird.\nDie Dashboards lassen sich über Grafana-Variablen weiter anpassen, um nach\nProjekt, Ref/Branch oder Zeitraum zu filtern.\n\n![Lösungsarchitektur](https://res.cloudinary.com/about-gitlab-com/image/upload/v1777382608/Blog/Imported/blog-building-ci-cd-observability-stack-for-gitlab-self-managed/image1.png)\n\nDie Lösung benötigt zwei Exporter:\n\n* **Pipeline Exporter:** Erfasst CI/CD-Metriken über die GitLab API\n  (Pipeline-Dauer, Job-Status, Deployments)\n* **Node Exporter:** Erfasst Host-Metriken (CPU, Arbeitsspeicher, Disk)\n  für die Infrastruktur-Korrelation\n\n**Voraussetzungen:**\n\n* GitLab Self-Managed Version 18.1+\n* **Container-Orchestrierungsplattform:** Ein Kubernetes-Cluster (empfohlen\n  für Unternehmens-Deployments) oder eine Container-Runtime wie Docker/Podman\n  für kleinere Umgebungen oder Proof-of-Concept-Deployments. Die primäre\n  Deployment-Anleitung unten zielt auf Kubernetes; eine Docker-Compose-Alternative\n  ist im Anhang für lokales Testen und Evaluation verfügbar\n* GitLab Personal Access Token (Scope **read_api**)\n\nDie vollständige Implementierungsanleitung mit allen Kubernetes-Manifesten\nfolgt direkt im Anschluss.\n\n\n## Kubernetes-Deployment (empfohlen)\n\nFür Unternehmensumgebungen wird jede Komponente als separates Deployment in\neinem dedizierten Namespace deployed. Dieser Ansatz integriert sich in\nbestehende Cluster-Infrastruktur, Secrets-Management und Network-Policies.\n\n### 1. Namespace und Secret erstellen\n\n```bash\nkubectl create namespace gitlab-observability\n\n# GitLab-Token-Secret erstellen (siehe Abschnitt Secrets-Management\n# für unternehmensgerechte Ansätze mit externen Secret-Operatoren)\nkubectl create secret generic gitlab-token \\\n  --from-literal=token=glpat-xxxxxxxxxxxx \\\n  -n gitlab-observability\n```\n\n### 2. Pipeline Exporter deployen\n\n```yaml\n# exporter-deployment.yaml\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: gitlab-ci-pipelines-exporter\n  namespace: gitlab-observability\nspec:\n  replicas: 1\n  selector:\n    matchLabels:\n      app: gitlab-ci-pipelines-exporter\n  template:\n    metadata:\n      labels:\n        app: gitlab-ci-pipelines-exporter\n    spec:\n      containers:\n        - name: exporter\n          image: mvisonneau/gitlab-ci-pipelines-exporter:latest\n          ports:\n            - containerPort: 8080\n          env:\n            - name: GCPE_GITLAB_TOKEN\n              valueFrom:\n                secretKeyRef:\n                  name: gitlab-token\n                  key: token\n            - name: GCPE_CONFIG\n              value: /etc/gcpe/config.yml\n          volumeMounts:\n            - name: config\n              mountPath: /etc/gcpe\n      volumes:\n        - name: config\n          configMap:\n            name: gcpe-config\n---\napiVersion: v1\nkind: Service\nmetadata:\n  name: gitlab-ci-pipelines-exporter\n  namespace: gitlab-observability\nspec:\n  selector:\n    app: gitlab-ci-pipelines-exporter\n  ports:\n    - port: 8080\n      targetPort: 8080\n```\n\n### 3. Node Exporter deployen (DaemonSet)\n\n```yaml\n# node-exporter-daemonset.yaml\napiVersion: apps/v1\nkind: DaemonSet\nmetadata:\n  name: node-exporter\n  namespace: gitlab-observability\nspec:\n  selector:\n    matchLabels:\n      app: node-exporter\n  template:\n    metadata:\n      labels:\n        app: node-exporter\n    spec:\n      containers:\n        - name: node-exporter\n          image: prom/node-exporter:latest\n          ports:\n            - containerPort: 9100\n---\napiVersion: v1\nkind: Service\nmetadata:\n  name: node-exporter\n  namespace: gitlab-observability\nspec:\n  selector:\n    app: node-exporter\n  ports:\n    - port: 9100\n      targetPort: 9100\n```\n\n### 4. Prometheus deployen\n\n```yaml\n# prometheus-deployment.yaml\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: prometheus\n  namespace: gitlab-observability\nspec:\n  replicas: 1\n  selector:\n    matchLabels:\n      app: prometheus\n  template:\n    metadata:\n      labels:\n        app: prometheus\n    spec:\n      containers:\n        - name: prometheus\n          image: prom/prometheus:latest\n          ports:\n            - containerPort: 9090\n          volumeMounts:\n            - name: config\n              mountPath: /etc/prometheus\n      volumes:\n        - name: config\n          configMap:\n            name: prometheus-config\n---\napiVersion: v1\nkind: Service\nmetadata:\n  name: prometheus\n  namespace: gitlab-observability\nspec:\n  selector:\n    app: prometheus\n  ports:\n    - port: 9090\n      targetPort: 9090\n```\n\n### 5. Grafana deployen\n\nDas folgende Grafana-Deployment startet mit deaktivierter Authentifizierung\n(`GF_AUTH_ANONYMOUS_ENABLED: true`) für den einfachen Einstieg.\n\n**Diese Einstellung erlaubt jedem mit Netzwerkzugang, alle Dashboards ohne\nAnmeldung einzusehen.** Für Produktions-Deployments diese Variable entfernen\noder auf false setzen und einen geeigneten Authentifizierungs-Provider\n(LDAP, SAML/SSO oder OAuth) konfigurieren, um den Zugriff auf autorisierte\nNutzende zu beschränken.\n\n```yaml\n# grafana-deployment.yaml\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: grafana\n  namespace: gitlab-observability\nspec:\n  replicas: 1\n  selector:\n    matchLabels:\n      app: grafana\n  template:\n    metadata:\n      labels:\n        app: grafana\n    spec:\n      containers:\n        - name: grafana\n          image: grafana/grafana:10.0.0\n          ports:\n            - containerPort: 3000\n          env:\n            # Für Produktion ENTFERNEN oder auf 'false' setzen.\n            # Bei 'true' können alle Nutzenden mit Netzwerkzugang\n            # Dashboards ohne Authentifizierung einsehen.\n            - name: GF_AUTH_ANONYMOUS_ENABLED\n              value: 'true'\n          volumeMounts:\n            - name: dashboards-provider\n              mountPath: /etc/grafana/provisioning/dashboards\n            - name: datasources\n              mountPath: /etc/grafana/provisioning/datasources\n            - name: dashboards\n              mountPath: /var/lib/grafana/dashboards\n      volumes:\n        - name: dashboards-provider\n          configMap:\n            name: grafana-dashboards-provider\n        - name: datasources\n          configMap:\n            name: grafana-datasources\n        - name: dashboards\n          configMap:\n            name: grafana-dashboards\n---\napiVersion: v1\nkind: Service\nmetadata:\n  name: grafana\n  namespace: gitlab-observability\nspec:\n  selector:\n    app: grafana\n  ports:\n    - port: 3000\n      targetPort: 3000\n```\n\n### 6. Network Policy setzen\n\nDen Inter-Pod-Traffic auf die erforderlichen Kommunikationspfade beschränken:\n\n```yaml\n# network-policy.yaml\napiVersion: networking.k8s.io/v1\nkind: NetworkPolicy\nmetadata:\n  name: observability-policy\n  namespace: gitlab-observability\nspec:\n  podSelector: {}\n  policyTypes:\n    - Ingress\n  ingress:\n    # Prometheus scrapt Exporter und Node-Exporter\n    - from:\n        - podSelector:\n            matchLabels:\n              app: prometheus\n      ports:\n        - port: 8080\n        - port: 9100\n    # Grafana fragt Prometheus ab\n    - from:\n        - podSelector:\n            matchLabels:\n              app: grafana\n      ports:\n        - port: 9090\n```\n\n### 7. Validieren\n\n```bash\nkubectl get pods -n gitlab-observability\nkubectl port-forward svc/grafana 3000:3000 -n gitlab-observability\ncurl http://localhost:3000/api/health\n```\n\n\n## Konfigurationsreferenz\n\n### Exporter-Konfiguration\n\n```yaml\n# gitlab-ci-pipelines-exporter.yml (ConfigMap: gcpe-config)\nlog:\n  level: info\ngitlab:\n  url: https://gitlab.your-domain.com\n  maximum_requests_per_second: 10\nproject_defaults:\n  pull:\n    pipeline:\n      jobs:\n        enabled: true\nwildcards:\n  - owner:\n      name: your-group-name\n      kind: group\n    archived: false\n```\n\n### Prometheus-Konfiguration\n\n```yaml\n# prometheus.yml (ConfigMap: prometheus-config)\nglobal:\n  scrape_interval: 15s\nscrape_configs:\n  - job_name: 'gitlab-ci-pipelines-exporter'\n    static_configs:\n      - targets: ['gitlab-ci-pipelines-exporter:8080']\n  - job_name: 'node-exporter'\n    static_configs:\n      - targets: ['node-exporter:9100']\n```\n\n### Grafana-Datenquellen\n\n```yaml\n# datasources.yml (ConfigMap: grafana-datasources)\napiVersion: 1\ndatasources:\n  - name: Prometheus\n    type: prometheus\n    access: proxy\n    url: http://prometheus:9090\n    isDefault: true\n# dashboards.yml (ConfigMap: grafana-dashboards-provider)\napiVersion: 1\nproviders:\n  - name: 'default'\n    folder: 'GitLab CI/CD'\n    type: file\n    options:\n      path: /var/lib/grafana/dashboards\n```\n\n\n## Wichtige Metriken\n\n### Pipeline-Exporter-Metriken\n\n| Metrik | Beschreibung |\n| :---- | :---- |\n| `gitlab_ci_pipeline_duration_seconds` | Pipeline-Ausführungszeit |\n| `gitlab_ci_pipeline_status` | Pipeline-Erfolg/-Fehler nach Projekt |\n| `gitlab_ci_pipeline_job_duration_seconds` | Einzelne Job-Ausführungszeit |\n| `gitlab_ci_pipeline_job_status` | Job-Erfolgs-/-Fehlerstatus |\n| `gitlab_ci_pipeline_job_artifact_size_bytes` | Artifact-Speicherverbrauch |\n| `gitlab_ci_pipeline_coverage` | Code-Coverage-Prozentsatz |\n| `gitlab_ci_environment_deployment_count` | Deployment-Häufigkeit |\n| `gitlab_ci_environment_deployment_duration_seconds` | Deployment-Ausführungszeit |\n| `gitlab_ci_environment_behind_commits_count` | Environment-Drift gegenüber main |\n\n### Node-Exporter-Metriken\n\n| Metrik | Beschreibung |\n| :---- | :---- |\n| `node_cpu_seconds_total` | CPU-Auslastung |\n| `node_memory_MemAvailable_bytes` | Verfügbarer Arbeitsspeicher |\n| `node_filesystem_avail_bytes` | Verfügbarer Festplattenspeicher |\n| `node_load1` | 1-Minuten-Lastdurchschnitt |\n\n\n## Fehlerbehebung\n\n### Grafana-Plugin-Installation in Air-gapped-Umgebungen\n\nFür Offline-Umgebungen Plugins manuell installieren. Beispiel für Kubernetes:\n\n```bash\n# Plugin-ZIP in den Grafana-Pod kopieren\nkubectl cp grafana-polystat-panel-2.1.16.zip \\\n  gitlab-observability/grafana-\u003Cpod-id>:/tmp/\n# Plugin entpacken\nkubectl exec -it -n gitlab-observability deploy/grafana -- \\\n  sh -c \"unzip /tmp/grafana-polystat-panel-2.1.16.zip -d /var/lib/grafana/plugins/\"\n# Grafana-Pod neu starten\nkubectl rollout restart deployment/grafana -n gitlab-observability\n# Installation prüfen\nkubectl exec -it -n gitlab-observability deploy/grafana -- \\\n  ls -al /var/lib/grafana/plugins/\n```\n\n\n## Unternehmensaspekte\n\nFür regulierte Branchen gilt:\n\n* **Token-Sicherheit:** GitLab Personal Access Tokens in einem dedizierten\n  Secrets-Manager speichern, nicht hartcodiert in ConfigMaps. Token-Rotation\n  durchsetzen und den Scope auf **read\\_api** beschränken.\n* **Netzwerksegmentierung:** Hinter einem Reverse Proxy mit TLS-Terminierung\n  deployen. In Kubernetes einen Ingress-Controller mit automatisierter\n  Zertifikatsbereitstellung verwenden.\n* **Authentifizierung:** Grafana mit dem Identity Provider der Organisation\n  konfigurieren (SAML, LDAP oder OAuth/OIDC), um rollenbasierte\n  Zugriffskontrolle auf Dashboards durchzusetzen.\n\n\n## Warum GitLab?\n\nGitLabs API-First-Design ermöglicht individuelle Observability-Lösungen, die\nnative Funktionen wie Value Stream Analytics und DORA-Metriken ergänzen. Die\noffene Architektur erlaubt es Unternehmen, bewährte Open-Source-Werkzeuge –\nwie den gitlab-ci-pipelines-exporter – direkt in bestehende\nUnternehmensinfrastruktur zu integrieren, ohne etablierte Workflows zu\nunterbrechen.\n\nMit wachsender Observability-Reife bieten GitLabs eingebaute\nObservability-Funktionen einen natürlichen nächsten Schritt – tiefere,\nintegrierte Transparenz ohne zusätzliche Werkzeuge. Mehr zu den nativen\nPlattformfunktionen unter\n[GitLab Observability](https://docs.gitlab.com/operations/observability/observability/).\n",[111,23,22],{"featured":13,"template":14,"slug":735},"how-to-build-ci-cd-observability-at-scale",{"content":737,"config":747},{"body":738,"title":739,"description":740,"authors":741,"heroImage":743,"date":744,"category":11,"tags":745},"## Abschnitt 1: Das Modell verstehen\n*Für Engineering-Leads und Entscheidungsträger: Konzept, Anwendungsfälle und Architekturprinzipien. Konfigurationsdetails folgen in Abschnitt 2.*\n\nDie meisten CI/CD-Werkzeuge können einen Build ausführen und ein Deployment anstoßen. Der Unterschied zeigt sich erst dann, wenn die Delivery-Anforderungen komplexer werden: ein Monorepo mit einem Dutzend Services, Microservices über mehrere Repositories verteilt, Deployments in Dutzende von Umgebungen gleichzeitig – oder ein Platform-Team, das organisationsweite Standards durchsetzen will, ohne dabei zum Engpass zu werden.\n\nGitLabs Pipeline-Modell wurde für genau diese Komplexität entwickelt. Parent-Child-Pipelines, DAG-Execution, dynamische Pipeline-Generierung, Multi-Project-Trigger, Merge-Request-Pipelines mit Merged-Results-Verarbeitung und CI/CD Components lösen jeweils eine eigene Klasse von Problemen. Da sich diese Bausteine kombinieren lassen, erschließt das vollständige Modell mehr als nur kürzere Pipeline-Laufzeiten.\n\nDieser Artikel beschreibt die fünf Muster, bei denen das Modell seine Stärken deutlich zeigt – jeweils zugeordnet zu einem konkreten Engineering-Szenario. Konfigurationen und Implementierungsdetails folgen in Abschnitt 2.\n\n### 1. Monorepos: Parent-Child-Pipelines und DAG-Execution\n\n**Das Problem:** Ein Monorepo enthält Frontend, Backend und Dokumentation. Jeder Commit löst einen vollständigen Rebuild aller Komponenten aus – auch wenn sich nur eine README-Datei geändert hat.\n\nGitLab kombiniert zwei sich ergänzende Mechanismen: [Parent-Child-Pipelines](https://docs.gitlab.com/ci/pipelines/downstream_pipelines/#parent-child-pipelines) ermöglichen es einer übergeordneten Pipeline, isolierte Child-Pipelines zu starten. [DAG-Execution via `needs`](https://docs.gitlab.com/ci/yaml/#needs) bricht die starre Stage-Reihenfolge auf und startet Jobs, sobald ihre Abhängigkeiten abgeschlossen sind – nicht erst, wenn alle Jobs einer Stage fertig sind.\n\nEine Parent-Pipeline erkennt, welche Teile des Repos sich geändert haben, und löst ausschließlich die betroffenen Child-Pipelines aus. Jeder Service verwaltet seine eigene Pipeline-Konfiguration; Änderungen in einem Service können keine anderen beeinflussen. Damit bleibt die Komplexität beherrschbar, während das Repository und das Team wachsen.\n\nEinen technischen Aspekt gilt es dabei zu kennen: Wenn mehrere Dateien an einen einzelnen `trigger: include:`-Block übergeben werden, fusioniert GitLab sie zu einer einzigen Child-Pipeline-Konfiguration. Jobs aus diesen Dateien teilen denselben Pipeline-Kontext und können sich gegenseitig per `needs:` referenzieren – das ist die Voraussetzung für die DAG-Optimierung. Werden die Dateien stattdessen auf separate Trigger-Jobs aufgeteilt, entsteht jeweils eine isolierte Pipeline, und dateiübergreifende `needs:`-Referenzen funktionieren nicht.\n\nIn großen Monorepos lassen sich Pipeline-Laufzeiten durch DAG-Execution deutlich reduzieren, da Jobs nicht mehr auf unabhängige Arbeitsschritte in derselben Stage warten.\n\n### 2. Microservices: Cross-Repo-Pipelines über mehrere Projekte\n\n**Das Problem:** Frontend und Backend leben in separaten Repositories. Wenn das Frontend-Team eine Änderung ausliefert, ist nicht erkennbar, ob sie die Backend-Integration beeinträchtigt – und umgekehrt.\n\n[Multi-Project-Pipelines](https://docs.gitlab.com/ci/pipelines/downstream_pipelines/#multi-project-pipelines) ermöglichen es, aus einem Projekt heraus eine Pipeline in einem anderen Projekt auszulösen und auf das Ergebnis zu warten. Das auslösende Projekt sieht die verknüpfte Downstream-Pipeline direkt in seiner eigenen Pipeline-Ansicht.\n\nIn der Praxis erstellt die Frontend-Pipeline ein API-Contract-Artifact und veröffentlicht es, bevor die Backend-Pipeline ausgelöst wird. Das Backend ruft dieses Artifact über die [Jobs API](https://docs.gitlab.com/api/jobs/#download-a-single-artifact-file-from-specific-tag-or-branch) ab und validiert es, bevor weitere Schritte erlaubt sind. Wird eine Breaking Change erkannt, schlägt die Backend-Pipeline fehl – und mit ihr die Frontend-Pipeline. Probleme, die bisher erst in der Produktion sichtbar wurden, werden damit im Pipeline-Prozess abgefangen. Die Abhängigkeit zwischen Services wird sichtbar, nachvollziehbar und aktiv verwaltbar.\n\n![Cross-project pipelines](https://res.cloudinary.com/about-gitlab-com/image/upload/v1775738762/Blog/Imported/hackathon-fake-blog-post-s/image4_h6mfsb.png \"Cross-project pipelines\") *Cross-project pipelines*\n\n### 3. Multi-Tenant/Matrix-Deployments: Dynamische Child-Pipelines\n\n**Das Problem:** Dieselbe Anwendung wird in 15 Kundenumgebungen, drei Cloud-Regionen oder den Stages Dev/Staging/Prod deployed. Manuelle Anpassungen je Umgebung führen zu Konfigurationsdrift. Eine separate Pipeline pro Umgebung ist von Anfang an nicht wartbar.\n\n[Dynamische Child-Pipelines](https://docs.gitlab.com/ci/pipelines/downstream_pipelines/#dynamic-child-pipelines) generieren die Pipeline-Struktur zur Laufzeit. Ein Job führt ein Skript aus, das eine YAML-Datei erzeugt – und diese YAML-Datei wird zur Pipeline für den nächsten Schritt. Die Pipeline-Struktur selbst wird damit zu Daten.\n\nDas Generierungsskript iteriert über eine `ENVIRONMENTS`-Variable, statt jede Umgebung fest zu kodieren. Eine neue Umgebung lässt sich durch Anpassen der Variable hinzufügen – ohne Änderungen an der Pipeline-Konfiguration selbst. Trigger-Jobs erben mit `extends:` eine gemeinsame Template-Konfiguration, sodass `strategy: depend` einmal definiert und nicht für jeden Trigger-Job wiederholt wird. Ein `when: manual`-Gate für das Produktions-Deployment ist direkt in den Pipeline-Graph integriert.\n\nPlatform-Teams nutzen dieses Muster, um Dutzende von Umgebungen zu verwalten, ohne Pipeline-Logik zu duplizieren.\n\n![Dynamic pipeline](https://res.cloudinary.com/about-gitlab-com/image/upload/v1775738765/Blog/Imported/hackathon-fake-blog-post-s/image7_wr0kx2.png \"Dynamic pipeline\")\n\n### 4. MR-First-Delivery: Merge-Request-Pipelines, Merged-Results und Workflow-Routing\n\n**Das Problem:** Die Pipeline läuft bei jedem Push auf jeden Branch. Aufwändige Tests werden auf Feature-Branches ausgeführt, die nie gemergt werden. Gleichzeitig gibt es keine Garantie, dass das Getestete dem entspricht, was nach dem Merge auf `main` tatsächlich landet.\n\nGitLab kombiniert drei ineinandergreifende Mechanismen: [Merge-Request-Pipelines](https://docs.gitlab.com/ci/pipelines/merge_request_pipelines/) laufen ausschließlich dann, wenn ein Merge Request existiert – nicht bei jedem Branch-Push. Allein dadurch entfällt ein erheblicher Anteil unnötiger Compute-Ausführungen. [Merged-Results-Pipelines](https://docs.gitlab.com/ci/pipelines/merged_results_pipelines/) gehen einen Schritt weiter: GitLab erstellt einen temporären Merge-Commit aus dem Branch und dem aktuellen Ziel-Branch und führt die Pipeline dagegen aus. Getestet wird damit das tatsächliche Ergebnis des Merges – nicht der Branch in Isolation. [Workflow-Rules](https://docs.gitlab.com/ci/yaml/workflow/) definieren schließlich, welcher Pipeline-Typ unter welchen Bedingungen ausgeführt wird. Die `$CI_OPEN_MERGE_REQUESTS`-Guard verhindert dabei, dass für einen Branch mit offenem MR doppelte Pipelines ausgelöst werden.\n\nDas Ergebnis ist ein Pipeline-Verhalten, das sich je nach Kontext unterscheidet: Ein Push auf einen Feature-Branch ohne offenen MR führt nur Lint und Unit-Tests aus. Sobald ein MR geöffnet wird, wechseln die Workflow-Rules auf eine MR-Pipeline mit der vollständigen Test-Suite gegen das Merged-Result. Ein Merge auf `main` stellt ein manuelles Produktions-Deployment in die Warteschlange. Der Nightly-Scan läuft einmalig als geplante Pipeline – nicht bei jedem Commit.\n\nMerged-Results-Pipelines fangen dabei die Klasse von Fehlern ab, die erst nach einem Merge sichtbar werden – bevor sie `main` erreichen.\n\n### 5. Governed Pipelines: CI/CD Components\n\n**Das Problem:** Das Platform-Team hat den richtigen Weg für Build, Test und Deploy definiert. Jedes Anwendungsteam pflegt jedoch eine eigene `.gitlab-ci.yml` mit subtilen Abweichungen. Security-Scanning wird übersprungen. Deployment-Standards driften. Audits werden aufwändig.\n\n[CI/CD Components](https://docs.gitlab.com/ci/components/) ermöglichen es Platform-Teams, versionierte, wiederverwendbare Pipeline-Bausteine zu veröffentlichen. Anwendungsteams binden sie mit einer einzigen `include:`-Zeile ein – kein Copy-Paste, kein Drift. Components sind über den [CI/CD Catalog](https://docs.gitlab.com/ci/components/#cicd-catalog) auffindbar, sodass Teams bewährte Bausteine finden und übernehmen können, ohne das Platform-Team direkt einschalten zu müssen.\n\nDrei Zeilen `include:` ersetzen hunderte von duplizierten YAML-Zeilen. Das Platform-Team kann einen Security-Fix in einer neuen Komponentenversion veröffentlichen – Teams steigen auf ihrem eigenen Zeitplan um, oder das Platform-Team fixiert alle auf eine Mindestversion. In beiden Fällen propagiert eine Änderung organisationsweit, statt repo-für-repo angewendet zu werden.\n\nKombiniert mit [Resource Groups](https://docs.gitlab.com/ci/resource_groups/) zur Vermeidung konkurrierender Deployments und [Protected Environments](https://docs.gitlab.com/ci/environments/protected_environments/) für Freigabe-Gates entsteht eine governed Delivery-Plattform, auf der **Compliance der Standard ist, nicht die Ausnahme**. Platform-Teams setzen Vorgaben durch, ohne zum Engpass zu werden.\n\n![Component pipeline (imported jobs)](https://res.cloudinary.com/about-gitlab-com/image/upload/v1775738776/Blog/Imported/hackathon-fake-blog-post-s/image2_pizuxd.png \"Component pipeline (imported jobs)\")\n\n## Das Modell als Ganzes\n\nKeines dieser Muster existiert isoliert. Der Wert von GitLabs Pipeline-Modell liegt in der Kombinierbarkeit seiner Bausteine:\n\n- Ein Monorepo nutzt Parent-Child-Pipelines, und jede Child-Pipeline nutzt DAG-Execution.\n- Eine Microservices-Plattform nutzt Multi-Project-Pipelines, und jedes Projekt nutzt MR-Pipelines mit Merged-Results.\n- Eine governed Plattform nutzt CI/CD Components, um die obigen Muster organisationsweit zu standardisieren.\n\nDie meisten Teams entdecken eines dieser Muster, wenn sie auf ein konkretes Problem stoßen. Teams, die das vollständige Modell verstehen, entwickeln daraus eine Delivery-Infrastruktur, die tatsächlich abbildet, wie ihre Engineering-Organisation arbeitet – und mit ihr wächst.\n\n## Weitere Muster\n\nDas Pipeline-Modell geht über die fünf vorgestellten Muster hinaus:\n\n- [Review Apps mit dynamischen Umgebungen](https://docs.gitlab.com/ci/environments/) erstellen für jeden Feature-Branch eine Live-Vorschau und räumen sie automatisch auf, wenn der MR geschlossen wird.\n- [Caching- und Artifact-Strategien](https://docs.gitlab.com/ci/caching/) sind nach der strukturellen Arbeit häufig der direkteste Weg zur weiteren Laufzeitoptimierung – ohne die Pipeline-Struktur zu verändern.\n- [Geplante und API-ausgelöste Pipelines](https://docs.gitlab.com/ci/pipelines/schedules/) eignen sich für Workloads, die nicht bei jedem Code-Push laufen sollten: Nightly-Security-Scans, Compliance-Reports und Release-Automatisierung lassen sich als geplante oder [API-ausgelöste](https://docs.gitlab.com/ci/triggers/) Pipelines mit `$CI_PIPELINE_SOURCE`-Routing modellieren.\n\n> [GitLab Ultimate kostenlos testen](https://about.gitlab.com/de-de/free-trial/) und Pipeline-Logik ab heute einsetzen.\n\n## Für deutsche Unternehmen: Regulatorischer Kontext\n\nTeams, die Pipeline-Governance nach Muster 5 einführen, adressieren dabei möglicherweise auch Anforderungen, die regulatorische Frameworks an sichere Softwareentwicklungsprozesse stellen.\n\nCI/CD Components mit erzwungenen Security-Gates könnten Anforderungen an sichere Entwicklungsprozesse betreffen – beispielsweise in Bereichen, die Frameworks wie NIS2, ISO 27001 oder BSI IT-Grundschutz an den Software-Entwicklungslebenszyklus adressieren. Protected Environments und Resource Groups betreffen ähnliche Themen im Bereich Änderungskontrolle und Umgebungstrennung, wie sie in Governance-Frameworks typischerweise explizit formuliert sind.\n\nMulti-Project-Pipelines mit API-Contract-Validierung (Muster 2) schaffen Sichtbarkeit über Service-Abhängigkeiten hinweg – ein Aspekt, den Frameworks zur Lieferkettensicherheit adressieren.\n\nMerged-Results-Pipelines (Muster 4) dokumentieren automatisch, dass das tatsächliche Merge-Ergebnis getestet wurde, nicht nur der Feature-Branch in Isolation. Dies könnte Anforderungen an nachvollziehbare Änderungsprozesse betreffen, wie sie in Change-Management-Kontrollen verschiedener Sicherheitsframeworks formuliert sind.\n\nFür konkrete Compliance-Anforderungen im eigenen regulatorischen Umfeld empfiehlt sich Rücksprache mit entsprechender Fachberatung.\n\n## Abschnitt 2: Konfiguration und Implementierung\n\n*Für Entwicklungsteams und DevOps-Praktiker: ausgewählte Konfigurationsbeispiele zu den Mustern 1, 4 und 5. Für vollständige Konfigurationen aller Muster: [englischer Originalartikel](https://about.gitlab.com/blog/5-ways-gitlab-pipeline-logic-solves-real-engineering-problems/).*\n\nDie folgenden Konfigurationen sind illustrativ aufgebaut. Die Skripte verwenden `echo`-Befehle, um das Wesentliche sichtbar zu halten. Für den produktiven Einsatz werden die `echo`-Befehle durch die tatsächlichen Build-, Test- und Deploy-Schritte ersetzt.\n\n### Muster 1: Parent-Child-Pipelines und DAG-Execution\n\nEine Parent-Pipeline erkennt Änderungen und löst nur die betroffenen Child-Pipelines aus:\n\n```yaml # .gitlab-ci.yml stages:\n  - trigger\n\ntrigger-services:\n  stage: trigger\n  trigger:\n    include:\n      - local: '.gitlab/ci/api-service.yml'\n      - local: '.gitlab/ci/web-service.yml'\n      - local: '.gitlab/ci/worker-service.yml'\n    strategy: depend\n```\n\nInnerhalb der Child-Pipeline ermöglicht `needs:` DAG-Execution – der Test startet, sobald der Build abgeschlossen ist, ohne auf andere Jobs in derselben Stage zu warten:\n\n```yaml # .gitlab/ci/api-service.yml stages:\n  - build\n  - test\n\nbuild-api:\n  stage: build\n  script:\n    - echo \"Building API service\"\n\ntest-api:\n  stage: test\n  needs: [build-api]\n  script:\n    - echo \"Running API tests\"\n```\n\n![Local downstream pipelines](https://res.cloudinary.com/about-gitlab-com/image/upload/v1775738759/Blog/Imported/hackathon-fake-blog-post-s/image3_vwj3rz.png \"Local downstream pipelines\")\n\n### Muster 4: MR-First-Delivery\n\nWorkflow-Rules, MR-Pipelines und Merged-Results zusammen ergeben ein kontextabhängiges Pipeline-Verhalten:\n\n```yaml # .gitlab-ci.yml workflow:\n  rules:\n    - if: $CI_PIPELINE_SOURCE == \"merge_request_event\"\n    - if: $CI_COMMIT_BRANCH && $CI_OPEN_MERGE_REQUESTS\n      when: never\n    - if: $CI_COMMIT_BRANCH\n    - if: $CI_PIPELINE_SOURCE == \"schedule\"\n\nstages:\n  - fast-checks\n  - expensive-tests\n  - deploy\n\nlint-code:\n  stage: fast-checks\n  script:\n    - echo \"Running linter\"\n  rules:\n    - if: $CI_PIPELINE_SOURCE == \"push\"\n    - if: $CI_PIPELINE_SOURCE == \"merge_request_event\"\n    - if: $CI_COMMIT_BRANCH == \"main\"\n\nunit-tests:\n  stage: fast-checks\n  script:\n    - echo \"Running unit tests\"\n  rules:\n    - if: $CI_PIPELINE_SOURCE == \"push\"\n    - if: $CI_PIPELINE_SOURCE == \"merge_request_event\"\n    - if: $CI_COMMIT_BRANCH == \"main\"\n\nintegration-tests:\n  stage: expensive-tests\n  script:\n    - echo \"Running integration tests (15 min)\"\n  rules:\n    - if: $CI_PIPELINE_SOURCE == \"merge_request_event\"\n    - if: $CI_COMMIT_BRANCH == \"main\"\n\ne2e-tests:\n  stage: expensive-tests\n  script:\n    - echo \"Running E2E tests (30 min)\"\n  rules:\n    - if: $CI_PIPELINE_SOURCE == \"merge_request_event\"\n    - if: $CI_COMMIT_BRANCH == \"main\"\n\nnightly-comprehensive-scan:\n  stage: expensive-tests\n  script:\n    - echo \"Running full nightly suite (2 hours)\"\n  rules:\n    - if: $CI_PIPELINE_SOURCE == \"schedule\"\n\ndeploy-production:\n  stage: deploy\n  script:\n    - echo \"Deploying to production\"\n  rules:\n    - if: $CI_COMMIT_BRANCH == \"main\"\n      when: manual\n```\n\n![Conditional pipelines (within a branch with no MR)](https://res.cloudinary.com/about-gitlab-com/image/upload/v1775738768/Blog/Imported/hackathon-fake-blog-post-s/image6_dnfcny.png \"Conditional pipelines (within a branch with no MR)\")\n\n![Conditional pipelines (within an MR)](https://res.cloudinary.com/about-gitlab-com/image/upload/v1775738772/Blog/Imported/hackathon-fake-blog-post-s/image1_wyiafu.png \"Conditional pipelines (within an MR)\")\n\n![Conditional pipelines (on the main branch)](https://res.cloudinary.com/about-gitlab-com/image/upload/v1775738774/Blog/Imported/hackathon-fake-blog-post-s/image5_r6lkfd.png \"Conditional pipelines (on the main branch)\")\n\n### Muster 5: CI/CD Components\n\nEine Komponentendefinition aus einer gemeinsamen Bibliothek:\n\n```yaml # templates/deploy.yml spec:\n  inputs:\n    stage:\n      default: deploy\n    environment:\n      default: production\n--- deploy-job:\n  stage: $[[ inputs.stage ]]\n  script:\n    - echo \"Deploying $APP_NAME to $[[ inputs.environment ]]\"\n    - echo \"Deploy URL: $DEPLOY_URL\"\n  environment:\n    name: $[[ inputs.environment ]]\n```\n\nSo bindet ein Anwendungsteam die Komponenten ein:\n\n```yaml # Application repo: .gitlab-ci.yml variables:\n  APP_NAME: \"my-awesome-app\"\n  DEPLOY_URL: \"https://api.example.com\"\n\ninclude:\n  - component: gitlab.com/my-org/component-library/build@v1.0.6\n  - component: gitlab.com/my-org/component-library/test@v1.0.6\n  - component: gitlab.com/my-org/component-library/deploy@v1.0.6\n    inputs:\n      environment: staging\n\nstages:\n  - build\n  - test\n  - deploy\n```\n\n### Orientierung zu den Mustern 2 und 3\n\n**Muster 2 (Multi-Project-Pipelines):** Das Frontend-Repository publiziert ein API-Contract-Artifact und löst anschließend die Backend-Pipeline aus. Das Backend ruft das Artifact über die GitLab Jobs API ab und validiert es. Der `integration-test`-Job läuft dabei nur dann, wenn er von einer Upstream-Pipeline ausgelöst wurde (`$CI_PIPELINE_SOURCE == \"pipeline\"`), nicht bei einem eigenständigen Push. Die Frontend-Projekt-ID wird als CI/CD-Variable gesetzt, um Hardcoding zu vermeiden. Vollständige Konfigurationen beider Repositories: [englischer Originalartikel](https://about.gitlab.com/blog/5-ways-gitlab-pipeline-logic-solves-real-engineering-problems/#2-microservices-cross-repo-multi-project-pipelines).\n\n**Muster 3 (Dynamische Child-Pipelines):** Ein `generate-config`-Job erzeugt zur Laufzeit environment-spezifische YAML-Dateien. Trigger-Jobs nutzen `extends:` für gemeinsam genutzte Konfiguration und `needs:` für sequenzielle Promotion (dev → staging → prod mit manuellem Gate). Vollständige Konfiguration: [englischer Originalartikel](https://about.gitlab.com/blog/5-ways-gitlab-pipeline-logic-solves-real-engineering-problems/#3-multi-tenant--matrix-deployments-dynamic-child-pipelines).\n\n## Weiterführende Artikel\n\n- [Variable and artifact sharing in GitLab parent-child pipelines](https://about.gitlab.com/blog/variable-and-artifact-sharing-in-gitlab-parent-child-pipelines/)\n- [CI/CD inputs: Secure and preferred method to pass parameters to a pipeline](https://about.gitlab.com/blog/ci-cd-inputs-secure-and-preferred-method-to-pass-parameters-to-a-pipeline/)\n- [Tutorial: How to set up your first GitLab CI/CD component](https://about.gitlab.com/blog/tutorial-how-to-set-up-your-first-gitlab-ci-cd-component/)\n- [How to include file references in your CI/CD components](https://about.gitlab.com/blog/how-to-include-file-references-in-your-ci-cd-components/)\n- [FAQ: GitLab CI/CD Catalog](https://about.gitlab.com/blog/faq-gitlab-ci-cd-catalog/)\n- [Building a GitLab CI/CD pipeline for a monorepo the easy way](https://about.gitlab.com/blog/building-a-gitlab-ci-cd-pipeline-for-a-monorepo-the-easy-way/)\n- [A CI/CD component builder's journey](https://about.gitlab.com/blog/a-ci-component-builders-journey/)\n- [CI/CD Catalog goes GA: No more building pipelines from scratch](https://about.gitlab.com/blog/ci-cd-catalog-goes-ga-no-more-building-pipelines-from-scratch/)","5 GitLab-Pipeline-Muster für komplexe Engineering-Herausforderungen","Wie Parent-Child-Pipelines, DAG-Execution, MR-Pipelines und CI/CD Components komplexe Delivery-Probleme lösen – von Monorepos bis zur governed Plattform.",[742],"Omid Khan","https://res.cloudinary.com/about-gitlab-com/image/upload/v1772721753/frfsm1qfscwrmsyzj1qn.png","2026-04-09",[111,746,22,24],"DevOps platform",{"featured":28,"template":14,"slug":748},"5-ways-gitlab-pipeline-logic-solves-real-engineering-problems",{"content":750,"config":761},{"category":11,"tags":751,"body":753,"date":754,"heroImage":755,"authors":756,"title":759,"description":760},[22,752,111],"git","Enterprise-Migrationen in regulierten Branchen wie Finanzwesen und Automobilindustrie erfordern systematisches Risikomanagement gemäß NIS2-Richtlinie Artikel 21. Die mehrstufige Migrationsstruktur mit Testläufen vor Produktions-Wellen und kontrollierten Change-Freezes demonstriert Business-Continuity-Management in der Praxis. GitLab Professional Services organisiert Migrationen in Wellen von 200-300 Projekten, um Komplexität zu managen und API-Rate-Limits zu respektieren.\n\n## Überblick\n\nGitLab bietet [Congregate](https://gitlab.com/gitlab-org/professional-services-automation/tools/migration/congregate/) (maintained by GitLab Professional Services) und [eingebauten Git-Repository-Import](https://docs.gitlab.com/user/project/import/repo_by_url/) für Migrationen von Azure DevOps (ADO). Beide Optionen unterstützen Repository-basierte oder Bulk-Migration und erhalten Git-Commit-History, Branches und Tags. Mit Congregate werden zusätzliche Assets wie Wikis, Work Items, CI/CD-Variablen, Container-Images, Packages und Pipelines migriert (siehe [Feature-Matrix](https://gitlab.com/gitlab-org/professional-services-automation/tools/migration/congregate/-/blob/master/customer/ado-migration-features-matrix.md)).\n\nEnterprises folgen typischerweise einem mehrstufigen Ansatz:\n\n- Repositories von ADO zu GitLab migrieren (Congregate oder eingebauter Import)\n- Pipelines von Azure Pipelines zu GitLab CI/CD migrieren\n- Verbleibende Assets wie Boards, Work Items und Artifacts zu GitLab Issues, Epics und Package/Container Registries migrieren\n\nMehrstufige Migrationsphasen (siehe [Diagramm](https://about.gitlab.com/blog/migration-from-azure-devops-to-gitlab/#overview)):\n\n- Prerequisites (IdP, Runners, Change Management)\n- Migration Phase (Source Code, History, Work Items)\n- Post-Migration (Pipelines, Assets, Security)\n\n## Migration planen\n\n**Zentrale Planungsfragen:**\n\n- Wie schnell muss die Migration abgeschlossen werden?\n- Was genau wird migriert?\n- Wer führt die Migration durch?\n- Welche Organisationsstruktur wird in GitLab benötigt?\n- Welche Einschränkungen, Limitierungen oder Fallstricke müssen berücksichtigt werden?\n\nDie Timeline bestimmt weitgehend den Migrationsansatz. Identifizierung von Champions oder Teams mit ADO- und GitLab-Erfahrung unterstützt Adoption und Guidance.\n\n**Inventar erstellen:**\n\nDas GitLab Professional Services [Evaluate](https://gitlab.com/gitlab-org/professional-services-automation/tools/utilities/evaluate#beta-azure-devops)-Tool produziert ein vollständiges Inventar der Azure DevOps Organisation: Repositories, PR-Counts, Contributors, Pipelines, Work Items, CI/CD-Variablen. Bei Professional Services Engagements wird dieser Report mit Engagement Manager oder Technical Architect geteilt für Migrationsplanung.\n\nMigrations-Timing wird primär bestimmt durch Pull-Request-Count, Repository-Größe und Contribution-Menge. Beispiel: 1.000 kleine Repositories mit wenigen PRs migrieren schneller als wenige Repositories mit Zehntausenden PRs. Inventar-Daten ermöglichen Aufwands-Schätzung und Test-Run-Planung.\n\nIn Professional Services Engagements werden Migrationen in Wellen von 200-300 Projekten organisiert, um Komplexität zu managen und API-Rate-Limits zu respektieren (sowohl [GitLab](https://docs.gitlab.com/security/rate_limits/) als auch [ADO](https://learn.microsoft.com/en-us/azure/devops/integrate/concepts/rate-limits?view=azure-devops)).\n\n**Tool-Auswahl:**\n\nGitLabs [eingebauter Repository-Importer](https://docs.gitlab.com/user/project/import/repo_by_url/) migriert Git-Repositories (Commits, Branches, Tags) einzeln. Congregate erhält Pull Requests (in GitLab: Merge Requests), Kommentare und Metadata; der eingebaute Import fokussiert auf Git-Daten (History, Branches, Tags).\n\nAssets die typischerweise separate Migration oder manuelle Neuerstellung erfordern:\n\n- Azure Pipelines → GitLab CI/CD Pipelines (siehe [CI/CD YAML](https://docs.gitlab.com/ci/yaml/) oder [CI/CD Components](https://docs.gitlab.com/ci/components/)). Alternativ: AI-basierte Pipeline-Konvertierung in Congregate.\n- Work Items und Boards → GitLab Issues, Epics, Issue Boards\n- Artifacts, Container-Images (ACR) → GitLab Package/Container Registry\n- Service Hooks, externe Integrationen → in GitLab neu erstellen\n\n[Permissions-Modelle](https://docs.gitlab.com/user/permissions/) unterscheiden sich zwischen ADO und GitLab. Permissions-Mapping planen statt exakter Preservation zu erwarten.\n\n**Organisationsstruktur in GitLab:**\n\nEmpfohlener Ansatz: ADO-Organisationen auf GitLab-Groups mappen (nicht viele kleine Groups). Migration als Gelegenheit nutzen, GitLab-Struktur zu rationalisieren (siehe [Struktur-Diagramm](https://about.gitlab.com/blog/migration-from-azure-devops-to-gitlab/#planning-your-migration)):\n\n- **ADO Organization** → GitLab Top-level Group\n- **ADO Project** → GitLab Subgroup (optional)\n- **ADO Repository** → GitLab Project\n\nWeitere Empfehlungen:\n\n- Subgroups und Project-Level-Permissions für verwandte Repositories nutzen\n- Zugriff über GitLab Groups und Group-Membership managen\n- GitLab [Permissions](https://docs.gitlab.com/user/permissions/) und [SAML Group Links](https://docs.gitlab.com/user/group/saml_sso/group_sync/) für Enterprise-RBAC-Modell evaluieren\n\n**ADO Work Items Migration:**\n\nADO Boards und Work Items mappen zu GitLab Issues, Epics und Issue Boards. ADO Epics und Features werden GitLab Epics. Andere Work-Item-Typen (User Stories, Tasks, Bugs) werden project-scoped Issues. Standard-Felder bleiben erhalten; ausgewählte Custom Fields können migriert werden. Parent-Child-Relationships bleiben erhalten. Links zu Pull Requests werden zu Merge-Request-Links konvertiert.\n\n![Migration eines Work Items zu GitLab Issue](https://res.cloudinary.com/about-gitlab-com/image/upload/v1764769188/ztesjnxxfbwmfmtckyga.png)\n\n**Pipelines-Migration:**\n\nCongregate bietet [AI-basierte Konvertierung](https://gitlab.com/gitlab-org/professional-services-automation/tools/migration/congregate/-/merge_requests/1298) für multi-stage YAML-Pipelines von Azure DevOps zu GitLab CI/CD. Die automatisierte Konvertierung funktioniert optimal für einfache Single-File-Pipelines und liefert einen funktionalen Ausgangspunkt, nicht ein produktionsreifes `.gitlab-ci.yml`-File. Das Tool generiert funktional äquivalente GitLab-Pipelines zur weiteren Optimierung.\n\n- Konvertiert Azure Pipelines YAML zu `.gitlab-ci.yml` automatisch\n- Geeignet für straightforward Single-File-Pipeline-Konfigurationen\n- Liefert Boilerplate zur Migrations-Beschleunigung, nicht finales Production-Artifact\n- Erfordert Review und Anpassung für komplexe Szenarien, Custom Tasks oder Enterprise-Requirements\n- Unterstützt keine Azure DevOps Classic Release Pipelines\n\nRepository-Owner sollten [GitLab CI/CD Dokumentation](https://docs.gitlab.com/ci/) konsultieren für weitere Pipeline-Optimierung nach initialer Konvertierung.\n\n## Migrationen durchführen\n\nNach der Planung werden Migrationen in Stages durchgeführt, beginnend mit Trial Runs. Trial Migrations identifizieren organisations-spezifische Issues frühzeitig und ermöglichen Duration-Messung, Outcome-Validierung und Approach-Finetuning vor Production.\n\n**Was Trial Migrations validieren:**\n\n- Ob Repository und Assets erfolgreich migrieren (History, Branches, Tags; plus MRs/Comments bei Congregate)\n- Ob Destination sofort nutzbar ist (Permissions, Runners, CI/CD-Variablen, Integrationen)\n- Wie lange jeder Batch benötigt für Schedule- und Stakeholder-Expectations\n\n**Downtime-Guidance:**\n\nGitLabs eingebauter Git-Import und Congregate erfordern inhärent keine Downtime. Für Production-Wellen: Changes in ADO freezen (Branch-Protections oder Read-only), um verpasste Commits, PR-Updates oder mid-migration Work Items zu vermeiden. Trial Runs erfordern keine Freezes.\n\n**Batching-Guidance:**\n\nTrial-Batches back-to-back durchführen für kürzere elapsed Time. Teams validieren Results asynchron. Geplante Group/Subgroup-Struktur für Batch-Definition nutzen und API-Rate-Limits respektieren.\n\n**Empfohlene Schritte:**\n\n1. Test-Destination in GitLab erstellen (GitLab.com: dedicated Group/Namespace; Self-managed: Top-level Group)\n2. Authentication vorbereiten (Azure DevOps PAT, GitLab Personal Access Token mit api und read_repository Scopes)\n3. Trial Migrations durchführen (Repos only: eingebauter Import; Repos + PRs/MRs: Congregate)\n4. Post-Trial Follow-up (Repo-History, Branches, Tags, Merge Requests, Issues/Epics, Labels, Relationships verifizieren)\n5. Permissions/Roles, Protected Branches, Runners/Tags, Variables/Secrets, Integrations/Webhooks prüfen\n6. Pipelines (`.gitlab-ci.yml`) oder konvertierte Pipelines validieren\n7. User-Validierung für Functionality und Data-Fidelity\n8. Production-Migrationen in Waves durchführen (Change-Freezes in ADO erzwingen, Progress und Logs monitoren)\n\n\u003Cfigure class=\"video_container\">\n  \u003Ciframe src=\"https://www.youtube.com/embed/ibIXGfrVbi4?si=ZxOVnXjCF-h4Ne0N\" frameborder=\"0\" allowfullscreen=\"true\">\u003C/iframe>\n\u003C/figure>\n\n## Zentrale ADO-zu-GitLab-Mappings\n\nWichtigste Struktur-Mappings für Migrationsplanung:\n\n- **ADO Organization** → GitLab Group (Top-level Namespace)\n- **ADO Project** → GitLab Group oder Subgroup (Permissions-Boundary)\n- **ADO Repository** → GitLab Project (Git-Repo plus Issues, CI/CD, Wiki)\n- **Pull Request** → Merge Request (Code Review, Approvals)\n- **Azure Pipelines** → GitLab CI/CD (`.gitlab-ci.yml`)\n- **Agent Pools** → GitLab Runners (Job-Execution)\n- **Work Items** → GitLab Issues/Epics (Planning-Funktionalität)\n- **Variable Groups** → CI/CD Variables (Project/Group/Instance Level)\n\nFür vollständige Terminologie-Referenztabelle mit allen Mappings siehe [englische Originalversion](https://about.gitlab.com/blog/migration-from-azure-devops-to-gitlab/).\n\n## Professional Services Migrationsmuster\n\nGitLab Professional Services nutzt bewährte Muster für Enterprise-Migrationen:\n\n**Systematische Planung:** Evaluate-Tool liefert vollständiges Inventar (Repositories, PRs, Contributors, Pipelines, Work Items). Klassifikation nach Komplexität (Work Items = Planning-Team-Involvement; Pipelines = Conversion-Requirements) ermöglicht Timeline-Schätzung und Batch-Definition.\n\n**Controlled Execution:** Wellen von 200-300 Projekten managen Komplexität und respektieren API-Rate-Limits. Trial-Migrationen vor Production-Waves identifizieren Issues frühzeitig. Change-Freezes in ADO während Production-Wellen vermeiden mid-migration Updates.\n\n**Risikominimierung:** Multi-Phase-Ansatz (Prerequisites → Migration → Post-migration) mit Validierungs-Checkpoints an jeder Phase. Trial-Runs ermöglichen asynchrone Team-Validierung ohne Production-Impact.\n\n## Praktische Implementierung\n\nFür vollständige Implementierungsdetails, Konfigurationsbeispiele, Pipeline-Konvertierungs-Patterns und detaillierte Post-Migration-Checklisten siehe [englische Originalversion](https://about.gitlab.com/blog/migration-from-azure-devops-to-gitlab/).\n\nWeitere Professional Services Ressourcen:\n\n- [Professional Services Full Catalog](https://about.gitlab.com/professional-services/catalog/)\n- [Congregate Migration Tool](https://gitlab.com/gitlab-org/professional-services-automation/tools/migration/congregate/)\n- [Evaluate Inventory Tool](https://gitlab.com/gitlab-org/professional-services-automation/tools/utilities/evaluate)\n","2025-12-03","https://res.cloudinary.com/about-gitlab-com/image/upload/v1749658924/Blog/Hero%20Images/securitylifecycle-light.png",[757,758],"Evgeny Rudinsky","Michael Leopard","Migration von Azure DevOps zu GitLab systematisch planen","Professional Services Migrationsansatz mit mehrstufiger Struktur, 200-300 Projekt-Wellen und systematischem Risikomanagement für Enterprise-Migrationen.",{"featured":28,"template":14,"slug":762},"migration-from-azure-devops-to-gitlab",{"promotions":764},[765,779,790,802],{"id":766,"categories":767,"header":769,"text":770,"button":771,"image":776},"ai-modernization",[768],"ai-ml","Is AI achieving its promise at scale?","Quiz will take 5 minutes or less",{"text":772,"config":773},"Get your AI maturity score",{"href":774,"dataGaName":775,"dataGaLocation":252},"/assessments/ai-modernization-assessment/","modernization assessment",{"config":777},{"src":778},"https://res.cloudinary.com/about-gitlab-com/image/upload/v1772138786/qix0m7kwnd8x2fh1zq49.png",{"id":780,"categories":781,"header":782,"text":770,"button":783,"image":787},"devops-modernization",[23,580],"Are you just managing tools or shipping innovation?",{"text":784,"config":785},"Get your DevOps maturity score",{"href":786,"dataGaName":775,"dataGaLocation":252},"/assessments/devops-modernization-assessment/",{"config":788},{"src":789},"https://res.cloudinary.com/about-gitlab-com/image/upload/v1772138785/eg818fmakweyuznttgid.png",{"id":791,"categories":792,"header":794,"text":770,"button":795,"image":799},"security-modernization",[793],"security","Are you trading speed for security?",{"text":796,"config":797},"Get your security maturity score",{"href":798,"dataGaName":775,"dataGaLocation":252},"/assessments/security-modernization-assessment/",{"config":800},{"src":801},"https://res.cloudinary.com/about-gitlab-com/image/upload/v1772138786/p4pbqd9nnjejg5ds6mdk.png",{"id":803,"paths":804,"header":806,"text":807,"button":808,"image":813},"github-azure-migration",[762,805],"integrating-azure-devops-scm-and-gitlab","Is your team ready for GitHub's Azure move?","GitHub is already rebuilding around Azure. Find out what it means for you.",{"text":809,"config":810},"See how GitLab compares to GitHub",{"href":811,"dataGaName":812,"dataGaLocation":252},"/compare/gitlab-vs-github/github-azure-migration/","github azure migration",{"config":814},{"src":789},{"header":816,"blurb":817,"button":818,"secondaryButton":823},"Beginne noch heute, schneller zu entwickeln","Entdecke, was dein Team mit der intelligenten Orchestrierungsplattform für DevSecOps erreichen kann.\n",{"text":819,"config":820},"Kostenlosen Test starten",{"href":821,"dataGaName":49,"dataGaLocation":822},"https://gitlab.com/-/trial_registrations/new?glm_content=default-saas-trial&glm_source=about.gitlab.com/de-de/","feature",{"text":51,"config":824},{"href":53,"dataGaName":54,"dataGaLocation":822},1777934886049]