[{"data":1,"prerenderedAt":5984},["ShallowReactive",2],{"/blog/using-child-pipelines-to-continuously-deploy-to-five-environments":3,"navigation-en-us":5200,"banner-en-us":5627,"footer-en-us":5637,"blog-post-authors-en-us-Olivier Dupré":5880,"blog-related-posts-en-us-using-child-pipelines-to-continuously-deploy-to-five-environments":5895,"blog-promotions-en-us":5921,"next-steps-en-us":5975},{"id":4,"title":5,"authors":6,"body":8,"category":5178,"date":5179,"description":5180,"extension":5181,"externalUrl":5182,"faq":5182,"featured":5183,"heroImage":5184,"meta":5185,"navigation":128,"path":5186,"seo":5187,"slug":5191,"stem":5192,"tags":5193,"template":5198,"updatedDate":5182,"__hash__":5199},"blogPosts/en-us/blog/using-child-pipelines-to-continuously-deploy-to-five-environments.md","Using child pipelines to continuously deploy to five environments",[7],"Olivier Dupré",{"type":9,"value":10,"toc":5166},"minimark",[11,22,37,40,78,98,205,223,227,255,258,264,267,270,273,276,279,289,292,295,302,307,310,313,316,319,326,329,336,711,717,1017,1020,1037,1040,1043,1051,1058,1087,1090,1094,1097,1166,1180,1209,1227,1241,1250,1268,1286,1313,1326,1344,1348,1361,1366,1402,1408,1837,1852,1861,1870,1879,1901,1904,1908,1911,1922,1928,1966,1972,1984,1992,1999,2036,2039,2042,2052,2058,2061,2066,2069,2091,2094,2097,2100,2104,2107,2171,2174,2177,2181,2184,2295,2301,2305,2326,2339,2346,2610,2621,2625,2633,2636,2655,2665,2681,2964,2967,2987,2995,2999,3006,3016,3022,3025,3028,3244,3262,3269,3273,3279,4553,4556,4569,4573,4576,4601,4631,4637,5104,5117,5121,5137,5148,5162],[12,13,14,15,21],"p",{},"DevSecOps teams sometimes require the ability to manage continuous deployment across multiple environments — and they need to do so without changing their workflows. The ",[16,17,20],"a",{"href":18,"rel":19},"https://about.gitlab.com/",[],"GitLab DevSecOps platform"," supports this need, including temporary, on-the-fly sandboxes, with a minimalist approach. In this article, you'll learn how to run continuous deployment of infrastructure using Terraform, over multiple environments.",[12,23,24,25,30,31,36],{},"This strategy can easily be applied to any project, whether it is infrastructure as code (IaC) relying on another technology, such as ",[16,26,29],{"href":27,"rel":28},"https://www.pulumi.com/",[],"Pulumi"," or ",[16,32,35],{"href":33,"rel":34},"https://www.ansible.com/",[],"Ansible",", source code in any language, or a monorepo that mixes many languages.",[12,38,39],{},"The final pipeline that you will have at the end of this tutorial will deploy:",[41,42,43,52,59,66,72],"ul",{},[44,45,46,47,51],"li",{},"A temporary ",[48,49,50],"strong",{},"review"," environment for each feature branch.",[44,53,54,55,58],{},"An ",[48,56,57],{},"integration"," environment, easy to wipe out and deployed from the main branch.",[44,60,61,62,65],{},"A ",[48,63,64],{},"QA"," environment, also deployed from the main branch, to run quality assurance steps.",[44,67,61,68,71],{},[48,69,70],{},"staging"," environment, deployed for every tag. This is the last round before production.",[44,73,61,74,77],{},[48,75,76],{},"production"," environment, just after the staging environment. This one is triggered manually for demonstration, but can also be continuously deployed.",[79,80,81,84],"blockquote",{},[12,82,83],{},"Here is the legend for the flow charts in this article:",[41,85,86,89,92,95],{},[44,87,88],{},"Round boxes are the GitLab branches.",[44,90,91],{},"Square boxes are the environments.",[44,93,94],{},"Text on the arrows are the actions to flow from one box to the next.",[44,96,97],{},"Angled squares are decision steps.",[99,100,105],"pre",{"className":101,"code":102,"language":103,"meta":104,"style":104},"language-mermaid shiki shiki-themes github-light","flowchart 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\n","mermaid","",[106,107,108,117,123,130,136,142,148,153,159,165,170,176,182,187,193,199],"code",{"__ignoreMap":104},[109,110,113],"span",{"class":111,"line":112},"line",1,[109,114,116],{"class":115},"sgsFI","flowchart LR\n",[109,118,120],{"class":111,"line":119},2,[109,121,122],{"class":115},"    A(main) -->|new feature| B(feature_X)\n",[109,124,126],{"class":111,"line":125},3,[109,127,129],{"emptyLinePlaceholder":128},true,"\n",[109,131,133],{"class":111,"line":132},4,[109,134,135],{"class":115},"    B -->|auto deploy| C[review/feature_X]\n",[109,137,139],{"class":111,"line":138},5,[109,140,141],{"class":115},"    B -->|merge| D(main)\n",[109,143,145],{"class":111,"line":144},6,[109,146,147],{"class":115},"    C -->|destroy| D\n",[109,149,151],{"class":111,"line":150},7,[109,152,129],{"emptyLinePlaceholder":128},[109,154,156],{"class":111,"line":155},8,[109,157,158],{"class":115},"    D -->|auto deploy| E[integration]\n",[109,160,162],{"class":111,"line":161},9,[109,163,164],{"class":115},"    E -->|manual| F[qa]\n",[109,166,168],{"class":111,"line":167},10,[109,169,129],{"emptyLinePlaceholder":128},[109,171,173],{"class":111,"line":172},11,[109,174,175],{"class":115},"    D -->|tag| G(X.Y.Z)\n",[109,177,179],{"class":111,"line":178},12,[109,180,181],{"class":115},"    F -->|validate| G\n",[109,183,185],{"class":111,"line":184},13,[109,186,129],{"emptyLinePlaceholder":128},[109,188,190],{"class":111,"line":189},14,[109,191,192],{"class":115},"    G -->|auto deploy| H[staging]\n",[109,194,196],{"class":111,"line":195},15,[109,197,198],{"class":115},"    H -->|manual| I{plan}\n",[109,200,202],{"class":111,"line":201},16,[109,203,204],{"class":115},"    I -->|manual| J[production]\n",[12,206,207,208,212,213,217,218,222],{},"On each step, you'll learn the ",[16,209,211],{"href":210},"#why","why"," and the ",[16,214,216],{"href":215},"#what","what"," before moving to the ",[16,219,221],{"href":220},"#how","how",". This will help you fully understand and replicate this tutorial.",[224,225,226],"h2",{"id":211},"Why",[41,228,229,237,245],{},[44,230,231,236],{},[16,232,235],{"href":233,"rel":234},"https://about.gitlab.com/topics/ci-cd/#what-is-continuous-integration-ci",[],"Continuous integration"," is almost a de facto standard. Most companies have implemented CI pipelines or are willing to standardize their practice.",[44,238,239,244],{},[16,240,243],{"href":241,"rel":242},"https://about.gitlab.com/topics/ci-cd/#what-is-continuous-delivery-cd",[],"Continuous delivery",", which pushes artifacts to a repository or registry at the end of the CI pipeline, is also popular.",[44,246,247,248,254],{},"Continuous deployment, which goes further and deploys these artifacts automatically, is less widespread. When it has been implemented, we see it essentially in the application field. When discussing continuously deploying  infrastructure, the picture seems less obvious, and is more about managing several environments. In contrast, testing, securing, and verifying the infrastructure's code seems more challenging. And this is one of the fields where DevOps has not yet reached its maturity. One of the other fields is to shift security left, integrating security teams and, more importantly, security concerns, earlier in the delivery lifecycle, to upgrade from DevOps to ",[249,250,251],"em",{},[48,252,253],{},"DevSecOps",".",[12,256,257],{},"Given this high-level picture, in this tutorial, you will work toward a simple, yet efficient way to implement DevSecOps for your infrastructure through the example of deploying resources to five environments, gradually progressing from development to production.",[12,259,260,263],{},[48,261,262],{},"Note:"," Even if I advocate embracing a FinOps approach and reducing the number of environments, sometimes there are excellent reasons to maintain more than just dev, staging, and production. So, please, adapt the examples below to match your needs.",[224,265,266],{"id":216},"What",[12,268,269],{},"The rise of cloud technology has driven the usage of IaC. Ansible and Terraform were among the first to pave the road here. OpenTofu, Pulumi, AWS CDK, Google Deploy Manager, and many others joined the party.",[12,271,272],{},"Defining IaC is a perfect solution to feel safe when deploying infrastructure. You can test it, deploy it, and replay it again and again until you reach your goal.",[12,274,275],{},"Unfortunately, we often see companies maintain several branches, or even repositories, for each of their target environments. And this is where the problems start. They are no longer enforcing a process. They are no longer ensuring that any change in the production code base has been accurately tested in previous environments. And they start seeing drifts from one environment to the other.",[12,277,278],{},"I realized this tutorial was necessary when, at a conference I attended, every participant said they do not have a workflow that enforces the infrastructure to be tested thoroughly before being deployed to production. And they all agreed that sometimes they patch the code directly in production. Sure, this is fast, but is it safe? How do you report back to previous environments? How do you ensure there are no side effects? How do you control whether you are putting your company at risk with new vulnerabilities being pushed too quickly in production?",[12,280,281,282,284,285,288],{},"The question of ",[249,283,211],{}," DevOps teams deploy directly to production is critical here. Is it because the pipeline could be more efficient or faster? Is there no automation? Or, even worse, because there is ",[249,286,287],{},"no way to test accurately outside of production","?",[12,290,291],{},"In the next section, you will learn how to implement automation for your infrastructure and ensure that your DevOps team can effectively test what you are doing before pushing to any environment impacting others. You will see how your code is secured and its deployment is controlled, end-to-end.",[224,293,294],{"id":221},"How",[12,296,297,298,301],{},"As mentioned earlier, there are many IaC languages out there nowadays and we objectively cannot cover ",[249,299,300],{},"all"," of them in a single article. So, I will rely on a basic Terraform code running on Version 1.4. Please do not focus on the IaC language itself but instead on the process that you could apply to your own ecosystem.",[303,304,306],"h3",{"id":305},"the-terraform-code","The Terraform code",[12,308,309],{},"Let's start with a fundamental Terraform code.",[12,311,312],{},"We are going to deploy to AWS, a virtual private cloud (VPC), which is a virtual network. In that VPC, we will deploy a public and a private subnet. As their name implies, they are subnets of the main VPC. Finally, we will add an Elastic Cloud Compute (EC2) instance (a virtual machine) in the public subnet.",[12,314,315],{},"This demonstrates the deployment of four resources without adding too much complexity. The idea is to focus on the pipeline, not the code.",[12,317,318],{},"Here is the target we want to reach for your repository.",[12,320,321],{},[322,323],"img",{"alt":324,"src":325},"target for repository","https://res.cloudinary.com/about-gitlab-com/image/upload/v1750097033/Blog/Content%20Images/Blog/Content%20Images/image5_aHR0cHM6_1750097033415.png",[12,327,328],{},"Let’s do it step by step.",[12,330,331,332,335],{},"First, we declare all resources in a ",[106,333,334],{},"terraform/main.tf"," file:",[99,337,341],{"className":338,"code":339,"language":340,"meta":104,"style":104},"language-terraform shiki shiki-themes github-light","provider \"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","terraform",[106,342,343,356,373,378,382,395,409,413,422,433,438,442,446,458,479,492,496,505,517,522,527,539,556,570,575,584,594,599,604,609,622,638,653,658,678,683,692,701,706],{"__ignoreMap":104},[109,344,345,349,353],{"class":111,"line":112},[109,346,348],{"class":347},"s7eDp","provider",[109,350,352],{"class":351},"sYu0t"," \"aws\"",[109,354,355],{"class":115}," {\n",[109,357,358,361,365,368,370],{"class":111,"line":119},[109,359,360],{"class":115},"  region",[109,362,364],{"class":363},"sD7c4"," =",[109,366,367],{"class":115}," var",[109,369,254],{"class":363},[109,371,372],{"class":115},"aws_default_region\n",[109,374,375],{"class":111,"line":125},[109,376,377],{"class":115},"}\n",[109,379,380],{"class":111,"line":132},[109,381,129],{"emptyLinePlaceholder":128},[109,383,384,387,390,393],{"class":111,"line":138},[109,385,386],{"class":347},"resource",[109,388,389],{"class":351}," \"aws_vpc\"",[109,391,392],{"class":351}," \"main\"",[109,394,355],{"class":115},[109,396,397,400,402,404,406],{"class":111,"line":144},[109,398,399],{"class":115},"  cidr_block",[109,401,364],{"class":363},[109,403,367],{"class":115},[109,405,254],{"class":363},[109,407,408],{"class":115},"aws_vpc_cidr\n",[109,410,411],{"class":111,"line":150},[109,412,129],{"emptyLinePlaceholder":128},[109,414,415,418,420],{"class":111,"line":155},[109,416,417],{"class":115},"  tags",[109,419,364],{"class":363},[109,421,355],{"class":115},[109,423,424,427,430],{"class":111,"line":161},[109,425,426],{"class":115},"    Name     ",[109,428,429],{"class":363},"=",[109,431,432],{"class":115}," var.aws_resources_name\n",[109,434,435],{"class":111,"line":167},[109,436,437],{"class":115},"  }\n",[109,439,440],{"class":111,"line":172},[109,441,377],{"class":115},[109,443,444],{"class":111,"line":178},[109,445,129],{"emptyLinePlaceholder":128},[109,447,448,450,453,456],{"class":111,"line":184},[109,449,386],{"class":347},[109,451,452],{"class":351}," \"aws_subnet\"",[109,454,455],{"class":351}," \"public_subnet\"",[109,457,355],{"class":115},[109,459,460,463,466,469,471,474,476],{"class":111,"line":189},[109,461,462],{"class":115},"  vpc_id",[109,464,465],{"class":363},"     =",[109,467,468],{"class":115}," aws_vpc",[109,470,254],{"class":363},[109,472,473],{"class":115},"main",[109,475,254],{"class":363},[109,477,478],{"class":115},"id\n",[109,480,481,483,485,487,489],{"class":111,"line":195},[109,482,399],{"class":115},[109,484,364],{"class":363},[109,486,367],{"class":115},[109,488,254],{"class":363},[109,490,491],{"class":115},"aws_public_subnet_cidr\n",[109,493,494],{"class":111,"line":201},[109,495,129],{"emptyLinePlaceholder":128},[109,497,499,501,503],{"class":111,"line":498},17,[109,500,417],{"class":115},[109,502,364],{"class":363},[109,504,355],{"class":115},[109,506,508,511,513],{"class":111,"line":507},18,[109,509,510],{"class":115},"    Name ",[109,512,429],{"class":363},[109,514,516],{"class":515},"sYBdl"," \"Public Subnet\"\n",[109,518,520],{"class":111,"line":519},19,[109,521,437],{"class":115},[109,523,525],{"class":111,"line":524},20,[109,526,377],{"class":115},[109,528,530,532,534,537],{"class":111,"line":529},21,[109,531,386],{"class":347},[109,533,452],{"class":351},[109,535,536],{"class":351}," \"private_subnet\"",[109,538,355],{"class":115},[109,540,542,544,546,548,550,552,554],{"class":111,"line":541},22,[109,543,462],{"class":115},[109,545,465],{"class":363},[109,547,468],{"class":115},[109,549,254],{"class":363},[109,551,473],{"class":115},[109,553,254],{"class":363},[109,555,478],{"class":115},[109,557,559,561,563,565,567],{"class":111,"line":558},23,[109,560,399],{"class":115},[109,562,364],{"class":363},[109,564,367],{"class":115},[109,566,254],{"class":363},[109,568,569],{"class":115},"aws_private_subnet_cidr\n",[109,571,573],{"class":111,"line":572},24,[109,574,129],{"emptyLinePlaceholder":128},[109,576,578,580,582],{"class":111,"line":577},25,[109,579,417],{"class":115},[109,581,364],{"class":363},[109,583,355],{"class":115},[109,585,587,589,591],{"class":111,"line":586},26,[109,588,510],{"class":115},[109,590,429],{"class":363},[109,592,593],{"class":515}," \"Private Subnet\"\n",[109,595,597],{"class":111,"line":596},27,[109,598,437],{"class":115},[109,600,602],{"class":111,"line":601},28,[109,603,377],{"class":115},[109,605,607],{"class":111,"line":606},29,[109,608,129],{"emptyLinePlaceholder":128},[109,610,612,614,617,620],{"class":111,"line":611},30,[109,613,386],{"class":347},[109,615,616],{"class":351}," \"aws_instance\"",[109,618,619],{"class":351}," \"sandbox\"",[109,621,355],{"class":115},[109,623,625,628,631,633,635],{"class":111,"line":624},31,[109,626,627],{"class":115},"  ami",[109,629,630],{"class":363},"           =",[109,632,367],{"class":115},[109,634,254],{"class":363},[109,636,637],{"class":115},"aws_ami_id\n",[109,639,641,644,646,648,650],{"class":111,"line":640},32,[109,642,643],{"class":115},"  instance_type",[109,645,364],{"class":363},[109,647,367],{"class":115},[109,649,254],{"class":363},[109,651,652],{"class":115},"aws_instance_type\n",[109,654,656],{"class":111,"line":655},33,[109,657,129],{"emptyLinePlaceholder":128},[109,659,661,664,666,669,671,674,676],{"class":111,"line":660},34,[109,662,663],{"class":115},"  subnet_id",[109,665,364],{"class":363},[109,667,668],{"class":115}," aws_subnet",[109,670,254],{"class":363},[109,672,673],{"class":115},"public_subnet",[109,675,254],{"class":363},[109,677,478],{"class":115},[109,679,681],{"class":111,"line":680},35,[109,682,129],{"emptyLinePlaceholder":128},[109,684,686,688,690],{"class":111,"line":685},36,[109,687,417],{"class":115},[109,689,364],{"class":363},[109,691,355],{"class":115},[109,693,695,697,699],{"class":111,"line":694},37,[109,696,426],{"class":115},[109,698,429],{"class":363},[109,700,432],{"class":115},[109,702,704],{"class":111,"line":703},38,[109,705,437],{"class":115},[109,707,709],{"class":111,"line":708},39,[109,710,377],{"class":115},[12,712,713,714,335],{},"As you can see, there are a couple of variables that are needed for this code, so let's declare them in a ",[106,715,716],{},"terraform/variables.tf",[99,718,720],{"className":338,"code":719,"language":340,"meta":104,"style":104},"variable \"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",[106,721,722,732,742,753,757,761,770,779,787,797,801,805,814,823,831,840,844,848,857,866,874,883,887,891,900,909,917,926,930,934,943,952,960,969,973,977,986,995,1003,1012],{"__ignoreMap":104},[109,723,724,727,730],{"class":111,"line":112},[109,725,726],{"class":347},"variable",[109,728,729],{"class":351}," \"aws_ami_id\"",[109,731,355],{"class":115},[109,733,734,737,739],{"class":111,"line":119},[109,735,736],{"class":115},"  description",[109,738,364],{"class":363},[109,740,741],{"class":515}," \"The AMI ID of the image being deployed.\"\n",[109,743,744,747,750],{"class":111,"line":125},[109,745,746],{"class":115},"  type",[109,748,749],{"class":363},"        =",[109,751,752],{"class":363}," string\n",[109,754,755],{"class":111,"line":132},[109,756,377],{"class":115},[109,758,759],{"class":111,"line":138},[109,760,129],{"emptyLinePlaceholder":128},[109,762,763,765,768],{"class":111,"line":144},[109,764,726],{"class":347},[109,766,767],{"class":351}," \"aws_instance_type\"",[109,769,355],{"class":115},[109,771,772,774,776],{"class":111,"line":150},[109,773,736],{"class":115},[109,775,364],{"class":363},[109,777,778],{"class":515}," \"The instance type of the VM being deployed.\"\n",[109,780,781,783,785],{"class":111,"line":155},[109,782,746],{"class":115},[109,784,749],{"class":363},[109,786,752],{"class":363},[109,788,789,792,794],{"class":111,"line":161},[109,790,791],{"class":115},"  default",[109,793,465],{"class":363},[109,795,796],{"class":515}," \"t2.micro\"\n",[109,798,799],{"class":111,"line":167},[109,800,377],{"class":115},[109,802,803],{"class":111,"line":172},[109,804,129],{"emptyLinePlaceholder":128},[109,806,807,809,812],{"class":111,"line":178},[109,808,726],{"class":347},[109,810,811],{"class":351}," \"aws_vpc_cidr\"",[109,813,355],{"class":115},[109,815,816,818,820],{"class":111,"line":184},[109,817,736],{"class":115},[109,819,364],{"class":363},[109,821,822],{"class":515}," \"The CIDR of the VPC.\"\n",[109,824,825,827,829],{"class":111,"line":189},[109,826,746],{"class":115},[109,828,749],{"class":363},[109,830,752],{"class":363},[109,832,833,835,837],{"class":111,"line":195},[109,834,791],{"class":115},[109,836,465],{"class":363},[109,838,839],{"class":515}," \"10.0.0.0/16\"\n",[109,841,842],{"class":111,"line":201},[109,843,377],{"class":115},[109,845,846],{"class":111,"line":498},[109,847,129],{"emptyLinePlaceholder":128},[109,849,850,852,855],{"class":111,"line":507},[109,851,726],{"class":347},[109,853,854],{"class":351}," \"aws_public_subnet_cidr\"",[109,856,355],{"class":115},[109,858,859,861,863],{"class":111,"line":519},[109,860,736],{"class":115},[109,862,364],{"class":363},[109,864,865],{"class":515}," \"The CIDR of the public subnet.\"\n",[109,867,868,870,872],{"class":111,"line":524},[109,869,746],{"class":115},[109,871,749],{"class":363},[109,873,752],{"class":363},[109,875,876,878,880],{"class":111,"line":529},[109,877,791],{"class":115},[109,879,465],{"class":363},[109,881,882],{"class":515}," \"10.0.1.0/24\"\n",[109,884,885],{"class":111,"line":541},[109,886,377],{"class":115},[109,888,889],{"class":111,"line":558},[109,890,129],{"emptyLinePlaceholder":128},[109,892,893,895,898],{"class":111,"line":572},[109,894,726],{"class":347},[109,896,897],{"class":351}," \"aws_private_subnet_cidr\"",[109,899,355],{"class":115},[109,901,902,904,906],{"class":111,"line":577},[109,903,736],{"class":115},[109,905,364],{"class":363},[109,907,908],{"class":515}," \"The CIDR of the private subnet.\"\n",[109,910,911,913,915],{"class":111,"line":586},[109,912,746],{"class":115},[109,914,749],{"class":363},[109,916,752],{"class":363},[109,918,919,921,923],{"class":111,"line":596},[109,920,791],{"class":115},[109,922,465],{"class":363},[109,924,925],{"class":515}," \"10.0.2.0/24\"\n",[109,927,928],{"class":111,"line":601},[109,929,377],{"class":115},[109,931,932],{"class":111,"line":606},[109,933,129],{"emptyLinePlaceholder":128},[109,935,936,938,941],{"class":111,"line":611},[109,937,726],{"class":347},[109,939,940],{"class":351}," \"aws_default_region\"",[109,942,355],{"class":115},[109,944,945,947,949],{"class":111,"line":624},[109,946,736],{"class":115},[109,948,364],{"class":363},[109,950,951],{"class":515}," \"Default region where resources are deployed.\"\n",[109,953,954,956,958],{"class":111,"line":640},[109,955,746],{"class":115},[109,957,749],{"class":363},[109,959,752],{"class":363},[109,961,962,964,966],{"class":111,"line":655},[109,963,791],{"class":115},[109,965,465],{"class":363},[109,967,968],{"class":515}," \"eu-west-3\"\n",[109,970,971],{"class":111,"line":660},[109,972,377],{"class":115},[109,974,975],{"class":111,"line":680},[109,976,129],{"emptyLinePlaceholder":128},[109,978,979,981,984],{"class":111,"line":685},[109,980,726],{"class":347},[109,982,983],{"class":351}," \"aws_resources_name\"",[109,985,355],{"class":115},[109,987,988,990,992],{"class":111,"line":694},[109,989,736],{"class":115},[109,991,364],{"class":363},[109,993,994],{"class":515}," \"Default name for the resources.\"\n",[109,996,997,999,1001],{"class":111,"line":703},[109,998,746],{"class":115},[109,1000,749],{"class":363},[109,1002,752],{"class":363},[109,1004,1005,1007,1009],{"class":111,"line":708},[109,1006,791],{"class":115},[109,1008,465],{"class":363},[109,1010,1011],{"class":515}," \"demo\"\n",[109,1013,1015],{"class":111,"line":1014},40,[109,1016,377],{"class":115},[12,1018,1019],{},"Already, we are almost good to go on the IaC side. What's missing is a way to share the Terraform states. For those who don't know, Terraform works schematically doing the following:",[41,1021,1022,1028],{},[44,1023,1024,1027],{},[106,1025,1026],{},"plan"," checks the differences between the current state of the infrastructure and what is defined in the code. Then, it outputs the differences.",[44,1029,1030,1033,1034,1036],{},[106,1031,1032],{},"apply"," applies the differences in the ",[106,1035,1026],{}," and updates the state.",[12,1038,1039],{},"First round, the state is empty, then it is filled with the details (ID, etc.) of the resources applied by Terraform.",[12,1041,1042],{},"The problem is: Where is that state stored? How do we share it so several developers can collaborate on code?",[12,1044,1045,1046,254],{},"The solution is fairly simple: Leverage GitLab to store and share the state for you through a ",[16,1047,1050],{"href":1048,"rel":1049},"https://docs.gitlab.com/user/infrastructure/iac/terraform_state/",[],"Terraform HTTP backend",[12,1052,1053,1054,1057],{},"The first step in using this backend is to create the most simple ",[106,1055,1056],{},"terraform/backend.tf"," file. The second step will be handled in the pipeline.",[99,1059,1061],{"className":338,"code":1060,"language":340,"meta":104,"style":104},"terraform {\n  backend \"http\" {\n  }\n}\n",[106,1062,1063,1069,1079,1083],{"__ignoreMap":104},[109,1064,1065,1067],{"class":111,"line":112},[109,1066,340],{"class":347},[109,1068,355],{"class":115},[109,1070,1071,1074,1077],{"class":111,"line":119},[109,1072,1073],{"class":347},"  backend",[109,1075,1076],{"class":351}," \"http\"",[109,1078,355],{"class":115},[109,1080,1081],{"class":111,"line":125},[109,1082,437],{"class":115},[109,1084,1085],{"class":111,"line":132},[109,1086,377],{"class":115},[12,1088,1089],{},"Et voilà! We have a bare minimum Terraform code to deploy these four resources. We will provide the variable values at the runtime, so let's do that later.",[303,1091,1093],{"id":1092},"the-workflow","The workflow",[12,1095,1096],{},"The workflow that we are going to implement now is the following:",[99,1098,1100],{"className":101,"code":1099,"language":103,"meta":104,"style":104},"flowchart 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",[106,1101,1102,1106,1110,1114,1118,1122,1126,1130,1134,1138,1142,1146,1150,1154,1158,1162],{"__ignoreMap":104},[109,1103,1104],{"class":111,"line":112},[109,1105,116],{"class":115},[109,1107,1108],{"class":111,"line":119},[109,1109,122],{"class":115},[109,1111,1112],{"class":111,"line":125},[109,1113,129],{"emptyLinePlaceholder":128},[109,1115,1116],{"class":111,"line":132},[109,1117,135],{"class":115},[109,1119,1120],{"class":111,"line":138},[109,1121,141],{"class":115},[109,1123,1124],{"class":111,"line":144},[109,1125,147],{"class":115},[109,1127,1128],{"class":111,"line":150},[109,1129,129],{"emptyLinePlaceholder":128},[109,1131,1132],{"class":111,"line":155},[109,1133,158],{"class":115},[109,1135,1136],{"class":111,"line":161},[109,1137,164],{"class":115},[109,1139,1140],{"class":111,"line":167},[109,1141,129],{"emptyLinePlaceholder":128},[109,1143,1144],{"class":111,"line":172},[109,1145,175],{"class":115},[109,1147,1148],{"class":111,"line":178},[109,1149,181],{"class":115},[109,1151,1152],{"class":111,"line":184},[109,1153,129],{"emptyLinePlaceholder":128},[109,1155,1156],{"class":111,"line":189},[109,1157,192],{"class":115},[109,1159,1160],{"class":111,"line":195},[109,1161,198],{"class":115},[109,1163,1164],{"class":111,"line":201},[109,1165,204],{"class":115},[1167,1168,1169],"ol",{},[44,1170,1171,1172,1175,1176,1179],{},"Create a ",[48,1173,1174],{},"feature"," branch. This will continuously run all scanners on the code to ensure that it is still compliant and secured. This code will be continuously deployed to a temporary environment ",[106,1177,1178],{},"review/feature_branch"," with the name of the current branch. This is a safe environment where the developers and operations teams can test their code without impacting anybody. This is also where we will enforce the process, like enforcing code reviews and running scanners, to ensure that the quality and security of the code are acceptable and do not put your assets at risk. The infrastructure deployed by this branch is automatically destroyed when the branch is closed. This helps you keep your budget under control.",[99,1181,1183],{"className":101,"code":1182,"language":103,"meta":104,"style":104},"flowchart 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",[106,1184,1185,1189,1193,1197,1201,1205],{"__ignoreMap":104},[109,1186,1187],{"class":111,"line":112},[109,1188,116],{"class":115},[109,1190,1191],{"class":111,"line":119},[109,1192,122],{"class":115},[109,1194,1195],{"class":111,"line":125},[109,1196,129],{"emptyLinePlaceholder":128},[109,1198,1199],{"class":111,"line":132},[109,1200,135],{"class":115},[109,1202,1203],{"class":111,"line":138},[109,1204,141],{"class":115},[109,1206,1207],{"class":111,"line":144},[109,1208,147],{"class":115},[1167,1210,1211],{"start":119},[44,1212,1213,1214,1217,1218,1223,1224,1226],{},"Once approved, the feature branch will be ",[48,1215,1216],{},"merged"," into the main branch. This is a ",[16,1219,1222],{"href":1220,"rel":1221},"https://docs.gitlab.com/user/project/protected_branches/",[],"protected branch"," where no one can push. This is mandatory to ensure that every change request to production is thoroughly tested. That branch is also continuously deployed. The target here is the ",[106,1225,57],{}," environment. To keep this environment slightly more stable, its deletion is not automated but can be triggered manually.",[99,1228,1230],{"className":101,"code":1229,"language":103,"meta":104,"style":104},"flowchart LR\n    D(main) -->|auto deploy| E[integration]\n",[106,1231,1232,1236],{"__ignoreMap":104},[109,1233,1234],{"class":111,"line":112},[109,1235,116],{"class":115},[109,1237,1238],{"class":111,"line":119},[109,1239,1240],{"class":115},"    D(main) -->|auto deploy| E[integration]\n",[1167,1242,1243],{"start":125},[44,1244,1245,1246,1249],{},"From there, manual approval is required to trigger the next deployment. This will deploy the main branch to the ",[106,1247,1248],{},"qa"," environment. Here, I have set a rule to prevent deletion from the pipeline. The idea is that this environment should be quite stable (after all, it's already the third environment), and I would like to prevent deletion by mistake. Feel free to adapt the rules to match your processes.",[99,1251,1253],{"className":101,"code":1252,"language":103,"meta":104,"style":104},"flowchart LR\n    D(main)-->|auto deploy| E[integration]\n    E -->|manual| F[qa]\n",[106,1254,1255,1259,1264],{"__ignoreMap":104},[109,1256,1257],{"class":111,"line":112},[109,1258,116],{"class":115},[109,1260,1261],{"class":111,"line":119},[109,1262,1263],{"class":115},"    D(main)-->|auto deploy| E[integration]\n",[109,1265,1266],{"class":111,"line":125},[109,1267,164],{"class":115},[1167,1269,1270],{"start":132},[44,1271,1272,1273,1276,1277,1282,1283,1285],{},"To proceed, we will need to ",[48,1274,1275],{},"tag"," the code. We are relying on ",[16,1278,1281],{"href":1279,"rel":1280},"https://docs.gitlab.com/user/project/protected_tags/",[],"protected tags"," here to ensure that only a specific set of users are allowed to deploy to these last two environments. This will immediately trigger a deployment to the ",[106,1284,70],{}," environment.",[99,1287,1289],{"className":101,"code":1288,"language":103,"meta":104,"style":104},"flowchart LR\n    D(main) -->|tag| G(X.Y.Z)\n    F[qa] -->|validate| G\n\n    G -->|auto deploy| H[staging]\n",[106,1290,1291,1295,1300,1305,1309],{"__ignoreMap":104},[109,1292,1293],{"class":111,"line":112},[109,1294,116],{"class":115},[109,1296,1297],{"class":111,"line":119},[109,1298,1299],{"class":115},"    D(main) -->|tag| G(X.Y.Z)\n",[109,1301,1302],{"class":111,"line":125},[109,1303,1304],{"class":115},"    F[qa] -->|validate| G\n",[109,1306,1307],{"class":111,"line":132},[109,1308,129],{"emptyLinePlaceholder":128},[109,1310,1311],{"class":111,"line":138},[109,1312,192],{"class":115},[1167,1314,1315],{"start":138},[44,1316,1317,1318,1320,1321,254],{},"Finally, we are landing to ",[106,1319,76],{},". When discussing infrastructure, it is often challenging to deploy progressively (10%, 25%, etc.), so we will deploy the whole infrastructure. Still, we control that deployment with a manual trigger of this last step. And to enforce maximum control on this highly critical environment, we will control it as a ",[16,1322,1325],{"href":1323,"rel":1324},"https://docs.gitlab.com/ci/environments/protected_environments/",[],"protected environment",[99,1327,1329],{"className":101,"code":1328,"language":103,"meta":104,"style":104},"flowchart LR\n    H[staging] -->|manual| I{plan}\n    I -->|manual| J[production]\n",[106,1330,1331,1335,1340],{"__ignoreMap":104},[109,1332,1333],{"class":111,"line":112},[109,1334,116],{"class":115},[109,1336,1337],{"class":111,"line":119},[109,1338,1339],{"class":115},"    H[staging] -->|manual| I{plan}\n",[109,1341,1342],{"class":111,"line":125},[109,1343,204],{"class":115},[303,1345,1347],{"id":1346},"the-pipeline","The pipeline",[12,1349,1350,1351,1355,1356,254],{},"To implement the above ",[16,1352,1354],{"href":1353},"#the-workflow","workflow",", we are now going to implement a pipeline with two ",[16,1357,1360],{"href":1358,"rel":1359},"https://docs.gitlab.com/ci/pipelines/downstream_pipelines/",[],"downstream pipelines",[1362,1363,1365],"h4",{"id":1364},"the-main-pipeline","The main pipeline",[12,1367,1368,1369,1372,1373,1376,1377,1379,1380,1383,1384,1387,1388,1391,1392,1394,1395,1397,1398,1401],{},"Let's start with the main pipeline. This is the one that will be triggered automatically on any ",[48,1370,1371],{},"push to a feature branch",", any ",[48,1374,1375],{},"merge to the default branch",", or any ",[48,1378,1275],{},". ",[249,1381,1382],{},"The one"," that will do true ",[48,1385,1386],{},"continuous deployment"," to the following environments: ",[106,1389,1390],{},"dev",", ",[106,1393,57],{},", and ",[106,1396,70],{},". And it is declared in the ",[106,1399,1400],{},".gitlab-ci.yml"," file at the root of your project.",[12,1403,1404],{},[322,1405],{"alt":1406,"src":1407},"the repository target","https://res.cloudinary.com/about-gitlab-com/image/upload/v1750097033/Blog/Content%20Images/Blog/Content%20Images/image1_aHR0cHM6_1750097033417.png",[99,1409,1413],{"className":1410,"code":1411,"language":1412,"meta":104,"style":104},"language-yml shiki shiki-themes github-light","Stages:\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","yml",[106,1414,1415,1424,1432,1439,1443,1450,1460,1467,1477,1487,1494,1504,1518,1525,1538,1550,1554,1560,1570,1576,1586,1596,1606,1619,1626,1639,1643,1649,1657,1663,1672,1681,1689,1695,1706,1710,1716,1724,1730,1739,1747,1756,1763,1775,1780,1786,1792,1798,1804,1810,1818,1831],{"__ignoreMap":104},[109,1416,1417,1421],{"class":111,"line":112},[109,1418,1420],{"class":1419},"shJU0","Stages",[109,1422,1423],{"class":115},":\n",[109,1425,1426,1429],{"class":111,"line":119},[109,1427,1428],{"class":115},"  - ",[109,1430,1431],{"class":515},"test\n",[109,1433,1434,1436],{"class":111,"line":125},[109,1435,1428],{"class":115},[109,1437,1438],{"class":515},"environments\n",[109,1440,1441],{"class":111,"line":132},[109,1442,129],{"emptyLinePlaceholder":128},[109,1444,1445,1448],{"class":111,"line":138},[109,1446,1447],{"class":1419},".environment",[109,1449,1423],{"class":115},[109,1451,1452,1455,1458],{"class":111,"line":144},[109,1453,1454],{"class":1419},"  stage",[109,1456,1457],{"class":115},": ",[109,1459,1438],{"class":515},[109,1461,1462,1465],{"class":111,"line":150},[109,1463,1464],{"class":1419},"  variables",[109,1466,1423],{"class":115},[109,1468,1469,1472,1474],{"class":111,"line":155},[109,1470,1471],{"class":1419},"    TF_ROOT",[109,1473,1457],{"class":115},[109,1475,1476],{"class":515},"terraform\n",[109,1478,1479,1482,1484],{"class":111,"line":161},[109,1480,1481],{"class":1419},"    TF_CLI_ARGS_plan",[109,1483,1457],{"class":115},[109,1485,1486],{"class":515},"\"-var-file=../vars/$variables_file.tfvars\"\n",[109,1488,1489,1492],{"class":111,"line":167},[109,1490,1491],{"class":1419},"  trigger",[109,1493,1423],{"class":115},[109,1495,1496,1499,1501],{"class":111,"line":172},[109,1497,1498],{"class":1419},"    include",[109,1500,1457],{"class":115},[109,1502,1503],{"class":515},".gitlab-ci/.first-layer.gitlab-ci.yml\n",[109,1505,1506,1509,1511,1514],{"class":111,"line":178},[109,1507,1508],{"class":1419},"    strategy",[109,1510,1457],{"class":115},[109,1512,1513],{"class":515},"depend",[109,1515,1517],{"class":1516},"sAwPA","            # Wait for the triggered pipeline to successfully complete\n",[109,1519,1520,1523],{"class":111,"line":184},[109,1521,1522],{"class":1419},"    forward",[109,1524,1423],{"class":115},[109,1526,1527,1530,1532,1535],{"class":111,"line":189},[109,1528,1529],{"class":1419},"      yaml_variables",[109,1531,1457],{"class":115},[109,1533,1534],{"class":351},"true",[109,1536,1537],{"class":1516},"      # Forward variables defined in the trigger job\n",[109,1539,1540,1543,1545,1547],{"class":111,"line":195},[109,1541,1542],{"class":1419},"      pipeline_variables",[109,1544,1457],{"class":115},[109,1546,1534],{"class":351},[109,1548,1549],{"class":1516},"  # Forward manual pipeline variables and scheduled pipeline variables\n",[109,1551,1552],{"class":111,"line":201},[109,1553,129],{"emptyLinePlaceholder":128},[109,1555,1556,1558],{"class":111,"line":498},[109,1557,50],{"class":1419},[109,1559,1423],{"class":115},[109,1561,1562,1565,1567],{"class":111,"line":507},[109,1563,1564],{"class":1419},"  extends",[109,1566,1457],{"class":115},[109,1568,1569],{"class":515},".environment\n",[109,1571,1572,1574],{"class":111,"line":519},[109,1573,1464],{"class":1419},[109,1575,1423],{"class":115},[109,1577,1578,1581,1583],{"class":111,"line":524},[109,1579,1580],{"class":1419},"    environment",[109,1582,1457],{"class":115},[109,1584,1585],{"class":515},"review/$CI_COMMIT_REF_SLUG\n",[109,1587,1588,1591,1593],{"class":111,"line":529},[109,1589,1590],{"class":1419},"    TF_STATE_NAME",[109,1592,1457],{"class":115},[109,1594,1595],{"class":515},"$CI_COMMIT_REF_SLUG\n",[109,1597,1598,1601,1603],{"class":111,"line":541},[109,1599,1600],{"class":1419},"    variables_file",[109,1602,1457],{"class":115},[109,1604,1605],{"class":515},"review\n",[109,1607,1608,1611,1613,1616],{"class":111,"line":558},[109,1609,1610],{"class":1419},"    TF_VAR_aws_resources_name",[109,1612,1457],{"class":115},[109,1614,1615],{"class":515},"$CI_COMMIT_REF_SLUG",[109,1617,1618],{"class":1516},"  # Used in the tag Name of the resources deployed, to easily differenciate them\n",[109,1620,1621,1624],{"class":111,"line":572},[109,1622,1623],{"class":1419},"  rules",[109,1625,1423],{"class":115},[109,1627,1628,1631,1634,1636],{"class":111,"line":577},[109,1629,1630],{"class":115},"    - ",[109,1632,1633],{"class":1419},"if",[109,1635,1457],{"class":115},[109,1637,1638],{"class":515},"$CI_COMMIT_BRANCH && $CI_COMMIT_BRANCH != $CI_DEFAULT_BRANCH\n",[109,1640,1641],{"class":111,"line":586},[109,1642,129],{"emptyLinePlaceholder":128},[109,1644,1645,1647],{"class":111,"line":596},[109,1646,57],{"class":1419},[109,1648,1423],{"class":115},[109,1650,1651,1653,1655],{"class":111,"line":601},[109,1652,1564],{"class":1419},[109,1654,1457],{"class":115},[109,1656,1569],{"class":515},[109,1658,1659,1661],{"class":111,"line":606},[109,1660,1464],{"class":1419},[109,1662,1423],{"class":115},[109,1664,1665,1667,1669],{"class":111,"line":611},[109,1666,1580],{"class":1419},[109,1668,1457],{"class":115},[109,1670,1671],{"class":515},"integration\n",[109,1673,1674,1676,1678],{"class":111,"line":624},[109,1675,1590],{"class":1419},[109,1677,1457],{"class":115},[109,1679,1680],{"class":515},"$environment\n",[109,1682,1683,1685,1687],{"class":111,"line":640},[109,1684,1600],{"class":1419},[109,1686,1457],{"class":115},[109,1688,1680],{"class":515},[109,1690,1691,1693],{"class":111,"line":655},[109,1692,1623],{"class":1419},[109,1694,1423],{"class":115},[109,1696,1697,1699,1701,1703],{"class":111,"line":660},[109,1698,1630],{"class":115},[109,1700,1633],{"class":1419},[109,1702,1457],{"class":115},[109,1704,1705],{"class":515},"$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH\n",[109,1707,1708],{"class":111,"line":680},[109,1709,129],{"emptyLinePlaceholder":128},[109,1711,1712,1714],{"class":111,"line":685},[109,1713,70],{"class":1419},[109,1715,1423],{"class":115},[109,1717,1718,1720,1722],{"class":111,"line":694},[109,1719,1564],{"class":1419},[109,1721,1457],{"class":115},[109,1723,1569],{"class":515},[109,1725,1726,1728],{"class":111,"line":703},[109,1727,1464],{"class":1419},[109,1729,1423],{"class":115},[109,1731,1732,1734,1736],{"class":111,"line":708},[109,1733,1580],{"class":1419},[109,1735,1457],{"class":115},[109,1737,1738],{"class":515},"staging\n",[109,1740,1741,1743,1745],{"class":111,"line":1014},[109,1742,1590],{"class":1419},[109,1744,1457],{"class":115},[109,1746,1680],{"class":515},[109,1748,1750,1752,1754],{"class":111,"line":1749},41,[109,1751,1600],{"class":1419},[109,1753,1457],{"class":115},[109,1755,1680],{"class":515},[109,1757,1759,1761],{"class":111,"line":1758},42,[109,1760,1623],{"class":1419},[109,1762,1423],{"class":115},[109,1764,1766,1768,1770,1772],{"class":111,"line":1765},43,[109,1767,1630],{"class":115},[109,1769,1633],{"class":1419},[109,1771,1457],{"class":115},[109,1773,1774],{"class":515},"$CI_COMMIT_TAG\n",[109,1776,1778],{"class":111,"line":1777},44,[109,1779,129],{"emptyLinePlaceholder":128},[109,1781,1783],{"class":111,"line":1782},45,[109,1784,1785],{"class":1516},"#### TWEAK\n",[109,1787,1789],{"class":111,"line":1788},46,[109,1790,1791],{"class":1516},"# This tweak is needed to display vulnerability results in the merge widgets.\n",[109,1793,1795],{"class":111,"line":1794},47,[109,1796,1797],{"class":1516},"# As soon as this issue https://gitlab.com/gitlab-org/gitlab/-/issues/439700 is resolved, the `include` instruction below can be removed.\n",[109,1799,1801],{"class":111,"line":1800},48,[109,1802,1803],{"class":1516},"# 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",[109,1805,1807],{"class":111,"line":1806},49,[109,1808,1809],{"class":1516},"# Note: This workaround is perfectly safe and will not slow down your pipeline.\n",[109,1811,1813,1816],{"class":111,"line":1812},50,[109,1814,1815],{"class":1419},"include",[109,1817,1423],{"class":115},[109,1819,1821,1823,1826,1828],{"class":111,"line":1820},51,[109,1822,1428],{"class":115},[109,1824,1825],{"class":1419},"template",[109,1827,1457],{"class":115},[109,1829,1830],{"class":515},"Security/SAST-IaC.gitlab-ci.yml\n",[109,1832,1834],{"class":111,"line":1833},52,[109,1835,1836],{"class":1516},"#### END TWEAK\n",[12,1838,1839,1840,1843,1844,1847,1848,1851],{},"This pipeline runs only two stages: ",[106,1841,1842],{},"test"," and  ",[106,1845,1846],{},"environments",". The former is needed for the ",[249,1849,1850],{},"TWEAK"," to run scanners. The later triggers a child pipeline with a different set of variables for each case defined above (push to the branch, merge to the default branch, or tag).",[12,1853,1854,1855,1860],{},"We are adding here a dependency with the keyword ",[16,1856,1859],{"href":1857,"rel":1858},"https://docs.gitlab.com/ci/yaml/#triggerstrategy",[],"strategy:depend"," on our child pipeline so the pipeline view in GitLab will be updated only once the deployment is finished.",[12,1862,1863,1864,1869],{},"As you can see here, we are defining a base job, ",[16,1865,1868],{"href":1866,"rel":1867},"https://docs.gitlab.com/ci/jobs/#hide-jobs",[],"hidden",", and we are extending it with specific variables and rules to trigger only one deployment for each target environment.",[12,1871,1872,1873,1878],{},"Besides the ",[16,1874,1877],{"href":1875,"rel":1876},"https://docs.gitlab.com/ci/variables/predefined_variables/",[],"predefined variables",", we are using two new entries that we need to define:",[1167,1880,1881,1891],{},[44,1882,1883,1887,1888],{},[16,1884,1886],{"href":1885},"#the-variable-definitions","The variables specific"," to each environment: ",[106,1889,1890],{},"../vars/$variables_file.tfvars",[44,1892,1893,1897,1898],{},[16,1894,1896],{"href":1895},"#the-child-pipeline","The child pipeline",", defined in ",[106,1899,1900],{},".gitlab-ci/.first-layer.gitlab-ci.yml",[12,1902,1903],{},"Let's start with the smallest part, the variable definitions.",[303,1905,1907],{"id":1906},"the-variable-definitions","The variable definitions",[12,1909,1910],{},"We are going here to mix two solutions to provide variables to Terraform:",[41,1912,1913],{},[44,1914,1915,1916,1921],{},"The first one using ",[16,1917,1920],{"href":1918,"rel":1919},"https://developer.hashicorp.com/terraform/language/values/variables#variable-definitions-tfvars-files",[],".tfvars files"," for all non-sensitive input, which should be stored within GitLab.",[12,1923,1924],{},[322,1925],{"alt":1926,"src":1927},"solution one to provide variables to Terraform","https://res.cloudinary.com/about-gitlab-com/image/upload/v1750097034/Blog/Content%20Images/Blog/Content%20Images/image2_aHR0cHM6_1750097033419.png",[41,1929,1930],{},[44,1931,1932,1933,1938,1939,1942,1943,1391,1948,1394,1953,1958,1959,1962,1963,1965],{},"The second using ",[16,1934,1937],{"href":1935,"rel":1936},"https://developer.hashicorp.com/terraform/language/values/variables#environment-variables",[],"environment variables"," with the prefix ",[106,1940,1941],{},"TF_VAR",". That second way to inject variables, associated with the GitLab capacity to ",[16,1944,1947],{"href":1945,"rel":1946},"https://docs.gitlab.com/ci/variables/#mask-a-cicd-variable",[],"mask variables",[16,1949,1952],{"href":1950,"rel":1951},"https://docs.gitlab.com/ci/variables/#protect-a-cicd-variable",[],"protect them",[16,1954,1957],{"href":1955,"rel":1956},"https://docs.gitlab.com/ci/environments/#limit-the-environment-scope-of-a-cicd-variable",[],"scope them to environments"," is a powerful solution to ",[48,1960,1961],{},"prevent sensitive information leakages",". (If you consider your production’s private CIDR very sensitive, you could protect it like this, ensuring it is only available for the ",[106,1964,76],{}," environment, for pipelines running against protected branches and tags, and that its value is masked in the job’s logs.)",[12,1967,1968],{},[322,1969],{"alt":1970,"src":1971},"solution two to provide variables to Terraform","https://res.cloudinary.com/about-gitlab-com/image/upload/v1750097034/Blog/Content%20Images/Blog/Content%20Images/image4_aHR0cHM6_1750097033422.png",[12,1973,1974,1975,1983],{},"Additionally, each variable file should be controlled through a ",[16,1976,1979,1982],{"href":1977,"rel":1978},"https://docs.gitlab.com/user/project/codeowners/",[],[106,1980,1981],{},"CODEOWNERS"," file"," to set who can modify each of them.",[99,1985,1990],{"className":1986,"code":1988,"language":1989,"meta":104},[1987],"language-text","[Production owners]\nvars/production.tfvars @operations-group\n\n[Staging owners]\nvars/staging.tfvars @odupre @operations-group\n\n[CodeOwners owners]\nCODEOWNERS @odupre\n","text",[106,1991,1988],{"__ignoreMap":104},[12,1993,1994,1995,1998],{},"This article is not a Terraform training, so we will go very fast and simply show here the ",[106,1996,1997],{},"vars/review.tfvars"," file. Subsequent environment files are, of course, very similar. Just set the non-sensitive variables and their values here.",[99,2000,2004],{"className":2001,"code":2002,"language":2003,"meta":104,"style":104},"language-shell shiki shiki-themes github-light","aws_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","shell",[106,2005,2006,2016,2026],{"__ignoreMap":104},[109,2007,2008,2011,2013],{"class":111,"line":112},[109,2009,2010],{"class":347},"aws_vpc_cidr",[109,2012,364],{"class":515},[109,2014,2015],{"class":515}," \"10.1.0.0/16\"\n",[109,2017,2018,2021,2023],{"class":111,"line":119},[109,2019,2020],{"class":347},"aws_public_subnet_cidr",[109,2022,364],{"class":515},[109,2024,2025],{"class":515}," \"10.1.1.0/24\"\n",[109,2027,2028,2031,2033],{"class":111,"line":125},[109,2029,2030],{"class":347},"aws_private_subnet_cidr",[109,2032,364],{"class":515},[109,2034,2035],{"class":515}," \"10.1.2.0/24\"\n",[1362,2037,1896],{"id":2038},"the-child-pipeline",[12,2040,2041],{},"This one is where the actual work is done. So, it is slightly more complex than the first one. But there is no difficulty here that we cannot overcome together!",[12,2043,2044,2045,2049,2050,254],{},"As we have seen in the definition of the ",[16,2046,2048],{"href":2047},"#the-main-pipeline","main pipeline",", that downstream pipeline is declared in the file ",[106,2051,1900],{},[12,2053,2054],{},[322,2055],{"alt":2056,"src":2057},"Downstream pipeline declared in file","https://res.cloudinary.com/about-gitlab-com/image/upload/v1750097033/Blog/Content%20Images/Blog/Content%20Images/image3_aHR0cHM6_1750097033424.png",[12,2059,2060],{},"Let's break it down into small chunks. We'll see the big picture at the end.",[2062,2063,2065],"h5",{"id":2064},"run-terraform-commands-and-secure-the-code","Run Terraform commands and secure the code",[12,2067,2068],{},"First, we want to run a pipeline for Terraform. We, at GitLab, are open source. So, our Terraform template is open source. And you simply need to include it. This can be achieved with the following snippet:",[99,2070,2072],{"className":1410,"code":2071,"language":1412,"meta":104,"style":104},"include:\n  - template: Terraform.gitlab-ci.yml\n\n",[106,2073,2074,2080],{"__ignoreMap":104},[109,2075,2076,2078],{"class":111,"line":112},[109,2077,1815],{"class":1419},[109,2079,1423],{"class":115},[109,2081,2082,2084,2086,2088],{"class":111,"line":119},[109,2083,1428],{"class":115},[109,2085,1825],{"class":1419},[109,2087,1457],{"class":115},[109,2089,2090],{"class":515},"Terraform.gitlab-ci.yml\n",[12,2092,2093],{},"This template runs for you the Terraform checks on the formatting and validates your code, before planning and applying it. It also allows you to destroy what you have deployed.",[12,2095,2096],{},"And, because GitLab is the a single, unified DevSecOps platform, we are also automatically including two security scanners within that template to find potential threats in your code and warn you before you deploy it to the next environments.",[12,2098,2099],{},"Now that we have checked, secured, built, and deployed our code, let's do some tricks.",[2062,2101,2103],{"id":2102},"share-cache-between-jobs","Share cache between jobs",[12,2105,2106],{},"We will cache the job results to reuse them in subsequent pipeline jobs. This is as simple as adding the following piece of code:",[99,2108,2110],{"className":1410,"code":2109,"language":1412,"meta":104,"style":104},"default:\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",[106,2111,2112,2119,2130,2142,2149,2157,2164],{"__ignoreMap":104},[109,2113,2114,2117],{"class":111,"line":112},[109,2115,2116],{"class":1419},"default",[109,2118,1423],{"class":115},[109,2120,2121,2124,2127],{"class":111,"line":119},[109,2122,2123],{"class":1419},"  cache",[109,2125,2126],{"class":115},":  ",[109,2128,2129],{"class":1516},"# Use a shared cache or tagged runners to ensure terraform can run on apply and destroy\n",[109,2131,2132,2134,2137,2139],{"class":111,"line":125},[109,2133,1630],{"class":115},[109,2135,2136],{"class":1419},"key",[109,2138,1457],{"class":115},[109,2140,2141],{"class":515},"cache-$CI_COMMIT_REF_SLUG\n",[109,2143,2144,2147],{"class":111,"line":132},[109,2145,2146],{"class":1419},"      fallback_keys",[109,2148,1423],{"class":115},[109,2150,2151,2154],{"class":111,"line":138},[109,2152,2153],{"class":115},"        - ",[109,2155,2156],{"class":515},"cache-$CI_DEFAULT_BRANCH\n",[109,2158,2159,2162],{"class":111,"line":144},[109,2160,2161],{"class":1419},"      paths",[109,2163,1423],{"class":115},[109,2165,2166,2168],{"class":111,"line":150},[109,2167,2153],{"class":115},[109,2169,2170],{"class":351},".\n",[12,2172,2173],{},"Here, we are defining a different cache for each commit, falling back to the main branch name if needed.",[12,2175,2176],{},"If we look carefully at the templates that we are using, we can see that it has some rules to control when jobs are run. We want to run all controls (both QA and security) on all branches. So, we are going to override these settings.",[2062,2178,2180],{"id":2179},"run-controls-on-all-branches","Run controls on all branches",[12,2182,2183],{},"GitLab templates are a powerful feature where one can override only a piece of the template. Here, we are interested only in overwriting the rules of some jobs to always run quality and security checks. Everything else defined for these jobs will stay as defined in the template.",[99,2185,2187],{"className":1410,"code":2186,"language":1412,"meta":104,"style":104},"fmt:\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",[106,2188,2189,2196,2202,2214,2218,2225,2231,2241,2245,2252,2258,2268,2272,2279,2285],{"__ignoreMap":104},[109,2190,2191,2194],{"class":111,"line":112},[109,2192,2193],{"class":1419},"fmt",[109,2195,1423],{"class":115},[109,2197,2198,2200],{"class":111,"line":119},[109,2199,1623],{"class":1419},[109,2201,1423],{"class":115},[109,2203,2204,2206,2209,2211],{"class":111,"line":125},[109,2205,1630],{"class":115},[109,2207,2208],{"class":1419},"when",[109,2210,1457],{"class":115},[109,2212,2213],{"class":515},"always\n",[109,2215,2216],{"class":111,"line":132},[109,2217,129],{"emptyLinePlaceholder":128},[109,2219,2220,2223],{"class":111,"line":138},[109,2221,2222],{"class":1419},"validate",[109,2224,1423],{"class":115},[109,2226,2227,2229],{"class":111,"line":144},[109,2228,1623],{"class":1419},[109,2230,1423],{"class":115},[109,2232,2233,2235,2237,2239],{"class":111,"line":150},[109,2234,1630],{"class":115},[109,2236,2208],{"class":1419},[109,2238,1457],{"class":115},[109,2240,2213],{"class":515},[109,2242,2243],{"class":111,"line":155},[109,2244,129],{"emptyLinePlaceholder":128},[109,2246,2247,2250],{"class":111,"line":161},[109,2248,2249],{"class":1419},"kics-iac-sast",[109,2251,1423],{"class":115},[109,2253,2254,2256],{"class":111,"line":167},[109,2255,1623],{"class":1419},[109,2257,1423],{"class":115},[109,2259,2260,2262,2264,2266],{"class":111,"line":172},[109,2261,1630],{"class":115},[109,2263,2208],{"class":1419},[109,2265,1457],{"class":115},[109,2267,2213],{"class":515},[109,2269,2270],{"class":111,"line":178},[109,2271,129],{"emptyLinePlaceholder":128},[109,2273,2274,2277],{"class":111,"line":184},[109,2275,2276],{"class":1419},"iac-sast",[109,2278,1423],{"class":115},[109,2280,2281,2283],{"class":111,"line":189},[109,2282,1623],{"class":1419},[109,2284,1423],{"class":115},[109,2286,2287,2289,2291,2293],{"class":111,"line":195},[109,2288,1630],{"class":115},[109,2290,2208],{"class":1419},[109,2292,1457],{"class":115},[109,2294,2213],{"class":515},[12,2296,2297,2298,2300],{},"Now that we have enforced the quality and security controls, we want to differentiate how the main environments (integration and staging) in the ",[16,2299,1354],{"href":1353}," and review environments behave. Let's start by defining the main environment’s behavior, and we will tweak this configuration for the review environments.",[2062,2302,2304],{"id":2303},"cd-to-integration-and-staging","CD to integration and staging",[12,2306,2307,2308,2311,2312,2315,2316,2319,2320,2322,2323,2325],{},"As defined earlier, we want to deploy the main branch and the tags to these two environments. We are adding rules to control that on both the ",[106,2309,2310],{},"build"," and ",[106,2313,2314],{},"deploy"," jobs. Then, we want to enable ",[106,2317,2318],{},"destroy"," only for the ",[106,2321,57],{}," as we have defined ",[106,2324,70],{}," to be too critical to be deleted with a single click. This is error-prone and we don't want to do that.",[12,2327,2328,2329,2331,2332,2334,2335,2338],{},"Finally, we are linking the ",[106,2330,2314],{}," job to the ",[106,2333,2318],{}," one, so we can ",[106,2336,2337],{},"stop"," the environment directly from GitLab GUI.",[12,2340,2341,2342,2345],{},"The ",[106,2343,2344],{},"GIT_STRATEGY"," is here to prevent retrieving the code from the source branch in the runner when destroying. This would fail if the branch has been deleted manually, so we are relying on the cache to get everything we need to run the Terraform instructions.",[99,2347,2349],{"className":1410,"code":2348,"language":1412,"meta":104,"style":104},"build:  # 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:\n    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",[106,2350,2351,2360,2367,2377,2387,2393,2403,2413,2417,2426,2432,2440,2449,2459,2465,2475,2485,2489,2495,2504,2510,2520,2527,2534,2540,2548,2557,2563,2577,2587,2601],{"__ignoreMap":104},[109,2352,2353,2355,2357],{"class":111,"line":112},[109,2354,2310],{"class":1419},[109,2356,2126],{"class":115},[109,2358,2359],{"class":1516},"# terraform plan\n",[109,2361,2362,2365],{"class":111,"line":119},[109,2363,2364],{"class":1419},"  environment",[109,2366,1423],{"class":115},[109,2368,2369,2372,2374],{"class":111,"line":125},[109,2370,2371],{"class":1419},"    name",[109,2373,1457],{"class":115},[109,2375,2376],{"class":515},"$TF_STATE_NAME\n",[109,2378,2379,2382,2384],{"class":111,"line":132},[109,2380,2381],{"class":1419},"    action",[109,2383,1457],{"class":115},[109,2385,2386],{"class":515},"prepare\n",[109,2388,2389,2391],{"class":111,"line":138},[109,2390,1623],{"class":1419},[109,2392,1423],{"class":115},[109,2394,2395,2397,2399,2401],{"class":111,"line":144},[109,2396,1630],{"class":115},[109,2398,1633],{"class":1419},[109,2400,1457],{"class":115},[109,2402,1705],{"class":515},[109,2404,2405,2407,2409,2411],{"class":111,"line":150},[109,2406,1630],{"class":115},[109,2408,1633],{"class":1419},[109,2410,1457],{"class":115},[109,2412,1774],{"class":515},[109,2414,2415],{"class":111,"line":155},[109,2416,129],{"emptyLinePlaceholder":128},[109,2418,2419,2421,2423],{"class":111,"line":161},[109,2420,2314],{"class":1419},[109,2422,1457],{"class":115},[109,2424,2425],{"class":1516},"# 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",[109,2427,2428,2430],{"class":111,"line":167},[109,2429,2364],{"class":1419},[109,2431,1423],{"class":115},[109,2433,2434,2436,2438],{"class":111,"line":172},[109,2435,2371],{"class":1419},[109,2437,1457],{"class":115},[109,2439,2376],{"class":515},[109,2441,2442,2444,2446],{"class":111,"line":178},[109,2443,2381],{"class":1419},[109,2445,1457],{"class":115},[109,2447,2448],{"class":515},"start\n",[109,2450,2451,2454,2456],{"class":111,"line":184},[109,2452,2453],{"class":1419},"    on_stop",[109,2455,1457],{"class":115},[109,2457,2458],{"class":515},"destroy\n",[109,2460,2461,2463],{"class":111,"line":189},[109,2462,1623],{"class":1419},[109,2464,1423],{"class":115},[109,2466,2467,2469,2471,2473],{"class":111,"line":195},[109,2468,1630],{"class":115},[109,2470,1633],{"class":1419},[109,2472,1457],{"class":115},[109,2474,1705],{"class":515},[109,2476,2477,2479,2481,2483],{"class":111,"line":201},[109,2478,1630],{"class":115},[109,2480,1633],{"class":1419},[109,2482,1457],{"class":115},[109,2484,1774],{"class":515},[109,2486,2487],{"class":111,"line":498},[109,2488,129],{"emptyLinePlaceholder":128},[109,2490,2491,2493],{"class":111,"line":507},[109,2492,2318],{"class":1419},[109,2494,1423],{"class":115},[109,2496,2497,2499,2501],{"class":111,"line":519},[109,2498,1564],{"class":1419},[109,2500,1457],{"class":115},[109,2502,2503],{"class":515},".terraform:destroy\n",[109,2505,2506,2508],{"class":111,"line":524},[109,2507,1464],{"class":1419},[109,2509,1423],{"class":115},[109,2511,2512,2515,2517],{"class":111,"line":529},[109,2513,2514],{"class":1419},"    GIT_STRATEGY",[109,2516,1457],{"class":115},[109,2518,2519],{"class":515},"none\n",[109,2521,2522,2525],{"class":111,"line":541},[109,2523,2524],{"class":1419},"  dependencies",[109,2526,1423],{"class":115},[109,2528,2529,2531],{"class":111,"line":558},[109,2530,1630],{"class":115},[109,2532,2533],{"class":515},"build\n",[109,2535,2536,2538],{"class":111,"line":572},[109,2537,2364],{"class":1419},[109,2539,1423],{"class":115},[109,2541,2542,2544,2546],{"class":111,"line":577},[109,2543,2371],{"class":1419},[109,2545,1457],{"class":115},[109,2547,2376],{"class":515},[109,2549,2550,2552,2554],{"class":111,"line":586},[109,2551,2381],{"class":1419},[109,2553,1457],{"class":115},[109,2555,2556],{"class":515},"stop\n",[109,2558,2559,2561],{"class":111,"line":596},[109,2560,1623],{"class":1419},[109,2562,1423],{"class":115},[109,2564,2565,2567,2569,2571,2574],{"class":111,"line":601},[109,2566,1630],{"class":115},[109,2568,1633],{"class":1419},[109,2570,1457],{"class":115},[109,2572,2573],{"class":515},"$CI_COMMIT_TAG",[109,2575,2576],{"class":1516},"  # Do not destroy production\n",[109,2578,2579,2582,2584],{"class":111,"line":606},[109,2580,2581],{"class":1419},"      when",[109,2583,1457],{"class":115},[109,2585,2586],{"class":515},"never\n",[109,2588,2589,2591,2593,2595,2598],{"class":111,"line":611},[109,2590,1630],{"class":115},[109,2592,1633],{"class":1419},[109,2594,1457],{"class":115},[109,2596,2597],{"class":515},"$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH && $TF_DESTROY == \"true\"",[109,2599,2600],{"class":1516}," # Manually destroy integration env.\n",[109,2602,2603,2605,2607],{"class":111,"line":624},[109,2604,2581],{"class":1419},[109,2606,1457],{"class":115},[109,2608,2609],{"class":515},"manual\n",[12,2611,2612,2613,2311,2615,2617,2618,2620],{},"As said, this matches the need to deploy to ",[106,2614,57],{},[106,2616,70],{},". But we are still missing a temporary environment where the developers can experience and validate their code without impacts on others. This is where the deployment to the ",[106,2619,50],{}," environment takes place.",[2062,2622,2624],{"id":2623},"cd-to-review-environments","CD to review environments",[12,2626,2627,2628,2311,2630,2632],{},"Deploying to review environment is not too different than deploying to ",[106,2629,57],{},[106,2631,70],{},". So we will once again leverage GitLab's capacity to overwrite only pieces of job definition here.",[12,2634,2635],{},"First, we set rules to run these jobs only on feature branches.",[12,2637,2638,2639,2642,2643,2646,2647,2650,2651,2654],{},"Then, we link the ",[106,2640,2641],{},"deploy_review"," job to ",[106,2644,2645],{},"destroy_review",". This will allow us to stop the environment ",[48,2648,2649],{},"manually"," from the GitLab user interface, but more importantly, it will ",[48,2652,2653],{},"automatically trigger the environment destruction"," when the feature branch is closed. This is a good FinOps practice to help you control your operational expenditures.",[12,2656,2657,2658,2660,2661,2664],{},"Since Terraform needs a plan file to destroy an infrastructure, exactly like it needs one to build an infrastructure, then we are adding a dependency from ",[106,2659,2645],{}," to ",[106,2662,2663],{},"build_review",", to retrieve its artifacts.",[12,2666,2667,2668,2671,2672,2660,2674,2677,2678,254],{},"Finally, we see here that the environment's name is set to ",[106,2669,2670],{},"$environment",". It has been set in the ",[16,2673,2048],{"href":2047},[106,2675,2676],{},"review/$CI_COMMIT_REF_SLUG",", and forwarded to this child pipeline with the instruction ",[106,2679,2680],{},"trigger:forward:yaml_variables:true",[99,2682,2684],{"className":1410,"code":2683,"language":1412,"meta":104,"style":104},"build_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",[106,2685,2686,2692,2700,2706,2716,2724,2735,2744,2748,2754,2763,2769,2776,2782,2790,2798,2807,2812,2818,2828,2836,2846,2854,2858,2864,2872,2878,2884,2890,2898,2906,2912,2924,2932,2946,2954],{"__ignoreMap":104},[109,2687,2688,2690],{"class":111,"line":112},[109,2689,2663],{"class":1419},[109,2691,1423],{"class":115},[109,2693,2694,2696,2698],{"class":111,"line":119},[109,2695,1564],{"class":1419},[109,2697,1457],{"class":115},[109,2699,2533],{"class":515},[109,2701,2702,2704],{"class":111,"line":125},[109,2703,1623],{"class":1419},[109,2705,1423],{"class":115},[109,2707,2708,2710,2712,2714],{"class":111,"line":132},[109,2709,1630],{"class":115},[109,2711,1633],{"class":1419},[109,2713,1457],{"class":115},[109,2715,1774],{"class":515},[109,2717,2718,2720,2722],{"class":111,"line":138},[109,2719,2581],{"class":1419},[109,2721,1457],{"class":115},[109,2723,2586],{"class":515},[109,2725,2726,2728,2730,2732],{"class":111,"line":144},[109,2727,1630],{"class":115},[109,2729,1633],{"class":1419},[109,2731,1457],{"class":115},[109,2733,2734],{"class":515},"$CI_COMMIT_BRANCH != $CI_DEFAULT_BRANCH\n",[109,2736,2737,2739,2741],{"class":111,"line":150},[109,2738,2581],{"class":1419},[109,2740,1457],{"class":115},[109,2742,2743],{"class":515},"on_success\n",[109,2745,2746],{"class":111,"line":155},[109,2747,129],{"emptyLinePlaceholder":128},[109,2749,2750,2752],{"class":111,"line":161},[109,2751,2641],{"class":1419},[109,2753,1423],{"class":115},[109,2755,2756,2758,2760],{"class":111,"line":167},[109,2757,1564],{"class":1419},[109,2759,1457],{"class":115},[109,2761,2762],{"class":515},"deploy\n",[109,2764,2765,2767],{"class":111,"line":172},[109,2766,2524],{"class":1419},[109,2768,1423],{"class":115},[109,2770,2771,2773],{"class":111,"line":178},[109,2772,1630],{"class":115},[109,2774,2775],{"class":515},"build_review\n",[109,2777,2778,2780],{"class":111,"line":184},[109,2779,2364],{"class":1419},[109,2781,1423],{"class":115},[109,2783,2784,2786,2788],{"class":111,"line":189},[109,2785,2371],{"class":1419},[109,2787,1457],{"class":115},[109,2789,1680],{"class":515},[109,2791,2792,2794,2796],{"class":111,"line":195},[109,2793,2381],{"class":1419},[109,2795,1457],{"class":115},[109,2797,2448],{"class":515},[109,2799,2800,2802,2804],{"class":111,"line":201},[109,2801,2453],{"class":1419},[109,2803,1457],{"class":115},[109,2805,2806],{"class":515},"destroy_review\n",[109,2808,2809],{"class":111,"line":498},[109,2810,2811],{"class":1516},"    # url: https://$CI_ENVIRONMENT_SLUG.example.com\n",[109,2813,2814,2816],{"class":111,"line":507},[109,2815,1623],{"class":1419},[109,2817,1423],{"class":115},[109,2819,2820,2822,2824,2826],{"class":111,"line":519},[109,2821,1630],{"class":115},[109,2823,1633],{"class":1419},[109,2825,1457],{"class":115},[109,2827,1774],{"class":515},[109,2829,2830,2832,2834],{"class":111,"line":524},[109,2831,2581],{"class":1419},[109,2833,1457],{"class":115},[109,2835,2586],{"class":515},[109,2837,2838,2840,2842,2844],{"class":111,"line":529},[109,2839,1630],{"class":115},[109,2841,1633],{"class":1419},[109,2843,1457],{"class":115},[109,2845,2734],{"class":515},[109,2847,2848,2850,2852],{"class":111,"line":541},[109,2849,2581],{"class":1419},[109,2851,1457],{"class":115},[109,2853,2743],{"class":515},[109,2855,2856],{"class":111,"line":558},[109,2857,129],{"emptyLinePlaceholder":128},[109,2859,2860,2862],{"class":111,"line":572},[109,2861,2645],{"class":1419},[109,2863,1423],{"class":115},[109,2865,2866,2868,2870],{"class":111,"line":577},[109,2867,1564],{"class":1419},[109,2869,1457],{"class":115},[109,2871,2458],{"class":515},[109,2873,2874,2876],{"class":111,"line":586},[109,2875,2524],{"class":1419},[109,2877,1423],{"class":115},[109,2879,2880,2882],{"class":111,"line":596},[109,2881,1630],{"class":115},[109,2883,2775],{"class":515},[109,2885,2886,2888],{"class":111,"line":601},[109,2887,2364],{"class":1419},[109,2889,1423],{"class":115},[109,2891,2892,2894,2896],{"class":111,"line":606},[109,2893,2371],{"class":1419},[109,2895,1457],{"class":115},[109,2897,1680],{"class":515},[109,2899,2900,2902,2904],{"class":111,"line":611},[109,2901,2381],{"class":1419},[109,2903,1457],{"class":115},[109,2905,2556],{"class":515},[109,2907,2908,2910],{"class":111,"line":624},[109,2909,1623],{"class":1419},[109,2911,1423],{"class":115},[109,2913,2914,2916,2918,2920,2922],{"class":111,"line":640},[109,2915,1630],{"class":115},[109,2917,1633],{"class":1419},[109,2919,1457],{"class":115},[109,2921,2573],{"class":515},[109,2923,2576],{"class":1516},[109,2925,2926,2928,2930],{"class":111,"line":655},[109,2927,2581],{"class":1419},[109,2929,1457],{"class":115},[109,2931,2586],{"class":515},[109,2933,2934,2936,2938,2940,2943],{"class":111,"line":660},[109,2935,1630],{"class":115},[109,2937,1633],{"class":1419},[109,2939,1457],{"class":115},[109,2941,2942],{"class":515},"$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH",[109,2944,2945],{"class":1516},"   # Do not destroy staging\n",[109,2947,2948,2950,2952],{"class":111,"line":680},[109,2949,2581],{"class":1419},[109,2951,1457],{"class":115},[109,2953,2586],{"class":515},[109,2955,2956,2958,2960,2962],{"class":111,"line":685},[109,2957,1630],{"class":115},[109,2959,2208],{"class":1419},[109,2961,1457],{"class":115},[109,2963,2609],{"class":515},[12,2965,2966],{},"So, to recap, we now have a pipeline that can:",[41,2968,2969,2972,2980],{},[44,2970,2971],{},"Deploy temporary review environments, which are automatically cleaned up when the feature branch is closed",[44,2973,2974,2975,2660,2978],{},"Continuously deploy the ",[48,2976,2977],{},"default branch",[106,2979,57],{},[44,2981,2974,2982,2660,2985],{},[48,2983,2984],{},"tags",[106,2986,70],{},[12,2988,2989,2990,2311,2992,2994],{},"Let's now add an extra layer, where we will deploy, based on a manual trigger this time, to ",[106,2991,1248],{},[106,2993,76],{}," environments.",[2062,2996,2998],{"id":2997},"continously-deploy-to-qa-and-production","Continously deploy to QA and production",[12,3000,3001,3002,3005],{},"Because not everybody is willing to deploy continuously to production, we will add a manual validation to the next two deployments. From a purely ",[48,3003,3004],{},"CD"," perspective, we would not add this trigger, but take this as an opportunity to learn how to run jobs from other triggers.",[12,3007,3008,3009,3012,3013,3015],{},"So far, we have started a ",[16,3010,3011],{"href":1895},"child pipeline"," from the ",[16,3014,2048],{"href":2047}," to run all deployments.",[12,3017,3018,3019,3021],{},"Since we want to run other deployments from the default branch and the tags, we will add another layer dedicated to these additional steps. Nothing new here. We will just repeat exactly the same process as the one we only did for the ",[16,3020,2048],{"href":2047},". Going this way allows you to manipulate as many layers as you need. I have already seen up to nine environments in some places.",[12,3023,3024],{},"Without arguing once again on the benefits to have fewer environments, the process that we are using here makes it very easy to implement the same pipeline all the way from early stages to final delivery, while keeping your pipeline definition simple and split in small chunks that you can maintain at no cost.",[12,3026,3027],{},"To prevent variable conflicts here, we are just using new var names to identify the Terraform state and input file.",[99,3029,3031],{"className":1410,"code":3030,"language":1412,"meta":104,"style":104},".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",[106,3032,3033,3040,3049,3055,3063,3069,3078,3083,3089,3099,3109,3113,3119,3128,3134,3144,3153,3163,3169,3179,3183,3189,3197,3203,3212,3220,3228,3234],{"__ignoreMap":104},[109,3034,3035,3038],{"class":111,"line":112},[109,3036,3037],{"class":1419},".2nd_layer",[109,3039,1423],{"class":115},[109,3041,3042,3044,3046],{"class":111,"line":119},[109,3043,1454],{"class":1419},[109,3045,1457],{"class":115},[109,3047,3048],{"class":515},"2nd_layer\n",[109,3050,3051,3053],{"class":111,"line":125},[109,3052,1464],{"class":1419},[109,3054,1423],{"class":115},[109,3056,3057,3059,3061],{"class":111,"line":132},[109,3058,1471],{"class":1419},[109,3060,1457],{"class":115},[109,3062,1476],{"class":515},[109,3064,3065,3067],{"class":111,"line":138},[109,3066,1491],{"class":1419},[109,3068,1423],{"class":115},[109,3070,3071,3073,3075],{"class":111,"line":144},[109,3072,1498],{"class":1419},[109,3074,1457],{"class":115},[109,3076,3077],{"class":515},".gitlab-ci/.second-layer.gitlab-ci.yml\n",[109,3079,3080],{"class":111,"line":150},[109,3081,3082],{"class":1516},"    # 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",[109,3084,3085,3087],{"class":111,"line":155},[109,3086,1522],{"class":1419},[109,3088,1423],{"class":115},[109,3090,3091,3093,3095,3097],{"class":111,"line":161},[109,3092,1529],{"class":1419},[109,3094,1457],{"class":115},[109,3096,1534],{"class":351},[109,3098,1537],{"class":1516},[109,3100,3101,3103,3105,3107],{"class":111,"line":167},[109,3102,1542],{"class":1419},[109,3104,1457],{"class":115},[109,3106,1534],{"class":351},[109,3108,1549],{"class":1516},[109,3110,3111],{"class":111,"line":172},[109,3112,129],{"emptyLinePlaceholder":128},[109,3114,3115,3117],{"class":111,"line":178},[109,3116,1248],{"class":1419},[109,3118,1423],{"class":115},[109,3120,3121,3123,3125],{"class":111,"line":184},[109,3122,1564],{"class":1419},[109,3124,1457],{"class":115},[109,3126,3127],{"class":515},".2nd_layer\n",[109,3129,3130,3132],{"class":111,"line":189},[109,3131,1464],{"class":1419},[109,3133,1423],{"class":115},[109,3135,3136,3139,3141],{"class":111,"line":195},[109,3137,3138],{"class":1419},"    TF_STATE_NAME_2",[109,3140,1457],{"class":115},[109,3142,3143],{"class":515},"qa\n",[109,3145,3146,3148,3150],{"class":111,"line":201},[109,3147,1580],{"class":1419},[109,3149,1457],{"class":115},[109,3151,3152],{"class":515},"$TF_STATE_NAME_2\n",[109,3154,3155,3158,3160],{"class":111,"line":498},[109,3156,3157],{"class":1419},"    TF_CLI_ARGS_plan_2",[109,3159,1457],{"class":115},[109,3161,3162],{"class":515},"\"-var-file=../vars/$TF_STATE_NAME_2.tfvars\"\n",[109,3164,3165,3167],{"class":111,"line":507},[109,3166,1623],{"class":1419},[109,3168,1423],{"class":115},[109,3170,3171,3173,3175,3177],{"class":111,"line":519},[109,3172,1630],{"class":115},[109,3174,1633],{"class":1419},[109,3176,1457],{"class":115},[109,3178,1705],{"class":515},[109,3180,3181],{"class":111,"line":524},[109,3182,129],{"emptyLinePlaceholder":128},[109,3184,3185,3187],{"class":111,"line":529},[109,3186,76],{"class":1419},[109,3188,1423],{"class":115},[109,3190,3191,3193,3195],{"class":111,"line":541},[109,3192,1564],{"class":1419},[109,3194,1457],{"class":115},[109,3196,3127],{"class":515},[109,3198,3199,3201],{"class":111,"line":558},[109,3200,1464],{"class":1419},[109,3202,1423],{"class":115},[109,3204,3205,3207,3209],{"class":111,"line":572},[109,3206,3138],{"class":1419},[109,3208,1457],{"class":115},[109,3210,3211],{"class":515},"production\n",[109,3213,3214,3216,3218],{"class":111,"line":577},[109,3215,1580],{"class":1419},[109,3217,1457],{"class":115},[109,3219,3152],{"class":515},[109,3221,3222,3224,3226],{"class":111,"line":586},[109,3223,3157],{"class":1419},[109,3225,1457],{"class":115},[109,3227,3162],{"class":515},[109,3229,3230,3232],{"class":111,"line":596},[109,3231,1623],{"class":1419},[109,3233,1423],{"class":115},[109,3235,3236,3238,3240,3242],{"class":111,"line":601},[109,3237,1630],{"class":115},[109,3239,1633],{"class":1419},[109,3241,1457],{"class":115},[109,3243,1774],{"class":515},[12,3245,3246,3249,3250,3253,3254,3256,3257,3261],{},[48,3247,3248],{},"One important trick here is the strategy used for the new downstream pipeline."," We leave that ",[106,3251,3252],{},"trigger:strategy"," to its default value; otherwise, the ",[16,3255,2048],{"href":2047}," would wait for your ",[16,3258,3260],{"href":3259},"#the-grand-child-pipeline","grand-child pipeline"," to finish. With a manual trigger, this could last for a very long time and make your pipeline dashboard harder to read and understand.",[12,3263,3264,3265,3268],{},"You have probably already wondered what is the content of that ",[106,3266,3267],{},".gitlab-ci/.second-layer.gitlab-ci.yml"," file we are including here.  We will cover that in the next section.",[2062,3270,3272],{"id":3271},"the-first-layer-complete-pipeline-definition","The first layer complete pipeline definition",[12,3274,3275,3276,3278],{},"If you are looking for a complete view of this first layer (stored in ",[106,3277,1900],{},"), just expand the section below.",[99,3280,3282],{"className":1410,"code":3281,"language":1412,"meta":104,"style":104},"variables:\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:\n    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",[106,3283,3284,3291,3301,3311,3321,3325,3331,3341,3345,3351,3359,3369,3375,3381,3387,3393,3397,3404,3411,3417,3423,3429,3436,3446,3450,3456,3462,3472,3476,3482,3488,3498,3502,3508,3514,3525,3533,3544,3552,3562,3566,3572,3578,3588,3596,3606,3614,3624,3628,3633,3638,3643,3648,3654,3660,3665,3674,3681,3690,3699,3706,3717,3728,3733,3742,3749,3758,3767,3776,3783,3794,3805,3810,3817,3826,3833,3842,3849,3856,3863,3872,3881,3888,3901,3910,3923,3932,3937,3942,3947,3953,3959,3965,3971,3976,3983,3992,3999,4010,4019,4030,4039,4044,4051,4060,4067,4074,4081,4090,4099,4108,4113,4120,4131,4140,4151,4160,4165,4172,4181,4188,4195,4202,4211,4220,4227,4240,4249,4262,4271,4282,4287,4292,4297,4303,4309,4315,4320,4327,4336,4343,4352,4359,4368,4373,4380,4391,4402,4407,4414,4423,4430,4439,4448,4457,4464,4475,4480,4487,4496,4503,4512,4521,4530,4537,4548],{"__ignoreMap":104},[109,3285,3286,3289],{"class":111,"line":112},[109,3287,3288],{"class":1419},"variables",[109,3290,1423],{"class":115},[109,3292,3293,3296,3298],{"class":111,"line":119},[109,3294,3295],{"class":1419},"  TF_VAR_aws_ami_id",[109,3297,1457],{"class":115},[109,3299,3300],{"class":515},"$AWS_AMI_ID\n",[109,3302,3303,3306,3308],{"class":111,"line":125},[109,3304,3305],{"class":1419},"  TF_VAR_aws_instance_type",[109,3307,1457],{"class":115},[109,3309,3310],{"class":515},"$AWS_INSTANCE_TYPE\n",[109,3312,3313,3316,3318],{"class":111,"line":132},[109,3314,3315],{"class":1419},"  TF_VAR_aws_default_region",[109,3317,1457],{"class":115},[109,3319,3320],{"class":515},"$AWS_DEFAULT_REGION\n",[109,3322,3323],{"class":111,"line":138},[109,3324,129],{"emptyLinePlaceholder":128},[109,3326,3327,3329],{"class":111,"line":144},[109,3328,1815],{"class":1419},[109,3330,1423],{"class":115},[109,3332,3333,3335,3337,3339],{"class":111,"line":150},[109,3334,1428],{"class":115},[109,3336,1825],{"class":1419},[109,3338,1457],{"class":115},[109,3340,2090],{"class":515},[109,3342,3343],{"class":111,"line":155},[109,3344,129],{"emptyLinePlaceholder":128},[109,3346,3347,3349],{"class":111,"line":161},[109,3348,2116],{"class":1419},[109,3350,1423],{"class":115},[109,3352,3353,3355,3357],{"class":111,"line":167},[109,3354,2123],{"class":1419},[109,3356,2126],{"class":115},[109,3358,2129],{"class":1516},[109,3360,3361,3363,3365,3367],{"class":111,"line":172},[109,3362,1630],{"class":115},[109,3364,2136],{"class":1419},[109,3366,1457],{"class":115},[109,3368,2141],{"class":515},[109,3370,3371,3373],{"class":111,"line":178},[109,3372,2146],{"class":1419},[109,3374,1423],{"class":115},[109,3376,3377,3379],{"class":111,"line":184},[109,3378,2153],{"class":115},[109,3380,2156],{"class":515},[109,3382,3383,3385],{"class":111,"line":189},[109,3384,2161],{"class":1419},[109,3386,1423],{"class":115},[109,3388,3389,3391],{"class":111,"line":195},[109,3390,2153],{"class":115},[109,3392,2170],{"class":351},[109,3394,3395],{"class":111,"line":201},[109,3396,129],{"emptyLinePlaceholder":128},[109,3398,3399,3402],{"class":111,"line":498},[109,3400,3401],{"class":1419},"stages",[109,3403,1423],{"class":115},[109,3405,3406,3408],{"class":111,"line":507},[109,3407,1428],{"class":115},[109,3409,3410],{"class":515},"validate\n",[109,3412,3413,3415],{"class":111,"line":519},[109,3414,1428],{"class":115},[109,3416,1431],{"class":515},[109,3418,3419,3421],{"class":111,"line":524},[109,3420,1428],{"class":115},[109,3422,2533],{"class":515},[109,3424,3425,3427],{"class":111,"line":529},[109,3426,1428],{"class":115},[109,3428,2762],{"class":515},[109,3430,3431,3433],{"class":111,"line":541},[109,3432,1428],{"class":115},[109,3434,3435],{"class":515},"cleanup\n",[109,3437,3438,3440,3443],{"class":111,"line":558},[109,3439,1428],{"class":115},[109,3441,3442],{"class":515},"2nd_layer",[109,3444,3445],{"class":1516},"       # Use to deploy a 2nd environment on both the main branch and on the tags\n",[109,3447,3448],{"class":111,"line":572},[109,3449,129],{"emptyLinePlaceholder":128},[109,3451,3452,3454],{"class":111,"line":577},[109,3453,2193],{"class":1419},[109,3455,1423],{"class":115},[109,3457,3458,3460],{"class":111,"line":586},[109,3459,1623],{"class":1419},[109,3461,1423],{"class":115},[109,3463,3464,3466,3468,3470],{"class":111,"line":596},[109,3465,1630],{"class":115},[109,3467,2208],{"class":1419},[109,3469,1457],{"class":115},[109,3471,2213],{"class":515},[109,3473,3474],{"class":111,"line":601},[109,3475,129],{"emptyLinePlaceholder":128},[109,3477,3478,3480],{"class":111,"line":606},[109,3479,2222],{"class":1419},[109,3481,1423],{"class":115},[109,3483,3484,3486],{"class":111,"line":611},[109,3485,1623],{"class":1419},[109,3487,1423],{"class":115},[109,3489,3490,3492,3494,3496],{"class":111,"line":624},[109,3491,1630],{"class":115},[109,3493,2208],{"class":1419},[109,3495,1457],{"class":115},[109,3497,2213],{"class":515},[109,3499,3500],{"class":111,"line":640},[109,3501,129],{"emptyLinePlaceholder":128},[109,3503,3504,3506],{"class":111,"line":655},[109,3505,2249],{"class":1419},[109,3507,1423],{"class":115},[109,3509,3510,3512],{"class":111,"line":660},[109,3511,1623],{"class":1419},[109,3513,1423],{"class":115},[109,3515,3516,3518,3520,3522],{"class":111,"line":680},[109,3517,1630],{"class":115},[109,3519,1633],{"class":1419},[109,3521,1457],{"class":115},[109,3523,3524],{"class":515},"$SAST_DISABLED == 'true' || $SAST_DISABLED == '1'\n",[109,3526,3527,3529,3531],{"class":111,"line":685},[109,3528,2581],{"class":1419},[109,3530,1457],{"class":115},[109,3532,2586],{"class":515},[109,3534,3535,3537,3539,3541],{"class":111,"line":694},[109,3536,1630],{"class":115},[109,3538,1633],{"class":1419},[109,3540,1457],{"class":115},[109,3542,3543],{"class":515},"$SAST_EXCLUDED_ANALYZERS =~ /kics/\n",[109,3545,3546,3548,3550],{"class":111,"line":703},[109,3547,2581],{"class":1419},[109,3549,1457],{"class":115},[109,3551,2586],{"class":515},[109,3553,3554,3556,3558,3560],{"class":111,"line":708},[109,3555,1630],{"class":115},[109,3557,2208],{"class":1419},[109,3559,1457],{"class":115},[109,3561,2743],{"class":515},[109,3563,3564],{"class":111,"line":1014},[109,3565,129],{"emptyLinePlaceholder":128},[109,3567,3568,3570],{"class":111,"line":1749},[109,3569,2276],{"class":1419},[109,3571,1423],{"class":115},[109,3573,3574,3576],{"class":111,"line":1758},[109,3575,1623],{"class":1419},[109,3577,1423],{"class":115},[109,3579,3580,3582,3584,3586],{"class":111,"line":1765},[109,3581,1630],{"class":115},[109,3583,1633],{"class":1419},[109,3585,1457],{"class":115},[109,3587,3524],{"class":515},[109,3589,3590,3592,3594],{"class":111,"line":1777},[109,3591,2581],{"class":1419},[109,3593,1457],{"class":115},[109,3595,2586],{"class":515},[109,3597,3598,3600,3602,3604],{"class":111,"line":1782},[109,3599,1630],{"class":115},[109,3601,1633],{"class":1419},[109,3603,1457],{"class":115},[109,3605,3543],{"class":515},[109,3607,3608,3610,3612],{"class":111,"line":1788},[109,3609,2581],{"class":1419},[109,3611,1457],{"class":115},[109,3613,2586],{"class":515},[109,3615,3616,3618,3620,3622],{"class":111,"line":1794},[109,3617,1630],{"class":115},[109,3619,2208],{"class":1419},[109,3621,1457],{"class":115},[109,3623,2743],{"class":515},[109,3625,3626],{"class":111,"line":1800},[109,3627,129],{"emptyLinePlaceholder":128},[109,3629,3630],{"class":111,"line":1806},[109,3631,3632],{"class":1516},"###########################################################################################################\n",[109,3634,3635],{"class":111,"line":1812},[109,3636,3637],{"class":1516},"## Integration env. and Staging. env\n",[109,3639,3640],{"class":111,"line":1820},[109,3641,3642],{"class":1516},"##  * Auto-deploy to Integration on merge to main.\n",[109,3644,3645],{"class":111,"line":1833},[109,3646,3647],{"class":1516},"##  * Auto-deploy to Staging on tag.\n",[109,3649,3651],{"class":111,"line":3650},53,[109,3652,3653],{"class":1516},"##  * Integration can be manually destroyed if TF_DESTROY is set to true.\n",[109,3655,3657],{"class":111,"line":3656},54,[109,3658,3659],{"class":1516},"##  * Destroy of next env. is not automated to prevent errors.\n",[109,3661,3663],{"class":111,"line":3662},55,[109,3664,3632],{"class":1516},[109,3666,3668,3670,3672],{"class":111,"line":3667},56,[109,3669,2310],{"class":1419},[109,3671,2126],{"class":115},[109,3673,2359],{"class":1516},[109,3675,3677,3679],{"class":111,"line":3676},57,[109,3678,2364],{"class":1419},[109,3680,1423],{"class":115},[109,3682,3684,3686,3688],{"class":111,"line":3683},58,[109,3685,2371],{"class":1419},[109,3687,1457],{"class":115},[109,3689,2376],{"class":515},[109,3691,3693,3695,3697],{"class":111,"line":3692},59,[109,3694,2381],{"class":1419},[109,3696,1457],{"class":115},[109,3698,2386],{"class":515},[109,3700,3702,3704],{"class":111,"line":3701},60,[109,3703,1623],{"class":1419},[109,3705,1423],{"class":115},[109,3707,3709,3711,3713,3715],{"class":111,"line":3708},61,[109,3710,1630],{"class":115},[109,3712,1633],{"class":1419},[109,3714,1457],{"class":115},[109,3716,1705],{"class":515},[109,3718,3720,3722,3724,3726],{"class":111,"line":3719},62,[109,3721,1630],{"class":115},[109,3723,1633],{"class":1419},[109,3725,1457],{"class":115},[109,3727,1774],{"class":515},[109,3729,3731],{"class":111,"line":3730},63,[109,3732,129],{"emptyLinePlaceholder":128},[109,3734,3736,3738,3740],{"class":111,"line":3735},64,[109,3737,2314],{"class":1419},[109,3739,1457],{"class":115},[109,3741,2425],{"class":1516},[109,3743,3745,3747],{"class":111,"line":3744},65,[109,3746,2364],{"class":1419},[109,3748,1423],{"class":115},[109,3750,3752,3754,3756],{"class":111,"line":3751},66,[109,3753,2371],{"class":1419},[109,3755,1457],{"class":115},[109,3757,2376],{"class":515},[109,3759,3761,3763,3765],{"class":111,"line":3760},67,[109,3762,2381],{"class":1419},[109,3764,1457],{"class":115},[109,3766,2448],{"class":515},[109,3768,3770,3772,3774],{"class":111,"line":3769},68,[109,3771,2453],{"class":1419},[109,3773,1457],{"class":115},[109,3775,2458],{"class":515},[109,3777,3779,3781],{"class":111,"line":3778},69,[109,3780,1623],{"class":1419},[109,3782,1423],{"class":115},[109,3784,3786,3788,3790,3792],{"class":111,"line":3785},70,[109,3787,1630],{"class":115},[109,3789,1633],{"class":1419},[109,3791,1457],{"class":115},[109,3793,1705],{"class":515},[109,3795,3797,3799,3801,3803],{"class":111,"line":3796},71,[109,3798,1630],{"class":115},[109,3800,1633],{"class":1419},[109,3802,1457],{"class":115},[109,3804,1774],{"class":515},[109,3806,3808],{"class":111,"line":3807},72,[109,3809,129],{"emptyLinePlaceholder":128},[109,3811,3813,3815],{"class":111,"line":3812},73,[109,3814,2318],{"class":1419},[109,3816,1423],{"class":115},[109,3818,3820,3822,3824],{"class":111,"line":3819},74,[109,3821,1564],{"class":1419},[109,3823,1457],{"class":115},[109,3825,2503],{"class":515},[109,3827,3829,3831],{"class":111,"line":3828},75,[109,3830,1464],{"class":1419},[109,3832,1423],{"class":115},[109,3834,3836,3838,3840],{"class":111,"line":3835},76,[109,3837,2514],{"class":1419},[109,3839,1457],{"class":115},[109,3841,2519],{"class":515},[109,3843,3845,3847],{"class":111,"line":3844},77,[109,3846,2524],{"class":1419},[109,3848,1423],{"class":115},[109,3850,3852,3854],{"class":111,"line":3851},78,[109,3853,1630],{"class":115},[109,3855,2533],{"class":515},[109,3857,3859,3861],{"class":111,"line":3858},79,[109,3860,2364],{"class":1419},[109,3862,1423],{"class":115},[109,3864,3866,3868,3870],{"class":111,"line":3865},80,[109,3867,2371],{"class":1419},[109,3869,1457],{"class":115},[109,3871,2376],{"class":515},[109,3873,3875,3877,3879],{"class":111,"line":3874},81,[109,3876,2381],{"class":1419},[109,3878,1457],{"class":115},[109,3880,2556],{"class":515},[109,3882,3884,3886],{"class":111,"line":3883},82,[109,3885,1623],{"class":1419},[109,3887,1423],{"class":115},[109,3889,3891,3893,3895,3897,3899],{"class":111,"line":3890},83,[109,3892,1630],{"class":115},[109,3894,1633],{"class":1419},[109,3896,1457],{"class":115},[109,3898,2573],{"class":515},[109,3900,2576],{"class":1516},[109,3902,3904,3906,3908],{"class":111,"line":3903},84,[109,3905,2581],{"class":1419},[109,3907,1457],{"class":115},[109,3909,2586],{"class":515},[109,3911,3913,3915,3917,3919,3921],{"class":111,"line":3912},85,[109,3914,1630],{"class":115},[109,3916,1633],{"class":1419},[109,3918,1457],{"class":115},[109,3920,2597],{"class":515},[109,3922,2600],{"class":1516},[109,3924,3926,3928,3930],{"class":111,"line":3925},86,[109,3927,2581],{"class":1419},[109,3929,1457],{"class":115},[109,3931,2609],{"class":515},[109,3933,3935],{"class":111,"line":3934},87,[109,3936,3632],{"class":1516},[109,3938,3940],{"class":111,"line":3939},88,[109,3941,129],{"emptyLinePlaceholder":128},[109,3943,3945],{"class":111,"line":3944},89,[109,3946,3632],{"class":1516},[109,3948,3950],{"class":111,"line":3949},90,[109,3951,3952],{"class":1516},"## Dev env.\n",[109,3954,3956],{"class":111,"line":3955},91,[109,3957,3958],{"class":1516},"##  * Temporary environment. Lives and dies with the Merge Request.\n",[109,3960,3962],{"class":111,"line":3961},92,[109,3963,3964],{"class":1516},"##  * Auto-deploy on push to feature branch.\n",[109,3966,3968],{"class":111,"line":3967},93,[109,3969,3970],{"class":1516},"##  * Auto-destroy on when Merge Request is closed.\n",[109,3972,3974],{"class":111,"line":3973},94,[109,3975,3632],{"class":1516},[109,3977,3979,3981],{"class":111,"line":3978},95,[109,3980,2663],{"class":1419},[109,3982,1423],{"class":115},[109,3984,3986,3988,3990],{"class":111,"line":3985},96,[109,3987,1564],{"class":1419},[109,3989,1457],{"class":115},[109,3991,2533],{"class":515},[109,3993,3995,3997],{"class":111,"line":3994},97,[109,3996,1623],{"class":1419},[109,3998,1423],{"class":115},[109,4000,4002,4004,4006,4008],{"class":111,"line":4001},98,[109,4003,1630],{"class":115},[109,4005,1633],{"class":1419},[109,4007,1457],{"class":115},[109,4009,1774],{"class":515},[109,4011,4013,4015,4017],{"class":111,"line":4012},99,[109,4014,2581],{"class":1419},[109,4016,1457],{"class":115},[109,4018,2586],{"class":515},[109,4020,4022,4024,4026,4028],{"class":111,"line":4021},100,[109,4023,1630],{"class":115},[109,4025,1633],{"class":1419},[109,4027,1457],{"class":115},[109,4029,2734],{"class":515},[109,4031,4033,4035,4037],{"class":111,"line":4032},101,[109,4034,2581],{"class":1419},[109,4036,1457],{"class":115},[109,4038,2743],{"class":515},[109,4040,4042],{"class":111,"line":4041},102,[109,4043,129],{"emptyLinePlaceholder":128},[109,4045,4047,4049],{"class":111,"line":4046},103,[109,4048,2641],{"class":1419},[109,4050,1423],{"class":115},[109,4052,4054,4056,4058],{"class":111,"line":4053},104,[109,4055,1564],{"class":1419},[109,4057,1457],{"class":115},[109,4059,2762],{"class":515},[109,4061,4063,4065],{"class":111,"line":4062},105,[109,4064,2524],{"class":1419},[109,4066,1423],{"class":115},[109,4068,4070,4072],{"class":111,"line":4069},106,[109,4071,1630],{"class":115},[109,4073,2775],{"class":515},[109,4075,4077,4079],{"class":111,"line":4076},107,[109,4078,2364],{"class":1419},[109,4080,1423],{"class":115},[109,4082,4084,4086,4088],{"class":111,"line":4083},108,[109,4085,2371],{"class":1419},[109,4087,1457],{"class":115},[109,4089,1680],{"class":515},[109,4091,4093,4095,4097],{"class":111,"line":4092},109,[109,4094,2381],{"class":1419},[109,4096,1457],{"class":115},[109,4098,2448],{"class":515},[109,4100,4102,4104,4106],{"class":111,"line":4101},110,[109,4103,2453],{"class":1419},[109,4105,1457],{"class":115},[109,4107,2806],{"class":515},[109,4109,4111],{"class":111,"line":4110},111,[109,4112,2811],{"class":1516},[109,4114,4116,4118],{"class":111,"line":4115},112,[109,4117,1623],{"class":1419},[109,4119,1423],{"class":115},[109,4121,4123,4125,4127,4129],{"class":111,"line":4122},113,[109,4124,1630],{"class":115},[109,4126,1633],{"class":1419},[109,4128,1457],{"class":115},[109,4130,1774],{"class":515},[109,4132,4134,4136,4138],{"class":111,"line":4133},114,[109,4135,2581],{"class":1419},[109,4137,1457],{"class":115},[109,4139,2586],{"class":515},[109,4141,4143,4145,4147,4149],{"class":111,"line":4142},115,[109,4144,1630],{"class":115},[109,4146,1633],{"class":1419},[109,4148,1457],{"class":115},[109,4150,2734],{"class":515},[109,4152,4154,4156,4158],{"class":111,"line":4153},116,[109,4155,2581],{"class":1419},[109,4157,1457],{"class":115},[109,4159,2743],{"class":515},[109,4161,4163],{"class":111,"line":4162},117,[109,4164,129],{"emptyLinePlaceholder":128},[109,4166,4168,4170],{"class":111,"line":4167},118,[109,4169,2645],{"class":1419},[109,4171,1423],{"class":115},[109,4173,4175,4177,4179],{"class":111,"line":4174},119,[109,4176,1564],{"class":1419},[109,4178,1457],{"class":115},[109,4180,2458],{"class":515},[109,4182,4184,4186],{"class":111,"line":4183},120,[109,4185,2524],{"class":1419},[109,4187,1423],{"class":115},[109,4189,4191,4193],{"class":111,"line":4190},121,[109,4192,1630],{"class":115},[109,4194,2775],{"class":515},[109,4196,4198,4200],{"class":111,"line":4197},122,[109,4199,2364],{"class":1419},[109,4201,1423],{"class":115},[109,4203,4205,4207,4209],{"class":111,"line":4204},123,[109,4206,2371],{"class":1419},[109,4208,1457],{"class":115},[109,4210,1680],{"class":515},[109,4212,4214,4216,4218],{"class":111,"line":4213},124,[109,4215,2381],{"class":1419},[109,4217,1457],{"class":115},[109,4219,2556],{"class":515},[109,4221,4223,4225],{"class":111,"line":4222},125,[109,4224,1623],{"class":1419},[109,4226,1423],{"class":115},[109,4228,4230,4232,4234,4236,4238],{"class":111,"line":4229},126,[109,4231,1630],{"class":115},[109,4233,1633],{"class":1419},[109,4235,1457],{"class":115},[109,4237,2573],{"class":515},[109,4239,2576],{"class":1516},[109,4241,4243,4245,4247],{"class":111,"line":4242},127,[109,4244,2581],{"class":1419},[109,4246,1457],{"class":115},[109,4248,2586],{"class":515},[109,4250,4252,4254,4256,4258,4260],{"class":111,"line":4251},128,[109,4253,1630],{"class":115},[109,4255,1633],{"class":1419},[109,4257,1457],{"class":115},[109,4259,2942],{"class":515},[109,4261,2945],{"class":1516},[109,4263,4265,4267,4269],{"class":111,"line":4264},129,[109,4266,2581],{"class":1419},[109,4268,1457],{"class":115},[109,4270,2586],{"class":515},[109,4272,4274,4276,4278,4280],{"class":111,"line":4273},130,[109,4275,1630],{"class":115},[109,4277,2208],{"class":1419},[109,4279,1457],{"class":115},[109,4281,2609],{"class":515},[109,4283,4285],{"class":111,"line":4284},131,[109,4286,3632],{"class":1516},[109,4288,4290],{"class":111,"line":4289},132,[109,4291,129],{"emptyLinePlaceholder":128},[109,4293,4295],{"class":111,"line":4294},133,[109,4296,3632],{"class":1516},[109,4298,4300],{"class":111,"line":4299},134,[109,4301,4302],{"class":1516},"## Second layer\n",[109,4304,4306],{"class":111,"line":4305},135,[109,4307,4308],{"class":1516},"##  * Deploys from main branch to qa env.\n",[109,4310,4312],{"class":111,"line":4311},136,[109,4313,4314],{"class":1516},"##  * Deploys from tag to production.\n",[109,4316,4318],{"class":111,"line":4317},137,[109,4319,3632],{"class":1516},[109,4321,4323,4325],{"class":111,"line":4322},138,[109,4324,3037],{"class":1419},[109,4326,1423],{"class":115},[109,4328,4330,4332,4334],{"class":111,"line":4329},139,[109,4331,1454],{"class":1419},[109,4333,1457],{"class":115},[109,4335,3048],{"class":515},[109,4337,4339,4341],{"class":111,"line":4338},140,[109,4340,1464],{"class":1419},[109,4342,1423],{"class":115},[109,4344,4346,4348,4350],{"class":111,"line":4345},141,[109,4347,1471],{"class":1419},[109,4349,1457],{"class":115},[109,4351,1476],{"class":515},[109,4353,4355,4357],{"class":111,"line":4354},142,[109,4356,1491],{"class":1419},[109,4358,1423],{"class":115},[109,4360,4362,4364,4366],{"class":111,"line":4361},143,[109,4363,1498],{"class":1419},[109,4365,1457],{"class":115},[109,4367,3077],{"class":515},[109,4369,4371],{"class":111,"line":4370},144,[109,4372,3082],{"class":1516},[109,4374,4376,4378],{"class":111,"line":4375},145,[109,4377,1522],{"class":1419},[109,4379,1423],{"class":115},[109,4381,4383,4385,4387,4389],{"class":111,"line":4382},146,[109,4384,1529],{"class":1419},[109,4386,1457],{"class":115},[109,4388,1534],{"class":351},[109,4390,1537],{"class":1516},[109,4392,4394,4396,4398,4400],{"class":111,"line":4393},147,[109,4395,1542],{"class":1419},[109,4397,1457],{"class":115},[109,4399,1534],{"class":351},[109,4401,1549],{"class":1516},[109,4403,4405],{"class":111,"line":4404},148,[109,4406,129],{"emptyLinePlaceholder":128},[109,4408,4410,4412],{"class":111,"line":4409},149,[109,4411,1248],{"class":1419},[109,4413,1423],{"class":115},[109,4415,4417,4419,4421],{"class":111,"line":4416},150,[109,4418,1564],{"class":1419},[109,4420,1457],{"class":115},[109,4422,3127],{"class":515},[109,4424,4426,4428],{"class":111,"line":4425},151,[109,4427,1464],{"class":1419},[109,4429,1423],{"class":115},[109,4431,4433,4435,4437],{"class":111,"line":4432},152,[109,4434,3138],{"class":1419},[109,4436,1457],{"class":115},[109,4438,3143],{"class":515},[109,4440,4442,4444,4446],{"class":111,"line":4441},153,[109,4443,1580],{"class":1419},[109,4445,1457],{"class":115},[109,4447,3152],{"class":515},[109,4449,4451,4453,4455],{"class":111,"line":4450},154,[109,4452,3157],{"class":1419},[109,4454,1457],{"class":115},[109,4456,3162],{"class":515},[109,4458,4460,4462],{"class":111,"line":4459},155,[109,4461,1623],{"class":1419},[109,4463,1423],{"class":115},[109,4465,4467,4469,4471,4473],{"class":111,"line":4466},156,[109,4468,1630],{"class":115},[109,4470,1633],{"class":1419},[109,4472,1457],{"class":115},[109,4474,1705],{"class":515},[109,4476,4478],{"class":111,"line":4477},157,[109,4479,129],{"emptyLinePlaceholder":128},[109,4481,4483,4485],{"class":111,"line":4482},158,[109,4484,76],{"class":1419},[109,4486,1423],{"class":115},[109,4488,4490,4492,4494],{"class":111,"line":4489},159,[109,4491,1564],{"class":1419},[109,4493,1457],{"class":115},[109,4495,3127],{"class":515},[109,4497,4499,4501],{"class":111,"line":4498},160,[109,4500,1464],{"class":1419},[109,4502,1423],{"class":115},[109,4504,4506,4508,4510],{"class":111,"line":4505},161,[109,4507,3138],{"class":1419},[109,4509,1457],{"class":115},[109,4511,3211],{"class":515},[109,4513,4515,4517,4519],{"class":111,"line":4514},162,[109,4516,1580],{"class":1419},[109,4518,1457],{"class":115},[109,4520,3152],{"class":515},[109,4522,4524,4526,4528],{"class":111,"line":4523},163,[109,4525,3157],{"class":1419},[109,4527,1457],{"class":115},[109,4529,3162],{"class":515},[109,4531,4533,4535],{"class":111,"line":4532},164,[109,4534,1623],{"class":1419},[109,4536,1423],{"class":115},[109,4538,4540,4542,4544,4546],{"class":111,"line":4539},165,[109,4541,1630],{"class":115},[109,4543,1633],{"class":1419},[109,4545,1457],{"class":115},[109,4547,1774],{"class":515},[109,4549,4551],{"class":111,"line":4550},166,[109,4552,3632],{"class":1516},[12,4554,4555],{},"At this stage, we are already deploying safely to three environments. That is my personal ideal recommendation. However, if you need more environments, add them to your CD pipeline.",[12,4557,4558,4559,4562,4563,4565,4566,4568],{},"You have certainly already noted that we include a downstream pipeline with the keyword ",[106,4560,4561],{},"trigger:include",". This includes the file ",[106,4564,3267],{},". We want to run almost the same pipeline so obviously, its content is very similar to the one we have detailed above. The main advantage here to define this ",[16,4567,3260],{"href":3259}," is that it lives on its own, making both variables and rules way easier to define.",[303,4570,4572],{"id":4571},"the-grand-child-pipeline","The grand-child pipeline",[12,4574,4575],{},"This second layer pipeline is a brand new pipeline. Hence, it needs to mimic the first layer definition with:",[41,4577,4578,4584,4591],{},[44,4579,4580,254],{},[16,4581,4583],{"href":4582},"#run-terraform-commands-and-secure-the-code","Inclusion of the Terraform template",[44,4585,4586,4590],{},[16,4587,4589],{"href":4588},"#run-controls-on-all-branches","Enforcement of security checks",". Terraform validation would be duplicates of the first layer, but security scanners may find threats that did not yet exist when scanners previously ran (for example, if you deploy to production a couple of days after your deployment to staging).",[44,4592,4593,4597,4598,4600],{},[16,4594,4596],{"href":4595},"#cd-to-review-environments","Overwrite build and deploy jobs to set specific rules",". Note that the ",[106,4599,2318],{}," stage is no longer automated to prevent too fast deletions.",[12,4602,4603,4604,2311,4607,4610,4611,4613,4614,4616,4617,4619,4620,4622,4623,4626,4627,4630],{},"As explained above, the ",[106,4605,4606],{},"TF_STATE_NAME",[106,4608,4609],{},"TF_CLI_ARGS_plan"," have been provided from the ",[16,4612,2048],{"href":2047}," to the ",[16,4615,3011],{"href":1895},". We needed another variable name to pass these values from the ",[16,4618,3011],{"href":1895}," to here, the ",[16,4621,3260],{"href":3259},". This is why they are postfixed with ",[106,4624,4625],{},"_2"," in the child pipeline and the value is copied back to the appropriate variable during the ",[106,4628,4629],{},"before_script"," here.",[12,4632,4633,4634,4636],{},"Since we have already broken down each step above, we can zoom out here directly to the broad view of the global second layer definition (stored in ",[106,4635,3267],{},").",[99,4638,4640],{"className":1410,"code":4639,"language":1412,"meta":104,"style":104},"# 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:\n    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",[106,4641,4642,4647,4651,4657,4666,4670,4676,4682,4688,4694,4700,4704,4710,4716,4726,4730,4736,4742,4752,4756,4762,4768,4778,4786,4796,4804,4814,4818,4822,4827,4832,4837,4842,4846,4854,4862,4872,4878,4884,4890,4896,4902,4910,4918,4928,4935,4942,4948,4958,4962,4971,4979,4989,4995,5001,5007,5013,5019,5027,5035,5043,5049,5055,5061,5071,5082,5092,5100],{"__ignoreMap":104},[109,4643,4644],{"class":111,"line":112},[109,4645,4646],{"class":1516},"# Use to deploy a second environment on both the default branch and the tags.\n",[109,4648,4649],{"class":111,"line":119},[109,4650,129],{"emptyLinePlaceholder":128},[109,4652,4653,4655],{"class":111,"line":125},[109,4654,1815],{"class":1419},[109,4656,1423],{"class":115},[109,4658,4659,4662,4664],{"class":111,"line":132},[109,4660,4661],{"class":1419},"  template",[109,4663,1457],{"class":115},[109,4665,2090],{"class":515},[109,4667,4668],{"class":111,"line":138},[109,4669,129],{"emptyLinePlaceholder":128},[109,4671,4672,4674],{"class":111,"line":144},[109,4673,3401],{"class":1419},[109,4675,1423],{"class":115},[109,4677,4678,4680],{"class":111,"line":150},[109,4679,1428],{"class":115},[109,4681,3410],{"class":515},[109,4683,4684,4686],{"class":111,"line":155},[109,4685,1428],{"class":115},[109,4687,1431],{"class":515},[109,4689,4690,4692],{"class":111,"line":161},[109,4691,1428],{"class":115},[109,4693,2533],{"class":515},[109,4695,4696,4698],{"class":111,"line":167},[109,4697,1428],{"class":115},[109,4699,2762],{"class":515},[109,4701,4702],{"class":111,"line":172},[109,4703,129],{"emptyLinePlaceholder":128},[109,4705,4706,4708],{"class":111,"line":178},[109,4707,2193],{"class":1419},[109,4709,1423],{"class":115},[109,4711,4712,4714],{"class":111,"line":184},[109,4713,1623],{"class":1419},[109,4715,1423],{"class":115},[109,4717,4718,4720,4722,4724],{"class":111,"line":189},[109,4719,1630],{"class":115},[109,4721,2208],{"class":1419},[109,4723,1457],{"class":115},[109,4725,2586],{"class":515},[109,4727,4728],{"class":111,"line":195},[109,4729,129],{"emptyLinePlaceholder":128},[109,4731,4732,4734],{"class":111,"line":201},[109,4733,2222],{"class":1419},[109,4735,1423],{"class":115},[109,4737,4738,4740],{"class":111,"line":498},[109,4739,1623],{"class":1419},[109,4741,1423],{"class":115},[109,4743,4744,4746,4748,4750],{"class":111,"line":507},[109,4745,1630],{"class":115},[109,4747,2208],{"class":1419},[109,4749,1457],{"class":115},[109,4751,2586],{"class":515},[109,4753,4754],{"class":111,"line":519},[109,4755,129],{"emptyLinePlaceholder":128},[109,4757,4758,4760],{"class":111,"line":524},[109,4759,2249],{"class":1419},[109,4761,1423],{"class":115},[109,4763,4764,4766],{"class":111,"line":529},[109,4765,1623],{"class":1419},[109,4767,1423],{"class":115},[109,4769,4770,4772,4774,4776],{"class":111,"line":541},[109,4771,1630],{"class":115},[109,4773,1633],{"class":1419},[109,4775,1457],{"class":115},[109,4777,3524],{"class":515},[109,4779,4780,4782,4784],{"class":111,"line":558},[109,4781,2581],{"class":1419},[109,4783,1457],{"class":115},[109,4785,2586],{"class":515},[109,4787,4788,4790,4792,4794],{"class":111,"line":572},[109,4789,1630],{"class":115},[109,4791,1633],{"class":1419},[109,4793,1457],{"class":115},[109,4795,3543],{"class":515},[109,4797,4798,4800,4802],{"class":111,"line":577},[109,4799,2581],{"class":1419},[109,4801,1457],{"class":115},[109,4803,2586],{"class":515},[109,4805,4806,4808,4810,4812],{"class":111,"line":586},[109,4807,1630],{"class":115},[109,4809,2208],{"class":1419},[109,4811,1457],{"class":115},[109,4813,2213],{"class":515},[109,4815,4816],{"class":111,"line":596},[109,4817,129],{"emptyLinePlaceholder":128},[109,4819,4820],{"class":111,"line":601},[109,4821,3632],{"class":1516},[109,4823,4824],{"class":111,"line":606},[109,4825,4826],{"class":1516},"## QA env. and Prod. env\n",[109,4828,4829],{"class":111,"line":611},[109,4830,4831],{"class":1516},"##  * Manually trigger build and auto-deploy in QA\n",[109,4833,4834],{"class":111,"line":624},[109,4835,4836],{"class":1516},"##  * Manually trigger both build and deploy in Production\n",[109,4838,4839],{"class":111,"line":640},[109,4840,4841],{"class":1516},"##  * Destroy of these env. is not automated to prevent errors.\n",[109,4843,4844],{"class":111,"line":655},[109,4845,3632],{"class":1516},[109,4847,4848,4850,4852],{"class":111,"line":660},[109,4849,2310],{"class":1419},[109,4851,2126],{"class":115},[109,4853,2359],{"class":1516},[109,4855,4856,4858,4860],{"class":111,"line":680},[109,4857,2123],{"class":1419},[109,4859,2126],{"class":115},[109,4861,2129],{"class":1516},[109,4863,4864,4866,4868,4870],{"class":111,"line":685},[109,4865,1630],{"class":115},[109,4867,2136],{"class":1419},[109,4869,1457],{"class":115},[109,4871,3152],{"class":515},[109,4873,4874,4876],{"class":111,"line":694},[109,4875,2146],{"class":1419},[109,4877,1423],{"class":115},[109,4879,4880,4882],{"class":111,"line":703},[109,4881,2153],{"class":115},[109,4883,2156],{"class":515},[109,4885,4886,4888],{"class":111,"line":708},[109,4887,2161],{"class":1419},[109,4889,1423],{"class":115},[109,4891,4892,4894],{"class":111,"line":1014},[109,4893,2153],{"class":115},[109,4895,2170],{"class":351},[109,4897,4898,4900],{"class":111,"line":1749},[109,4899,2364],{"class":1419},[109,4901,1423],{"class":115},[109,4903,4904,4906,4908],{"class":111,"line":1758},[109,4905,2371],{"class":1419},[109,4907,1457],{"class":115},[109,4909,3152],{"class":515},[109,4911,4912,4914,4916],{"class":111,"line":1765},[109,4913,2381],{"class":1419},[109,4915,1457],{"class":115},[109,4917,2386],{"class":515},[109,4919,4920,4923,4925],{"class":111,"line":1777},[109,4921,4922],{"class":1419},"  before_script",[109,4924,2126],{"class":115},[109,4926,4927],{"class":1516},"# 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",[109,4929,4930,4932],{"class":111,"line":1782},[109,4931,1630],{"class":115},[109,4933,4934],{"class":515},"TF_STATE_NAME=$TF_STATE_NAME_2\n",[109,4936,4937,4939],{"class":111,"line":1788},[109,4938,1630],{"class":115},[109,4940,4941],{"class":515},"TF_CLI_ARGS_plan=$TF_CLI_ARGS_plan_2\n",[109,4943,4944,4946],{"class":111,"line":1794},[109,4945,1623],{"class":1419},[109,4947,1423],{"class":115},[109,4949,4950,4952,4954,4956],{"class":111,"line":1800},[109,4951,1630],{"class":115},[109,4953,2208],{"class":1419},[109,4955,1457],{"class":115},[109,4957,2609],{"class":515},[109,4959,4960],{"class":111,"line":1806},[109,4961,129],{"emptyLinePlaceholder":128},[109,4963,4964,4966,4968],{"class":111,"line":1812},[109,4965,2314],{"class":1419},[109,4967,1457],{"class":115},[109,4969,4970],{"class":1516},"# terraform apply\n",[109,4972,4973,4975,4977],{"class":111,"line":1820},[109,4974,2123],{"class":1419},[109,4976,2126],{"class":115},[109,4978,2129],{"class":1516},[109,4980,4981,4983,4985,4987],{"class":111,"line":1833},[109,4982,1630],{"class":115},[109,4984,2136],{"class":1419},[109,4986,1457],{"class":115},[109,4988,3152],{"class":515},[109,4990,4991,4993],{"class":111,"line":3650},[109,4992,2146],{"class":1419},[109,4994,1423],{"class":115},[109,4996,4997,4999],{"class":111,"line":3656},[109,4998,2153],{"class":115},[109,5000,2156],{"class":515},[109,5002,5003,5005],{"class":111,"line":3662},[109,5004,2161],{"class":1419},[109,5006,1423],{"class":115},[109,5008,5009,5011],{"class":111,"line":3667},[109,5010,2153],{"class":115},[109,5012,2170],{"class":351},[109,5014,5015,5017],{"class":111,"line":3676},[109,5016,2364],{"class":1419},[109,5018,1423],{"class":115},[109,5020,5021,5023,5025],{"class":111,"line":3683},[109,5022,2371],{"class":1419},[109,5024,1457],{"class":115},[109,5026,3152],{"class":515},[109,5028,5029,5031,5033],{"class":111,"line":3692},[109,5030,2381],{"class":1419},[109,5032,1457],{"class":115},[109,5034,2448],{"class":515},[109,5036,5037,5039,5041],{"class":111,"line":3701},[109,5038,4922],{"class":1419},[109,5040,2126],{"class":115},[109,5042,4927],{"class":1516},[109,5044,5045,5047],{"class":111,"line":3708},[109,5046,1630],{"class":115},[109,5048,4934],{"class":515},[109,5050,5051,5053],{"class":111,"line":3719},[109,5052,1630],{"class":115},[109,5054,4941],{"class":515},[109,5056,5057,5059],{"class":111,"line":3730},[109,5058,1623],{"class":1419},[109,5060,1423],{"class":115},[109,5062,5063,5065,5067,5069],{"class":111,"line":3735},[109,5064,1630],{"class":115},[109,5066,1633],{"class":1419},[109,5068,1457],{"class":115},[109,5070,1705],{"class":515},[109,5072,5073,5075,5077,5079],{"class":111,"line":3744},[109,5074,1630],{"class":115},[109,5076,1633],{"class":1419},[109,5078,1457],{"class":115},[109,5080,5081],{"class":515},"$CI_COMMIT_TAG && $TF_AUTO_DEPLOY == \"true\"\n",[109,5083,5084,5086,5088,5090],{"class":111,"line":3751},[109,5085,1630],{"class":115},[109,5087,1633],{"class":1419},[109,5089,1457],{"class":115},[109,5091,1774],{"class":515},[109,5093,5094,5096,5098],{"class":111,"line":3760},[109,5095,2581],{"class":1419},[109,5097,1457],{"class":115},[109,5099,2609],{"class":515},[109,5101,5102],{"class":111,"line":3769},[109,5103,3632],{"class":1516},[12,5105,5106,5107,5110,5111,5116],{},"Et voilà. ",[48,5108,5109],{},"We are ready to go."," Feel free to change the way you control your job executions, leveraging for example GitLab's capacity to ",[16,5112,5115],{"href":5113,"rel":5114},"https://docs.gitlab.com/ci/jobs/job_control/#run-a-job-after-a-delay",[],"delay a job"," before deploying to production.",[224,5118,5120],{"id":5119},"try-it-yourself","Try it yourself",[12,5122,5123,5124,5127,5128,5131,5132,1394,5135,254],{},"We finally reached our destination. We are now able to control ",[48,5125,5126],{},"deployments to five different environments",", with only the ",[48,5129,5130],{},"feature branches",", the ",[48,5133,5134],{},"main branch",[48,5136,2984],{},[41,5138,5139,5142,5145],{},[44,5140,5141],{},"We are intensively reusing GitLab open source templates to ensure efficiency and security in our pipelines.",[44,5143,5144],{},"We are leveraging GitLab template capacities to overwrite only the blocks that need custom control.",[44,5146,5147],{},"We have split the pipeline in small chunks, controlling the downstream pipelines to match exactly what we need.",[12,5149,5150,5151,5156,5157,5161],{},"From there, the floor is yours. You could, for example, easily update the main pipeline to trigger downstream pipelines for your software source code, with the ",[16,5152,5155],{"href":5153,"rel":5154},"https://docs.gitlab.com/ci/yaml/#ruleschanges",[],"trigger:rules:changes"," keyword. And use another ",[16,5158,1825],{"href":5159,"rel":5160},"https://gitlab.com/gitlab-org/gitlab/-/tree/master/lib/gitlab/ci/templates/",[]," depending on the changes that happened. But that is another story.",[5163,5164,5165],"style",{},"html pre.shiki code .sgsFI, html code.shiki .sgsFI{--shiki-default:#24292E}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .s7eDp, html code.shiki .s7eDp{--shiki-default:#6F42C1}html pre.shiki code .sYu0t, html code.shiki .sYu0t{--shiki-default:#005CC5}html pre.shiki code .sD7c4, html code.shiki .sD7c4{--shiki-default:#D73A49}html pre.shiki code .sYBdl, html code.shiki .sYBdl{--shiki-default:#032F62}html pre.shiki code .shJU0, html code.shiki .shJU0{--shiki-default:#22863A}html pre.shiki code .sAwPA, html code.shiki .sAwPA{--shiki-default:#6A737D}",{"title":104,"searchDepth":119,"depth":119,"links":5167},[5168,5169,5170,5177],{"id":211,"depth":119,"text":226},{"id":216,"depth":119,"text":266},{"id":221,"depth":119,"text":294,"children":5171},[5172,5173,5174,5175,5176],{"id":305,"depth":125,"text":306},{"id":1092,"depth":125,"text":1093},{"id":1346,"depth":125,"text":1347},{"id":1906,"depth":125,"text":1907},{"id":4571,"depth":125,"text":4572},{"id":5119,"depth":119,"text":5120},"engineering","2024-09-26","Learn how to manage continuous deployment to multiple environments, including temporary, on-the-fly sandboxes, with a minimalist GitLab workflow.","md",null,false,"https://res.cloudinary.com/about-gitlab-com/image/upload/v1750097012/Blog/Hero%20Images/Blog/Hero%20Images/AdobeStock_397632156_3Ldy1urjMStQCl4qnOBvE0_1750097011626.jpg",{},"/en-us/blog/using-child-pipelines-to-continuously-deploy-to-five-environments",{"title":5,"description":5180,"ogTitle":5,"ogDescription":5180,"noIndex":5183,"ogImage":5184,"ogUrl":5188,"ogSiteName":5189,"ogType":5190,"canonicalUrls":5188},"https://about.gitlab.com/blog/using-child-pipelines-to-continuously-deploy-to-five-environments","https://about.gitlab.com","article","using-child-pipelines-to-continuously-deploy-to-five-environments","en-us/blog/using-child-pipelines-to-continuously-deploy-to-five-environments",[5194,5195,3004,5196,5197],"CI/CD","CI","DevSecOps platform","tutorial","BlogPost","zqK1p_Eg031Tc-bqyHWqHcbcUhVlP1D4eAIi_ztuEuw",{"logo":5201,"freeTrial":5206,"sales":5211,"login":5216,"items":5221,"search":5547,"minimal":5578,"duo":5597,"switchNav":5606,"pricingDeployment":5617},{"config":5202},{"href":5203,"dataGaName":5204,"dataGaLocation":5205},"/","gitlab logo","header",{"text":5207,"config":5208},"Get free trial",{"href":5209,"dataGaName":5210,"dataGaLocation":5205},"https://gitlab.com/-/trial_registrations/new?glm_source=about.gitlab.com&glm_content=default-saas-trial/","free trial",{"text":5212,"config":5213},"Request a demo",{"href":5214,"dataGaName":5215,"dataGaLocation":5205},"/sales/?contact-topic=request-demo","sales",{"text":5217,"config":5218},"Sign in",{"href":5219,"dataGaName":5220,"dataGaLocation":5205},"https://gitlab.com/users/sign_in/","sign in",[5222,5251,5350,5355,5469,5525],{"text":5223,"config":5224,"menu":5226},"Platform",{"dataNavLevelOne":5225},"platform",{"type":5227,"columns":5228},"cards",[5229,5235,5243],{"title":5223,"description":5230,"link":5231},"The intelligent orchestration platform for DevSecOps",{"text":5232,"config":5233},"Explore our Platform",{"href":5234,"dataGaName":5225,"dataGaLocation":5205},"/platform/",{"title":5236,"description":5237,"link":5238},"GitLab Duo Agent Platform","Agentic AI for the entire software lifecycle",{"text":5239,"config":5240},"Meet GitLab Duo",{"href":5241,"dataGaName":5242,"dataGaLocation":5205},"/gitlab-duo-agent-platform/","gitlab duo agent platform",{"title":5244,"description":5245,"link":5246},"Why GitLab","See the top reasons enterprises choose GitLab",{"text":5247,"config":5248},"Learn more",{"href":5249,"dataGaName":5250,"dataGaLocation":5205},"/why-gitlab/","why gitlab",{"text":5252,"left":128,"config":5253,"menu":5255},"Product",{"dataNavLevelOne":5254},"solutions",{"type":5256,"link":5257,"columns":5261,"feature":5329},"lists",{"text":5258,"config":5259},"View all Solutions",{"href":5260,"dataGaName":5254,"dataGaLocation":5205},"/solutions/",[5262,5285,5308],{"title":5263,"description":5264,"link":5265,"items":5270},"Automation","CI/CD and automation to accelerate deployment",{"config":5266},{"icon":5267,"href":5268,"dataGaName":5269,"dataGaLocation":5205},"AutomatedCodeAlt","/solutions/delivery-automation/","automated software delivery",[5271,5274,5277,5281],{"text":5194,"config":5272},{"href":5273,"dataGaLocation":5205,"dataGaName":5194},"/solutions/continuous-integration/",{"text":5236,"config":5275},{"href":5241,"dataGaLocation":5205,"dataGaName":5276},"gitlab duo agent platform - product menu",{"text":5278,"config":5279},"Source Code Management",{"href":5280,"dataGaLocation":5205,"dataGaName":5278},"/solutions/source-code-management/",{"text":5282,"config":5283},"Automated Software Delivery",{"href":5268,"dataGaLocation":5205,"dataGaName":5284},"Automated software delivery",{"title":5286,"description":5287,"link":5288,"items":5293},"Security","Deliver code faster without compromising security",{"config":5289},{"href":5290,"dataGaName":5291,"dataGaLocation":5205,"icon":5292},"/solutions/application-security-testing/","security and compliance","ShieldCheckLight",[5294,5298,5303],{"text":5295,"config":5296},"Application Security Testing",{"href":5290,"dataGaName":5297,"dataGaLocation":5205},"Application security testing",{"text":5299,"config":5300},"Software Supply Chain Security",{"href":5301,"dataGaLocation":5205,"dataGaName":5302},"/solutions/supply-chain/","Software supply chain security",{"text":5304,"config":5305},"Software Compliance",{"href":5306,"dataGaName":5307,"dataGaLocation":5205},"/solutions/software-compliance/","software compliance",{"title":5309,"link":5310,"items":5315},"Measurement",{"config":5311},{"icon":5312,"href":5313,"dataGaName":5314,"dataGaLocation":5205},"DigitalTransformation","/solutions/visibility-measurement/","visibility and measurement",[5316,5320,5324],{"text":5317,"config":5318},"Visibility & Measurement",{"href":5313,"dataGaLocation":5205,"dataGaName":5319},"Visibility and Measurement",{"text":5321,"config":5322},"Value Stream Management",{"href":5323,"dataGaLocation":5205,"dataGaName":5321},"/solutions/value-stream-management/",{"text":5325,"config":5326},"Analytics & Insights",{"href":5327,"dataGaLocation":5205,"dataGaName":5328},"/solutions/analytics-and-insights/","Analytics and insights",{"title":5330,"type":5256,"items":5331},"GitLab for",[5332,5338,5344],{"text":5333,"config":5334},"Enterprise",{"icon":5335,"href":5336,"dataGaLocation":5205,"dataGaName":5337},"Building","/enterprise/","enterprise",{"text":5339,"config":5340},"Small Business",{"icon":5341,"href":5342,"dataGaLocation":5205,"dataGaName":5343},"Work","/small-business/","small business",{"text":5345,"config":5346},"Public Sector",{"icon":5347,"href":5348,"dataGaLocation":5205,"dataGaName":5349},"Organization","/solutions/public-sector/","public sector",{"text":5351,"config":5352},"Pricing",{"href":5353,"dataGaName":5354,"dataGaLocation":5205,"dataNavLevelOne":5354},"/pricing/","pricing",{"text":5356,"config":5357,"menu":5359},"Resources",{"dataNavLevelOne":5358},"resources",{"type":5256,"link":5360,"columns":5364,"feature":5458},{"text":5361,"config":5362},"View all resources",{"href":5363,"dataGaName":5358,"dataGaLocation":5205},"/resources/",[5365,5398,5425],{"title":5366,"items":5367},"Getting started",[5368,5373,5378,5383,5388,5393],{"text":5369,"config":5370},"Install",{"href":5371,"dataGaName":5372,"dataGaLocation":5205},"/install/","install",{"text":5374,"config":5375},"Quick start guides",{"href":5376,"dataGaName":5377,"dataGaLocation":5205},"/get-started/","quick setup checklists",{"text":5379,"config":5380},"Learn",{"href":5381,"dataGaLocation":5205,"dataGaName":5382},"https://university.gitlab.com/","learn",{"text":5384,"config":5385},"Product documentation",{"href":5386,"dataGaName":5387,"dataGaLocation":5205},"https://docs.gitlab.com/","product documentation",{"text":5389,"config":5390},"Best practice videos",{"href":5391,"dataGaName":5392,"dataGaLocation":5205},"/getting-started-videos/","best practice videos",{"text":5394,"config":5395},"Integrations",{"href":5396,"dataGaName":5397,"dataGaLocation":5205},"/integrations/","integrations",{"title":5399,"items":5400},"Discover",[5401,5406,5411,5416,5420],{"text":5402,"config":5403},"Customer success stories",{"href":5404,"dataGaName":5405,"dataGaLocation":5205},"/customers/","customer success stories",{"text":5407,"config":5408},"Blog",{"href":5409,"dataGaName":5410,"dataGaLocation":5205},"/blog/","blog",{"text":5412,"config":5413},"Demo Hub",{"href":5414,"dataGaName":5415,"dataGaLocation":5205},"/demo-hub/","demo hub",{"text":5417,"config":5418},"The Source",{"href":5419,"dataGaName":5410,"dataGaLocation":5205},"/the-source/",{"text":5421,"config":5422},"Remote",{"href":5423,"dataGaName":5424,"dataGaLocation":5205},"https://handbook.gitlab.com/handbook/company/culture/all-remote/","remote",{"title":5426,"items":5427},"Connect",[5428,5433,5438,5443,5448,5453],{"text":5429,"config":5430},"GitLab Services",{"href":5431,"dataGaName":5432,"dataGaLocation":5205},"/services/","services",{"text":5434,"config":5435},"Contribute",{"href":5436,"dataGaName":5437,"dataGaLocation":5205},"https://contributors.gitlab.com","contribute",{"text":5439,"config":5440},"Community",{"href":5441,"dataGaName":5442,"dataGaLocation":5205},"/community/","community",{"text":5444,"config":5445},"Forum",{"href":5446,"dataGaName":5447,"dataGaLocation":5205},"https://forum.gitlab.com/","forum",{"text":5449,"config":5450},"Events",{"href":5451,"dataGaName":5452,"dataGaLocation":5205},"/events/","events",{"text":5454,"config":5455},"Partners",{"href":5456,"dataGaName":5457,"dataGaLocation":5205},"/partners/","partners",{"config":5459,"title":5462,"text":5463,"link":5464},{"background":5460,"textColor":5461},"url('https://res.cloudinary.com/about-gitlab-com/image/upload/v1777322348/qpq8yrgn8knii57omj0c.png')","#000","What’s new in GitLab","Stay updated with our latest features and improvements.",{"text":5465,"config":5466},"Read the latest",{"href":5467,"dataGaName":5468,"dataGaLocation":5205},"/whats-new/","whats new",{"text":5470,"config":5471,"menu":5473},"Company",{"dataNavLevelOne":5472},"company",{"type":5256,"columns":5474},[5475],{"items":5476},[5477,5482,5488,5490,5495,5500,5505,5510,5515,5520],{"text":5478,"config":5479},"About",{"href":5480,"dataGaName":5481,"dataGaLocation":5205},"/company/","about",{"text":5483,"config":5484,"footerGa":5487},"Jobs",{"href":5485,"dataGaName":5486,"dataGaLocation":5205},"/jobs/","jobs",{"dataGaName":5486},{"text":5449,"config":5489},{"href":5451,"dataGaName":5452,"dataGaLocation":5205},{"text":5491,"config":5492},"Leadership",{"href":5493,"dataGaName":5494,"dataGaLocation":5205},"/company/team/e-group/","leadership",{"text":5496,"config":5497},"Handbook",{"href":5498,"dataGaName":5499,"dataGaLocation":5205},"https://handbook.gitlab.com/","handbook",{"text":5501,"config":5502},"Investor relations",{"href":5503,"dataGaName":5504,"dataGaLocation":5205},"https://ir.gitlab.com/overview/default.aspx","investor relations",{"text":5506,"config":5507},"Trust Center",{"href":5508,"dataGaName":5509,"dataGaLocation":5205},"/security/","trust center",{"text":5511,"config":5512},"AI Transparency Center",{"href":5513,"dataGaName":5514,"dataGaLocation":5205},"/ai-transparency-center/","ai transparency center",{"text":5516,"config":5517},"Newsletter",{"href":5518,"dataGaName":5519,"dataGaLocation":5205},"/company/contact/#contact-forms","newsletter",{"text":5521,"config":5522},"Press",{"href":5523,"dataGaName":5524,"dataGaLocation":5205},"/press/","press",{"text":5526,"config":5527,"menu":5528},"Contact us",{"dataNavLevelOne":5472},{"type":5256,"columns":5529},[5530],{"items":5531},[5532,5537,5542],{"text":5533,"config":5534},"Talk to sales",{"href":5535,"dataGaName":5536,"dataGaLocation":5205},"/sales/","talk to sales",{"text":5538,"config":5539},"Support portal",{"href":5540,"dataGaName":5541,"dataGaLocation":5205},"https://support.gitlab.com/hc/en-us","support portal",{"text":5543,"config":5544},"Customer portal",{"href":5545,"dataGaName":5546,"dataGaLocation":5205},"https://customers.gitlab.com/customers/sign_in/","customer portal",{"close":5548,"login":5549,"suggestions":5556},"Close",{"text":5550,"link":5551},"To search repositories and projects, login to",{"text":5552,"config":5553},"gitlab.com",{"href":5219,"dataGaName":5554,"dataGaLocation":5555},"search login","search",{"text":5557,"default":5558},"Suggestions",[5559,5561,5565,5567,5571,5575],{"text":5236,"config":5560},{"href":5241,"dataGaName":5236,"dataGaLocation":5555},{"text":5562,"config":5563},"Code Suggestions (AI)",{"href":5564,"dataGaName":5562,"dataGaLocation":5555},"/solutions/code-suggestions/",{"text":5194,"config":5566},{"href":5273,"dataGaName":5194,"dataGaLocation":5555},{"text":5568,"config":5569},"GitLab on AWS",{"href":5570,"dataGaName":5568,"dataGaLocation":5555},"/partners/technology-partners/aws/",{"text":5572,"config":5573},"GitLab on Google Cloud",{"href":5574,"dataGaName":5572,"dataGaLocation":5555},"/partners/technology-partners/google-cloud-platform/",{"text":5576,"config":5577},"Why GitLab?",{"href":5249,"dataGaName":5576,"dataGaLocation":5555},{"freeTrial":5579,"mobileIcon":5584,"desktopIcon":5589,"secondaryButton":5592},{"text":5580,"config":5581},"Start free trial",{"href":5582,"dataGaName":5210,"dataGaLocation":5583},"https://gitlab.com/-/trials/new/","nav",{"altText":5585,"config":5586},"Gitlab Icon",{"src":5587,"dataGaName":5588,"dataGaLocation":5583},"https://res.cloudinary.com/about-gitlab-com/image/upload/v1758203874/jypbw1jx72aexsoohd7x.svg","gitlab icon",{"altText":5585,"config":5590},{"src":5591,"dataGaName":5588,"dataGaLocation":5583},"https://res.cloudinary.com/about-gitlab-com/image/upload/v1758203875/gs4c8p8opsgvflgkswz9.svg",{"text":5593,"config":5594},"Get Started",{"href":5595,"dataGaName":5596,"dataGaLocation":5583},"https://gitlab.com/-/trial_registrations/new?glm_source=about.gitlab.com/get-started/","get started",{"freeTrial":5598,"mobileIcon":5602,"desktopIcon":5604},{"text":5599,"config":5600},"Learn more about GitLab Duo",{"href":5241,"dataGaName":5601,"dataGaLocation":5583},"gitlab duo",{"altText":5585,"config":5603},{"src":5587,"dataGaName":5588,"dataGaLocation":5583},{"altText":5585,"config":5605},{"src":5591,"dataGaName":5588,"dataGaLocation":5583},{"button":5607,"mobileIcon":5612,"desktopIcon":5614},{"text":5608,"config":5609},"/switch",{"href":5610,"dataGaName":5611,"dataGaLocation":5583},"#contact","switch",{"altText":5585,"config":5613},{"src":5587,"dataGaName":5588,"dataGaLocation":5583},{"altText":5585,"config":5615},{"src":5616,"dataGaName":5588,"dataGaLocation":5583},"https://res.cloudinary.com/about-gitlab-com/image/upload/v1773335277/ohhpiuoxoldryzrnhfrh.png",{"freeTrial":5618,"mobileIcon":5623,"desktopIcon":5625},{"text":5619,"config":5620},"Back to pricing",{"href":5353,"dataGaName":5621,"dataGaLocation":5583,"icon":5622},"back to pricing","GoBack",{"altText":5585,"config":5624},{"src":5587,"dataGaName":5588,"dataGaLocation":5583},{"altText":5585,"config":5626},{"src":5591,"dataGaName":5588,"dataGaLocation":5583},{"title":5628,"titleMobile":5629,"button":5630,"config":5635},"Duo Agent Platform delivers 400% ROI, per new Forrester Consulting study.","400% ROI: Forrester TEI for GitLab Duo",{"text":5247,"config":5631},{"href":5632,"dataGaName":5633,"dataGaLocation":5634},"https://about.gitlab.com/blog/gitlab-duo-agent-platform-delivers-400-percent-roi/","forrester-tei-dap-banner","global-banner",{"layout":5636,"disabled":5183},"release",{"data":5638},{"text":5639,"source":5640,"edit":5646,"contribute":5651,"config":5656,"items":5661,"minimal":5869},"Git is a trademark of Software Freedom Conservancy and our use of 'GitLab' is under license",{"text":5641,"config":5642},"View page source",{"href":5643,"dataGaName":5644,"dataGaLocation":5645},"https://gitlab.com/gitlab-com/marketing/digital-experience/about-gitlab-com/","page source","footer",{"text":5647,"config":5648},"Edit this page",{"href":5649,"dataGaName":5650,"dataGaLocation":5645},"https://gitlab.com/gitlab-com/marketing/digital-experience/about-gitlab-com/-/blob/main/content/","web ide",{"text":5652,"config":5653},"Please contribute",{"href":5654,"dataGaName":5655,"dataGaLocation":5645},"https://gitlab.com/gitlab-com/marketing/digital-experience/about-gitlab-com/-/blob/main/CONTRIBUTING.md/","please contribute",{"twitter":5657,"facebook":5658,"youtube":5659,"linkedin":5660},"https://twitter.com/gitlab","https://www.facebook.com/gitlab","https://www.youtube.com/channel/UCnMGQ8QHMAnVIsI3xJrihhg","https://www.linkedin.com/company/gitlab-com",[5662,5709,5761,5805,5837],{"title":5351,"links":5663,"subMenu":5678},[5664,5668,5673],{"text":5665,"config":5666},"View plans",{"href":5353,"dataGaName":5667,"dataGaLocation":5645},"view plans",{"text":5669,"config":5670},"Why Premium?",{"href":5671,"dataGaName":5672,"dataGaLocation":5645},"/pricing/premium/","why premium",{"text":5674,"config":5675},"Why Ultimate?",{"href":5676,"dataGaName":5677,"dataGaLocation":5645},"/pricing/ultimate/","why ultimate",[5679],{"title":5680,"links":5681},"Contact Us",[5682,5685,5687,5689,5694,5699,5704],{"text":5683,"config":5684},"Contact sales",{"href":5535,"dataGaName":5215,"dataGaLocation":5645},{"text":5538,"config":5686},{"href":5540,"dataGaName":5541,"dataGaLocation":5645},{"text":5543,"config":5688},{"href":5545,"dataGaName":5546,"dataGaLocation":5645},{"text":5690,"config":5691},"Status",{"href":5692,"dataGaName":5693,"dataGaLocation":5645},"https://status.gitlab.com/","status",{"text":5695,"config":5696},"Terms of use",{"href":5697,"dataGaName":5698,"dataGaLocation":5645},"/terms/","terms of use",{"text":5700,"config":5701},"Privacy statement",{"href":5702,"dataGaName":5703,"dataGaLocation":5645},"/privacy/","privacy statement",{"text":5705,"config":5706},"Cookie preferences",{"dataGaName":5707,"dataGaLocation":5645,"id":5708,"isOneTrustButton":128},"cookie preferences","ot-sdk-btn",{"title":5252,"links":5710,"subMenu":5718},[5711,5714],{"text":5196,"config":5712},{"href":5234,"dataGaName":5713,"dataGaLocation":5645},"devsecops platform",{"text":5715,"config":5716},"AI-Assisted Development",{"href":5241,"dataGaName":5717,"dataGaLocation":5645},"ai-assisted development",[5719],{"title":5720,"links":5721},"Topics",[5722,5727,5732,5737,5742,5746,5751,5756],{"text":5723,"config":5724},"CICD",{"href":5725,"dataGaName":5726,"dataGaLocation":5645},"/topics/ci-cd/","cicd",{"text":5728,"config":5729},"GitOps",{"href":5730,"dataGaName":5731,"dataGaLocation":5645},"/topics/gitops/","gitops",{"text":5733,"config":5734},"DevOps",{"href":5735,"dataGaName":5736,"dataGaLocation":5645},"/topics/devops/","devops",{"text":5738,"config":5739},"Version Control",{"href":5740,"dataGaName":5741,"dataGaLocation":5645},"/topics/version-control/","version control",{"text":253,"config":5743},{"href":5744,"dataGaName":5745,"dataGaLocation":5645},"/topics/devsecops/","devsecops",{"text":5747,"config":5748},"Cloud Native",{"href":5749,"dataGaName":5750,"dataGaLocation":5645},"/topics/cloud-native/","cloud native",{"text":5752,"config":5753},"AI for Coding",{"href":5754,"dataGaName":5755,"dataGaLocation":5645},"/topics/devops/ai-for-coding/","ai for coding",{"text":5757,"config":5758},"Agentic AI",{"href":5759,"dataGaName":5760,"dataGaLocation":5645},"/topics/agentic-ai/","agentic ai",{"title":5762,"links":5763},"Solutions",[5764,5766,5768,5773,5777,5780,5784,5787,5789,5792,5795,5800],{"text":5295,"config":5765},{"href":5290,"dataGaName":5295,"dataGaLocation":5645},{"text":5284,"config":5767},{"href":5268,"dataGaName":5269,"dataGaLocation":5645},{"text":5769,"config":5770},"Agile development",{"href":5771,"dataGaName":5772,"dataGaLocation":5645},"/solutions/agile-delivery/","agile delivery",{"text":5774,"config":5775},"SCM",{"href":5280,"dataGaName":5776,"dataGaLocation":5645},"source code management",{"text":5723,"config":5778},{"href":5273,"dataGaName":5779,"dataGaLocation":5645},"continuous integration & delivery",{"text":5781,"config":5782},"Value stream management",{"href":5323,"dataGaName":5783,"dataGaLocation":5645},"value stream management",{"text":5728,"config":5785},{"href":5786,"dataGaName":5731,"dataGaLocation":5645},"/solutions/gitops/",{"text":5333,"config":5788},{"href":5336,"dataGaName":5337,"dataGaLocation":5645},{"text":5790,"config":5791},"Small business",{"href":5342,"dataGaName":5343,"dataGaLocation":5645},{"text":5793,"config":5794},"Public sector",{"href":5348,"dataGaName":5349,"dataGaLocation":5645},{"text":5796,"config":5797},"Education",{"href":5798,"dataGaName":5799,"dataGaLocation":5645},"/solutions/education/","education",{"text":5801,"config":5802},"Financial services",{"href":5803,"dataGaName":5804,"dataGaLocation":5645},"/solutions/finance/","financial services",{"title":5356,"links":5806},[5807,5809,5811,5813,5816,5818,5821,5823,5825,5827,5829,5831,5833,5835],{"text":5369,"config":5808},{"href":5371,"dataGaName":5372,"dataGaLocation":5645},{"text":5374,"config":5810},{"href":5376,"dataGaName":5377,"dataGaLocation":5645},{"text":5379,"config":5812},{"href":5381,"dataGaName":5382,"dataGaLocation":5645},{"text":5384,"config":5814},{"href":5386,"dataGaName":5815,"dataGaLocation":5645},"docs",{"text":5407,"config":5817},{"href":5409,"dataGaName":5410,"dataGaLocation":5645},{"text":5819,"config":5820},"What's new",{"href":5467,"dataGaName":5468,"dataGaLocation":5645},{"text":5402,"config":5822},{"href":5404,"dataGaName":5405,"dataGaLocation":5645},{"text":5421,"config":5824},{"href":5423,"dataGaName":5424,"dataGaLocation":5645},{"text":5429,"config":5826},{"href":5431,"dataGaName":5432,"dataGaLocation":5645},{"text":5434,"config":5828},{"href":5436,"dataGaName":5437,"dataGaLocation":5645},{"text":5439,"config":5830},{"href":5441,"dataGaName":5442,"dataGaLocation":5645},{"text":5444,"config":5832},{"href":5446,"dataGaName":5447,"dataGaLocation":5645},{"text":5449,"config":5834},{"href":5451,"dataGaName":5452,"dataGaLocation":5645},{"text":5454,"config":5836},{"href":5456,"dataGaName":5457,"dataGaLocation":5645},{"title":5470,"links":5838},[5839,5841,5843,5845,5847,5849,5853,5858,5860,5862,5864],{"text":5478,"config":5840},{"href":5480,"dataGaName":5472,"dataGaLocation":5645},{"text":5483,"config":5842},{"href":5485,"dataGaName":5486,"dataGaLocation":5645},{"text":5491,"config":5844},{"href":5493,"dataGaName":5494,"dataGaLocation":5645},{"text":5496,"config":5846},{"href":5498,"dataGaName":5499,"dataGaLocation":5645},{"text":5501,"config":5848},{"href":5503,"dataGaName":5504,"dataGaLocation":5645},{"text":5850,"config":5851},"Sustainability",{"href":5852,"dataGaName":5850,"dataGaLocation":5645},"/sustainability/",{"text":5854,"config":5855},"Diversity, inclusion and belonging (DIB)",{"href":5856,"dataGaName":5857,"dataGaLocation":5645},"/diversity-inclusion-belonging/","Diversity, inclusion and belonging",{"text":5506,"config":5859},{"href":5508,"dataGaName":5509,"dataGaLocation":5645},{"text":5516,"config":5861},{"href":5518,"dataGaName":5519,"dataGaLocation":5645},{"text":5521,"config":5863},{"href":5523,"dataGaName":5524,"dataGaLocation":5645},{"text":5865,"config":5866},"Modern Slavery Transparency Statement",{"href":5867,"dataGaName":5868,"dataGaLocation":5645},"https://handbook.gitlab.com/handbook/legal/modern-slavery-act-transparency-statement/","modern slavery transparency statement",{"items":5870},[5871,5874,5877],{"text":5872,"config":5873},"Terms",{"href":5697,"dataGaName":5698,"dataGaLocation":5645},{"text":5875,"config":5876},"Cookies",{"dataGaName":5707,"dataGaLocation":5645,"id":5708,"isOneTrustButton":128},{"text":5878,"config":5879},"Privacy",{"href":5702,"dataGaName":5703,"dataGaLocation":5645},[5881],{"id":5882,"title":5883,"body":5182,"config":5884,"content":5886,"description":5182,"extension":1412,"meta":5890,"navigation":128,"path":5891,"seo":5892,"stem":5893,"__hash__":5894},"blogAuthors/en-us/blog/authors/olivier-dupr.yml","Olivier Dupr",{"template":5885},"BlogAuthor",{"name":7,"config":5887},{"headshot":5888,"ctfId":5889},"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",[5896,5905,5913],{"title":5897,"description":5898,"heroImage":5899,"category":5178,"date":5900,"authors":5901,"slug":5904,"externalUrl":5182},"Confidential AI for GitLab Self-Hosted","Give developers AI coding agents in GitLab Duo without source code leaving a hardware-encrypted boundary — no GPUs needed.","https://res.cloudinary.com/about-gitlab-com/image/upload/v1773866173/vte9qh8rriznvyclhkes.png","2026-08-06",[5902,5903],"Mathias Ewald","Martin Paloncy, Edgeless Systems","confidential-ai-for-gitlab-self-hosted",{"title":5906,"description":5907,"heroImage":5908,"category":5178,"date":5909,"authors":5910,"slug":5912,"externalUrl":5182},"Green DevOps: Why carbon measurement belongs in your CI/CD pipeline","CI/CD pipelines have a hidden carbon cost. Here's why measuring it matters, and how you can get started with Eco CI and Carmen in GitLab.","https://res.cloudinary.com/about-gitlab-com/image/upload/v1765809212/noh0mdfn9o94ry9ykura.png","2026-07-09",[5911],"Lysanne Pinto","green-devops-carbon-measurement-cicd-pipeline",{"title":5914,"description":5915,"heroImage":5916,"category":5178,"date":5917,"authors":5918,"slug":5920,"externalUrl":5182},"How to build CI/CD observability at scale","This practical guide to GitLab pipeline analytics helps self-managed users gain operational insights using Prometheus and Grafana.","https://res.cloudinary.com/about-gitlab-com/image/upload/v1774465167/n5hlvrsrheadeccyr1oz.png","2026-04-28",[5919],"Paul Meresanu","how-to-build-ci-cd-observability-at-scale",{"promotions":5922},[5923,5937,5949,5961],{"id":5924,"categories":5925,"header":5927,"text":5928,"button":5929,"image":5934},"ai-modernization",[5926],"ai","Is AI achieving its promise at scale?","Quiz will take 5 minutes or less",{"text":5930,"config":5931},"Get your AI maturity score",{"href":5932,"dataGaName":5933,"dataGaLocation":5410},"/assessments/ai-modernization-assessment/","modernization assessment",{"config":5935},{"src":5936},"https://res.cloudinary.com/about-gitlab-com/image/upload/v1772138786/qix0m7kwnd8x2fh1zq49.png",{"id":5938,"categories":5939,"header":5941,"text":5928,"button":5942,"image":5946},"devops-modernization",[5940,5745],"product","Are you just managing tools or shipping innovation?",{"text":5943,"config":5944},"Get your DevOps maturity score",{"href":5945,"dataGaName":5933,"dataGaLocation":5410},"/assessments/devops-modernization-assessment/",{"config":5947},{"src":5948},"https://res.cloudinary.com/about-gitlab-com/image/upload/v1772138785/eg818fmakweyuznttgid.png",{"id":5950,"categories":5951,"header":5953,"text":5928,"button":5954,"image":5958},"security-modernization",[5952],"security","Are you trading speed for security?",{"text":5955,"config":5956},"Get your security maturity score",{"href":5957,"dataGaName":5933,"dataGaLocation":5410},"/assessments/security-modernization-assessment/",{"config":5959},{"src":5960},"https://res.cloudinary.com/about-gitlab-com/image/upload/v1772138786/p4pbqd9nnjejg5ds6mdk.png",{"id":5962,"paths":5963,"header":5966,"text":5967,"button":5968,"image":5973},"github-azure-migration",[5964,5965],"migration-from-azure-devops-to-gitlab","integrating-azure-devops-scm-and-gitlab","Is your team ready for GitHub's Azure move?","GitHub is already rebuilding around Azure. Find out what it means for you.",{"text":5969,"config":5970},"See how GitLab compares to GitHub",{"href":5971,"dataGaName":5972,"dataGaLocation":5410},"/compare/gitlab-vs-github/github-azure-migration/","github azure migration",{"config":5974},{"src":5948},{"header":5976,"blurb":5977,"button":5978,"secondaryButton":5982},"Start building faster today","See what your team can do with the intelligent orchestration platform for DevSecOps.\n",{"text":5979,"config":5980},"Get your free trial",{"href":5981,"dataGaName":5210,"dataGaLocation":1174},"https://gitlab.com/-/trial_registrations/new?glm_content=default-saas-trial&glm_source=about.gitlab.com/",{"text":5683,"config":5983},{"href":5535,"dataGaName":5215,"dataGaLocation":1174},1786803756158]