[{"data":1,"prerenderedAt":833},["ShallowReactive",2],{"/de-de/blog/ci-deployment-and-environments":3,"navigation-de-de":44,"banner-de-de":458,"footer-de-de":468,"blog-post-authors-de-de-Ivan Nemytchenko|Cesar Saavedra":704,"blog-related-posts-de-de-ci-deployment-and-environments":730,"blog-promotions-de-de":770,"next-steps-de-de":823},{"id":4,"title":5,"authorSlugs":6,"authors":9,"body":12,"category":13,"categorySlug":13,"config":14,"content":18,"date":22,"description":19,"extension":28,"externalUrl":29,"featured":16,"heroImage":21,"isFeatured":16,"meta":30,"navigation":31,"path":32,"publishedDate":22,"rawbody":33,"seo":34,"slug":15,"stem":38,"tagSlugs":39,"tags":42,"template":17,"updatedDate":27,"__hash__":43},"blogPosts/de-de/blog/ci-deployment-and-environments.yml","Deployment mit GitLab CI an verschiedene Umgebungen: So funktioniert es",[7,8],"ivan-nemytchenko","cesar-saavedra",[10,11],"Ivan Nemytchenko","Cesar Saavedra","In diesem ausführlichen Artikel wollen wir dir zeigen, wie du GitLab CI für eine automatisierte Kompilierung und Bereitstellung nutzen kannst. Als Ausgangspunkt haben wir das folgende Szenario gewählt: Du bist der (die) glückliche Besitzer(in), Redakteur(in) und alleinige Entwickler(in) eines imaginären Nachrichtenportals.\nDa du deinen Projekt-Code bereits auf GitLab.com hostest, ist dir bewusst, dass du mit GitLab [CI/CD-Tests](https://docs.gitlab.com/ci/testing/) durchführen kannst. Jetzt aber möchtest du wissen, ob das Tool auch für deine [Bereitstellung](/de-de/blog/how-to-keep-up-with-ci-cd-best-practices/) verwendet werden kann - und was für Optionen dir dafür zur Verfügung stehen.\n\nUm nicht von den Details spezifischer Tech-Stacks abgelenkt zu werden, gehen wir in unserem Beispiel davon aus, dass die App nur aus HTML-Dateien besteht. Es gibt keinen serverseitigen Code, keine komplizierte Kompilierung der JavaScript-Assets.\n\nAls Zielplattform wählen wir [Amazon S3](https://aws.amazon.com/s3/) - ebenfalls eine einfache Lösung.\n\n> **Achtung:** Ziel des Artikels ist es nicht, dir möglichst viele kleinteilige Bausteine zu bieten, die du dann mit Kopieren und Einfügen in deinen Code integrierst. Vielmehr möchten wir dir die Prinzipien und Funktionalitäten von [GitLab CI](/de-de/solutions/continuous-integration/) vermitteln, so dass du sie einfacher auf deinen Tech-Stack anwenden kannst.\n\n## Inhaltsverzeichnis\n- [Der Anfang der Geschichte](#der-anfang-der-geschichte)\n- [Die erste automatisierte Bereitstellung](#die-erste-automatisierte-bereitstellung)\n  - [Wie man Geheimes geheim hält](#wie-man-geheimes-geheim-hält)\n  - [Wie du nicht geheime Variablen spezifizierst und nutzt](#wie-du-nicht-geheime-variablen-spezifizierst-und-nutzt)\n- [Wie Teams GitLab CI für die Bereitstellung nutzen können](#wie-teams-gitlab-ci-für-die-bereitstellung-nutzen-können)\n  - [Wie du einen separaten Ort für das Testen von Code einrichtest](#wie-du-einen-separaten-ort-für-das-testen-von-code-einrichtest)\n- [Einführung: Umgebungen (environments)](#einführung-umgebungen-environments)\n- [Fehlerbehebung bei der Bereitstellung](#fehlerbehebung-bei-der-bereitstellung)\n- [Slack-Benachrichtigungen für Bereitstellungen](#slack-benachrichtigungen-für-bereitstellungen)\n- [Skalierbarkeit von Teamarbeit](#skalierbarkeit-von-teamarbeit)\n  - [Wie du mit Notfällen umgehst](#wie-du-mit-notfällen-umgehst)\n  - [Es wird Zeit, Review Apps zu verwenden](#es-wird-zeit-review-apps-zu-verwenden)\n  - [Bereitstellung auf verschiedenen Plattformen](#bereitstellung-auf-verschiedenen-plattformen)\n- [Fünf Kernpunkte](#fünf-kernpunkte)\n\nLass uns ganz am Anfang beginnen. Da, wo es noch keine kontinuierliche Integration (continuous integration, CI) gibt.\n\n## Der Anfang der Geschichte\n\n**Deployment**: Was verstehen wir unter dem Begriff „Bereitstellung\"? In unserem Fall möchten wir, dass eine große Zahl an HTML-Dateien in deinem S3-Bucket - der bereits für statisches Webseiten-[Hosting](http://docs.aws.amazon.com/AmazonS3/latest/dev/HowDoIWebsiteConfiguration.html?shortFooter=true) konfiguriert wurde - erscheint.\n\nHier führen unzählige Wege nach Rom. In unserem Beispiel werden wir die [awscli](http://docs.aws.amazon.com/cli/latest/reference/s3/cp.html#examples)-Bibliothek von Amazon selbst verwenden.\n\nSo sieht der vollständige Befehl aus:\n\n```shell\naws s3 cp ./ s3://yourbucket/ --recursive --exclude \"*\" --include \"*.html\"\n```\n\n![Manual deployment](https://about.gitlab.com/images/blogimages/ci-deployment-and-environments/13.jpg)\nDie Übertragung des Code mittels Push-Befehl in ein Repository und die eigentliche Bereitstellung sind zwei voneinander unabhängige Prozesse.\n\n\nWichtiges Detail: Der [Befehl](http://docs.aws.amazon.com/cli/latest/userguide/cli-chap-getting-started.html#config-settings-and-precedence) erwartet von dir, dass du `AWS_ACCESS_KEY_ID` und `AWS_SECRET_ACCESS_KEY` Umgebungs-Variablen bereitstellst. Das bedeutet, dass du gegebenenfalls die `AWS_DEFAULT_REGION` festlegen musst.\n\n\n\nLass uns nun versuchen, diesen Prozess mit [GitLab CI](/de-de/solutions/continuous-integration/) zu automatisieren.\n\n## Die erste automatisierte Bereitstellung\n\nMit GitLab macht es keinen Unterschied, welche Befehle du verwendest. Du kannst GitLab so einrichten, dass es genau auf deine persönlichen Bedürfnisse zugeschnitten ist und wie ein lokales Terminal auf deinem Rechner funktioniert.\nSolange du von dort aus die Befehle ausführst, kannst du CI damit beauftragen, dasselbe für dich in GitLab zu tun. Platziere dein Script einfach in *.gitlab-ci.yml*,pushe deinen Code –und siehe da: CI erzeugt einen Job und führt deine Befehle aus.\n\nUm unser Ausgangsszenario ein wenig auszuschmücken, fügen wir ihm nun ein wenig Kontext hinzu: Unsere Webseite ist klein, sie hat täglich 20-30 Besucher und das Code-Repository besitzt nur einen einzigen Standard-Branch: `main`.\n\nUnser Ziel: Das Einrichten einer automatisierten Bereitstellung.\n\nLass uns damit anfangen, dass wir den oben erwähnten Befehl verwenden, um in der *.gitlab-ci.yml*\\-Datei einen Job zu spezifizieren:\n\n```yaml\ndeploy:\n  script: aws s3 cp ./ s3://yourbucket/ --recursive --exclude \"*\" --include \"*.html\"\n```\n\nHat leider nicht geklappt:\n![Failed command](https://about.gitlab.com/images/blogimages/ci-deployment-and-environments/fail1.png)\n\nEs ist unsere Aufgabe dafür zu sorgen, dass eine ausführbare `aws`\\-Datei vorliegt. Um `awscli` installieren zu können, benötigen wir `pip`, ein Tool zur Installation von Python-Paketen. Unser Vorschlag: Spezifiziere dafür ein Docker-Image mit vorinstalliertem Python. Das nämlich sollte `pip` beinhalten.\n\n```yaml\ndeploy:\n  image: python:latest\n  script:\n  - pip install awscli\n  - aws s3 cp ./ s3://yourbucket/ --recursive --exclude \"*\" --include \"*.html\"\n```\n\n![Automated deployment](https://about.gitlab.com/images/blogimages/ci-deployment-and-environments/14.jpg)\nYou push your code to GitLab, and it is automatically deployed by CI.\n \nDu pushst deinen Code zu GitLab und dadurch wird dieser automatisch von CI bereitgestellt. Damit hast du dein erstes Ziel einer automatisierten Kompilierung und Bereitstellung erreicht.\nDie Installation von `awscli` verlängert die benötigte Zeit, den Job auszuführen. Das aber soll uns im Augenblick nicht stören. Wenn du den Prozess beschleunigen musst, kannst du jederzeit nach einem [Docker](https://hub.docker.com/explore/)-Image mit vorinstalliertem `awscli` suchen oder selbst ein solches Image erstellen.\n\n\nWir sollten außerdem die folgenden Gitlab-CI-Environment-Variablen nicht vergessen, die du dir gerade aus der [AWS](https://console.aws.amazon.com/)-Konsole gezogen hast:\n```yaml\nvariables:\n  AWS_ACCESS_KEY_ID: \"AKIAIOSFODNN7EXAMPLE\"\n  AWS_SECRET_ACCESS_KEY: \"wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY\"\ndeploy:\n  image: python:latest\n  script:\n  - pip install awscli\n  - aws s3 cp ./ s3://yourbucket/ --recursive --exclude \"*\" --include \"*.html\"\n```\nDas sollte zwar an sich funktionieren,aber es ist dennoch eine gute Idee, geheime Schlüssel zu schützen \\- sogar in einem privaten Repository. Suchen wir also mal nach einer Lösung.\n\n### Wie man Geheimes geheim hält\n\nEs gibt in GitLab einen eigenen Ort für geheime Variablen: **Settings > CI/CD > Variables**\n\n![Picture of Variables page](https://res.cloudinary.com/about-gitlab-com/image/upload/v1749674076/Blog/Content%20Images/add-variable-updated.png)\n\nAlles, was du dort ablegst, wird in GitLab-CI-Umgebungs-Variablen verwandelt.\n\nWenn du nun das Kontrollkästchen für Maskenvariablen (*mask variables*) markierst, obfuskierst du die Variablen im Job-Log. Das bedeutet, dass du den Zugriff Dritter auf diese Daten erheblich erschwerst. Als Nächstes setzt du ein Häkchen im Kontrollkästchen „Variable schützen” (*Protect variable*). Dadurch wird die entsprechende Variable nur noch über Pipelines exportiert, die auf geschützten Branches und Tags laufen. Nur Nutzer(innen) mit „Owner”- oder „Maintainer”-Status haben Zugriff auf diesen Bereich.\nWir könnten Umgebungs-Variablen aus unserer GitLab-CI\\-Konfiguration entfernen. Stattdessen aber wollen wir sie zu einem anderen Zweck verwenden.\n\n### Wie du nicht geheime Variablen spezifizierst und nutzt\n\nWenn deine Konfiguration wächst, kann es nützlich sein, einige der Parameter zu Beginn der Konfiguration als Variablen zu belassen. Das gilt umso mehr, wenn du sie an mehr als einer Stelle verwendest. Obwohl das in unserer Situation nicht der Fall ist, wollen wir den S3-Bucket-Namen als [**Variable**](https://docs.gitlab.com/ci/variables/) verwenden, um das Prinzip zu verdeutlichen:\n\n```yaml\nvariables:\n  S3_BUCKET_NAME: \"yourbucket\"\ndeploy:\n  image: python:latest\n  script:\n  - pip install awscli\n  - aws s3 cp ./ s3://$S3_BUCKET_NAME/ --recursive --exclude \"*\" --include \"*.html\"\n```\n\nSoweit so gut:\n\n![Successful build](https://about.gitlab.com/images/blogimages/ci-deployment-and-environments/build.png)\n\nIn unserem hypothetischen Szenario ist es dir gelungen, mehr Besucher auf deine Seite zu bekommen. Daher unterstützt dich jetzt  ein(e) Entwickler(in).\n\nWerfen wir deswegen einen Blick darauf, wie Teamarbeit den GitLab-CI-Workflow verändert.\n\n## Wie Teams GitLab CI für die Bereitstellung nutzen können\n\nDa nun zwei Mitarbeiter(innen) am gleichen Repository arbeiten, ist es nicht mehr sinnvoll, die `main`\\-Branch für die Bereitstellung zu nutzen. Deswegen entscheidest du dich dafür, zwei separate Branches zu erzeugen: Eines für neue Features und das andere für neue Artikel. Am Endes willst du beide dann in `main` zusammenführen.\nDabei gibt es aber leider ein Problem – deine aktuelle CI-Konfiguration interessiert sich nicht für Branches. Sobald du etwas zu GitLab pushst, wird es auch für S3 bereitgestellt.\nZum Glück lässt sich dieses Problem recht einfach beheben. Füge lediglich `only: main` zu deinem `deploy`\\-Job hinzu.\n\n![Automated deployment of main branch](https://res.cloudinary.com/about-gitlab-com/image/upload/v1749674076/Blog/Content%20Images/15-updated.png)\nDu willst deiner Produktions-Website zwar nicht jede Branch hinzufügen, aber es wäre durchaus gut, wenn du dir deine Änderungen an Feature-Branches per Vorschau ansehen könntest.\n \n### Wie du einen separaten Ort für das Testen von Code einrichtest\n\nDein Entwickler, nennen wir ihn Patrick, erinnert dich daran, dass es ein Feature namens [GitLab Pages](https://docs.gitlab.com/user/project/pages/) gibt. Es scheint ideal dafür zu sein, dir eine Vorschau dessen zu bieten, woran du gerade arbeitest.\n\nUm Websites auf GitLab Pages zu [hosten](/blog/gitlab-pages-setup/), sollte deine CI-Konfiguration drei einfache Voraussetzungen erfüllen:\n\n* Der *Job* sollte als `pages` angelegt werden  * Es sollte einen `artifacts`\\-Bereich mit einem öffentlichen Ordner geben  * Du solltest alles, was du hosten willst, in den `public-`Ordner legen\n\nDie Inhalte des öffentlichen Ordners werden an folgendem Ort gehostet: `http://\u003Cusername>.gitlab.io/\u003Cprojectname>/`\n\n\nNach der Anwendung der [Beispielkonfiguration für plain-html-Websites](https://gitlab.com/pages/plain-html/blob/master/.gitlab-ci.yml), sieht die vollständige CI-Konfiguration so aus:\n\n```yaml\nvariables:\n  S3_BUCKET_NAME: \"yourbucket\"\n\ndeploy:\n  image: python:latest\n  script:\n  - pip install awscli\n  - aws s3 cp ./ s3://$S3_BUCKET_NAME/ --recursive --exclude \"*\" --include \"*.html\"\n  only:\n  - main\n\npages:\n  image: alpine:latest\n  script:\n  - mkdir -p ./public\n  - cp ./*.html ./public/\n  artifacts:\n    paths:\n    - public\n  except:\n  - main\n```\n\nWir haben zwei Jobs spezifiziert. Ein Job stellt die Website für deine Kunden auf S3 bereit (`deploy`). Die andere (`pages`) stellt die Website auf GitLab Pages bereit. Aus diesem Grund nennen wir sie jeweils „Produktionsumgebung” und „Prüfungsumgebung” *(Staging Environment*).\n\n![Deployment to two places](https://res.cloudinary.com/about-gitlab-com/image/upload/v1749674076/Blog/Content%20Images/16-updated.png)\nAlle Branches, mit Ausnahme von main, werden auf GitLab Pages bereitgestellt.\n\n\n## Einführung: Umgebungen (environments)\n\nGitLab bietet [Support für Umgebungen](https://docs.gitlab.com/ci/environments/) (einschließlich dynamischer und statischer Umgebungen). Dazu musst du lediglich die zutreffende Umgebung für den jeweiligen Deployment-Job festlegen:\n\n```yaml\nvariables:\n  S3_BUCKET_NAME: \"yourbucket\"\n\ndeploy to production:\n  environment: production\n  image: python:latest\n  script:\n  - pip install awscli\n  - aws s3 cp ./ s3://$S3_BUCKET_NAME/ --recursive --exclude \"*\" --include \"*.html\"\n  only:\n  - main\n\npages:\n  image: alpine:latest\n  environment: staging\n  script:\n  - mkdir -p ./public\n  - cp ./*.html ./public/\n  artifacts:\n    paths:\n    - public\n  except:\n  - main\n```\n\nGitLab trackt deine CI-Bereitstellungen. So weißt du jederzeit, was aktuell auf deinen Servern bereitgestellt wird:\n\n![List of environments](https://res.cloudinary.com/about-gitlab-com/image/upload/v1749674076/Blog/Content%20Images/envs-updated.png)\n\nGitLab bietet eine vollständige Aufzeichnung deiner Bereitstellungen für alle deine aktuellen CI-Umgebungen:\n\n![List of deployments to staging environment](https://res.cloudinary.com/about-gitlab-com/image/upload/v1749674077/Blog/Content%20Images/staging-env-detail-updated.png)\n\n![Environments](https://res.cloudinary.com/about-gitlab-com/image/upload/v1749674077/Blog/Content%20Images/17-updated.png)\n\nNun, da wir alles automatisiert und eingerichtet haben, können wir uns den neuen Herausforderungen stellen, die uns erwarten.\n\n## Fehlerbehebung bei der Bereitstellung\n\nUnd da ist es schon wieder passiert: Du hast deine Feature-Branch gepusht, um sie in der „Staging-Umgebung” in der Vorschau zu sehen und nur eine Minute später hat Patrick seine Branch gepusht. Die Folge: Die Staging-Umgebung wurde mit seinem Beitrag überschrieben. Wie ärgerlich\\!\\! Das passiert heute schon zum dritten Mal\\!\n\nVorschlag: \u003Ci class=\"far fa-lightbulb\" style=\"color:#FFD900; font-size:.85em\" aria-hidden=\"true\">\u003C/i>\nWarum verwenden wir nicht Slack, um uns über CI-Bereitstellungen auf dem Laufenden zu halten? So können wir verhindern, dass wir uns bei der Bereitstellung gegenseitig in die Quere kommen.\n\n> Lerne, wie man [GitLab in Slack](https://docs.gitlab.com/user/project/integrations/gitlab_slack_application/) integriert.\n\n## Slack-Benachrichtigungen für Bereitstellungen\n\nDas Einrichten von Slack-Benachrichtigungen ist ein recht unkomplizierter Vorgang.\nDer Gedanke dahinter ist, die eintreffende WebHook-URL von Slack zu nehmen …\n\n![image11](https://about.gitlab.com/images/blogimages/ci-deployment-and-environments/queue.jpg)\n\n… und sie in Settings \\> Integrations \\> Slack notifications zusammen mit deinem Slack-Benutzernamen einzutragen:\n\n![image12](https://about.gitlab.com/images/blogimages/ci-deployment-and-environments/slack-integration-arrow.png)\n\nDas einzige, worüber du auf dem Laufenden gehalten werden möchtest, sind Bereitstellungen. Deswegen kannst du die Häkchen aus allen Kontrollkästchen außer dem für „Deployment” in den obengenannten Einstellungen entfernen. Das war’s auch schon. Ab jetzt wirst du über jede erfolgte Bereitstellung informiert:\n![image13](https://about.gitlab.com/images/blogimages/ci-deployment-and-environments/slack.png)\n## Skalierbarkeit von Teamarbeit\n\nEinige Zeit später ist deine Website wirklich beliebt geworden und dein Team ist von zwei auf acht Mitarbeiter angewachsen. Diese arbeiten parallel an Entwicklungs-Jobs. So kommt es recht häufig vor, dass mehrere von ihnen aufeinander warten müssen, weil jemand gerade eine Vorschau in der Staging-Umgebung durchführt. Damit ist die Idee, jede Branch in Staging bereitzustellen, obsolet geworden.\n\n![Queue of branches for review on Staging](https://about.gitlab.com/images/blogimages/ci-deployment-and-environments/queue.jpg)\n\nEs ist an der Zeit, den Prozess ein letztes Mal zu modifizieren. Du und dein Team sind zu dem Entschluss gekommen, dass alle, die ihre Änderungen auf dem Staging-Server ansehen möchten, diese zuerst mit der Staging-Branch zusammenführen sollen.\nDazu bedarf es nur einer minimalen Änderung von  `.gitlab-ci.yml`:\n\n```yaml\nexcept:\n- main\n```\n\nEs wird zu:\n\n```yaml\nonly:\n- staging\n```\n\n![Staging branch](https://res.cloudinary.com/about-gitlab-com/image/upload/v1749674077/Blog/Content%20Images/18-updated.png)\nDie Mitarbeiter müssen nun ihre Feature-Branches zusammenführen, bevor sie diese auf dem Staging-Server als Vorschau betrachten können.\n\n\nNatürlich erfordert dies zusätzliche Zeit und einen Mehraufwand für das Mergen. Aber alle stimmen überein, dass dies besser ist, als jedes Mal zu warten.\n\n### Wie du mit Notfällen umgehst\n\nDu kannst nicht alles kontrollieren. Manchmal geht einfach etwas schief. Nehmen wir ein Beispiel: Ein(e) Mitarbeiter(in) hat die Branches nicht korrekt zusammengeführt und das Ergebnis direkt in die Produktions-Umgebung gepusht \\- genau zu einem Zeitpunkt, als deine Website bei HackerNews ganz oben stand\\! Tausende Besucher haben so dein komplett zerschossenes Layout gesehen, statt deiner eigentlich so schönen Main-Page.\nZum Glück hat ein Team-Mitglied den **Rollback-Button** entdeckt. Damit konntest du die Website bereits eine Minute, nachdem das Problem entdeckt wurde, auf den alten Stand zurücksetzen.\n\n![List of environments](https://res.cloudinary.com/about-gitlab-com/image/upload/v1749674077/Blog/Content%20Images/prod-env-rollback-arrow-updated.png)\nDer Rollback-Button erlaubt es, eine Webseite schnell auf den Ursprungszustand zurückzusetzen.\n\nRollback führt alles zu dem alten Job mit dem vorigen commit zurück.\nTrotzdem stellt dich diese Lösung des Problems noch nicht zufrieden. Du entschließt dich, die automatische Bereitstellung in die Produktions-Umgebung auszuschalten und stattdessen zum manuellen CI-Deployment zurückzukehren. Dazu fügst du deinem Job `when: manual` hinzu.\n\nWie du bereits erwartet hast, erfolgen ab jetzt keine automatischen Bereitstellungen in die Produktion mehr. Um eine manuelle Bereitstellung durchzuführen, gehe zu **CI/CD \\> Pipelines**, und klicke auf den Button:\n\n![Skipped job is available for manual launch](https://res.cloudinary.com/about-gitlab-com/image/upload/v1749674076/Blog/Content%20Images/manual-pipeline-arrow-updated.png)\n\nSpringen wir nun in die Zukunft. Endlich ist dein Unternehmen zu einer Aktiengesellschaft herangewachsen. Inzwischen arbeiten hunderte Mitarbeiter(innen) an der Website. Das bedeutet, dass die Kompromisse aus der Vergangenheit nicht mehr praxistauglich sind.\n\n### Es wird Zeit, Review Apps zu verwenden\n\nDer nächste logische Schritt besteht darin, ein temporäres Objekt der Applikation über die Feature-Branch zum Prüfen zu booten.\nIn unserem Fall richten wir dazu einen weiteren S3-Bucket ein. Der einzige Unterschied besteht darin, dass wir die Inhalte unserer Website in einen „Ordner” mit dem Namen der Entwicklungs-Branch kopieren. Nun sieht die URL so aus:\n\n`http://\u003CREVIEW_S3_BUCKET_NAME>.s3-website-us-east-1.amazonaws.com/\u003Cbranchname>/`\n\nHier ist der Ersatz für den `pages`\\-Job, den wir zuvor benutzt haben:\n\n```yaml\nreview apps:\n  variables:\n    S3_BUCKET_NAME: \"reviewbucket\"\n  image: python:latest\n  environment: review\n  script:\n  - pip install awscli\n  - mkdir -p ./$CI_BUILD_REF_NAME\n  - cp ./*.html ./$CI_BUILD_REF_NAME/\n  - aws s3 cp ./ s3://$S3_BUCKET_NAME/ --recursive --exclude \"*\" --include \"*.html\"\n```\n\nEs ist interessant zu hinterfragen, woher wir diese `$CI_BUILD_REF_NAME`\\-Variable bekommen haben. GitLab CI definiert viele [Umgebungs-Variablen](https://docs.gitlab.com/ci/variables/predefined_variables/) vor, so dass du sie direkt in deinen Jobs verwenden kannst.\nBeachte, dass wir die `S3_BUCKET_NAME`\\-Variable im Job definiert haben. Damit kannst du Definitionen auf höchstem Level umschreiben.\n\n\nHier ist eine visuelle Darstellung dieser Konfiguration:\n![Review apps]![How to use GitLab CI - update - 19 - updated](https://res.cloudinary.com/about-gitlab-com/image/upload/v1749674077/Blog/Content%20Images/19-updated.png)\n\nWie genau die Review Apps implementiert werden, hängt von einer Vielzahl Faktoren ab, darunter dein Tech-Stack und dein Bereitstellungs-Prozess. Das ist sehr komplex und deswegen wollen wir in diesem Blogpost nicht näher darauf eingehen.\nImmerhin können wir mit Gewissheit behaupten, dass der Prozess sich nicht mehr ganz so einfach darstellen wird wie noch bei unserer statischen html-Website. An dieser Stelle sei nur ein Beispiel genannt: Du musst diese Objekte temporär anlegen. Wenn du sie nun mit der gesamten benötigten Software und allen Diensten automatisch hochfahren möchtest, ist das keine triviale Angelegenheit mehr. Dennoch ist es machbar, vor allem, wenn du Docker-Container verwendest \\- oder zumindest *Chef* oder *Ansible*.\nWir werden uns mit Docker-Bereitstellungen in einem zukünftigen Blogpost beschäftigen. Ich fühle mich ehrlich gesagt ein wenig schuldig, dass ich mich bei der Diskussion des Bereitstellungs-Prozesses nur auf das einfache Kopieren von html-Dateien beschränke und kein einziges wirklich anspruchsvolles Szenario durchgehe. Wenn du dringend mehr Informationen in diese Richtung benötigst, empfehle ich dir den englischsprachigen Artikel „[Building an Elixir Release into a Docker image using GitLab CI.](https://about.gitlab.com/blog/building-an-elixir-release-into-docker-image-using-gitlab-ci-part-1/)\"\n\nIn diesem Artikel aber möchte ich nur noch einen letzten Punkt behandeln.\n\n### Bereitstellung auf verschiedenen Plattformen\n\nIm echten Leben müssen wir uns nicht auf S3 und GitLab Pages beschränken. Wir hosten unsere Apps und Pakete auf verschiedenen Diensten \\- gleiches gilt somit auch für unsere CI-Bereitstellungen.\nDarüber hinaus kann auch der Fall eintreten, dass du dich dazu entschließt, auf eine neue Plattform zu migrieren. In dem Fall müsstest du alle deine Bereitstellungs-Skripte neu schreiben. Um den Aufwand zu minimieren, kannst du ein ungemein wertvolles Tool namens `dpl` benutzen.\nIn den Beispielen oben haben wir `awscli` verwendet, um den Code an einen Beispiel-Dienst zu liefern (in unserem Fall Amazon S3). Unabhängig davon aber, welches Tool und welches Zielsystem du verwendest, bleibt das Prinzip dasselbe: Du führst einen Befehl mit bestimmten Parametern aus und identifizierst dich mit einem geheimen Schlüssel.\nDas `dpl`\\-Bereitstellungs-Tool nutzt dieses Prinzip und bietet ein einheitliches Interface für diese [Liste von Providern](https://github.com/travis-ci/dpl#supported-providers) an.\nSo sähe ein Produktions-Bereitstellungs-Job aus, wenn wir `dpl` nutzen:\n\n```yaml\nvariables:\n  S3_BUCKET_NAME: \"yourbucket\"\n\ndeploy to production:\n  environment: production\n  image: ruby:latest\n  script:\n  - gem install dpl\n  - dpl --provider=s3 --bucket=$S3_BUCKET_NAME\n  only:\n  - main\n```\n\nWenn du an verschiedene Systeme bereitstellen möchtest oder öfter die Ziel-Plattform änderst, ergibt es Sinn, über die Nutzung von `dpl` nachzudenken, um deine Bereitstellungs-Skripte einheitlich zu halten.\n\n## Fünf Kernpunkte\n\n1. Eine Bereitstellung ist nichts weiter als ein Befehl (oder eine Kombination von Befehlen), die regelmäßig ausgeführt werden. Deswegen kannst du Bereitstellungen über GitLab CI laufen lassen.\n2. In den meisten Fällen wirst du einen oder mehrere geheime Schlüssel verwenden müssen, um die Befehle ausführen zu können. Speichere diese geheimen Schlüssel in **Settings \\> CI/CD \\> Variables**.\t\n\n3. Mit GitLab CI kannst du flexibel spezifizieren, an welche Branches du deployen willst.  \t\n\n4. Wenn du Bereitstellungen an verschiedene CI-Umgebungen durchführen möchtest, speichert GitLab die Bereitstellungen. Das erlaubt es dir, einen Rollback zu einer früheren Version durchzuführen. \n5. Für kritische Aspekte deiner Infrastruktur kannst du statt der automatischen Bereitstellung auf eine manuelle umstellen.\n\n\u003Cstyle>\n\nimg.illustration {\n  padding-left: 12%;\n  padding-right: 12%;\n\n}\n@media (max-width: 760px) {\n  img.illustration {\n    padding-left: 0px;\n    padding-right: 0px;\n  }\n}\n\u003C/style>","engineering",{"slug":15,"featured":16,"template":17},"ci-deployment-and-environments",false,"BlogPost",{"title":5,"description":19,"authors":20,"heroImage":21,"date":22,"body":12,"category":13,"tags":23,"updatedDate":27},"Erfahre, wie du GitLab CI für automatische Deployments in mehrere Umgebungen einrichtest, inklusive AWS S3-Integration und sicherer Variablenverwaltung.",[10,11],"https://res.cloudinary.com/about-gitlab-com/image/upload/v1749662033/Blog/Hero%20Images/intro.jpg","2021-02-05",[24,25,26],"CI","CD","tutorial","2025-05-16","yml",null,{},true,"/de-de/blog/ci-deployment-and-environments","seo:\n  title: 'Deployment mit GitLab CI an verschiedene Umgebungen: So funktioniert es'\n  description: >-\n    Erfahre, wie du GitLab CI für automatische Deployments in mehrere Umgebungen\n    einrichtest, inklusive AWS S3-Integration und sicherer Variablenverwaltung.\n  ogTitle: 'Deployment mit GitLab CI an verschiedene Umgebungen: So funktioniert es'\n  ogDescription: >-\n    Erfahre, wie du GitLab CI für automatische Deployments in mehrere Umgebungen\n    einrichtest, inklusive AWS S3-Integration und sicherer Variablenverwaltung.\n  noIndex: false\n  ogImage: >-\n    https://res.cloudinary.com/about-gitlab-com/image/upload/v1749662033/Blog/Hero%20Images/intro.jpg\n  ogUrl: https://about.gitlab.com/blog/ci-deployment-and-environments\n  ogSiteName: https://about.gitlab.com\n  ogType: article\n  canonicalUrls: https://about.gitlab.com/blog/ci-deployment-and-environments\ncontent:\n  title: 'Deployment mit GitLab CI an verschiedene Umgebungen: So funktioniert es'\n  description: >-\n    Erfahre, wie du GitLab CI für automatische Deployments in mehrere Umgebungen\n    einrichtest, inklusive AWS S3-Integration und sicherer Variablenverwaltung.\n  authors:\n    - Ivan Nemytchenko\n    - Cesar Saavedra\n  heroImage: >-\n    https://res.cloudinary.com/about-gitlab-com/image/upload/v1749662033/Blog/Hero%20Images/intro.jpg\n  date: '2021-02-05'\n  body: \"In diesem ausführlichen Artikel wollen wir dir zeigen, wie du GitLab CI\n    für eine automatisierte Kompilierung und Bereitstellung nutzen kannst. Als\n    Ausgangspunkt haben wir das folgende Szenario gewählt: Du bist der (die)\n    glückliche Besitzer(in), Redakteur(in) und alleinige Entwickler(in) eines\n    imaginären Nachrichtenportals.\\\n\n\n    Da du deinen Projekt-Code bereits auf GitLab.com hostest, ist dir bewusst,\n    dass du mit GitLab [CI/CD-Tests](https://docs.gitlab.com/ci/testing/)\n    durchführen kannst. Jetzt aber möchtest du wissen, ob das Tool auch für\n    deine\n    [Bereitstellung](/de-de/blog/how-to-keep-up-with-ci-cd-best-practices/)\n    verwendet werden kann - und was für Optionen dir dafür zur Verfügung stehen.\n\n\n    Um nicht von den Details spezifischer Tech-Stacks abgelenkt zu werden, gehen\n    wir in unserem Beispiel davon aus, dass die App nur aus HTML-Dateien\n    besteht. Es gibt keinen serverseitigen Code, keine komplizierte Kompilierung\n    der JavaScript-Assets.\n\n\n    Als Zielplattform wählen wir [Amazon S3](https://aws.amazon.com/s3/) -\n    ebenfalls eine einfache Lösung.\n\n\n    > **Achtung:** Ziel des Artikels ist es nicht, dir möglichst viele\n    kleinteilige Bausteine zu bieten, die du dann mit Kopieren und Einfügen in\n    deinen Code integrierst. Vielmehr möchten wir dir die Prinzipien und\n    Funktionalitäten von [GitLab CI](/de-de/solutions/continuous-integration/)\n    vermitteln, so dass du sie einfacher auf deinen Tech-Stack anwenden kannst.\n\n\n    ## Inhaltsverzeichnis\n\n    - [Der Anfang der Geschichte](#der-anfang-der-geschichte)\n\n    - [Die erste automatisierte\n    Bereitstellung](#die-erste-automatisierte-bereitstellung)\n\n    \\  - [Wie man Geheimes geheim hält](#wie-man-geheimes-geheim-hält)\n\n    \\  - [Wie du nicht geheime Variablen spezifizierst und\n    nutzt](#wie-du-nicht-geheime-variablen-spezifizierst-und-nutzt)\n\n    - [Wie Teams GitLab CI für die Bereitstellung nutzen\n    können](#wie-teams-gitlab-ci-für-die-bereitstellung-nutzen-können)\n\n    \\  - [Wie du einen separaten Ort für das Testen von Code\n    einrichtest](#wie-du-einen-separaten-ort-für-das-testen-von-code-einrichtes\\\n    t)\n\n    - [Einführung: Umgebungen\n    (environments)](#einführung-umgebungen-environments)\n\n    - [Fehlerbehebung bei der\n    Bereitstellung](#fehlerbehebung-bei-der-bereitstellung)\n\n    - [Slack-Benachrichtigungen für\n    Bereitstellungen](#slack-benachrichtigungen-für-bereitstellungen)\n\n    - [Skalierbarkeit von Teamarbeit](#skalierbarkeit-von-teamarbeit)\n\n    \\  - [Wie du mit Notfällen umgehst](#wie-du-mit-notfällen-umgehst)\n\n    \\  - [Es wird Zeit, Review Apps zu\n    verwenden](#es-wird-zeit-review-apps-zu-verwenden)\n\n    \\  - [Bereitstellung auf verschiedenen\n    Plattformen](#bereitstellung-auf-verschiedenen-plattformen)\n\n    - [Fünf Kernpunkte](#fünf-kernpunkte)\n\n\n    Lass uns ganz am Anfang beginnen. Da, wo es noch keine kontinuierliche\n    Integration (continuous integration, CI) gibt.\n\n\n    ## Der Anfang der Geschichte\n\n\n    **Deployment**: Was verstehen wir unter dem Begriff „Bereitstellung\\\"? In\n    unserem Fall möchten wir, dass eine große Zahl an HTML-Dateien in deinem\n    S3-Bucket - der bereits für statisches\n    Webseiten-[Hosting](http://docs.aws.amazon.com/AmazonS3/latest/dev/HowDoIWe\\\n    bsiteConfiguration.html?shortFooter=true) konfiguriert wurde - erscheint.\n\n\n    Hier führen unzählige Wege nach Rom. In unserem Beispiel werden wir die\n    [awscli](http://docs.aws.amazon.com/cli/latest/reference/s3/cp.html#example\\\n    s)-Bibliothek von Amazon selbst verwenden.\n\n\n    So sieht der vollständige Befehl aus:\n\n\n    ```shell\n\n    aws s3 cp ./ s3://yourbucket/ --recursive --exclude \\\"*\\\" --include\n    \\\"*.html\\\"\n\n    ```\n\n\n    ![Manual\n    deployment](https://about.gitlab.com/images/blogimages/ci-deployment-and-en\\\n    vironments/13.jpg)\n\n    Die Übertragung des Code mittels Push-Befehl in ein Repository und die\n    eigentliche Bereitstellung sind zwei voneinander unabhängige Prozesse.\n\n\n\n    Wichtiges Detail: Der\n    [Befehl](http://docs.aws.amazon.com/cli/latest/userguide/cli-chap-getting-s\\\n    tarted.html#config-settings-and-precedence) erwartet von dir, dass du\n    `AWS_ACCESS_KEY_ID` und `AWS_SECRET_ACCESS_KEY` Umgebungs-Variablen\n    bereitstellst. Das bedeutet, dass du gegebenenfalls die `AWS_DEFAULT_REGION`\n    festlegen musst.\n\n\n\n\n    Lass uns nun versuchen, diesen Prozess mit [GitLab\n    CI](/de-de/solutions/continuous-integration/) zu automatisieren.\n\n\n    ## Die erste automatisierte Bereitstellung\n\n\n    Mit GitLab macht es keinen Unterschied, welche Befehle du verwendest. Du\n    kannst GitLab so einrichten, dass es genau auf deine persönlichen\n    Bedürfnisse zugeschnitten ist und wie ein lokales Terminal auf deinem\n    Rechner funktioniert.\\\n\n\n    Solange du von dort aus die Befehle ausführst, kannst du CI damit\n    beauftragen, dasselbe für dich in GitLab zu tun. Platziere dein Script\n    einfach in *.gitlab-ci.yml*,pushe deinen Code –und siehe da: CI erzeugt\n    einen Job und führt deine Befehle aus.\n\n\n    Um unser Ausgangsszenario ein wenig auszuschmücken, fügen wir ihm nun ein\n    wenig Kontext hinzu: Unsere Webseite ist klein, sie hat täglich 20-30\n    Besucher und das Code-Repository besitzt nur einen einzigen Standard-Branch:\n    `main`.\n\n\n    Unser Ziel: Das Einrichten einer automatisierten Bereitstellung.\n\n\n    Lass uns damit anfangen, dass wir den oben erwähnten Befehl verwenden, um in\n    der *.gitlab-ci.yml*\\\\-Datei einen Job zu spezifizieren:\n\n\n    ```yaml\n\n    deploy:\n\n    \\  script: aws s3 cp ./ s3://yourbucket/ --recursive --exclude \\\"*\\\"\n    --include \\\"*.html\\\"\n\n    ```\n\n\n    Hat leider nicht geklappt:\n\n    ![Failed\n    command](https://about.gitlab.com/images/blogimages/ci-deployment-and-envir\\\n    onments/fail1.png)\n\n\n    Es ist unsere Aufgabe dafür zu sorgen, dass eine ausführbare `aws`\\\\-Datei\n    vorliegt. Um `awscli` installieren zu können, benötigen wir `pip`, ein Tool\n    zur Installation von Python-Paketen. Unser Vorschlag: Spezifiziere dafür ein\n    Docker-Image mit vorinstalliertem Python. Das nämlich sollte `pip`\n    beinhalten.\n\n\n    ```yaml\n\n    deploy:\n\n    \\  image: python:latest\n\n    \\  script:\n\n    \\  - pip install awscli\n\n    \\  - aws s3 cp ./ s3://yourbucket/ --recursive --exclude \\\"*\\\" --include\n    \\\"*.html\\\"\n\n    ```\n\n\n    ![Automated\n    deployment](https://about.gitlab.com/images/blogimages/ci-deployment-and-en\\\n    vironments/14.jpg)\n\n    You push your code to GitLab, and it is automatically deployed by CI.\n\n    \\ \\\n\n\n    Du pushst deinen Code zu GitLab und dadurch wird dieser automatisch von CI\n    bereitgestellt. Damit hast du dein erstes Ziel einer automatisierten\n    Kompilierung und Bereitstellung erreicht.\\\n\n\n    Die Installation von `awscli` verlängert die benötigte Zeit, den Job\n    auszuführen. Das aber soll uns im Augenblick nicht stören. Wenn du den\n    Prozess beschleunigen musst, kannst du jederzeit nach einem\n    [Docker](https://hub.docker.com/explore/)-Image mit vorinstalliertem\n    `awscli` suchen oder selbst ein solches Image erstellen.\n\n\n\n    Wir sollten außerdem die folgenden Gitlab-CI-Environment-Variablen nicht\n    vergessen, die du dir gerade aus der\n    [AWS](https://console.aws.amazon.com/)-Konsole gezogen hast:\\\n\n\n    ```yaml\n\n    variables:\n\n    \\  AWS_ACCESS_KEY_ID: \\\"AKIAIOSFODNN7EXAMPLE\\\"\n\n    \\  AWS_SECRET_ACCESS_KEY: \\\"wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY\\\"\n\n    deploy:\n\n    \\  image: python:latest\n\n    \\  script:\n\n    \\  - pip install awscli\n\n    \\  - aws s3 cp ./ s3://yourbucket/ --recursive --exclude \\\"*\\\" --include\n    \\\"*.html\\\"\n\n    ```\n\n    Das sollte zwar an sich funktionieren,aber es ist dennoch eine gute Idee,\n    geheime Schlüssel zu schützen \\\\- sogar in einem privaten Repository. Suchen\n    wir also mal nach einer Lösung.\n\n\n    ### Wie man Geheimes geheim hält\n\n\n    Es gibt in GitLab einen eigenen Ort für geheime Variablen: **Settings >\n    CI/CD > Variables**\n\n\n    ![Picture of Variables\n    page](https://res.cloudinary.com/about-gitlab-com/image/upload/v1749674076/\\\n    Blog/Content%20Images/add-variable-updated.png)\n\n\n    Alles, was du dort ablegst, wird in GitLab-CI-Umgebungs-Variablen\n    verwandelt.\n\n\n    Wenn du nun das Kontrollkästchen für Maskenvariablen (*mask variables*)\n    markierst, obfuskierst du die Variablen im Job-Log. Das bedeutet, dass du\n    den Zugriff Dritter auf diese Daten erheblich erschwerst. Als Nächstes setzt\n    du ein Häkchen im Kontrollkästchen „Variable schützen” (*Protect variable*).\n    Dadurch wird die entsprechende Variable nur noch über Pipelines exportiert,\n    die auf geschützten Branches und Tags laufen. Nur Nutzer(innen) mit „Owner”-\n    oder „Maintainer”-Status haben Zugriff auf diesen Bereich.\\\n\n\n    Wir könnten Umgebungs-Variablen aus unserer GitLab-CI\\\\-Konfiguration\n    entfernen. Stattdessen aber wollen wir sie zu einem anderen Zweck verwenden.\n\n\n    ### Wie du nicht geheime Variablen spezifizierst und nutzt\n\n\n    Wenn deine Konfiguration wächst, kann es nützlich sein, einige der Parameter\n    zu Beginn der Konfiguration als Variablen zu belassen. Das gilt umso mehr,\n    wenn du sie an mehr als einer Stelle verwendest. Obwohl das in unserer\n    Situation nicht der Fall ist, wollen wir den S3-Bucket-Namen als\n    [**Variable**](https://docs.gitlab.com/ci/variables/) verwenden, um das\n    Prinzip zu verdeutlichen:\n\n\n    ```yaml\n\n    variables:\n\n    \\  S3_BUCKET_NAME: \\\"yourbucket\\\"\n\n    deploy:\n\n    \\  image: python:latest\n\n    \\  script:\n\n    \\  - pip install awscli\n\n    \\  - aws s3 cp ./ s3://$S3_BUCKET_NAME/ --recursive --exclude \\\"*\\\"\n    --include \\\"*.html\\\"\n\n    ```\n\n\n    Soweit so gut:\n\n\n    ![Successful\n    build](https://about.gitlab.com/images/blogimages/ci-deployment-and-environ\\\n    ments/build.png)\n\n\n    In unserem hypothetischen Szenario ist es dir gelungen, mehr Besucher auf\n    deine Seite zu bekommen. Daher unterstützt dich jetzt  ein(e)\n    Entwickler(in).\n\n\n    Werfen wir deswegen einen Blick darauf, wie Teamarbeit den\n    GitLab-CI-Workflow verändert.\n\n\n    ## Wie Teams GitLab CI für die Bereitstellung nutzen können\n\n\n    Da nun zwei Mitarbeiter(innen) am gleichen Repository arbeiten, ist es nicht\n    mehr sinnvoll, die `main`\\\\-Branch für die Bereitstellung zu nutzen.\n    Deswegen entscheidest du dich dafür, zwei separate Branches zu erzeugen:\n    Eines für neue Features und das andere für neue Artikel. Am Endes willst du\n    beide dann in `main` zusammenführen.\\\n\n\n    Dabei gibt es aber leider ein Problem – deine aktuelle CI-Konfiguration\n    interessiert sich nicht für Branches. Sobald du etwas zu GitLab pushst, wird\n    es auch für S3 bereitgestellt.\\\n\n\n    Zum Glück lässt sich dieses Problem recht einfach beheben. Füge lediglich\n    `only: main` zu deinem `deploy`\\\\-Job hinzu.\n\n\n    ![Automated deployment of main\n    branch](https://res.cloudinary.com/about-gitlab-com/image/upload/v174967407\\\n    6/Blog/Content%20Images/15-updated.png)\n\n    Du willst deiner Produktions-Website zwar nicht jede Branch hinzufügen, aber\n    es wäre durchaus gut, wenn du dir deine Änderungen an Feature-Branches per\n    Vorschau ansehen könntest.\n\n    \\ \\\n\n\n    ### Wie du einen separaten Ort für das Testen von Code einrichtest\n\n\n    Dein Entwickler, nennen wir ihn Patrick, erinnert dich daran, dass es ein\n    Feature namens [GitLab\n    Pages](https://docs.gitlab.com/user/project/pages/) gibt. Es scheint\n    ideal dafür zu sein, dir eine Vorschau dessen zu bieten, woran du gerade\n    arbeitest.\n\n\n    Um Websites auf GitLab Pages zu [hosten](/blog/gitlab-pages-setup/), sollte\n    deine CI-Konfiguration drei einfache Voraussetzungen erfüllen:\n\n\n    * Der *Job* sollte als `pages` angelegt werden \\\n\n    * Es sollte einen `artifacts`\\\\-Bereich mit einem öffentlichen Ordner geben\n    \\\n\n    * Du solltest alles, was du hosten willst, in den `public-`Ordner legen\n\n\n    Die Inhalte des öffentlichen Ordners werden an folgendem Ort gehostet:\n    `http://\u003Cusername>.gitlab.io/\u003Cprojectname>/`\n\n\n\n    Nach der Anwendung der [Beispielkonfiguration für\n    plain-html-Websites](https://gitlab.com/pages/plain-html/blob/master/.gitla\\\n    b-ci.yml), sieht die vollständige CI-Konfiguration so aus:\n\n\n    ```yaml\n\n    variables:\n\n    \\  S3_BUCKET_NAME: \\\"yourbucket\\\"\n\n\n    deploy:\n\n    \\  image: python:latest\n\n    \\  script:\n\n    \\  - pip install awscli\n\n    \\  - aws s3 cp ./ s3://$S3_BUCKET_NAME/ --recursive --exclude \\\"*\\\"\n    --include \\\"*.html\\\"\n\n    \\  only:\n\n    \\  - main\n\n\n    pages:\n\n    \\  image: alpine:latest\n\n    \\  script:\n\n    \\  - mkdir -p ./public\n\n    \\  - cp ./*.html ./public/\n\n    \\  artifacts:\n\n    \\    paths:\n\n    \\    - public\n\n    \\  except:\n\n    \\  - main\n\n    ```\n\n\n    Wir haben zwei Jobs spezifiziert. Ein Job stellt die Website für deine\n    Kunden auf S3 bereit (`deploy`). Die andere (`pages`) stellt die Website auf\n    GitLab Pages bereit. Aus diesem Grund nennen wir sie jeweils\n    „Produktionsumgebung” und „Prüfungsumgebung” *(Staging Environment*).\n\n\n    ![Deployment to two\n    places](https://res.cloudinary.com/about-gitlab-com/image/upload/v174967407\\\n    6/Blog/Content%20Images/16-updated.png)\n\n    Alle Branches, mit Ausnahme von main, werden auf GitLab Pages\n    bereitgestellt.\n\n\n\n    ## Einführung: Umgebungen (environments)\n\n\n    GitLab bietet [Support für\n    Umgebungen](https://docs.gitlab.com/ci/environments/) (einschließlich\n    dynamischer und statischer Umgebungen). Dazu musst du lediglich die\n    zutreffende Umgebung für den jeweiligen Deployment-Job festlegen:\n\n\n    ```yaml\n\n    variables:\n\n    \\  S3_BUCKET_NAME: \\\"yourbucket\\\"\n\n\n    deploy to production:\n\n    \\  environment: production\n\n    \\  image: python:latest\n\n    \\  script:\n\n    \\  - pip install awscli\n\n    \\  - aws s3 cp ./ s3://$S3_BUCKET_NAME/ --recursive --exclude \\\"*\\\"\n    --include \\\"*.html\\\"\n\n    \\  only:\n\n    \\  - main\n\n\n    pages:\n\n    \\  image: alpine:latest\n\n    \\  environment: staging\n\n    \\  script:\n\n    \\  - mkdir -p ./public\n\n    \\  - cp ./*.html ./public/\n\n    \\  artifacts:\n\n    \\    paths:\n\n    \\    - public\n\n    \\  except:\n\n    \\  - main\n\n    ```\n\n\n    GitLab trackt deine CI-Bereitstellungen. So weißt du jederzeit, was aktuell\n    auf deinen Servern bereitgestellt wird:\n\n\n    ![List of\n    environments](https://res.cloudinary.com/about-gitlab-com/image/upload/v174\\\n    9674076/Blog/Content%20Images/envs-updated.png)\n\n\n    GitLab bietet eine vollständige Aufzeichnung deiner Bereitstellungen für\n    alle deine aktuellen CI-Umgebungen:\n\n\n    ![List of deployments to staging\n    environment](https://res.cloudinary.com/about-gitlab-com/image/upload/v1749\\\n    674077/Blog/Content%20Images/staging-env-detail-updated.png)\n\n\n    ![Environments](https://res.cloudinary.com/about-gitlab-com/image/upload/v1\\\n    749674077/Blog/Content%20Images/17-updated.png)\n\n\n    Nun, da wir alles automatisiert und eingerichtet haben, können wir uns den\n    neuen Herausforderungen stellen, die uns erwarten.\n\n\n    ## Fehlerbehebung bei der Bereitstellung\n\n\n    Und da ist es schon wieder passiert: Du hast deine Feature-Branch gepusht,\n    um sie in der „Staging-Umgebung” in der Vorschau zu sehen und nur eine\n    Minute später hat Patrick seine Branch gepusht. Die Folge: Die\n    Staging-Umgebung wurde mit seinem Beitrag überschrieben. Wie ärgerlich\\\\!\\\\!\n    Das passiert heute schon zum dritten Mal\\\\!\n\n\n    Vorschlag: \u003Ci class=\\\"far fa-lightbulb\\\" style=\\\"color:#FFD900;\n    font-size:.85em\\\" aria-hidden=\\\"true\\\">\u003C/i>\n\n    Warum verwenden wir nicht Slack, um uns über CI-Bereitstellungen auf dem\n    Laufenden zu halten? So können wir verhindern, dass wir uns bei der\n    Bereitstellung gegenseitig in die Quere kommen.\n\n\n    > Lerne, wie man [GitLab in\n    Slack](https://docs.gitlab.com/user/project/integrations/gitlab_slack_application/) integriert.\n\n\n    ## Slack-Benachrichtigungen für Bereitstellungen\n\n\n    Das Einrichten von Slack-Benachrichtigungen ist ein recht unkomplizierter\n    Vorgang.\\\n\n\n    Der Gedanke dahinter ist, die eintreffende WebHook-URL von Slack zu nehmen …\n\n\n    ![image11](https://about.gitlab.com/images/blogimages/ci-deployment-and-env\\\n    ironments/queue.jpg)\n\n\n    … und sie in Settings \\\\> Integrations \\\\> Slack notifications zusammen mit\n    deinem Slack-Benutzernamen einzutragen:\n\n\n    ![image12](https://about.gitlab.com/images/blogimages/ci-deployment-and-env\\\n    ironments/slack-integration-arrow.png)\n\n\n    Das einzige, worüber du auf dem Laufenden gehalten werden möchtest, sind\n    Bereitstellungen. Deswegen kannst du die Häkchen aus allen Kontrollkästchen\n    außer dem für „Deployment” in den obengenannten Einstellungen entfernen. Das\n    war’s auch schon. Ab jetzt wirst du über jede erfolgte Bereitstellung\n    informiert:\\\n\n\n    ![image13](https://about.gitlab.com/images/blogimages/ci-deployment-and-env\\\n    ironments/slack.png)\\\n\n\n    ## Skalierbarkeit von Teamarbeit\n\n\n    Einige Zeit später ist deine Website wirklich beliebt geworden und dein Team\n    ist von zwei auf acht Mitarbeiter angewachsen. Diese arbeiten parallel an\n    Entwicklungs-Jobs. So kommt es recht häufig vor, dass mehrere von ihnen\n    aufeinander warten müssen, weil jemand gerade eine Vorschau in der\n    Staging-Umgebung durchführt. Damit ist die Idee, jede Branch in Staging\n    bereitzustellen, obsolet geworden.\n\n\n    ![Queue of branches for review on\n    Staging](https://about.gitlab.com/images/blogimages/ci-deployment-and-envir\\\n    onments/queue.jpg)\n\n\n    Es ist an der Zeit, den Prozess ein letztes Mal zu modifizieren. Du und dein\n    Team sind zu dem Entschluss gekommen, dass alle, die ihre Änderungen auf dem\n    Staging-Server ansehen möchten, diese zuerst mit der Staging-Branch\n    zusammenführen sollen.\\\n\n\n    Dazu bedarf es nur einer minimalen Änderung von  `.gitlab-ci.yml`:\n\n\n    ```yaml\n\n    except:\n\n    - main\n\n    ```\n\n\n    Es wird zu:\n\n\n    ```yaml\n\n    only:\n\n    - staging\n\n    ```\n\n\n    ![Staging\n    branch](https://res.cloudinary.com/about-gitlab-com/image/upload/v174967407\\\n    7/Blog/Content%20Images/18-updated.png)\n\n    Die Mitarbeiter müssen nun ihre Feature-Branches zusammenführen, bevor sie\n    diese auf dem Staging-Server als Vorschau betrachten können.\n\n\n\n    Natürlich erfordert dies zusätzliche Zeit und einen Mehraufwand für das\n    Mergen. Aber alle stimmen überein, dass dies besser ist, als jedes Mal zu\n    warten.\n\n\n    ### Wie du mit Notfällen umgehst\n\n\n    Du kannst nicht alles kontrollieren. Manchmal geht einfach etwas schief.\n    Nehmen wir ein Beispiel: Ein(e) Mitarbeiter(in) hat die Branches nicht\n    korrekt zusammengeführt und das Ergebnis direkt in die Produktions-Umgebung\n    gepusht \\\\- genau zu einem Zeitpunkt, als deine Website bei HackerNews ganz\n    oben stand\\\\! Tausende Besucher haben so dein komplett zerschossenes Layout\n    gesehen, statt deiner eigentlich so schönen Main-Page.\\\n\n\n    Zum Glück hat ein Team-Mitglied den **Rollback-Button** entdeckt. Damit\n    konntest du die Website bereits eine Minute, nachdem das Problem entdeckt\n    wurde, auf den alten Stand zurücksetzen.\n\n\n    ![List of\n    environments](https://res.cloudinary.com/about-gitlab-com/image/upload/v174\\\n    9674077/Blog/Content%20Images/prod-env-rollback-arrow-updated.png)\n\n    Der Rollback-Button erlaubt es, eine Webseite schnell auf den\n    Ursprungszustand zurückzusetzen.\\\n\n\n\n    Rollback führt alles zu dem alten Job mit dem vorigen commit zurück.\\\n\n\n    Trotzdem stellt dich diese Lösung des Problems noch nicht zufrieden. Du\n    entschließt dich, die automatische Bereitstellung in die\n    Produktions-Umgebung auszuschalten und stattdessen zum manuellen\n    CI-Deployment zurückzukehren. Dazu fügst du deinem Job `when: manual` hinzu.\n\n\n    Wie du bereits erwartet hast, erfolgen ab jetzt keine automatischen\n    Bereitstellungen in die Produktion mehr. Um eine manuelle Bereitstellung\n    durchzuführen, gehe zu **CI/CD \\\\> Pipelines**, und klicke auf den Button:\n\n\n    ![Skipped job is available for manual\n    launch](https://res.cloudinary.com/about-gitlab-com/image/upload/v174967407\\\n    6/Blog/Content%20Images/manual-pipeline-arrow-updated.png)\n\n\n    Springen wir nun in die Zukunft. Endlich ist dein Unternehmen zu einer\n    Aktiengesellschaft herangewachsen. Inzwischen arbeiten hunderte\n    Mitarbeiter(innen) an der Website. Das bedeutet, dass die Kompromisse aus\n    der Vergangenheit nicht mehr praxistauglich sind.\n\n\n    ### Es wird Zeit, Review Apps zu verwenden\n\n\n    Der nächste logische Schritt besteht darin, ein temporäres Objekt der\n    Applikation über die Feature-Branch zum Prüfen zu booten.\\\n\n\n    In unserem Fall richten wir dazu einen weiteren S3-Bucket ein. Der einzige\n    Unterschied besteht darin, dass wir die Inhalte unserer Website in einen\n    „Ordner” mit dem Namen der Entwicklungs-Branch kopieren. Nun sieht die URL\n    so aus:\n\n\n    `http://\u003CREVIEW_S3_BUCKET_NAME>.s3-website-us-east-1.amazonaws.com/\u003Cbranchn\\\n    ame>/`\n\n\n    Hier ist der Ersatz für den `pages`\\\\-Job, den wir zuvor benutzt haben:\n\n\n    ```yaml\n\n    review apps:\n\n    \\  variables:\n\n    \\    S3_BUCKET_NAME: \\\"reviewbucket\\\"\n\n    \\  image: python:latest\n\n    \\  environment: review\n\n    \\  script:\n\n    \\  - pip install awscli\n\n    \\  - mkdir -p ./$CI_BUILD_REF_NAME\n\n    \\  - cp ./*.html ./$CI_BUILD_REF_NAME/\n\n    \\  - aws s3 cp ./ s3://$S3_BUCKET_NAME/ --recursive --exclude \\\"*\\\"\n    --include \\\"*.html\\\"\n\n    ```\n\n\n    Es ist interessant zu hinterfragen, woher wir diese\n    `$CI_BUILD_REF_NAME`\\\\-Variable bekommen haben. GitLab CI definiert viele\n    [Umgebungs-Variablen](https://docs.gitlab.com/ci/variables/predefined_variables/) vor, so dass du sie direkt in deinen Jobs verwenden kannst.\\\n\n\n    Beachte, dass wir die `S3_BUCKET_NAME`\\\\-Variable im Job definiert haben.\n    Damit kannst du Definitionen auf höchstem Level umschreiben.\n\n\n\n    Hier ist eine visuelle Darstellung dieser Konfiguration:\n\n    ![Review apps]![How to use GitLab CI - update - 19 -\n    updated](https://res.cloudinary.com/about-gitlab-com/image/upload/v17496740\\\n    77/Blog/Content%20Images/19-updated.png)\n\n\n    Wie genau die Review Apps implementiert werden, hängt von einer Vielzahl\n    Faktoren ab, darunter dein Tech-Stack und dein Bereitstellungs-Prozess. Das\n    ist sehr komplex und deswegen wollen wir in diesem Blogpost nicht näher\n    darauf eingehen.\\\n\n\n    Immerhin können wir mit Gewissheit behaupten, dass der Prozess sich nicht\n    mehr ganz so einfach darstellen wird wie noch bei unserer statischen\n    html-Website. An dieser Stelle sei nur ein Beispiel genannt: Du musst diese\n    Objekte temporär anlegen. Wenn du sie nun mit der gesamten benötigten\n    Software und allen Diensten automatisch hochfahren möchtest, ist das keine\n    triviale Angelegenheit mehr. Dennoch ist es machbar, vor allem, wenn du\n    Docker-Container verwendest \\\\- oder zumindest *Chef* oder *Ansible*.\\\n\n\n    Wir werden uns mit Docker-Bereitstellungen in einem zukünftigen Blogpost\n    beschäftigen. Ich fühle mich ehrlich gesagt ein wenig schuldig, dass ich\n    mich bei der Diskussion des Bereitstellungs-Prozesses nur auf das einfache\n    Kopieren von html-Dateien beschränke und kein einziges wirklich\n    anspruchsvolles Szenario durchgehe. Wenn du dringend mehr Informationen in\n    diese Richtung benötigst, empfehle ich dir den englischsprachigen Artikel\n    „[Building an Elixir Release into a Docker image using GitLab\n    CI.](https://about.gitlab.com/blog/building-an-elixir-release-into-docker-i\\\n    mage-using-gitlab-ci-part-1/)\\\"\n\n\n    In diesem Artikel aber möchte ich nur noch einen letzten Punkt behandeln.\n\n\n    ### Bereitstellung auf verschiedenen Plattformen\n\n\n    Im echten Leben müssen wir uns nicht auf S3 und GitLab Pages beschränken.\n    Wir hosten unsere Apps und Pakete auf verschiedenen Diensten \\\\- gleiches\n    gilt somit auch für unsere CI-Bereitstellungen.\\\n\n\n    Darüber hinaus kann auch der Fall eintreten, dass du dich dazu entschließt,\n    auf eine neue Plattform zu migrieren. In dem Fall müsstest du alle deine\n    Bereitstellungs-Skripte neu schreiben. Um den Aufwand zu minimieren, kannst\n    du ein ungemein wertvolles Tool namens `dpl` benutzen.\\\n\n\n    In den Beispielen oben haben wir `awscli` verwendet, um den Code an einen\n    Beispiel-Dienst zu liefern (in unserem Fall Amazon S3). Unabhängig davon\n    aber, welches Tool und welches Zielsystem du verwendest, bleibt das Prinzip\n    dasselbe: Du führst einen Befehl mit bestimmten Parametern aus und\n    identifizierst dich mit einem geheimen Schlüssel.\\\n\n\n    Das `dpl`\\\\-Bereitstellungs-Tool nutzt dieses Prinzip und bietet ein\n    einheitliches Interface für diese [Liste von\n    Providern](https://github.com/travis-ci/dpl#supported-providers) an.\\\n\n\n    So sähe ein Produktions-Bereitstellungs-Job aus, wenn wir `dpl` nutzen:\n\n\n    ```yaml\n\n    variables:\n\n    \\  S3_BUCKET_NAME: \\\"yourbucket\\\"\n\n\n    deploy to production:\n\n    \\  environment: production\n\n    \\  image: ruby:latest\n\n    \\  script:\n\n    \\  - gem install dpl\n\n    \\  - dpl --provider=s3 --bucket=$S3_BUCKET_NAME\n\n    \\  only:\n\n    \\  - main\n\n    ```\n\n\n    Wenn du an verschiedene Systeme bereitstellen möchtest oder öfter die\n    Ziel-Plattform änderst, ergibt es Sinn, über die Nutzung von `dpl`\n    nachzudenken, um deine Bereitstellungs-Skripte einheitlich zu halten.\n\n\n    ## Fünf Kernpunkte\n\n\n    1. Eine Bereitstellung ist nichts weiter als ein Befehl (oder eine\n    Kombination von Befehlen), die regelmäßig ausgeführt werden. Deswegen kannst\n    du Bereitstellungen über GitLab CI laufen lassen.\\\n\n\n    2. In den meisten Fällen wirst du einen oder mehrere geheime Schlüssel\n    verwenden müssen, um die Befehle ausführen zu können. Speichere diese\n    geheimen Schlüssel in **Settings \\\\> CI/CD \\\\> Variables**.\\t\n\n\n    3. Mit GitLab CI kannst du flexibel spezifizieren, an welche Branches du\n    deployen willst.  \\t\n\n\n    4. Wenn du Bereitstellungen an verschiedene CI-Umgebungen durchführen\n    möchtest, speichert GitLab die Bereitstellungen. Das erlaubt es dir, einen\n    Rollback zu einer früheren Version durchzuführen. \\\n\n\n    5. Für kritische Aspekte deiner Infrastruktur kannst du statt der\n    automatischen Bereitstellung auf eine manuelle umstellen.\n\n\n    \u003Cstyle>\n\n\n    img.illustration {\n\n    \\  padding-left: 12%;\n\n    \\  padding-right: 12%;\n\n\n    }\n\n    @media (max-width: 760px) {\n\n    \\  img.illustration {\n\n    \\    padding-left: 0px;\n\n    \\    padding-right: 0px;\n\n    \\  }\n\n    }\n\n    \u003C/style>\"\n  category: engineering\n  tags:\n    - CI\n    - CD\n    - tutorial\n  updatedDate: '2025-05-16'\nconfig:\n  slug: ci-deployment-and-environments\n  featured: false\n  template: BlogPost\n",{"title":5,"description":19,"ogTitle":5,"ogDescription":19,"noIndex":16,"ogImage":21,"ogUrl":35,"ogSiteName":36,"ogType":37,"canonicalUrls":35},"https://about.gitlab.com/blog/ci-deployment-and-environments","https://about.gitlab.com","article","de-de/blog/ci-deployment-and-environments",[40,41,26],"ci","cd",[24,25,26],"RudTDsgy_auZcFoFXNtb7Y4NGfw6mNvRlDP7HQQS2g0",{"data":45},{"logo":46,"freeTrial":51,"sales":56,"login":61,"items":66,"search":376,"minimal":410,"duo":428,"switchNav":437,"pricingDeployment":448},{"config":47},{"href":48,"dataGaName":49,"dataGaLocation":50},"/de-de/","gitlab logo","header",{"text":52,"config":53},"Kostenlose Testversion anfordern",{"href":54,"dataGaName":55,"dataGaLocation":50},"https://gitlab.com/-/trial_registrations/new?glm_source=about.gitlab.com/de-de&glm_content=default-saas-trial/","free trial",{"text":57,"config":58},"Vertrieb kontaktieren",{"href":59,"dataGaName":60,"dataGaLocation":50},"/de-de/sales/","sales",{"text":62,"config":63},"Anmelden",{"href":64,"dataGaName":65,"dataGaLocation":50},"https://gitlab.com/users/sign_in/","sign in",[67,94,191,196,297,357],{"text":68,"config":69,"cards":71},"Plattform",{"dataNavLevelOne":70},"platform",[72,78,86],{"title":68,"description":73,"link":74},"Die intelligente Orchestrierungsplattform für DevSecOps",{"text":75,"config":76},"Die Plattform erkunden",{"href":77,"dataGaName":70,"dataGaLocation":50},"/de-de/platform/",{"title":79,"description":80,"link":81},"GitLab Duo Agent Platform","Agentische KI für den gesamten Software-Lebenszyklus",{"text":82,"config":83},"Lerne GitLab Duo kennen",{"href":84,"dataGaName":85,"dataGaLocation":50},"/de-de/gitlab-duo-agent-platform/","gitlab duo agent platform",{"title":87,"description":88,"link":89},"Warum GitLab?","Erfahre, warum sich Unternehmen für GitLab entscheiden",{"text":90,"config":91},"Mehr erfahren",{"href":92,"dataGaName":93,"dataGaLocation":50},"/de-de/why-gitlab/","why gitlab",{"text":95,"left":31,"config":96,"link":98,"lists":102,"footer":173},"Produkt",{"dataNavLevelOne":97},"solutions",{"text":99,"config":100},"Alle Lösungen anzeigen",{"href":101,"dataGaName":97,"dataGaLocation":50},"/de-de/solutions/",[103,128,151],{"title":104,"description":105,"link":106,"items":111},"Automatisierung","CI/CD und Automatisierung zur Beschleunigung der Bereitstellung",{"config":107},{"icon":108,"href":109,"dataGaName":110,"dataGaLocation":50},"AutomatedCodeAlt","/de-de/solutions/delivery-automation/","automated software delivery",[112,116,119,124],{"text":113,"config":114},"CI/CD",{"href":115,"dataGaLocation":50,"dataGaName":113},"/de-de/solutions/continuous-integration/",{"text":79,"config":117},{"href":84,"dataGaLocation":50,"dataGaName":118},"gitlab duo agent platform - product menu",{"text":120,"config":121},"Quellcodeverwaltung",{"href":122,"dataGaLocation":50,"dataGaName":123},"/de-de/solutions/source-code-management/","Source Code Management",{"text":125,"config":126},"Automatische Softwarebereitstellung",{"href":109,"dataGaLocation":50,"dataGaName":127},"Automated software delivery",{"title":129,"description":130,"link":131,"items":136},"Sicherheit","Entwickle Code schneller ohne Abstriche bei der Sicherheit",{"config":132},{"href":133,"dataGaName":134,"dataGaLocation":50,"icon":135},"/de-de/solutions/application-security-testing/","security and compliance","ShieldCheckLight",[137,141,146],{"text":138,"config":139},"Anwendungssicherheitstests",{"href":133,"dataGaName":140,"dataGaLocation":50},"Application security testing",{"text":142,"config":143},"Schutz der Software-Lieferkette",{"href":144,"dataGaLocation":50,"dataGaName":145},"/de-de/solutions/supply-chain/","Software supply chain security",{"text":147,"config":148},"Software-Compliance",{"href":149,"dataGaName":150,"dataGaLocation":50},"/de-de/solutions/software-compliance/","software compliance",{"title":152,"link":153,"items":158},"Auswertung",{"config":154},{"icon":155,"href":156,"dataGaName":157,"dataGaLocation":50},"DigitalTransformation","/de-de/solutions/visibility-measurement/","visibility and measurement",[159,163,168],{"text":160,"config":161},"Sichtbarkeit und Auswertung",{"href":156,"dataGaLocation":50,"dataGaName":162},"Visibility and Measurement",{"text":164,"config":165},"Wertstrommanagement",{"href":166,"dataGaLocation":50,"dataGaName":167},"/de-de/solutions/value-stream-management/","Value Stream Management",{"text":169,"config":170},"Analysen und Einblicke",{"href":171,"dataGaLocation":50,"dataGaName":172},"/de-de/solutions/analytics-and-insights/","Analytics and insights",{"title":174,"items":175},"GitLab für",[176,181,186],{"text":177,"config":178},"Enterprise",{"href":179,"dataGaLocation":50,"dataGaName":180},"/de-de/enterprise/","enterprise",{"text":182,"config":183},"Kleinunternehmen",{"href":184,"dataGaLocation":50,"dataGaName":185},"/de-de/small-business/","small business",{"text":187,"config":188},"Öffentlicher Sektor",{"href":189,"dataGaLocation":50,"dataGaName":190},"/de-de/solutions/public-sector/","public sector",{"text":192,"config":193},"Preise",{"href":194,"dataGaName":195,"dataGaLocation":50,"dataNavLevelOne":195},"/de-de/pricing/","pricing",{"text":197,"config":198,"link":200,"lists":204,"feature":284},"Ressourcen",{"dataNavLevelOne":199},"resources",{"text":201,"config":202},"Alle Ressourcen anzeigen",{"href":203,"dataGaName":199,"dataGaLocation":50},"/de-de/resources/",[205,238,256],{"title":206,"items":207},"Erste Schritte",[208,213,218,223,228,233],{"text":209,"config":210},"Installieren",{"href":211,"dataGaName":212,"dataGaLocation":50},"/de-de/install/","install",{"text":214,"config":215},"Kurzanleitungen",{"href":216,"dataGaName":217,"dataGaLocation":50},"/de-de/get-started/","quick setup checklists",{"text":219,"config":220},"Lernen",{"href":221,"dataGaLocation":50,"dataGaName":222},"https://university.gitlab.com/","learn",{"text":224,"config":225},"Produktdokumentation",{"href":226,"dataGaName":227,"dataGaLocation":50},"https://docs.gitlab.com/","product documentation",{"text":229,"config":230},"Best-Practice-Videos",{"href":231,"dataGaName":232,"dataGaLocation":50},"/de-de/getting-started-videos/","best practice videos",{"text":234,"config":235},"Integrationen",{"href":236,"dataGaName":237,"dataGaLocation":50},"/de-de/integrations/","integrations",{"title":239,"items":240},"Entdecken",[241,246,251],{"text":242,"config":243},"Kundenerfolge",{"href":244,"dataGaName":245,"dataGaLocation":50},"/de-de/customers/","customer success stories",{"text":247,"config":248},"Blog",{"href":249,"dataGaName":250,"dataGaLocation":50},"/de-de/blog/","blog",{"text":252,"config":253},"Remote",{"href":254,"dataGaName":255,"dataGaLocation":50},"https://handbook.gitlab.com/handbook/company/culture/all-remote/","remote",{"title":257,"items":258},"Vernetzen",[259,264,269,274,279],{"text":260,"config":261},"GitLab Services",{"href":262,"dataGaName":263,"dataGaLocation":50},"/de-de/services/","services",{"text":265,"config":266},"Community",{"href":267,"dataGaName":268,"dataGaLocation":50},"/community/","community",{"text":270,"config":271},"Forum",{"href":272,"dataGaName":273,"dataGaLocation":50},"https://forum.gitlab.com/","forum",{"text":275,"config":276},"Veranstaltungen",{"href":277,"dataGaName":278,"dataGaLocation":50},"/events/","events",{"text":280,"config":281},"Partner",{"href":282,"dataGaName":283,"dataGaLocation":50},"/de-de/partners/","partners",{"background":285,"textColor":286,"text":287,"image":288,"link":292},"#2f2a6b","#fff","Perspektiven für die Softwareentwicklung der Zukunft",{"altText":289,"config":290},"The Source Promo-Karte",{"src":291},"https://res.cloudinary.com/about-gitlab-com/image/upload/v1758208064/dzl0dbift9xdizyelkk4.svg",{"text":293,"config":294},"Aktuelles",{"href":295,"dataGaName":296,"dataGaLocation":50},"/de-de/the-source/","the source",{"text":298,"config":299,"lists":301},"Unternehmen",{"dataNavLevelOne":300},"company",[302],{"items":303},[304,309,315,317,322,327,332,337,342,347,352],{"text":305,"config":306},"Über",{"href":307,"dataGaName":308,"dataGaLocation":50},"/de-de/company/","about",{"text":310,"config":311,"footerGa":314},"Karriere",{"href":312,"dataGaName":313,"dataGaLocation":50},"/jobs/","jobs",{"dataGaName":313},{"text":275,"config":316},{"href":277,"dataGaName":278,"dataGaLocation":50},{"text":318,"config":319},"Geschäftsführung",{"href":320,"dataGaName":321,"dataGaLocation":50},"/company/team/e-group/","leadership",{"text":323,"config":324},"Team",{"href":325,"dataGaName":326,"dataGaLocation":50},"/company/team/","team",{"text":328,"config":329},"Handbuch",{"href":330,"dataGaName":331,"dataGaLocation":50},"https://handbook.gitlab.com/","handbook",{"text":333,"config":334},"Investor Relations",{"href":335,"dataGaName":336,"dataGaLocation":50},"https://ir.gitlab.com/","investor relations",{"text":338,"config":339},"Trust Center",{"href":340,"dataGaName":341,"dataGaLocation":50},"/de-de/security/","trust center",{"text":343,"config":344},"AI Transparency Center",{"href":345,"dataGaName":346,"dataGaLocation":50},"/de-de/ai-transparency-center/","ai transparency center",{"text":348,"config":349},"Newsletter",{"href":350,"dataGaName":351,"dataGaLocation":50},"/company/contact/#contact-forms","newsletter",{"text":353,"config":354},"Presse",{"href":355,"dataGaName":356,"dataGaLocation":50},"/press/","press",{"text":358,"config":359,"lists":360},"Kontakt",{"dataNavLevelOne":300},[361],{"items":362},[363,366,371],{"text":57,"config":364},{"href":59,"dataGaName":365,"dataGaLocation":50},"talk to sales",{"text":367,"config":368},"Support-Portal",{"href":369,"dataGaName":370,"dataGaLocation":50},"https://support.gitlab.com","support portal",{"text":372,"config":373},"Kundenportal",{"href":374,"dataGaName":375,"dataGaLocation":50},"https://customers.gitlab.com/customers/sign_in/","customer portal",{"close":377,"login":378,"suggestions":385},"Schließen",{"text":379,"link":380},"Um Repositorys und Projekte zu durchsuchen, melde dich an bei",{"text":381,"config":382},"gitlab.com",{"href":64,"dataGaName":383,"dataGaLocation":384},"search login","search",{"text":386,"default":387},"Vorschläge",[388,390,395,397,402,407],{"text":79,"config":389},{"href":84,"dataGaName":79,"dataGaLocation":384},{"text":391,"config":392},"Codevorschläge (KI)",{"href":393,"dataGaName":394,"dataGaLocation":384},"/de-de/solutions/code-suggestions/","Code Suggestions (AI)",{"text":113,"config":396},{"href":115,"dataGaName":113,"dataGaLocation":384},{"text":398,"config":399},"GitLab auf AWS",{"href":400,"dataGaName":401,"dataGaLocation":384},"/de-de/partners/technology-partners/aws/","GitLab on AWS",{"text":403,"config":404},"GitLab auf Google Cloud",{"href":405,"dataGaName":406,"dataGaLocation":384},"/de-de/partners/technology-partners/google-cloud-platform/","GitLab on Google Cloud",{"text":87,"config":408},{"href":92,"dataGaName":409,"dataGaLocation":384},"Why GitLab?",{"freeTrial":411,"mobileIcon":416,"desktopIcon":421,"secondaryButton":424},{"text":412,"config":413},"Kostenlos testen",{"href":414,"dataGaName":55,"dataGaLocation":415},"https://gitlab.com/-/trials/new/","nav",{"altText":417,"config":418},"GitLab-Symbol",{"src":419,"dataGaName":420,"dataGaLocation":415},"https://res.cloudinary.com/about-gitlab-com/image/upload/v1758203874/jypbw1jx72aexsoohd7x.svg","gitlab icon",{"altText":417,"config":422},{"src":423,"dataGaName":420,"dataGaLocation":415},"https://res.cloudinary.com/about-gitlab-com/image/upload/v1758203875/gs4c8p8opsgvflgkswz9.svg",{"text":206,"config":425},{"href":426,"dataGaName":427,"dataGaLocation":415},"https://gitlab.com/-/trial_registrations/new?glm_source=about.gitlab.com/de-de/get-started/","get started",{"freeTrial":429,"mobileIcon":433,"desktopIcon":435},{"text":430,"config":431},"Mehr über GitLab Duo erfahren",{"href":84,"dataGaName":432,"dataGaLocation":415},"gitlab duo",{"altText":417,"config":434},{"src":419,"dataGaName":420,"dataGaLocation":415},{"altText":417,"config":436},{"src":423,"dataGaName":420,"dataGaLocation":415},{"button":438,"mobileIcon":443,"desktopIcon":445},{"text":439,"config":440},"/Option",{"href":441,"dataGaName":442,"dataGaLocation":415},"#contact","switch",{"altText":417,"config":444},{"src":419,"dataGaName":420,"dataGaLocation":415},{"altText":417,"config":446},{"src":447,"dataGaName":420,"dataGaLocation":415},"https://res.cloudinary.com/about-gitlab-com/image/upload/v1773335277/ohhpiuoxoldryzrnhfrh.png",{"freeTrial":449,"mobileIcon":454,"desktopIcon":456},{"text":450,"config":451},"Zurück zur Preisübersicht",{"href":194,"dataGaName":452,"dataGaLocation":415,"icon":453},"back to pricing","GoBack",{"altText":417,"config":455},{"src":419,"dataGaName":420,"dataGaLocation":415},{"altText":417,"config":457},{"src":423,"dataGaName":420,"dataGaLocation":415},{"title":459,"button":460,"config":465},"Sieh dir an, wie agentische KI die Softwarebereitstellung transformiert",{"text":461,"config":462},"GitLab Transcend jetzt ansehen",{"href":463,"dataGaName":464,"dataGaLocation":50},"/de-de/events/transcend/virtual/","transcend event",{"layout":466,"icon":467,"disabled":31},"release","AiStar",{"data":469},{"text":470,"source":471,"edit":477,"contribute":482,"config":487,"items":492,"minimal":695},"Git ist eine Marke von Software Freedom Conservancy und unsere Verwendung von „GitLab“ erfolgt unter Lizenz.",{"text":472,"config":473},"Quelltext der Seite anzeigen",{"href":474,"dataGaName":475,"dataGaLocation":476},"https://gitlab.com/gitlab-com/marketing/digital-experience/about-gitlab-com/","page source","footer",{"text":478,"config":479},"Diese Seite bearbeiten",{"href":480,"dataGaName":481,"dataGaLocation":476},"https://gitlab.com/gitlab-com/marketing/digital-experience/about-gitlab-com/-/blob/main/content/","web ide",{"text":483,"config":484},"Beteilige dich",{"href":485,"dataGaName":486,"dataGaLocation":476},"https://gitlab.com/gitlab-com/marketing/digital-experience/about-gitlab-com/-/blob/main/CONTRIBUTING.md/","please contribute",{"twitter":488,"facebook":489,"youtube":490,"linkedin":491},"https://x.com/gitlab","https://www.facebook.com/gitlab","https://www.youtube.com/channel/UCnMGQ8QHMAnVIsI3xJrihhg","https://www.linkedin.com/company/gitlab-com",[493,538,591,633,660],{"title":192,"links":494,"subMenu":509},[495,499,504],{"text":496,"config":497},"Tarife anzeigen",{"href":194,"dataGaName":498,"dataGaLocation":476},"view plans",{"text":500,"config":501},"Vorteile von Premium",{"href":502,"dataGaName":503,"dataGaLocation":476},"/de-de/pricing/premium/","why premium",{"text":505,"config":506},"Vorteile von Ultimate",{"href":507,"dataGaName":508,"dataGaLocation":476},"/de-de/pricing/ultimate/","why ultimate",[510],{"title":358,"links":511},[512,514,516,518,523,528,533],{"text":57,"config":513},{"href":59,"dataGaName":60,"dataGaLocation":476},{"text":367,"config":515},{"href":369,"dataGaName":370,"dataGaLocation":476},{"text":372,"config":517},{"href":374,"dataGaName":375,"dataGaLocation":476},{"text":519,"config":520},"Status",{"href":521,"dataGaName":522,"dataGaLocation":476},"https://status.gitlab.com/","status",{"text":524,"config":525},"Nutzungsbedingungen",{"href":526,"dataGaName":527,"dataGaLocation":476},"/terms/","terms of use",{"text":529,"config":530},"Datenschutzerklärung",{"href":531,"dataGaName":532,"dataGaLocation":476},"/de-de/privacy/","privacy statement",{"text":534,"config":535},"Cookie-Einstellungen",{"dataGaName":536,"dataGaLocation":476,"id":537,"isOneTrustButton":31},"cookie preferences","ot-sdk-btn",{"title":95,"links":539,"subMenu":548},[540,544],{"text":541,"config":542},"DevSecOps-Plattform",{"href":77,"dataGaName":543,"dataGaLocation":476},"devsecops platform",{"text":545,"config":546},"KI-unterstützte Entwicklung",{"href":84,"dataGaName":547,"dataGaLocation":476},"ai-assisted development",[549],{"title":550,"links":551},"Themen",[552,556,561,566,571,576,581,586],{"text":113,"config":553},{"href":554,"dataGaName":555,"dataGaLocation":476},"/de-de/topics/ci-cd/","cicd",{"text":557,"config":558},"GitOps",{"href":559,"dataGaName":560,"dataGaLocation":476},"/de-de/topics/gitops/","gitops",{"text":562,"config":563},"DevOps",{"href":564,"dataGaName":565,"dataGaLocation":476},"/de-de/topics/devops/","devops",{"text":567,"config":568},"Versionskontrolle",{"href":569,"dataGaName":570,"dataGaLocation":476},"/de-de/topics/version-control/","version control",{"text":572,"config":573},"DevSecOps",{"href":574,"dataGaName":575,"dataGaLocation":476},"/de-de/topics/devsecops/","devsecops",{"text":577,"config":578},"Cloud-nativ",{"href":579,"dataGaName":580,"dataGaLocation":476},"/de-de/topics/cloud-native/","cloud native",{"text":582,"config":583},"KI für das Programmieren",{"href":584,"dataGaName":585,"dataGaLocation":476},"/de-de/topics/devops/ai-for-coding/","ai for coding",{"text":587,"config":588},"Agentische KI",{"href":589,"dataGaName":590,"dataGaLocation":476},"/de-de/topics/agentic-ai/","agentic ai",{"title":592,"links":593},"Lösungen",[594,597,599,604,608,611,614,617,619,621,623,628],{"text":138,"config":595},{"href":133,"dataGaName":596,"dataGaLocation":476},"Application Security Testing",{"text":125,"config":598},{"href":109,"dataGaName":110,"dataGaLocation":476},{"text":600,"config":601},"Agile Entwicklung",{"href":602,"dataGaName":603,"dataGaLocation":476},"/de-de/solutions/agile-delivery/","agile delivery",{"text":605,"config":606},"SCM",{"href":122,"dataGaName":607,"dataGaLocation":476},"source code management",{"text":113,"config":609},{"href":115,"dataGaName":610,"dataGaLocation":476},"continuous integration & delivery",{"text":164,"config":612},{"href":166,"dataGaName":613,"dataGaLocation":476},"value stream management",{"text":557,"config":615},{"href":616,"dataGaName":560,"dataGaLocation":476},"/de-de/solutions/gitops/",{"text":177,"config":618},{"href":179,"dataGaName":180,"dataGaLocation":476},{"text":182,"config":620},{"href":184,"dataGaName":185,"dataGaLocation":476},{"text":187,"config":622},{"href":189,"dataGaName":190,"dataGaLocation":476},{"text":624,"config":625},"Bildungswesen",{"href":626,"dataGaName":627,"dataGaLocation":476},"/de-de/solutions/education/","education",{"text":629,"config":630},"Finanzdienstleistungen",{"href":631,"dataGaName":632,"dataGaLocation":476},"/de-de/solutions/finance/","financial services",{"title":197,"links":634},[635,637,639,641,644,646,648,650,652,654,656,658],{"text":209,"config":636},{"href":211,"dataGaName":212,"dataGaLocation":476},{"text":214,"config":638},{"href":216,"dataGaName":217,"dataGaLocation":476},{"text":219,"config":640},{"href":221,"dataGaName":222,"dataGaLocation":476},{"text":224,"config":642},{"href":226,"dataGaName":643,"dataGaLocation":476},"docs",{"text":247,"config":645},{"href":249,"dataGaName":250,"dataGaLocation":476},{"text":242,"config":647},{"href":244,"dataGaName":245,"dataGaLocation":476},{"text":252,"config":649},{"href":254,"dataGaName":255,"dataGaLocation":476},{"text":260,"config":651},{"href":262,"dataGaName":263,"dataGaLocation":476},{"text":265,"config":653},{"href":267,"dataGaName":268,"dataGaLocation":476},{"text":270,"config":655},{"href":272,"dataGaName":273,"dataGaLocation":476},{"text":275,"config":657},{"href":277,"dataGaName":278,"dataGaLocation":476},{"text":280,"config":659},{"href":282,"dataGaName":283,"dataGaLocation":476},{"title":298,"links":661},[662,664,666,668,670,672,674,679,684,686,688,690],{"text":305,"config":663},{"href":307,"dataGaName":300,"dataGaLocation":476},{"text":310,"config":665},{"href":312,"dataGaName":313,"dataGaLocation":476},{"text":318,"config":667},{"href":320,"dataGaName":321,"dataGaLocation":476},{"text":323,"config":669},{"href":325,"dataGaName":326,"dataGaLocation":476},{"text":328,"config":671},{"href":330,"dataGaName":331,"dataGaLocation":476},{"text":333,"config":673},{"href":335,"dataGaName":336,"dataGaLocation":476},{"text":675,"config":676},"Nachhaltigkeit",{"href":677,"dataGaName":678,"dataGaLocation":476},"/sustainability/","Sustainability",{"text":680,"config":681},"Vielfalt, Inklusion und Zugehörigkeit",{"href":682,"dataGaName":683,"dataGaLocation":476},"/de-de/diversity-inclusion-belonging/","Diversity, inclusion and belonging",{"text":338,"config":685},{"href":340,"dataGaName":341,"dataGaLocation":476},{"text":348,"config":687},{"href":350,"dataGaName":351,"dataGaLocation":476},{"text":353,"config":689},{"href":355,"dataGaName":356,"dataGaLocation":476},{"text":691,"config":692},"Transparenzerklärung zu moderner Sklaverei",{"href":693,"dataGaName":694,"dataGaLocation":476},"https://handbook.gitlab.com/handbook/legal/modern-slavery-act-transparency-statement/","modern slavery transparency statement",{"items":696},[697,699,702],{"text":524,"config":698},{"href":526,"dataGaName":527,"dataGaLocation":476},{"text":700,"config":701},"Cookies",{"dataGaName":536,"dataGaLocation":476,"id":537,"isOneTrustButton":31},{"text":529,"config":703},{"href":531,"dataGaName":532,"dataGaLocation":476},[705,718],{"id":706,"title":10,"body":29,"config":707,"content":709,"description":29,"extension":28,"meta":713,"navigation":31,"path":714,"seo":715,"stem":716,"__hash__":717},"blogAuthors/en-us/blog/authors/ivan-nemytchenko.yml",{"template":708},"BlogAuthor",{"name":10,"config":710},{"headshot":711,"ctfId":712},"https://res.cloudinary.com/about-gitlab-com/image/upload/v1749659488/Blog/Author%20Headshots/gitlab-logo-extra-whitespace.png","Ivan-Nemytchenko",{},"/en-us/blog/authors/ivan-nemytchenko",{},"en-us/blog/authors/ivan-nemytchenko","LIveI3a-Zqab8RyZk3ijJwKPTck0i-FN_JanwQMOZr4",{"id":719,"title":11,"body":29,"config":720,"content":721,"description":29,"extension":28,"meta":725,"navigation":31,"path":726,"seo":727,"stem":728,"__hash__":729},"blogAuthors/en-us/blog/authors/cesar-saavedra.yml",{"template":708},{"name":11,"config":722},{"headshot":723,"ctfId":724},"https://res.cloudinary.com/about-gitlab-com/image/upload/v1749659600/Blog/Author%20Headshots/csaavedra1-headshot.jpg","csaavedra1",{},"/en-us/blog/authors/cesar-saavedra",{},"en-us/blog/authors/cesar-saavedra","SMqRf-z0W5m5GROz_dXGjmuIb3YaOwm_n_RfeK16GcA",[731,744,758],{"content":732,"config":742},{"title":733,"description":734,"authors":735,"heroImage":737,"date":738,"body":739,"category":13,"tags":740},"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.",[736],"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",[113,741,26],"product",{"featured":16,"template":17,"slug":743},"how-to-build-ci-cd-observability-at-scale",{"content":745,"config":756},{"body":746,"title":747,"description":748,"authors":749,"heroImage":751,"date":752,"category":13,"tags":753},"## 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.",[750],"Omid Khan","https://res.cloudinary.com/about-gitlab-com/image/upload/v1772721753/frfsm1qfscwrmsyzj1qn.png","2026-04-09",[113,754,26,755],"DevOps platform","features",{"featured":31,"template":17,"slug":757},"5-ways-gitlab-pipeline-logic-solves-real-engineering-problems",{"content":759,"config":768},{"title":760,"description":761,"authors":762,"heroImage":764,"date":765,"body":766,"category":13,"tags":767},"GitLab Container Virtual Registry mit Docker Hardened Images einrichten","Mehrere Registries hinter einem Endpunkt – GitLab Container Virtual Registry mit Docker Hardened Images, Caching und Audit-Trail.",[763],"Tim Rizzi","https://res.cloudinary.com/about-gitlab-com/image/upload/v1772111172/mwhgbjawn62kymfwrhle.png","2026-03-12","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.",[26,741,755],{"featured":16,"template":17,"slug":769},"using-gitlab-container-virtual-registry-with-docker-hardened-images",{"promotions":771},[772,786,797,809],{"id":773,"categories":774,"header":776,"text":777,"button":778,"image":783},"ai-modernization",[775],"ai-ml","Is AI achieving its promise at scale?","Quiz will take 5 minutes or less",{"text":779,"config":780},"Get your AI maturity score",{"href":781,"dataGaName":782,"dataGaLocation":250},"/assessments/ai-modernization-assessment/","modernization assessment",{"config":784},{"src":785},"https://res.cloudinary.com/about-gitlab-com/image/upload/v1772138786/qix0m7kwnd8x2fh1zq49.png",{"id":787,"categories":788,"header":789,"text":777,"button":790,"image":794},"devops-modernization",[741,575],"Are you just managing tools or shipping innovation?",{"text":791,"config":792},"Get your DevOps maturity score",{"href":793,"dataGaName":782,"dataGaLocation":250},"/assessments/devops-modernization-assessment/",{"config":795},{"src":796},"https://res.cloudinary.com/about-gitlab-com/image/upload/v1772138785/eg818fmakweyuznttgid.png",{"id":798,"categories":799,"header":801,"text":777,"button":802,"image":806},"security-modernization",[800],"security","Are you trading speed for security?",{"text":803,"config":804},"Get your security maturity score",{"href":805,"dataGaName":782,"dataGaLocation":250},"/assessments/security-modernization-assessment/",{"config":807},{"src":808},"https://res.cloudinary.com/about-gitlab-com/image/upload/v1772138786/p4pbqd9nnjejg5ds6mdk.png",{"id":810,"paths":811,"header":814,"text":815,"button":816,"image":821},"github-azure-migration",[812,813],"migration-from-azure-devops-to-gitlab","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":817,"config":818},"See how GitLab compares to GitHub",{"href":819,"dataGaName":820,"dataGaLocation":250},"/compare/gitlab-vs-github/github-azure-migration/","github azure migration",{"config":822},{"src":796},{"header":824,"blurb":825,"button":826,"secondaryButton":831},"Beginne noch heute, schneller zu entwickeln","Entdecke, was dein Team mit der intelligenten Orchestrierungsplattform für DevSecOps erreichen kann.\n",{"text":827,"config":828},"Kostenlosen Test starten",{"href":829,"dataGaName":55,"dataGaLocation":830},"https://gitlab.com/-/trial_registrations/new?glm_content=default-saas-trial&glm_source=about.gitlab.com/de-de/","feature",{"text":57,"config":832},{"href":59,"dataGaName":60,"dataGaLocation":830},1777493578980]