[{"data":1,"prerenderedAt":781},["ShallowReactive",2],{"/de-de/blog/using-child-pipelines-to-continuously-deploy-to-five-environments":3,"navigation-de-de":44,"banner-de-de":446,"footer-de-de":456,"blog-post-authors-de-de-Olivier Dupré":661,"blog-related-posts-de-de-using-child-pipelines-to-continuously-deploy-to-five-environments":676,"blog-promotions-de-de":718,"next-steps-de-de":771},{"id":4,"title":5,"authorSlugs":6,"body":8,"categorySlug":9,"config":10,"content":14,"description":8,"extension":29,"isFeatured":12,"meta":30,"navigation":31,"path":32,"publishedDate":20,"seo":33,"stem":37,"tagSlugs":38,"__hash__":43},"blogPosts/de-de/blog/using-child-pipelines-to-continuously-deploy-to-five-environments.yml","Using Child Pipelines To Continuously Deploy To Five Environments",[7],"olivier-dupr",null,"engineering",{"slug":11,"featured":12,"template":13},"using-child-pipelines-to-continuously-deploy-to-five-environments",false,"BlogPost",{"title":15,"description":16,"authors":17,"heroImage":19,"date":20,"body":21,"category":9,"tags":22,"updatedDate":28},"Kontinuierliche Bereitstellung in fünf Umgebungen mithilfe von untergeordneten Pipelines","Erfahre, wie du die kontinuierliche Bereitstellung in verschiedenen Umgebungen – darunter temporäre, sofort einsatzbereite Sandboxes – mit einem minimalistischen GitLab-Workflow verwalten kannst.",[18],"Olivier Dupré","https://res.cloudinary.com/about-gitlab-com/image/upload/v1750097012/Blog/Hero%20Images/Blog/Hero%20Images/AdobeStock_397632156_3Ldy1urjMStQCl4qnOBvE0_1750097011626.jpg","2024-09-26","Manchmal brauchen DevSecOps-Teams die Möglichkeit, die kontinuierliche Bereitstellung über mehrere Umgebungen übergreifend zu verwalten, ohne dabei ihre Workflows zu verändern. Die [DevSecOps-Plattform von GitLab](https://about.gitlab.com/de-de/) macht dies mit einem minimalistischen Ansatz möglich, unter anderem für temporäre, sofort einsatzbereite Sandboxes. In diesem Artikel erfährst du, wie du die kontinuierliche Bereitstellung der Infrastruktur mit Terraform in verschiedenen Umgebungen ausführen kannst.\n\n> **Nahezu 100 % Uptime: NVIDIA skaliert mit GitLab global – ohne Ausfallzeiten** Verteilte Teams bei NVIDIA profitieren mit GitLab Geo von schneller verfügbaren Repositories, häufigeren Upgrades ohne Ausfallzeiten und global-synchronen sowie -sicheren Projekten. Erfahre, wie GitLab Premium hilft, Stabilität, Transparenz und Effizienz zu verbessern. **[Erfolgsstory lesen](https://about.gitlab.com/de-de/customers/nvidia/)**\n\nDiese Strategie kann einfach auf andere Projekte umgesetzt werden, egal, ob es sich um Infrastructure as Code (IaC), die auf einer anderen Technologie wie [Pulumi](https://www.pulumi.com/) oder [Ansible](https://www.ansible.com/) basiert, um Quellcode in beliebigen Sprachen oder ein Monorepo handelt, bei dem viele Sprachen gemischt verwendet werden.\n\nDie letzte Pipeline, die du am Ende dieses Tutorials hast, stellt Folgendes bereit:\n\n* Eine temporäre **Review-Umgebung** für jeden Feature-Branch.\n* Eine **Integrationsumgebung**, die einfach zu löschen und vom Haupt-Branch aus bereitzustellen ist.\n* Eine **QA-Umgebung**, die ebenfalls von dem Haupt-Branch bereitgestellt wird, um Qualitätssicherungsschritte durchzuführen.\n* Eine **Staging-Umgebung**, die für jedes Tag bereitgestellt wird. Dies ist die letzte Runde vor der Produktion.\n* Eine **Produktionsumgebung**, die direkt nach der Staging-Umgebung folgt. Diese wird zur Demonstration manuell ausgelöst, kann aber auch kontinuierlich bereitgestellt werden.\n\n>Hier findest du die Legende für die Flussdiagramme in diesem Artikel:\n> * Runde Boxen sind die GitLab-Branches.\n> * Eckige Boxen sind die Umgebungen.\n> * Der Text auf den Pfeilen sind die Aktionen, die von einem Feld zum nächsten fließen sollen.\n> * Eckige Quadrate sind Entscheidungsschritte.\n\n\u003Cpre class=\"mermaid\">\nflowchart LR\n    A(main) -->|new feature| B(feature_X)\n\n    B -->|auto deploy| C[review/feature_X]\n    B -->|merge| D(main)\n    C -->|destroy| D\n\n    D -->|auto deploy| E[integration]\n    E -->|manual| F[qa]\n\n    D -->|tag| G(X.Y.Z)\n    F -->|validate| G\n\n    G -->|auto deploy| H[staging]\n    H -->|manual| I{plan}\n    I -->|manual| J[production]\n\u003C/pre>\n\nBei jedem Schritt erfährst du das [Warum](#why) und das [Was](#what), bevor du zum [Wie](#how) übergehst. Dies wird dir helfen, dieses Tutorial vollständig zu verstehen und zu replizieren.\n\n## Warum\n\n* [Kontinuierliche Integration](https://about.gitlab.com/de-de/topics/ci-cd/#what-is-continuous-integration-ci) ist fast ein De-facto-Standard. Die meisten Unternehmen haben CI-Pipelines implementiert oder sind bereit, ihre Arbeitsweise zu standardisieren.\n\n* [Kontinuierliche Bereitstellung](https://about.gitlab.com/topics/ci-cd/#what-is-continuous-delivery-cd), wobei Artefakte in ein Repository oder eine Registry am Ende der CI-Pipeline gepusht werden, ist ebenfalls beliebt.\n\n* Kontinuierliche Bereitstellung, die weiter geht und diese Artefakte automatisch bereitstellt, ist allerdings weniger verbreitet. Wenn, dann wird sie vor allem im Bereich von Anwendungen implementiert. Wenn es um die kontinuierliche Bereitstellung von Infrastruktur geht, scheint das Bild weniger klar zu sein und es dreht sich vieles um das Management mehrerer Umgebungen. Im Gegensatz dazu scheint das Testen, Sichern und Überprüfen des Infrastruktur-Codes schwieriger zu sein. Dies ist eines der Felder, in denen DevOps noch nicht ausgereift ist. Ein anderer Anwendungsbereich ist, die Sicherheit im Vorfeld zu kontrollieren und Sicherheitsteams sowie – was noch wichtiger ist – Sicherheitsbedenken früher in den Lebenszyklus der Bereitstellung zu integrieren und so von DevOps auf ***DevSecOps*** upzugraden.\n\nAngesichts dessen wirst du in diesem Tutorial eine einfache und doch effiziente Möglichkeit erarbeiten, DevSecOps für deine Infrastruktur zu implementieren, indem du beispielsweise Ressourcen in fünf Umgebungen bereitstellst und dich schrittweise von der Entwicklung bis zur Produktion vorarbeitest.\n\n__Hinweis:__ Auch wenn ich einen FinOps-Ansatz und eine Reduktion der Umgebungen befürworte, gibt es manchmal gute Gründe, mehr als nur Entwicklung, Staging und Produktion aufrechtzuerhalten. Bitte passe die folgenden Beispiele an deine Bedürfnisse an.\n\n## Was\n\nDer Aufstieg der Cloud-Technologie hat die Nutzung von IaC vorangetrieben. Ansible und Terraform gehörten in diesem Bereich zu den Pionieren. OpenTofu, Pulumi, AWS CDK, Google Deploy Manager und viele andere folgten.\n\nIaC gilt als perfekte Lösung, um sich bei der Bereitstellung von Infrastruktur sicher zu fühlen. Du kannst sie testen, bereitstellen und immer wieder abspielen, bis du dein Ziel erreicht hast.\n\nLeider sehen wir oft, dass Unternehmen für jede ihrer Zielumgebungen mehrere Branches oder sogar Repositories unterhalten. Und hier beginnen die Probleme. Sie setzen einen Prozess nicht mehr durch. Sie stellen nicht mehr sicher, dass Änderungen in der Produktions-Codebase in früheren Umgebungen genauestens getestet wurden. Und sie beginnen, Drifts von einer Umgebung in die andere zu erleben.\n\nMir wurde klar, dass dieses Tutorial notwendig war, als auf einer Konferenz alle Teilnehmenden sagten, dass sie keinen Workflow haben, der durchsetzt, dass Infrastruktur genau getestet wird, bevor sie für die Produktion bereitgestellt wird. Und sie waren sich alle einig, dass sie manchmal den Code direkt in die Produktion patchen. Klar geht das schnell, aber ist es auch sicher? Wie meldet man an frühere Umgebungen zurück? Wie stellt man sicher, dass es keine Nebeneffekte gibt? Wie kontrolliert man, ob man das Unternehmen in Gefahr bringt, wenn neue Sicherheitslücken zu schnell in die Produktion gepusht werden?\n\nDie Frage, *warum* DevOps-Teams direkt in die Produktion implementieren, ist hier entscheidend. Liegt es daran, dass die Pipeline effizienter oder schneller sein könnte? Gibt es keine Automatisierung? Oder, noch schlimmer, gibt es *keine Möglichkeit, außerhalb der Produktion genau zu testen*?\n\nIm nächsten Abschnitt erfährst du, wie du Automatisierung für deine Infrastruktur implementieren und sicherstellen kannst, dass dein DevOps-Team effektiv testet, bevor etwas in eine Umgebung gepusht wird, die sich auf andere auswirkt. Du wirst sehen, wie dein Code gesichert und seine Bereitstellung durchgehend kontrolliert wird.\n\n## Wie\n\nWie bereits erwähnt, gibt es heutzutage viele Programmiersprachen für IaC, und wir können ganz einfach nicht *alle* in einem einzigen Artikel behandeln. Ich werde mich also auf einen grundlegenden Terraform-Code konzentrieren, der auf Version 1.4 läuft. Bitte fixiere dich nicht auf die Programmiersprache für IaC selbst, sondern auf den Prozess, den du für dein eigenes Ökosystem umsetzen kannst.\n\n### Der Terraform-Code\n\nBeginnen wir mit einem grundlegenden Terraform-Code.\n\nWir werden auf AWS bereitstellen, eine virtuelle private Cloud (VPC), die ein virtuelles Netzwerk ist. In dieser VPC werden wir ein öffentliches und ein privates Subnetz bereitstellen. Wie der Name schon sagt, handelt es sich um Subnetze der Haupt-VPC. Abschließend fügen wir eine EC2-Instanz (Elastic Cloud Compute; eine virtuelle Maschine) zum öffentlichen Subnetz hinzu.\n\nDies zeigt, wie vier Ressourcen bereitgestellt werden können, ohne zu komplex zu werden. Die Idee ist, sich auf die Pipeline zu konzentrieren, nicht auf den Code.\n\nHier ist das Ziel, das wir für dein Repository erreichen möchten.\n\n![Ziel für Repository](https://res.cloudinary.com/about-gitlab-com/image/upload/v1750097033/Blog/Content%20Images/Blog/Content%20Images/image5_aHR0cHM6_1750097033415.png)\n\nGehen wir Schritt für Schritt vor.\n\nZuerst deklarieren wir alle Ressourcen in der Datei `terraform/main.tf`:\n\n```terraform\nprovider \"aws\" {\n  region = var.aws_default_region\n}\n\nresource \"aws_vpc\" \"main\" {\n  cidr_block = var.aws_vpc_cidr\n\n  tags = {\n    Name     = var.aws_resources_name\n  }\n}\n\nresource \"aws_subnet\" \"public_subnet\" {\n  vpc_id     = aws_vpc.main.id\n  cidr_block = var.aws_public_subnet_cidr\n\n  tags = {\n    Name = \"Public Subnet\"\n  }\n}\nresource \"aws_subnet\" \"private_subnet\" {\n  vpc_id     = aws_vpc.main.id\n  cidr_block = var.aws_private_subnet_cidr\n\n  tags = {\n    Name = \"Private Subnet\"\n  }\n}\n\nresource \"aws_instance\" \"sandbox\" {\n  ami           = var.aws_ami_id\n  instance_type = var.aws_instance_type\n\n  subnet_id = aws_subnet.public_subnet.id\n\n  tags = {\n    Name     = var.aws_resources_name\n  }\n}\n```\n\nWie du sehen kannst, sind für diesen Code einige Variablen erforderlich, die wir also in der Datei `terraform/variables.tf` deklarieren:\n\n```terraform\nvariable \"aws_ami_id\" {\n  description = \"The AMI ID of the image being deployed.\"\n  type        = string\n}\n\nvariable \"aws_instance_type\" {\n  description = \"The instance type of the VM being deployed.\"\n  type        = string\n  default     = \"t2.micro\"\n}\n\nvariable \"aws_vpc_cidr\" {\n  description = \"The CIDR of the VPC.\"\n  type        = string\n  default     = \"10.0.0.0/16\"\n}\n\nvariable \"aws_public_subnet_cidr\" {\n  description = \"The CIDR of the public subnet.\"\n  type        = string\n  default     = \"10.0.1.0/24\"\n}\n\nvariable \"aws_private_subnet_cidr\" {\n  description = \"The CIDR of the private subnet.\"\n  type        = string\n  default     = \"10.0.2.0/24\"\n}\n\nvariable \"aws_default_region\" {\n  description = \"Default region where resources are deployed.\"\n  type        = string\n  default     = \"eu-west-3\"\n}\n\nvariable \"aws_resources_name\" {\n  description = \"Default name for the resources.\"\n  type        = string\n  default     = \"demo\"\n}\n```\n\nAuf der IaC-Seite sind wir damit auch schon fast fertig. Was fehlt, ist eine Möglichkeit, die Terraform-Zustände zu teilen. Für diejenigen, die es nicht wissen: Terraform funktioniert schematisch wie folgt:\n\n* `plan` überprüft die Unterschiede zwischen dem aktuellen Status der Infrastruktur und dem, was im Code definiert ist. Dann gibt es die Unterschiede aus.\n* `apply` wendet die Unterschiede im `plan` an und aktualisiert den Status.\n\nIn der ersten Runde ist der Status leer, dann wird er mit den Details (ID usw.) der von Terraform angewendeten Ressourcen gefüllt.\n\nDas Problem ist: Wo wird dieser Zustand gespeichert? Wie können wir ihn teilen, damit mehrere Entwickler(innen) am Code zusammenarbeiten können?\n\nDie Lösung ist ziemlich einfach: Nutze GitLab, um den Status über ein [Terraform-HTTP-Backend](https://docs.gitlab.com/ee/user/infrastructure/iac/terraform_state.html) zu speichern und freizugeben.\n\nDer erste Schritt bei der Verwendung dieses Backends besteht darin, die einfachste Datei, nämlich `terraform/backend.tf` zu erstellen. Der zweite Schritt erfolgt in der Pipeline.\n\n```terraform\nterraform {\n  backend \"http\" {\n  }\n}\n```\n\nEt voilà! Wir haben einen minimalen Terraform-Code, um diese vier Ressourcen bereitzustellen. Wir werden die Variablenwerte zur Laufzeit bereitstellen, also machen wir das später.\n\n### Der Workflow\n\nDer Workflow, den wir jetzt implementieren werden, sieht folgendermaßen aus:\n\n\u003Cpre class=\"mermaid\">\nflowchart LR\n    A(main) -->|new feature| B(feature_X)\n\n    B -->|auto deploy| C[review/feature_X]\n    B -->|merge| D(main)\n    C -->|destroy| D\n\n    D -->|auto deploy| E[integration]\n    E -->|manual| F[qa]\n\n    D -->|tag| G(X.Y.Z)\n    F -->|validate| G\n\n    G -->|auto deploy| H[staging]\n    H -->|manual| I{plan}\n    I -->|manual| J[production]\n\u003C/pre>\n\n1. Erstelle einen **Feature-Branch**. Dadurch werden alle Scanner kontinuierlich auf dem Code ausgeführt, um sicherzustellen, dass er immer konform und gesichert bleibt. Dieser Code wird kontinuierlich in der temporären Umgebung `review/feature_branch` mit dem Namen des aktuellen Branches bereitgestellt. Dies ist eine sichere Umgebung, in der die Entwickler(innen) und IT-Betriebsteams ihren Code ohne Auswirkungen auf andere testen können. Hier setzen wir auch den Prozess durch, z. B. indem Code Reviews durchgesetzt und Scanner ausgeführt werden, damit die Qualität und Sicherheit des Codes akzeptabel sind und deine Assets nicht gefährdet werden. Die von diesem Branch bereitgestellte Infrastruktur wird automatisch zerstört, wenn der Branch geschlossen wird. So behältst du dein Budget unter Kontrolle.\n\n\u003Cpre class=\"mermaid\">\nflowchart LR\n    A(main) -->|new feature| B(feature_X)\n\n    B -->|auto deploy| C[review/feature_X]\n    B -->|merge| D(main)\n    C -->|destroy| D\n\u003C/pre>\n\n2. Nach der Genehmigung wird der Feature-Branch mit dem Main-Branch **zusammengeführt**. Dies ist ein [geschützter Branch](https://docs.gitlab.com/ee/user/project/protected_branches.html), in den niemand pushen kann. Dies ist obligatorisch, um sicherzustellen, dass jede Änderungsanfrage an die Produktion gründlich getestet wird. Dieser Branch wird auch kontinuierlich bereitgestellt. Das Ziel hier ist die Umgebung `integration`. Damit diese Umgebung etwas stabiler bleibt, wird das Löschen nicht automatisiert, sondern kann manuell ausgelöst werden.\n\n\u003Cpre class=\"mermaid\">\nflowchart LR\n    D(main) -->|auto deploy| E[integration]\n\u003C/pre>\n\n3. Von dort aus ist eine manuelle Genehmigung erforderlich, um die nächste Bereitstellung auszulösen. Dadurch wird der Haupt-Branch in der Umgebung `qa` bereitgestellt. Hier habe ich eine Regel festgelegt, um das Löschen aus der Pipeline zu verhindern. Diese Umgebung sollte ziemlich stabil sein (schließlich ist es bereits die dritte Umgebung), und ich möchte das versehentliche Löschen verhindern. Du kannst die Regeln gerne an deine Prozesse anpassen.\n\n\u003Cpre class=\"mermaid\">\nflowchart LR\n    D(main)-->|auto deploy| E[integration]\n    E -->|manual| F[qa]\n\u003C/pre>\n\n4. Um fortzufahren, müssen wir den Code **taggen**. Wir setzen hier auf [geschützte Tags](https://docs.gitlab.com/ee/user/project/protected_tags.html), um sicherzustellen, dass nur eine bestimmte Gruppe von Benutzer(inne)n in diese letzten beiden Umgebungen bereitstellen darf. Dadurch wird sofort eine Bereitstellung in der Umgebung `staging` ausgelöst.\n\n\u003Cpre class=\"mermaid\">\nflowchart LR\n    D(main) -->|tag| G(X.Y.Z)\n    F[qa] -->|validate| G\n\n    G -->|auto deploy| H[staging]\n\u003C/pre>\n\n\n5. Schließlich landen wir bei `production`. Wenn es um Infrastruktur geht, ist es oft schwierig, sie schrittweise bereitzustellen (10 %, 25 % usw.), sodass wir gleich die gesamte Infrastruktur bereitstellen werden. Dennoch steuern wir diese Bereitstellung, indem dieser letzte Schritt manuell ausgelöst wird. Und um maximale Kontrolle über diese hochkritische Umgebung zu erzwingen, werden wir sie als [geschützte Umgebung](https://docs.gitlab.com/ee/ci/environments/protected_environments.html) kontrollieren.\n\n\u003Cpre class=\"mermaid\">\nflowchart LR\n    H[staging] -->|manual| I{plan}\n    I -->|manual| J[production]\n\u003C/pre>\n\n### Die Pipeline\n\nUm den oben genannten [Workflow](#the-workflow) zu implementieren, implementieren wir jetzt eine Pipeline mit zwei [Downstream-Pipelines](https://docs.gitlab.com/ee/ci/pipelines/downstream_pipelines.html).\n\n#### Die Haupt-Pipeline\n\nBeginnen wir mit der Haupt-Pipeline. Dies ist diejenige, die automatisch bei jedem **Push zu einem Feature-Branch**, jedem Zusammenführen zum Standard-Branch** oder jedem **Tag** ausgelöst wird. *Die Pipeline*, die eine echte **kontinuierliche Bereitstellung** in den folgenden Umgebungen durchführt: `dev`, `integration` und `staging`. Und das wird in der Datei `.gitlab-ci.yml` im Stamm deines Projekts deklariert.\n\n![Repository-Ziel](https://res.cloudinary.com/about-gitlab-com/image/upload/v1750097033/Blog/Content%20Images/Blog/Content%20Images/image1_aHR0cHM6_1750097033417.png)\n\n```yml\nStages:\n  - test\n  - environments\n\n.environment:\n  stage: environments\n  variables:\n    TF_ROOT: terraform\n    TF_CLI_ARGS_plan: \"-var-file=../vars/$variables_file.tfvars\"\n  trigger:\n    include: .gitlab-ci/.first-layer.gitlab-ci.yml\n    strategy: depend             # Wait for the triggered pipeline to successfully complete\n    forward:\n      yaml_variables: true      # Forward variables defined in the trigger job\n      pipeline_variables: true  # Forward manual pipeline variables and scheduled pipeline variables\n\nreview:\n  extends: .environment\n  variables:\n    environment: review/$CI_COMMIT_REF_SLUG\n    TF_STATE_NAME: $CI_COMMIT_REF_SLUG\n    variables_file: review\n    TF_VAR_aws_resources_name: $CI_COMMIT_REF_SLUG  # Used in the tag Name of the resources deployed, to easily differenciate them\n  rules:\n    - if: $CI_COMMIT_BRANCH && $CI_COMMIT_BRANCH != $CI_DEFAULT_BRANCH\n\nintegration:\n  extends: .environment\n  variables:\n    environment: integration\n    TF_STATE_NAME: $environment\n    variables_file: $environment\n  rules:\n    - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH\n\nstaging:\n  extends: .environment\n  variables:\n    environment: staging\n    TF_STATE_NAME: $environment\n    variables_file: $environment\n  rules:\n    - if: $CI_COMMIT_TAG\n\n#### TWEAK\n# This tweak is needed to display vulnerability results in the merge widgets.\n# As soon as this issue https://gitlab.com/gitlab-org/gitlab/-/issues/439700 is resolved, the `include` instruction below can be removed.\n# Until then, the SAST IaC scanners will run in the downstream pipelines, but their results will not be available directly in the merge request widget, making it harder to track them.\n# Note: This workaround is perfectly safe and will not slow down your pipeline.\ninclude:\n  - template: Security/SAST-IaC.gitlab-ci.yml\n#### END TWEAK\n\n```\n\nDiese Pipeline läuft nur in zwei Phasen: `test` und `environments`. Erstere wird benötigt, damit der *TWEAK* Scanner ausführen kann. Zweitere löst eine untergeordnete Pipeline mit einem anderen Satz von Variablen für jeden oben definierten Fall aus (Push zum Branch, Zusammenführen zum Standard-Branch oder Tag).\n\nWir fügen hier eine Abhängigkeit mit dem Schlüsselwort [strategy:depend](https://docs.gitlab.com/ee/ci/yaml/index.html#triggerstrategy) zu unserer untergeordneten Pipeline hinzu, sodass die Pipeline-Ansicht in GitLab erst aktualisiert wird, wenn die Bereitstellung abgeschlossen ist.\n\nWie du hier sehen kannst, definieren wir einen Basisjob, [hidden](https://docs.gitlab.com/ee/ci/jobs/#hide-jobs), und erweitern ihn um bestimmte Variablen und Regeln, um nur eine Bereitstellung für jede Zielumgebung auszulösen.\n\nNeben den [vordefinierten Variablen](https://docs.gitlab.com/ee/ci/variables/predefined_variables.html) verwenden wir zwei neue Einträge, die wir definieren müssen:\n1. [Die für jede Umgebung spezifischen Variablen](#the-variable-definitions): `../vars/$variables_file.tfvars`\n2. [Die untergeordnete Pipeline](#the-child-pipeline), definiert in `.gitlab-ci/.first-layer.gitlab-ci.yml`\n\nBeginnen wir mit dem kleinsten Teil, den Variablendefinitionen.\n\n### Die Variablendefinitionen\n\nWir werden hier zwei Lösungen mischen, um Terraform Variablen zur Verfügung zu stellen:\n\n* Die erste nutzt [.tfvars-Dateien](https://developer.hashicorp.com/terraform/language/values/variables#variable-definitions-tfvars-files) für alle nicht sensiblen Eingaben, die in GitLab gespeichert werden sollten.\n\n![Lösung 1 zur Bereitstellung von Variablen für Terraform](https://res.cloudinary.com/about-gitlab-com/image/upload/v1750097034/Blog/Content%20Images/Blog/Content%20Images/image2_aHR0cHM6_1750097033419.png)\n\n* Die zweite verwendet [Umgebungsvariablen](https://developer.hashicorp.com/terraform/language/values/variables#environment-variables) mit dem Präfix `TF_VAR`. Diese zweite Möglichkeit, Variablen zu injizieren, die mit der GitLab-Fähigkeit verbunden sind, [Variablen zu maskieren](https://docs.gitlab.com/ee/ci/variables/#mask-a-cicd-variable), [sie zu schützen](https://docs.gitlab.com/ee/ci/variables/#protect-a-cicd-variable) und [sie in Umgebungen zu übertragen](https://docs.gitlab.com/ee/ci/environments/index.html#limit-the-environment-scope-of-a-cicd-variable), ist eine leistungsstarke Lösung, um **Datenlecks sensibler Informationen zu verhindern**. (Wenn du das private CIDR deiner Produktion als sehr sensibel betrachtest, könntest du es so schützen, indem du sicherstellst, dass es nur für die Umgebung `production` verfügbar ist, für Pipelines, die gegen geschützte Branches und Tags ausgeführt werden, und dass sein Wert in den Protokollen des Jobs maskiert ist.)\n\n![Lösung 2 zur Bereitstellung von Variablen für Terraform](https://res.cloudinary.com/about-gitlab-com/image/upload/v1750097034/Blog/Content%20Images/Blog/Content%20Images/image4_aHR0cHM6_1750097033422.png)\n\nDarüber hinaus sollte jede Variablendatei über eine [Datei `CODEOWNERS`](https://docs.gitlab.com/ee/user/project/codeowners/) gesteuert werden, um festzulegen, wer sie ändern darf.\n\n```text\n[Production owners] vars/production.tfvars @operations-group\n\n[Staging owners]\nvars/staging.tfvars @odupre @operations-group\n\n[CodeOwners owners]\nCODEOWNERS @odupre\n```\n\nDieser Artikel ist kein Terraform-Training, daher halten wir das kurz und zeigen hier einfach die Datei `vars/review.tfvars`. Die nachfolgenden Umgebungsdateien sind sich natürlich sehr ähnlich. Lege hier einfach die nicht-sensiblen Variablen und ihre Werte fest.\n\n```shell\naws_vpc_cidr = \"10.1.0.0/16\"\naws_public_subnet_cidr = \"10.1.1.0/24\"\naws_private_subnet_cidr = \"10.1.2.0/24\"\n```\n\n#### Die untergeordnete Pipeline\n\nHier wird die eigentliche Arbeit erledigt. Sie ist also etwas komplexer als die erste Pipeline. Es gibt aber auch hier keine Schwierigkeit, die wir nicht gemeinsam überwinden können!\n\nWie wir bei der Definition der [Haupt-Pipeline](#the-main-pipeline) gesehen haben, wird diese Downstream-Pipeline in der Datei `.gitlab-ci/.first-layer.gitlab-ci.yml` deklariert.\n\n![In Datei deklarierte Downstream-Pipeline](https://res.cloudinary.com/about-gitlab-com/image/upload/v1750097033/Blog/Content%20Images/Blog/Content%20Images/image3_aHR0cHM6_1750097033424.png)\n\nZerlegen wir sie in kleine Stücke. Am Ende sehen wir dann das große Ganze.\n\n##### Terraform-Befehle ausführen und den Code sichern\n\nZuerst wollen wir eine Pipeline für Terraform ausführen. GitLab ist Open Source. Unsere Terraform-Vorlage ist also auch Open Source. Du kannst sie einfach einbeziehen. Dies erreichst du mit folgendem Code-Schnipsel:\n\n```yml\ninclude:\n  - template: Terraform.gitlab-ci.yml\n```\n\nDiese Vorlage führt für dich die Terraform-Prüfungen für die Formatierung durch und validiert deinen Code, bevor er geplant und angewendet wird. Es ermöglicht dir auch, das zu zerstören, was du bereitgestellt hast.\n\nDa GitLab eine vereinheitlichte DevSecOps-Plattform ist, fügen wir dieser Vorlage automatisch zwei Sicherheitsscanner hinzu, um potenzielle Bedrohungen in deinem Code zu finden und dich zu warnen, bevor du ihn in den nächsten Umgebungen bereitstellst.\n\nJetzt, da wir unseren Code überprüft, gesichert, erstellt und bereitgestellt haben, folgen ein paar Tricks.\n\n##### Zwischenspeicher zwischen Jobs teilen\n\nWir werden Job-Ergebnisse zwischenspeichern, um sie in folgenden Pipeline-Jobs wiederzuverwenden. Dies ist einfach, denn du musst nur den folgenden Code hinzufügen:\n\n```yml\ndefault:\n  cache:  # Use a shared cache or tagged runners to ensure terraform can run on apply and destroy\n    - key: cache-$CI_COMMIT_REF_SLUG\n      fallback_keys:\n        - cache-$CI_DEFAULT_BRANCH\n      paths:\n        - .\n\n```\n\nHier definieren wir einen anderen Zwischenspeicher für jeden Commit und greifen bei Bedarf auf den Namen des Haupt-Branchs zurück.\n\nWenn wir uns die Vorlagen, die wir verwenden, genau ansehen, stellen wir fest, dass sie einige Regeln haben, die zu kontrollieren sind, wenn Jobs ausgeführt werden. Wir wollen alle Kontrollen (sowohl QA als auch Sicherheit) in allen Branchen ausführen. Wir werden diese Einstellungen also überschreiben.\n\n##### Kontrollen in allen Branches ausführen\n\nGitLab-Vorlagen sind eine leistungsstarke Funktion, bei der man auch nur einen Teil der Vorlage überschreiben kann. Hier wollen wir die Regeln einiger Jobs überschreiben, um immer Qualitäts- und Sicherheitskontrollen durchzuführen. Alles andere, was für diese Jobs definiert ist, bleibt wie in der Vorlage definiert.\n\n```yml\nfmt:\n  rules:\n    - when: always\n\nvalidate:\n  rules:\n    - when: always\n\nkics-iac-sast:\n  rules:\n    - when: always\n\niac-sast:\n  rules:\n    - when: always\n```\n\nDa wir nun die Qualitäts- und Sicherheitskontrollen durchgesetzt haben, wollen wir unterscheiden, wie sich die Hauptumgebungen (Integration und Staging) im [Workflow](#the-workflow) und Review-Umgebungen verhalten. Beginnen wir mit der Definition des Verhaltens der Hauptumgebung. Wir werden dann diese Konfiguration für die Review-Umgebungen optimieren.\n\n##### CD für Integration und Staging\n\nWie zuvor definiert, möchten wir den Haupt-Branch und die Tags in diesen beiden Umgebungen bereitstellen. Wir fügen Regeln hinzu, um das sowohl bei den Jobs `build` als auch `deploy` zu kontrollieren. Dann wollen wir `destroy` nur für `integration` aktivieren, da wir definiert haben, dass `staging` zu kritisch ist, um mit einem einzigen Klick gelöscht zu werden. Das ist fehleranfällig, was wir nicht wollen.\n\nSchließlich verknüpfen wir den Job `deploy` mit dem Job `destroy`, damit wir die Umgebung direkt von der GitLab-GUI aus mit `stop` stoppen können.\n\nDie `GIT_STRATEGY` soll verhindern, dass der Code beim Zerstören aus dem Quell-Branch im Runner abgerufen wird. Dies würde fehlschlagen, wenn der Branch manuell gelöscht wurde. Daher verlassen wir uns auf den Zwischenspeicher, um alles zu erhalten, was wir zum Ausführen der Terraform-Anweisungen benötigen.\n\n```yml\nbuild:  # terraform plan\n  environment:\n    name: $TF_STATE_NAME\n    action: prepare\n  rules:\n    - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH\n    - if: $CI_COMMIT_TAG\n\ndeploy: # terraform apply --> automatically deploy on corresponding env (integration or staging) when merging to default branch or tagging. Second layer environments (qa and production) will be controlled manually\n  environment:   environment:     name: $TF_STATE_NAME\n    action: start\n    on_stop: destroy\n  rules:\n    - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH\n    - if: $CI_COMMIT_TAG\n\ndestroy:\n  extends: .terraform:destroy\n  variables:\n    GIT_STRATEGY: none\n  dependencies:\n    - build\n  environment:\n    name: $TF_STATE_NAME\n    action: stop\n  rules:\n    - if: $CI_COMMIT_TAG  # Do not destroy production\n      when: never\n    - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH && $TF_DESTROY == \"true\" # Manually destroy integration env.\n      when: manual\n```\n\nWie gesagt müssen diese Matches in `integration` und `staging` bereitstellen. Uns fehlt jedoch immer noch eine temporäre Umgebung, in der die Entwickler(innen) ihren Code ohne Auswirkungen auf andere erleben und validieren können. Hier findet die Bereitstellung in der Umgebung `review` statt.\n\n##### CD für Review-Umgebungen\n\nDie Bereitstellung in der Review-Umgebung unterscheidet sich nicht allzu sehr von der Bereitstellung in `integration` und `staging`. Wir werden also erneut die Möglichkeit von GitLab nutzen, hier nur Teile der Jobdefinition zu überschreiben.\n\nZuerst legen wir Regeln fest, um diese Jobs nur in Feature-Branches auszuführen.\n\nDann verknüpfen wir den Job `deploy_review` mit `destroy_review`. Dies ermöglicht es uns, die Umgebung **manuell** von der GitLab-Bedienoberfläche aus zu stoppen und – was noch wichtiger ist – es wird **automatisch die Zerstörung der Umgebung ausgelöst**, wenn der Feature-Branch geschlossen wird. Dies ist eine gute FinOps-Praxis, um dir zu helfen, deine Betriebsausgaben zu kontrollieren.\n\nDa Terraform eine Plandatei benötigt, um eine Infrastruktur zu zerstören (genau wie es eine solche Datei benötigt, um eine Infrastruktur aufzubauen), fügen wir eine Abhängigkeit von `destroy_review` zu `build_review` hinzu, um Artefakte abzurufen.\n\nSchließlich sehen wir hier, dass der Name der Umgebung auf `$environment` festgelegt ist. Es wurde in der [Haupt-Pipeline](#the-main-pipeline) auf `review/$CI_COMMIT_REF_SLUG` gesetzt und mit der Anweisung `trigger:forward:yaml_variables:true` an diese untergeordnete Pipeline weitergeleitet.\n\n```yml\nbuild_review:\n  extends: build\n  rules:\n    - if: $CI_COMMIT_TAG\n      when: never\n    - if: $CI_COMMIT_BRANCH != $CI_DEFAULT_BRANCH\n      when: on_success\n\ndeploy_review:\n  extends: deploy\n  dependencies:\n    - build_review\n  environment:\n    name: $environment\n    action: start\n    on_stop: destroy_review\n    # url: https://$CI_ENVIRONMENT_SLUG.example.com\n  rules:\n    - if: $CI_COMMIT_TAG\n      when: never\n    - if: $CI_COMMIT_BRANCH != $CI_DEFAULT_BRANCH\n      when: on_success\n\ndestroy_review:\n  extends: destroy\n  dependencies:\n    - build_review\n  environment:\n    name: $environment\n    action: stop\n  rules:\n    - if: $CI_COMMIT_TAG  # Do not destroy production\n      when: never\n    - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH   # Do not destroy staging\n      when: never\n    - when: manual\n```\n\nZusammenfassend können wir also sagen, dass wir jetzt eine Pipeline haben, die Folgendes kann:\n\n* Temporäre Review-Umgebungen bereitstellen, die automatisch gelöscht werden, wenn der Feature-Branch geschlossen wird\n* Den **Standard-Branch** kontinuierlich auf `integration` bereitstellen\n* Die **Tags** kontinuierlich auf `staging` bereitstellen\n\nFügen wir nun eine zusätzliche Ebene hinzu, auf der wir diesmal mit einem manuellen Auslöser in den Umgebungen `qa` und `production` bereitstellen werden.\n\n##### Kontinuierliche Bereitstellung in QA und Produktion\n\nDa nicht jedes Unternehmen kontinuierlich in der Produktion bereitstellen möchte, fügen wir den nächsten beiden Bereitstellungen eine manuelle Validierung hinzu. Aus einer reinen **CD**-Perspektive würden wir diesen Auslöser nicht hinzufügen, aber betrachte dies als Gelegenheit, zu lernen, wie man Jobs von anderen Auslösern aus ausführt.\n\nBisher haben wir eine [untergeordnete Pipeline](#the-child-pipeline) aus der [Haupt-Pipeline](#the-main-pipeline) gestartet, um alle Bereitstellungen auszuführen.\n\nDa wir andere Bereitstellungen aus dem Standard-Branch und den Tags ausführen möchten, fügen wir eine weitere Ebene für diese zusätzlichen Schritte hinzu. Hier gibt es nichts Neues. Wir wiederholen einfach genau das, was wir nur für die [Haupt-Pipeline](#the-main-pipeline) gemacht haben. Auf diese Weise kannst du so viele Ebenen bearbeiten, wie du brauchst. Ich habe schon einmal bis zu neun Umgebungen gesehen.\n\nWir wollen hier nicht über die Vorteile diskutieren, die es mit sich bringt, weniger Umgebungen zu haben. Der hier verwendete Prozess macht es jedenfalls sehr einfach, die gleiche Pipeline von der Anfangsphase bis zur endgültigen Lieferung zu implementieren, während deine Pipeline-Definition einfach und in kleine, einfach zu wartende Teile aufgeteilt bleibt.\n\nUm hier Variablenkonflikte zu vermeiden, verwenden wir nur neue Variablennamen, um den Terraform-Status und die Eingabedatei zu identifizieren.\n\n```yml\n.2nd_layer:\n  stage: 2nd_layer\n  variables:\n    TF_ROOT: terraform\n  trigger:\n    include: .gitlab-ci/.second-layer.gitlab-ci.yml\n    # strategy: depend            # Do NOT wait for the downstream pipeline to finish to mark upstream pipeline as successful. Andernfalls schlagen alle Pipelines fehl, wenn eine Pipeline-Zeitüberschreitung vor der Bereitstellung auf die 2. Ebene erreicht wurde.\n    forward:\n      yaml_variables: true      # Forward variables defined in the trigger job\n      pipeline_variables: true  # Forward manual pipeline variables and scheduled pipeline variables\n\nqa:\n  extends: .2nd_layer\n  variables:\n    TF_STATE_NAME_2: qa\n    environment: $TF_STATE_NAME_2\n    TF_CLI_ARGS_plan_2: \"-var-file=../vars/$TF_STATE_NAME_2.tfvars\"\n  rules:\n    - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH\n\nproduction:\n  extends: .2nd_layer\n  variables:\n    TF_STATE_NAME_2: production\n    environment: $TF_STATE_NAME_2\n    TF_CLI_ARGS_plan_2: \"-var-file=../vars/$TF_STATE_NAME_2.tfvars\"\n  rules:\n    - if: $CI_COMMIT_TAG\n```\n\n**Ein wichtiger Trick ist hier die Strategie, die für die neue Downstream-Pipeline verwendet wird.** Wir belassen `trigger:strategy` auf ihrem Standardwert. Andernfalls würde die [Haupt-Pipeline](#the-main-pipeline) warten, bis deine [Pipeline der zweiten Ebene](#the-grand-child-pipeline) abgeschlossen ist. Bei einem manuellen Auslöser kann dies sehr lange dauern und das Lesen und Verstehen deines Pipeline-Dashboards erschweren.\n\nDu hast dich wahrscheinlich schon gefragt, was der Inhalt der Datei `.gitlab-ci/.second-layer.gitlab-ci.yml` ist, die wir hier anführen. Wir gehen im nächsten Abschnitt darauf ein.\n\n##### Vollständige Pipeline-Definition auf der ersten Ebene\n\nWenn du eine vollständige Ansicht dieser ersten Ebene möchtest (gespeichert in `.gitlab-ci/.first-layer.gitlab-ci.yml`), erweitere einfach den Abschnitt unten.\n\n```yml\nvariables:\n  TF_VAR_aws_ami_id: $AWS_AMI_ID\n  TF_VAR_aws_instance_type: $AWS_INSTANCE_TYPE\n  TF_VAR_aws_default_region: $AWS_DEFAULT_REGION\n\ninclude:\n  - template: Terraform.gitlab-ci.yml\n\ndefault:\n  cache:  # Use a shared cache or tagged runners to ensure terraform can run on apply and destroy\n    - key: cache-$CI_COMMIT_REF_SLUG\n      fallback_keys:\n        - cache-$CI_DEFAULT_BRANCH\n      paths:\n        - .\n\nstages:\n  - validate\n  - test\n  - build\n  - deploy\n  - cleanup\n  - 2nd_layer       # Use to deploy a 2nd environment on both the main branch and on the tags\n\nfmt:\n  rules:\n    - when: always\n\nvalidate:\n  rules:\n    - when: always\n\nkics-iac-sast:\n  rules:\n    - if: $SAST_DISABLED == 'true' || $SAST_DISABLED == '1'\n      when: never\n    - if: $SAST_EXCLUDED_ANALYZERS =~ /kics/\n      when: never\n    - when: on_success\n\niac-sast:\n  rules:\n    - if: $SAST_DISABLED == 'true' || $SAST_DISABLED == '1'\n      when: never\n    - if: $SAST_EXCLUDED_ANALYZERS =~ /kics/\n      when: never\n    - when: on_success\n\n###########################################################################################################\n## Integration env. and Staging. env\n##  * Auto-deploy to Integration on merge to main.\n##  * Auto-deploy to Staging on tag.\n##  * Integration can be manually destroyed if TF_DESTROY is set to true.\n##  * Destroy of next env. is not automated to prevent errors.\n###########################################################################################################\nbuild:  # terraform plan\n  environment:\n    name: $TF_STATE_NAME\n    action: prepare\n  rules:\n    - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH\n    - if: $CI_COMMIT_TAG\n\ndeploy: # terraform apply --> automatically deploy on corresponding env (integration or staging) when merging to default branch or tagging. Second layer environments (qa and production) will be controlled manually\n  environment:     name: $TF_STATE_NAME\n    action: start\n    on_stop: destroy\n  rules:\n    - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH\n    - if: $CI_COMMIT_TAG\n\ndestroy:\n  extends: .terraform:destroy\n  variables:\n    GIT_STRATEGY: none\n  dependencies:\n    - build\n  environment:\n    name: $TF_STATE_NAME\n    action: stop\n  rules:\n    - if: $CI_COMMIT_TAG  # Do not destroy production\n      when: never\n    - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH && $TF_DESTROY == \"true\" # Manually destroy integration env.\n      when: manual\n###########################################################################################################\n\n###########################################################################################################\n## Dev env.\n##  * Temporary environment. Lives and dies with the Merge Request.\n##  * Auto-deploy on push to feature branch.\n##  * Auto-destroy on when Merge Request is closed.\n###########################################################################################################\nbuild_review:\n  extends: build\n  rules:\n    - if: $CI_COMMIT_TAG\n      when: never\n    - if: $CI_COMMIT_BRANCH != $CI_DEFAULT_BRANCH\n      when: on_success\n\ndeploy_review:\n  extends: deploy\n  dependencies:\n    - build_review\n  environment:\n    name: $environment\n    action: start\n    on_stop: destroy_review\n    # url: https://$CI_ENVIRONMENT_SLUG.example.com\n  rules:\n    - if: $CI_COMMIT_TAG\n      when: never\n    - if: $CI_COMMIT_BRANCH != $CI_DEFAULT_BRANCH\n      when: on_success\n\ndestroy_review:\n  extends: destroy\n  dependencies:\n    - build_review\n  environment:\n    name: $environment\n    action: stop\n  rules:\n    - if: $CI_COMMIT_TAG  # Do not destroy production\n      when: never\n    - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH   # Do not destroy staging\n      when: never\n    - when: manual\n###########################################################################################################\n\n###########################################################################################################\n## Second layer\n##  * Deploys from main branch to qa env.\n##  * Deploys from tag to production.\n###########################################################################################################\n.2nd_layer:\n  stage: 2nd_layer\n  variables:\n    TF_ROOT: terraform\n  trigger:\n    include: .gitlab-ci/.second-layer.gitlab-ci.yml\n    # strategy: depend            # Do NOT wait for the downstream pipeline to finish to mark upstream pipeline as successful. Otherwise, all pipelines will fail when reaching the pipeline timeout before deployment to 2nd layer.\n    forward:\n      yaml_variables: true      # Forward variables defined in the trigger job\n      pipeline_variables: true  # Forward manual pipeline variables and scheduled pipeline variables\n\nqa:\n  extends: .2nd_layer\n  variables:\n    TF_STATE_NAME_2: qa\n    environment: $TF_STATE_NAME_2\n    TF_CLI_ARGS_plan_2: \"-var-file=../vars/$TF_STATE_NAME_2.tfvars\"\n  rules:\n    - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH\n\nproduction:\n  extends: .2nd_layer\n  variables:\n    TF_STATE_NAME_2: production\n    environment: $TF_STATE_NAME_2\n    TF_CLI_ARGS_plan_2: \"-var-file=../vars/$TF_STATE_NAME_2.tfvars\"\n  rules:\n    - if: $CI_COMMIT_TAG\n###########################################################################################################\n```\n\nIn dieser Phase stellen wir bereits sicher in drei Umgebungen bereit. Das ist meine persönliche Idealempfehlung. Wenn du jedoch mehr Umgebungen benötigst, füge diese deiner CD-Pipeline hinzu.\n\nDu hast sicherlich schon bemerkt, dass wir eine Downstream-Pipeline mit dem Stichwort `trigger:include` einbinden. Dazu gehört die Datei `.gitlab-ci/.second-layer.gitlab-ci.yml`. Da wir fast die gleiche Pipeline ausführen wollen, ist ihr Inhalt offensichtlich sehr ähnlich zu dem, den wir oben detailliert beschrieben haben. Der Hauptvorteil bei der Definition dieser [Pipeline der zweiten Ebene](#the-grand-child-pipeline) ist, dass sie allein besteht, was die Definition von Variablen und Regeln erleichtert.\n\n### Die Pipeline der zweiten Ebene\n\nDiese Pipeline der zweiten Ebene ist eine brandneue Pipeline. Daher muss es die Definition der ersten Ebene nachahmen mit:\n\n* [Aufnahme der Terraform-Vorlage](#run-terraform-commands-and-secure-the-code).\n* [Durchsetzung von Sicherheitskontrollen](#run-controls-on-all-branches). Bei der Terraform-Validierung handelt es sich um Duplikate der ersten Ebene. Sicherheitsscanner können jedoch Bedrohungen finden, die noch nicht vorhanden waren, als die Scanner zuvor ausgeführt wurden (z. B. wenn du einige Tage nach der Bereitstellung im Staging in der Produktion bereitstellst).\n* [Überschreiben von Build- und Bereitstellungs-Jobs, um spezifische Regeln festzulegen](#cd-to-review-environments). Beachte bitte, dass die Phase `destroy` nicht mehr automatisiert ist, um zu schnelle Löschvorgänge zu verhindern.\n\nWie oben erläutert, wurden `TF_STATE_NAME` und `TF_CLI_ARGS_plan` von der [Haupt-Pipeline](# the-main-pipeline) zur [untergeordneten Pipeline](#the-child-pipeline) bereitgestellt. Wir brauchten einen weiteren Variablennamen, um diese Werte von der [untergeordneten Pipeline](#the-child-pipeline) hierher, also an die [Pipeline der zweiten Ebene](#the-grand-child-pipeline), zu übergeben. Deshalb werden sie in der untergeordneten Pipeline mit dem Postfix `_2` versehen und der Wert wird während des `before_script` hier zurück in die entsprechende Variable kopiert.\n\nDa wir oben bereits jeden Schritt aufgeschlüsselt haben, können wir hier direkt auf die breite Ansicht der globalen Definition der zweiten Ebene zoomen (gespeichert in `.gitlab-ci/.second-layer.gitlab-ci.yml`).\n\n```yml\n# Use to deploy a second environment on both the default branch and the tags.\n\ninclude:\n  template: Terraform.gitlab-ci.yml\n\nstages:\n  - validate\n  - test\n  - build\n  - deploy\n\nfmt:\n  rules:\n    - when: never\n\nvalidate:\n  rules:\n    - when: never\n\nkics-iac-sast:\n  rules:\n    - if: $SAST_DISABLED == 'true' || $SAST_DISABLED == '1'\n      when: never\n    - if: $SAST_EXCLUDED_ANALYZERS =~ /kics/\n      when: never\n    - when: always\n\n###########################################################################################################\n## QA env. and Prod. env\n##  * Manually trigger build and auto-deploy in QA\n##  * Manually trigger both build and deploy in Production\n##  * Destroy of these env. is not automated to prevent errors.\n###########################################################################################################\nbuild:  # terraform plan\n  cache:  # Use a shared cache or tagged runners to ensure terraform can run on apply and destroy\n    - key: $TF_STATE_NAME_2\n      fallback_keys:\n        - cache-$CI_DEFAULT_BRANCH\n      paths:\n        - .\n  environment:\n    name: $TF_STATE_NAME_2\n    action: prepare\n  before_script:  # Hack to set new variable values on the second layer, while still using the same variable names. Otherwise, due to variable precedence order, setting new value in the trigger job, does not cascade these new values to the downstream pipeline\n    - TF_STATE_NAME=$TF_STATE_NAME_2\n    - TF_CLI_ARGS_plan=$TF_CLI_ARGS_plan_2\n  rules:\n    - when: manual\n\ndeploy: # terraform apply\n  cache:  # Use a shared cache or tagged runners to ensure terraform can run on apply and destroy\n    - key: $TF_STATE_NAME_2\n      fallback_keys:\n        - cache-$CI_DEFAULT_BRANCH\n      paths:\n        - .\n  environment:     name: $TF_STATE_NAME_2\n    action: start\n  before_script:  # Hack to set new variable values on the second layer, while still using the same variable names. Otherwise, due to variable precedence order, setting new value in the trigger job, does not cascade these new values to the downstream pipeline\n    - TF_STATE_NAME=$TF_STATE_NAME_2\n    - TF_CLI_ARGS_plan=$TF_CLI_ARGS_plan_2\n  rules:\n    - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH\n    - if: $CI_COMMIT_TAG && $TF_AUTO_DEPLOY == \"true\"\n    - if: $CI_COMMIT_TAG\n      when: manual\n###########################################################################################################\n```\n\nEt voilà. **Wir sind bereit.** Du kannst die Art und Weise ändern, wie du deine Jobausführungen kontrollierst, indem du bspw. die Möglichkeit von GitLab nutzt, [einen Job zu verzögern](https://docs.gitlab.com/ee/ci/jobs/job_control.html#run-a-job-after-a-delay), bevor du ihn in der Produktion bereitstellst.\n\n## Probiere es selbst\n\nWir haben endlich unser Ziel erreicht. Wir sind jetzt in der Lage, **Bereitstellungen in fünf verschiedenen Umgebungen** zu kontrollieren, wobei nur die **Feature-Branches**, der **Haupt-Branch** und **Tags** verwendet werden.\n* Wir verwenden Open-Source-Vorlagen von GitLab intensiv wieder, um Effizienz und Sicherheit in unseren Pipelines zu gewährleisten.\n* Wir nutzen GitLab-Vorlagen, um nur die Blöcke zu überschreiben, die eine benutzerdefinierte Kontrolle benötigen.\n* Wir haben die Pipeline in kleine Teile aufgeteilt und kontrollieren die Downstream-Pipelines so, dass sie genau dem entsprechen, was wir brauchen.\n\nAb hier gehört die Bühne ganz dir. Du kannst beispielsweise die Haupt-Pipeline einfach aktualisieren, um Downstream-Pipelines für deinen Software-Quellcode mit dem Schlüsselwort [trigger:rules:changes](https://docs.gitlab.com/ee/ci/yaml/#ruleschanges) auszulösen. Und verwende je nach den aufgetretenen Änderungen eine andere [Vorlage](https://gitlab.com/gitlab-org/gitlab/-/tree/master/lib/gitlab/ci/templates/). Aber das ist eine andere Geschichte.",[23,24,25,26,27],"CI/CD","CI","CD","DevSecOps platform","tutorial","2024-11-05","yml",{},true,"/de-de/blog/using-child-pipelines-to-continuously-deploy-to-five-environments",{"title":15,"description":16,"ogTitle":15,"ogDescription":16,"noIndex":12,"ogImage":19,"ogUrl":34,"ogSiteName":35,"ogType":36,"canonicalUrls":34},"https://about.gitlab.com/blog/using-child-pipelines-to-continuously-deploy-to-five-environments","https://about.gitlab.com","article","de-de/blog/using-child-pipelines-to-continuously-deploy-to-five-environments",[39,40,41,42,27],"cicd","ci","cd","devsecops-platform","AgKDw9w0cnW79ezbl_gvT4CjQbOZjVc_duJeenHr73s",{"data":45},{"logo":46,"freeTrial":51,"sales":56,"login":61,"items":66,"search":374,"minimal":409,"duo":427,"pricingDeployment":436},{"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,189,194,295,355],{"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},"Erkunde unsere Plattform",{"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 Softwareentwicklungszyklus",{"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},"Gründe, die für GitLab sprechen","Erfahre, warum Unternehmen auf GitLab setzen",{"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":171},"Produkt",{"dataNavLevelOne":97},"solutions",{"text":99,"config":100},"Alle Lösungen anzeigen",{"href":101,"dataGaName":97,"dataGaLocation":50},"/de-de/solutions/",[103,127,149],{"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,115,118,123],{"text":23,"config":113},{"href":114,"dataGaLocation":50,"dataGaName":23},"/de-de/solutions/continuous-integration/",{"text":79,"config":116},{"href":84,"dataGaLocation":50,"dataGaName":117},"gitlab duo agent platform - product menu",{"text":119,"config":120},"Quellcodeverwaltung",{"href":121,"dataGaLocation":50,"dataGaName":122},"/de-de/solutions/source-code-management/","Source Code Management",{"text":124,"config":125},"Automatisierte Softwarebereitstellung",{"href":109,"dataGaLocation":50,"dataGaName":126},"Automated software delivery",{"title":128,"description":129,"link":130,"items":135},"Sicherheit","Entwickle schneller, ohne die Sicherheit zu gefährden",{"config":131},{"href":132,"dataGaName":133,"dataGaLocation":50,"icon":134},"/de-de/solutions/application-security-testing/","security and compliance","ShieldCheckLight",[136,140,145],{"text":137,"config":138},"Application Security Testing",{"href":132,"dataGaName":139,"dataGaLocation":50},"Application security testing",{"text":141,"config":142},"Schutz der Software-Lieferkette",{"href":143,"dataGaLocation":50,"dataGaName":144},"/de-de/solutions/supply-chain/","Software supply chain security",{"text":146,"config":147},"Software Compliance",{"href":148,"dataGaName":146,"dataGaLocation":50},"/de-de/solutions/software-compliance/",{"title":150,"link":151,"items":156},"Bewertung",{"config":152},{"icon":153,"href":154,"dataGaName":155,"dataGaLocation":50},"DigitalTransformation","/de-de/solutions/visibility-measurement/","visibility and measurement",[157,161,166],{"text":158,"config":159},"Sichtbarkeit und Bewertung",{"href":154,"dataGaLocation":50,"dataGaName":160},"Visibility and Measurement",{"text":162,"config":163},"Wertstrommanagement",{"href":164,"dataGaLocation":50,"dataGaName":165},"/de-de/solutions/value-stream-management/","Value Stream Management",{"text":167,"config":168},"Analysen und Einblicke",{"href":169,"dataGaLocation":50,"dataGaName":170},"/de-de/solutions/analytics-and-insights/","Analytics and insights",{"title":172,"items":173},"GitLab für",[174,179,184],{"text":175,"config":176},"Enterprise",{"href":177,"dataGaLocation":50,"dataGaName":178},"/de-de/enterprise/","enterprise",{"text":180,"config":181},"Kleinunternehmen",{"href":182,"dataGaLocation":50,"dataGaName":183},"/de-de/small-business/","small business",{"text":185,"config":186},"den öffentlichen Sektor",{"href":187,"dataGaLocation":50,"dataGaName":188},"/de-de/solutions/public-sector/","public sector",{"text":190,"config":191},"Preise",{"href":192,"dataGaName":193,"dataGaLocation":50,"dataNavLevelOne":193},"/de-de/pricing/","pricing",{"text":195,"config":196,"link":198,"lists":202,"feature":282},"Ressourcen",{"dataNavLevelOne":197},"resources",{"text":199,"config":200},"Alle Ressourcen anzeigen",{"href":201,"dataGaName":197,"dataGaLocation":50},"/de-de/resources/",[203,236,254],{"title":204,"items":205},"Erste Schritte",[206,211,216,221,226,231],{"text":207,"config":208},"Installieren",{"href":209,"dataGaName":210,"dataGaLocation":50},"/de-de/install/","install",{"text":212,"config":213},"Kurzanleitungen",{"href":214,"dataGaName":215,"dataGaLocation":50},"/de-de/get-started/","quick setup checklists",{"text":217,"config":218},"Lernen",{"href":219,"dataGaLocation":50,"dataGaName":220},"https://university.gitlab.com/","learn",{"text":222,"config":223},"Produktdokumentation",{"href":224,"dataGaName":225,"dataGaLocation":50},"https://docs.gitlab.com/","product documentation",{"text":227,"config":228},"Best-Practice-Videos",{"href":229,"dataGaName":230,"dataGaLocation":50},"/de-de/getting-started-videos/","best practice videos",{"text":232,"config":233},"Integrationen",{"href":234,"dataGaName":235,"dataGaLocation":50},"/de-de/integrations/","integrations",{"title":237,"items":238},"Entdecken",[239,244,249],{"text":240,"config":241},"Kundenerfolge",{"href":242,"dataGaName":243,"dataGaLocation":50},"/de-de/customers/","customer success stories",{"text":245,"config":246},"Blog",{"href":247,"dataGaName":248,"dataGaLocation":50},"/de-de/blog/","blog",{"text":250,"config":251},"Remote",{"href":252,"dataGaName":253,"dataGaLocation":50},"https://handbook.gitlab.com/handbook/company/culture/all-remote/","remote",{"title":255,"items":256},"Vernetzen",[257,262,267,272,277],{"text":258,"config":259},"GitLab-Services",{"href":260,"dataGaName":261,"dataGaLocation":50},"/de-de/services/","services",{"text":263,"config":264},"Community",{"href":265,"dataGaName":266,"dataGaLocation":50},"/community/","community",{"text":268,"config":269},"Forum",{"href":270,"dataGaName":271,"dataGaLocation":50},"https://forum.gitlab.com/","forum",{"text":273,"config":274},"Veranstaltungen",{"href":275,"dataGaName":276,"dataGaLocation":50},"/events/","events",{"text":278,"config":279},"Partner",{"href":280,"dataGaName":281,"dataGaLocation":50},"/de-de/partners/","partners",{"backgroundColor":283,"textColor":284,"text":285,"image":286,"link":290},"#2f2a6b","#fff","Perspektiven für die Softwareentwicklung der Zukunft",{"altText":287,"config":288},"the source promo card",{"src":289},"https://res.cloudinary.com/about-gitlab-com/image/upload/v1758208064/dzl0dbift9xdizyelkk4.svg",{"text":291,"config":292},"Lies die News",{"href":293,"dataGaName":294,"dataGaLocation":50},"/de-de/the-source/","the source",{"text":296,"config":297,"lists":299},"Unternehmen",{"dataNavLevelOne":298},"company",[300],{"items":301},[302,307,313,315,320,325,330,335,340,345,350],{"text":303,"config":304},"Über",{"href":305,"dataGaName":306,"dataGaLocation":50},"/de-de/company/","about",{"text":308,"config":309,"footerGa":312},"Karriere",{"href":310,"dataGaName":311,"dataGaLocation":50},"/jobs/","jobs",{"dataGaName":311},{"text":273,"config":314},{"href":275,"dataGaName":276,"dataGaLocation":50},{"text":316,"config":317},"Geschäftsführung",{"href":318,"dataGaName":319,"dataGaLocation":50},"/company/team/e-group/","leadership",{"text":321,"config":322},"Team",{"href":323,"dataGaName":324,"dataGaLocation":50},"/company/team/","team",{"text":326,"config":327},"Handbuch",{"href":328,"dataGaName":329,"dataGaLocation":50},"https://handbook.gitlab.com/","handbook",{"text":331,"config":332},"Investor Relations",{"href":333,"dataGaName":334,"dataGaLocation":50},"https://ir.gitlab.com/","investor relations",{"text":336,"config":337},"Trust Center",{"href":338,"dataGaName":339,"dataGaLocation":50},"/de-de/security/","trust center",{"text":341,"config":342},"AI Transparency Center",{"href":343,"dataGaName":344,"dataGaLocation":50},"/de-de/ai-transparency-center/","ai transparency center",{"text":346,"config":347},"Newsletter",{"href":348,"dataGaName":349,"dataGaLocation":50},"/company/contact/#contact-forms","newsletter",{"text":351,"config":352},"Presse",{"href":353,"dataGaName":354,"dataGaLocation":50},"/press/","press",{"text":356,"config":357,"lists":358},"Kontakt",{"dataNavLevelOne":298},[359],{"items":360},[361,364,369],{"text":57,"config":362},{"href":59,"dataGaName":363,"dataGaLocation":50},"talk to sales",{"text":365,"config":366},"Support-Portal",{"href":367,"dataGaName":368,"dataGaLocation":50},"https://support.gitlab.com","support portal",{"text":370,"config":371},"Kundenportal",{"href":372,"dataGaName":373,"dataGaLocation":50},"https://customers.gitlab.com/customers/sign_in/","customer portal",{"close":375,"login":376,"suggestions":383},"Schließen",{"text":377,"link":378},"Um Repositories und Projekte zu durchsuchen, melde dich an bei",{"text":379,"config":380},"gitlab.com",{"href":64,"dataGaName":381,"dataGaLocation":382},"search login","search",{"text":384,"default":385},"Vorschläge",[386,388,393,395,400,405],{"text":79,"config":387},{"href":84,"dataGaName":79,"dataGaLocation":382},{"text":389,"config":390},"Code Suggestions (KI)",{"href":391,"dataGaName":392,"dataGaLocation":382},"/de-de/solutions/code-suggestions/","Code Suggestions (AI)",{"text":23,"config":394},{"href":114,"dataGaName":23,"dataGaLocation":382},{"text":396,"config":397},"GitLab auf AWS",{"href":398,"dataGaName":399,"dataGaLocation":382},"/de-de/partners/technology-partners/aws/","GitLab on AWS",{"text":401,"config":402},"GitLab auf Google Cloud",{"href":403,"dataGaName":404,"dataGaLocation":382},"/de-de/partners/technology-partners/google-cloud-platform/","GitLab on Google Cloud",{"text":406,"config":407},"Warum GitLab?",{"href":92,"dataGaName":408,"dataGaLocation":382},"Why GitLab?",{"freeTrial":410,"mobileIcon":415,"desktopIcon":420,"secondaryButton":423},{"text":411,"config":412},"Kostenlos testen",{"href":413,"dataGaName":55,"dataGaLocation":414},"https://gitlab.com/-/trials/new/","nav",{"altText":416,"config":417},"GitLab-Symbol",{"src":418,"dataGaName":419,"dataGaLocation":414},"https://res.cloudinary.com/about-gitlab-com/image/upload/v1758203874/jypbw1jx72aexsoohd7x.svg","gitlab icon",{"altText":416,"config":421},{"src":422,"dataGaName":419,"dataGaLocation":414},"https://res.cloudinary.com/about-gitlab-com/image/upload/v1758203875/gs4c8p8opsgvflgkswz9.svg",{"text":204,"config":424},{"href":425,"dataGaName":426,"dataGaLocation":414},"https://gitlab.com/-/trial_registrations/new?glm_source=about.gitlab.com/de-de/get-started/","get started",{"freeTrial":428,"mobileIcon":432,"desktopIcon":434},{"text":429,"config":430},"Erfahre mehr über GitLab Duo",{"href":84,"dataGaName":431,"dataGaLocation":414},"gitlab duo",{"altText":416,"config":433},{"src":418,"dataGaName":419,"dataGaLocation":414},{"altText":416,"config":435},{"src":422,"dataGaName":419,"dataGaLocation":414},{"freeTrial":437,"mobileIcon":442,"desktopIcon":444},{"text":438,"config":439},"Zurück zur Preisübersicht",{"href":192,"dataGaName":440,"dataGaLocation":414,"icon":441},"back to pricing","GoBack",{"altText":416,"config":443},{"src":418,"dataGaName":419,"dataGaLocation":414},{"altText":416,"config":445},{"src":422,"dataGaName":419,"dataGaLocation":414},{"title":447,"button":448,"config":453},"Sieh dir an, wie agentische KI die Softwarebereitstellung transformiert",{"text":449,"config":450},"GitLab Transcend jetzt ansehen",{"href":451,"dataGaName":452,"dataGaLocation":50},"/de-de/events/transcend/virtual/","transcend event",{"layout":454,"icon":455,"disabled":31},"release","AiStar",{"data":457},{"text":458,"source":459,"edit":465,"contribute":470,"config":475,"items":480,"minimal":653},"Git ist eine Marke von Software Freedom Conservancy und unsere Verwendung von „GitLab“ erfolgt unter Lizenz.",{"text":460,"config":461},"Quelltext der Seite anzeigen",{"href":462,"dataGaName":463,"dataGaLocation":464},"https://gitlab.com/gitlab-com/marketing/digital-experience/about-gitlab-com/","page source","footer",{"text":466,"config":467},"Diese Seite bearbeiten",{"href":468,"dataGaName":469,"dataGaLocation":464},"https://gitlab.com/gitlab-com/marketing/digital-experience/about-gitlab-com/-/blob/main/content/","web ide",{"text":471,"config":472},"Beteilige dich",{"href":473,"dataGaName":474,"dataGaLocation":464},"https://gitlab.com/gitlab-com/marketing/digital-experience/about-gitlab-com/-/blob/main/CONTRIBUTING.md/","please contribute",{"twitter":476,"facebook":477,"youtube":478,"linkedin":479},"https://x.com/gitlab","https://www.facebook.com/gitlab","https://www.youtube.com/channel/UCnMGQ8QHMAnVIsI3xJrihhg","https://www.linkedin.com/company/gitlab-com",[481,504,559,586,620],{"title":68,"links":482,"subMenu":487},[483],{"text":484,"config":485},"DevSecOps-Plattform",{"href":77,"dataGaName":486,"dataGaLocation":464},"devsecops platform",[488],{"title":190,"links":489},[490,494,499],{"text":491,"config":492},"Tarife anzeigen",{"href":192,"dataGaName":493,"dataGaLocation":464},"view plans",{"text":495,"config":496},"Vorteile von Premium",{"href":497,"dataGaName":498,"dataGaLocation":464},"/de-de/pricing/premium/","why premium",{"text":500,"config":501},"Vorteile von Ultimate",{"href":502,"dataGaName":503,"dataGaLocation":464},"/de-de/pricing/ultimate/","why ultimate",{"title":505,"links":506},"Lösungen",[507,512,515,517,522,527,531,534,537,542,544,546,549,554],{"text":508,"config":509},"Digitale Transformation",{"href":510,"dataGaName":511,"dataGaLocation":464},"/de-de/topics/digital-transformation/","digital transformation",{"text":513,"config":514},"Sicherheit und Compliance",{"href":132,"dataGaName":139,"dataGaLocation":464},{"text":124,"config":516},{"href":109,"dataGaName":110,"dataGaLocation":464},{"text":518,"config":519},"Agile Entwicklung",{"href":520,"dataGaName":521,"dataGaLocation":464},"/de-de/solutions/agile-delivery/","agile delivery",{"text":523,"config":524},"Cloud-Transformation",{"href":525,"dataGaName":526,"dataGaLocation":464},"/de-de/topics/cloud-native/","cloud transformation",{"text":528,"config":529},"SCM",{"href":121,"dataGaName":530,"dataGaLocation":464},"source code management",{"text":23,"config":532},{"href":114,"dataGaName":533,"dataGaLocation":464},"continuous integration & delivery",{"text":162,"config":535},{"href":164,"dataGaName":536,"dataGaLocation":464},"value stream management",{"text":538,"config":539},"GitOps",{"href":540,"dataGaName":541,"dataGaLocation":464},"/de-de/solutions/gitops/","gitops",{"text":175,"config":543},{"href":177,"dataGaName":178,"dataGaLocation":464},{"text":180,"config":545},{"href":182,"dataGaName":183,"dataGaLocation":464},{"text":547,"config":548},"Öffentlicher Sektor",{"href":187,"dataGaName":188,"dataGaLocation":464},{"text":550,"config":551},"Bildungswesen",{"href":552,"dataGaName":553,"dataGaLocation":464},"/de-de/solutions/education/","education",{"text":555,"config":556},"Finanzdienstleistungen",{"href":557,"dataGaName":558,"dataGaLocation":464},"/de-de/solutions/finance/","financial services",{"title":195,"links":560},[561,563,565,567,570,572,574,576,578,580,582,584],{"text":207,"config":562},{"href":209,"dataGaName":210,"dataGaLocation":464},{"text":212,"config":564},{"href":214,"dataGaName":215,"dataGaLocation":464},{"text":217,"config":566},{"href":219,"dataGaName":220,"dataGaLocation":464},{"text":222,"config":568},{"href":224,"dataGaName":569,"dataGaLocation":464},"docs",{"text":245,"config":571},{"href":247,"dataGaName":248,"dataGaLocation":464},{"text":240,"config":573},{"href":242,"dataGaName":243,"dataGaLocation":464},{"text":250,"config":575},{"href":252,"dataGaName":253,"dataGaLocation":464},{"text":258,"config":577},{"href":260,"dataGaName":261,"dataGaLocation":464},{"text":263,"config":579},{"href":265,"dataGaName":266,"dataGaLocation":464},{"text":268,"config":581},{"href":270,"dataGaName":271,"dataGaLocation":464},{"text":273,"config":583},{"href":275,"dataGaName":276,"dataGaLocation":464},{"text":278,"config":585},{"href":280,"dataGaName":281,"dataGaLocation":464},{"title":296,"links":587},[588,590,592,594,596,598,600,604,609,611,613,615],{"text":303,"config":589},{"href":305,"dataGaName":298,"dataGaLocation":464},{"text":308,"config":591},{"href":310,"dataGaName":311,"dataGaLocation":464},{"text":316,"config":593},{"href":318,"dataGaName":319,"dataGaLocation":464},{"text":321,"config":595},{"href":323,"dataGaName":324,"dataGaLocation":464},{"text":326,"config":597},{"href":328,"dataGaName":329,"dataGaLocation":464},{"text":331,"config":599},{"href":333,"dataGaName":334,"dataGaLocation":464},{"text":601,"config":602},"Sustainability",{"href":603,"dataGaName":601,"dataGaLocation":464},"/sustainability/",{"text":605,"config":606},"Vielfalt, Inklusion und Zugehörigkeit",{"href":607,"dataGaName":608,"dataGaLocation":464},"/de-de/diversity-inclusion-belonging/","Diversity, inclusion and belonging",{"text":336,"config":610},{"href":338,"dataGaName":339,"dataGaLocation":464},{"text":346,"config":612},{"href":348,"dataGaName":349,"dataGaLocation":464},{"text":351,"config":614},{"href":353,"dataGaName":354,"dataGaLocation":464},{"text":616,"config":617},"Transparenzerklärung zu moderner Sklaverei",{"href":618,"dataGaName":619,"dataGaLocation":464},"https://handbook.gitlab.com/handbook/legal/modern-slavery-act-transparency-statement/","modern slavery transparency statement",{"title":621,"links":622},"Nimm Kontakt auf",[623,626,631,633,638,643,648],{"text":624,"config":625},"Sprich mit einem Experten/einer Expertin",{"href":59,"dataGaName":60,"dataGaLocation":464},{"text":627,"config":628},"Support",{"href":629,"dataGaName":630,"dataGaLocation":464},"https://support.gitlab.com/hc/en-us/articles/11626483177756-GitLab-Support","get help",{"text":370,"config":632},{"href":372,"dataGaName":373,"dataGaLocation":464},{"text":634,"config":635},"Status",{"href":636,"dataGaName":637,"dataGaLocation":464},"https://status.gitlab.com/","status",{"text":639,"config":640},"Nutzungsbedingungen",{"href":641,"dataGaName":642,"dataGaLocation":464},"/terms/","terms of use",{"text":644,"config":645},"Datenschutzerklärung",{"href":646,"dataGaName":647,"dataGaLocation":464},"/de-de/privacy/","privacy statement",{"text":649,"config":650},"Cookie-Einstellungen",{"dataGaName":651,"dataGaLocation":464,"id":652,"isOneTrustButton":31},"cookie preferences","ot-sdk-btn",{"items":654},[655,657,659],{"text":639,"config":656},{"href":641,"dataGaName":642,"dataGaLocation":464},{"text":644,"config":658},{"href":646,"dataGaName":647,"dataGaLocation":464},{"text":649,"config":660},{"dataGaName":651,"dataGaLocation":464,"id":652,"isOneTrustButton":31},[662],{"id":663,"title":664,"body":8,"config":665,"content":667,"description":8,"extension":29,"meta":671,"navigation":31,"path":672,"seo":673,"stem":674,"__hash__":675},"blogAuthors/en-us/blog/authors/olivier-dupr.yml","Olivier Dupr",{"template":666},"BlogAuthor",{"name":18,"config":668},{"headshot":669,"ctfId":670},"https://res.cloudinary.com/about-gitlab-com/image/upload/v1750713474/cj6odchlpoqxbibenvye.png","4VIckvQsyfNxEtz4pM42aP",{},"/en-us/blog/authors/olivier-dupr",{},"en-us/blog/authors/olivier-dupr","KYUHajPcOeVlPPyPD8D4H56u7iQpJSPInWLi38Y1NA0",[677,691,704],{"content":678,"config":689},{"body":679,"title":680,"description":681,"authors":682,"heroImage":684,"date":685,"category":9,"tags":686},"## 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/ee/api/jobs.html#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.",[683],"Omid Khan","https://res.cloudinary.com/about-gitlab-com/image/upload/v1772721753/frfsm1qfscwrmsyzj1qn.png","2026-04-09",[23,687,27,688],"DevOps platform","features",{"featured":31,"template":13,"slug":690},"5-ways-gitlab-pipeline-logic-solves-real-engineering-problems",{"content":692,"config":702},{"title":693,"description":694,"authors":695,"heroImage":697,"date":698,"body":699,"category":9,"tags":700},"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.",[696],"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.",[27,701,688],"product",{"featured":12,"template":13,"slug":703},"using-gitlab-container-virtual-registry-with-docker-hardened-images",{"content":705,"config":716},{"category":9,"tags":706,"body":708,"date":709,"heroImage":710,"authors":711,"title":714,"description":715},[27,707,23],"git","Enterprise-Migrationen in regulierten Branchen wie Finanzwesen und Automobilindustrie erfordern systematisches Risikomanagement gemäß NIS2-Richtlinie Artikel 21. Die mehrstufige Migrationsstruktur mit Testläufen vor Produktions-Wellen und kontrollierten Change-Freezes demonstriert Business-Continuity-Management in der Praxis. GitLab Professional Services organisiert Migrationen in Wellen von 200-300 Projekten, um Komplexität zu managen und API-Rate-Limits zu respektieren.\n\n## Überblick\n\nGitLab bietet [Congregate](https://gitlab.com/gitlab-org/professional-services-automation/tools/migration/congregate/) (maintained by GitLab Professional Services) und [eingebauten Git-Repository-Import](https://docs.gitlab.com/user/project/import/repo_by_url/) für Migrationen von Azure DevOps (ADO). Beide Optionen unterstützen Repository-basierte oder Bulk-Migration und erhalten Git-Commit-History, Branches und Tags. Mit Congregate werden zusätzliche Assets wie Wikis, Work Items, CI/CD-Variablen, Container-Images, Packages und Pipelines migriert (siehe [Feature-Matrix](https://gitlab.com/gitlab-org/professional-services-automation/tools/migration/congregate/-/blob/master/customer/ado-migration-features-matrix.md)).\n\nEnterprises folgen typischerweise einem mehrstufigen Ansatz:\n\n- Repositories von ADO zu GitLab migrieren (Congregate oder eingebauter Import)\n- Pipelines von Azure Pipelines zu GitLab CI/CD migrieren\n- Verbleibende Assets wie Boards, Work Items und Artifacts zu GitLab Issues, Epics und Package/Container Registries migrieren\n\nMehrstufige Migrationsphasen (siehe [Diagramm](https://about.gitlab.com/blog/migration-from-azure-devops-to-gitlab/#overview)):\n\n- Prerequisites (IdP, Runners, Change Management)\n- Migration Phase (Source Code, History, Work Items)\n- Post-Migration (Pipelines, Assets, Security)\n\n## Migration planen\n\n**Zentrale Planungsfragen:**\n\n- Wie schnell muss die Migration abgeschlossen werden?\n- Was genau wird migriert?\n- Wer führt die Migration durch?\n- Welche Organisationsstruktur wird in GitLab benötigt?\n- Welche Einschränkungen, Limitierungen oder Fallstricke müssen berücksichtigt werden?\n\nDie Timeline bestimmt weitgehend den Migrationsansatz. Identifizierung von Champions oder Teams mit ADO- und GitLab-Erfahrung unterstützt Adoption und Guidance.\n\n**Inventar erstellen:**\n\nDas GitLab Professional Services [Evaluate](https://gitlab.com/gitlab-org/professional-services-automation/tools/utilities/evaluate#beta-azure-devops)-Tool produziert ein vollständiges Inventar der Azure DevOps Organisation: Repositories, PR-Counts, Contributors, Pipelines, Work Items, CI/CD-Variablen. Bei Professional Services Engagements wird dieser Report mit Engagement Manager oder Technical Architect geteilt für Migrationsplanung.\n\nMigrations-Timing wird primär bestimmt durch Pull-Request-Count, Repository-Größe und Contribution-Menge. Beispiel: 1.000 kleine Repositories mit wenigen PRs migrieren schneller als wenige Repositories mit Zehntausenden PRs. Inventar-Daten ermöglichen Aufwands-Schätzung und Test-Run-Planung.\n\nIn Professional Services Engagements werden Migrationen in Wellen von 200-300 Projekten organisiert, um Komplexität zu managen und API-Rate-Limits zu respektieren (sowohl [GitLab](https://docs.gitlab.com/security/rate_limits/) als auch [ADO](https://learn.microsoft.com/en-us/azure/devops/integrate/concepts/rate-limits?view=azure-devops)).\n\n**Tool-Auswahl:**\n\nGitLabs [eingebauter Repository-Importer](https://docs.gitlab.com/user/project/import/repo_by_url/) migriert Git-Repositories (Commits, Branches, Tags) einzeln. Congregate erhält Pull Requests (in GitLab: Merge Requests), Kommentare und Metadata; der eingebaute Import fokussiert auf Git-Daten (History, Branches, Tags).\n\nAssets die typischerweise separate Migration oder manuelle Neuerstellung erfordern:\n\n- Azure Pipelines → GitLab CI/CD Pipelines (siehe [CI/CD YAML](https://docs.gitlab.com/ci/yaml/) oder [CI/CD Components](https://docs.gitlab.com/ci/components/)). Alternativ: AI-basierte Pipeline-Konvertierung in Congregate.\n- Work Items und Boards → GitLab Issues, Epics, Issue Boards\n- Artifacts, Container-Images (ACR) → GitLab Package/Container Registry\n- Service Hooks, externe Integrationen → in GitLab neu erstellen\n\n[Permissions-Modelle](https://docs.gitlab.com/user/permissions/) unterscheiden sich zwischen ADO und GitLab. Permissions-Mapping planen statt exakter Preservation zu erwarten.\n\n**Organisationsstruktur in GitLab:**\n\nEmpfohlener Ansatz: ADO-Organisationen auf GitLab-Groups mappen (nicht viele kleine Groups). Migration als Gelegenheit nutzen, GitLab-Struktur zu rationalisieren (siehe [Struktur-Diagramm](https://about.gitlab.com/blog/migration-from-azure-devops-to-gitlab/#planning-your-migration)):\n\n- **ADO Organization** → GitLab Top-level Group\n- **ADO Project** → GitLab Subgroup (optional)\n- **ADO Repository** → GitLab Project\n\nWeitere Empfehlungen:\n\n- Subgroups und Project-Level-Permissions für verwandte Repositories nutzen\n- Zugriff über GitLab Groups und Group-Membership managen\n- GitLab [Permissions](https://docs.gitlab.com/ee/user/permissions.html) und [SAML Group Links](https://docs.gitlab.com/user/group/saml_sso/group_sync/) für Enterprise-RBAC-Modell evaluieren\n\n**ADO Work Items Migration:**\n\nADO Boards und Work Items mappen zu GitLab Issues, Epics und Issue Boards. ADO Epics und Features werden GitLab Epics. Andere Work-Item-Typen (User Stories, Tasks, Bugs) werden project-scoped Issues. Standard-Felder bleiben erhalten; ausgewählte Custom Fields können migriert werden. Parent-Child-Relationships bleiben erhalten. Links zu Pull Requests werden zu Merge-Request-Links konvertiert.\n\n![Migration eines Work Items zu GitLab Issue](https://res.cloudinary.com/about-gitlab-com/image/upload/v1764769188/ztesjnxxfbwmfmtckyga.png)\n\n**Pipelines-Migration:**\n\nCongregate bietet [AI-basierte Konvertierung](https://gitlab.com/gitlab-org/professional-services-automation/tools/migration/congregate/-/merge_requests/1298) für multi-stage YAML-Pipelines von Azure DevOps zu GitLab CI/CD. Die automatisierte Konvertierung funktioniert optimal für einfache Single-File-Pipelines und liefert einen funktionalen Ausgangspunkt, nicht ein produktionsreifes `.gitlab-ci.yml`-File. Das Tool generiert funktional äquivalente GitLab-Pipelines zur weiteren Optimierung.\n\n- Konvertiert Azure Pipelines YAML zu `.gitlab-ci.yml` automatisch\n- Geeignet für straightforward Single-File-Pipeline-Konfigurationen\n- Liefert Boilerplate zur Migrations-Beschleunigung, nicht finales Production-Artifact\n- Erfordert Review und Anpassung für komplexe Szenarien, Custom Tasks oder Enterprise-Requirements\n- Unterstützt keine Azure DevOps Classic Release Pipelines\n\nRepository-Owner sollten [GitLab CI/CD Dokumentation](https://docs.gitlab.com/ci/) konsultieren für weitere Pipeline-Optimierung nach initialer Konvertierung.\n\n## Migrationen durchführen\n\nNach der Planung werden Migrationen in Stages durchgeführt, beginnend mit Trial Runs. Trial Migrations identifizieren organisations-spezifische Issues frühzeitig und ermöglichen Duration-Messung, Outcome-Validierung und Approach-Finetuning vor Production.\n\n**Was Trial Migrations validieren:**\n\n- Ob Repository und Assets erfolgreich migrieren (History, Branches, Tags; plus MRs/Comments bei Congregate)\n- Ob Destination sofort nutzbar ist (Permissions, Runners, CI/CD-Variablen, Integrationen)\n- Wie lange jeder Batch benötigt für Schedule- und Stakeholder-Expectations\n\n**Downtime-Guidance:**\n\nGitLabs eingebauter Git-Import und Congregate erfordern inhärent keine Downtime. Für Production-Wellen: Changes in ADO freezen (Branch-Protections oder Read-only), um verpasste Commits, PR-Updates oder mid-migration Work Items zu vermeiden. Trial Runs erfordern keine Freezes.\n\n**Batching-Guidance:**\n\nTrial-Batches back-to-back durchführen für kürzere elapsed Time. Teams validieren Results asynchron. Geplante Group/Subgroup-Struktur für Batch-Definition nutzen und API-Rate-Limits respektieren.\n\n**Empfohlene Schritte:**\n\n1. Test-Destination in GitLab erstellen (GitLab.com: dedicated Group/Namespace; Self-managed: Top-level Group)\n2. Authentication vorbereiten (Azure DevOps PAT, GitLab Personal Access Token mit api und read_repository Scopes)\n3. Trial Migrations durchführen (Repos only: eingebauter Import; Repos + PRs/MRs: Congregate)\n4. Post-Trial Follow-up (Repo-History, Branches, Tags, Merge Requests, Issues/Epics, Labels, Relationships verifizieren)\n5. Permissions/Roles, Protected Branches, Runners/Tags, Variables/Secrets, Integrations/Webhooks prüfen\n6. Pipelines (`.gitlab-ci.yml`) oder konvertierte Pipelines validieren\n7. User-Validierung für Functionality und Data-Fidelity\n8. Production-Migrationen in Waves durchführen (Change-Freezes in ADO erzwingen, Progress und Logs monitoren)\n\n\u003Cfigure class=\"video_container\">\n  \u003Ciframe src=\"https://www.youtube.com/embed/ibIXGfrVbi4?si=ZxOVnXjCF-h4Ne0N\" frameborder=\"0\" allowfullscreen=\"true\">\u003C/iframe>\n\u003C/figure>\n\n## Zentrale ADO-zu-GitLab-Mappings\n\nWichtigste Struktur-Mappings für Migrationsplanung:\n\n- **ADO Organization** → GitLab Group (Top-level Namespace)\n- **ADO Project** → GitLab Group oder Subgroup (Permissions-Boundary)\n- **ADO Repository** → GitLab Project (Git-Repo plus Issues, CI/CD, Wiki)\n- **Pull Request** → Merge Request (Code Review, Approvals)\n- **Azure Pipelines** → GitLab CI/CD (`.gitlab-ci.yml`)\n- **Agent Pools** → GitLab Runners (Job-Execution)\n- **Work Items** → GitLab Issues/Epics (Planning-Funktionalität)\n- **Variable Groups** → CI/CD Variables (Project/Group/Instance Level)\n\nFür vollständige Terminologie-Referenztabelle mit allen Mappings siehe [englische Originalversion](https://about.gitlab.com/blog/migration-from-azure-devops-to-gitlab/).\n\n## Professional Services Migrationsmuster\n\nGitLab Professional Services nutzt bewährte Muster für Enterprise-Migrationen:\n\n**Systematische Planung:** Evaluate-Tool liefert vollständiges Inventar (Repositories, PRs, Contributors, Pipelines, Work Items). Klassifikation nach Komplexität (Work Items = Planning-Team-Involvement; Pipelines = Conversion-Requirements) ermöglicht Timeline-Schätzung und Batch-Definition.\n\n**Controlled Execution:** Wellen von 200-300 Projekten managen Komplexität und respektieren API-Rate-Limits. Trial-Migrationen vor Production-Waves identifizieren Issues frühzeitig. Change-Freezes in ADO während Production-Wellen vermeiden mid-migration Updates.\n\n**Risikominimierung:** Multi-Phase-Ansatz (Prerequisites → Migration → Post-migration) mit Validierungs-Checkpoints an jeder Phase. Trial-Runs ermöglichen asynchrone Team-Validierung ohne Production-Impact.\n\n## Praktische Implementierung\n\nFür vollständige Implementierungsdetails, Konfigurationsbeispiele, Pipeline-Konvertierungs-Patterns und detaillierte Post-Migration-Checklisten siehe [englische Originalversion](https://about.gitlab.com/blog/migration-from-azure-devops-to-gitlab/).\n\nWeitere Professional Services Ressourcen:\n\n- [Professional Services Full Catalog](https://about.gitlab.com/professional-services/catalog/)\n- [Congregate Migration Tool](https://gitlab.com/gitlab-org/professional-services-automation/tools/migration/congregate/)\n- [Evaluate Inventory Tool](https://gitlab.com/gitlab-org/professional-services-automation/tools/utilities/evaluate)\n","2025-12-03","https://res.cloudinary.com/about-gitlab-com/image/upload/v1749658924/Blog/Hero%20Images/securitylifecycle-light.png",[712,713],"Evgeny Rudinsky","Michael Leopard","Migration von Azure DevOps zu GitLab systematisch planen","Professional Services Migrationsansatz mit mehrstufiger Struktur, 200-300 Projekt-Wellen und systematischem Risikomanagement für Enterprise-Migrationen.",{"featured":31,"template":13,"slug":717},"migration-from-azure-devops-to-gitlab",{"promotions":719},[720,734,746,758],{"id":721,"categories":722,"header":724,"text":725,"button":726,"image":731},"ai-modernization",[723],"ai-ml","Is AI achieving its promise at scale?","Quiz will take 5 minutes or less",{"text":727,"config":728},"Get your AI maturity score",{"href":729,"dataGaName":730,"dataGaLocation":248},"/assessments/ai-modernization-assessment/","modernization assessment",{"config":732},{"src":733},"https://res.cloudinary.com/about-gitlab-com/image/upload/v1772138786/qix0m7kwnd8x2fh1zq49.png",{"id":735,"categories":736,"header":738,"text":725,"button":739,"image":743},"devops-modernization",[701,737],"devsecops","Are you just managing tools or shipping innovation?",{"text":740,"config":741},"Get your DevOps maturity score",{"href":742,"dataGaName":730,"dataGaLocation":248},"/assessments/devops-modernization-assessment/",{"config":744},{"src":745},"https://res.cloudinary.com/about-gitlab-com/image/upload/v1772138785/eg818fmakweyuznttgid.png",{"id":747,"categories":748,"header":750,"text":725,"button":751,"image":755},"security-modernization",[749],"security","Are you trading speed for security?",{"text":752,"config":753},"Get your security maturity score",{"href":754,"dataGaName":730,"dataGaLocation":248},"/assessments/security-modernization-assessment/",{"config":756},{"src":757},"https://res.cloudinary.com/about-gitlab-com/image/upload/v1772138786/p4pbqd9nnjejg5ds6mdk.png",{"id":759,"paths":760,"header":762,"text":763,"button":764,"image":769},"github-azure-migration",[717,761],"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":765,"config":766},"See how GitLab compares to GitHub",{"href":767,"dataGaName":768,"dataGaLocation":248},"/compare/gitlab-vs-github/github-azure-migration/","github azure migration",{"config":770},{"src":745},{"header":772,"blurb":773,"button":774,"secondaryButton":779},"Beginne noch heute, schneller zu entwickeln","Entdecke, was dein Team mit der intelligenten Orchestrierungsplattform für DevSecOps erreichen kann.\n",{"text":775,"config":776},"Kostenlosen Test starten",{"href":777,"dataGaName":55,"dataGaLocation":778},"https://gitlab.com/-/trial_registrations/new?glm_content=default-saas-trial&glm_source=about.gitlab.com/de-de/","feature",{"text":57,"config":780},{"href":59,"dataGaName":60,"dataGaLocation":778},1776449921196]