[{"data":1,"prerenderedAt":2247},["ShallowReactive",2],{"/blog/ci-deployment-and-environments":3,"navigation-en-us":1448,"banner-en-us":1875,"footer-en-us":1885,"blog-post-authors-en-us-Ivan Nemytchenko|Cesar Saavedra":2130,"blog-related-posts-en-us-ci-deployment-and-environments":2157,"blog-promotions-en-us":2183,"next-steps-en-us":2237},{"id":4,"title":5,"authors":6,"body":9,"category":1426,"date":1427,"description":1428,"extension":1429,"externalUrl":1430,"faq":1430,"featured":1431,"heroImage":1432,"meta":1433,"navigation":622,"path":1434,"seo":1435,"slug":1439,"stem":1440,"tags":1441,"template":1445,"updatedDate":1446,"__hash__":1447},"blogPosts/en-us/blog/ci-deployment-and-environments.md","How to use GitLab CI to deploy to multiple environments",[7,8],"Ivan Nemytchenko","Cesar Saavedra",{"type":10,"value":11,"toc":1408},"minimark",[12,28,31,40,48,51,56,69,78,81,129,137,157,162,166,178,184,193,221,228,247,291,310,319,386,389,394,400,406,413,419,423,434,488,491,497,500,504,513,516,526,533,537,546,554,584,590,599,757,766,773,777,788,943,946,952,955,961,967,970,974,977,980,991,995,998,1004,1007,1013,1032,1035,1053,1060,1063,1067,1070,1077,1083,1091,1098,1104,1107,1111,1114,1117,1122,1131,1209,1222,1232,1239,1242,1245,1252,1255,1259,1262,1269,1275,1285,1293,1371,1377,1381,1401,1405],[13,14,15,16,22,23,27],"p",{},"This post is a success story of one imaginary news portal, and you're the happy\nowner, the editor, and the only developer. Luckily, you already host your project\ncode on GitLab.com and know that you can\n",[17,18,21],"a",{"href":19,"rel":20},"https://docs.gitlab.com/ci/testing/",[],"run tests with GitLab CI/CD",".\nNow you’re curious if it can be ",[17,24,26],{"href":25},"/blog/how-to-keep-up-with-ci-cd-best-practices/","used for deployment",", and how far can you go with it.",[13,29,30],{},"To keep our story technology stack-agnostic, let's assume that the app is just a\nset of HTML files. No server-side code, no fancy JS assets compilation.",[13,32,33,34,39],{},"Destination platform is also simplistic – we will use ",[17,35,38],{"href":36,"rel":37},"https://aws.amazon.com/s3/",[],"Amazon S3",".",[13,41,42,43,47],{},"The goal of the article is not to give you a bunch of copy-pasteable snippets.\nThe goal is to show the principles and features of ",[17,44,46],{"href":45},"/solutions/continuous-integration/","GitLab CI"," so that you can easily apply them to your technology stack.",[13,49,50],{},"Let’s start from the beginning. There's no continuous integration (CI) in our story yet.",[52,53,55],"h2",{"id":54},"at-the-starting-line","At the starting line",[13,57,58,62,63,68],{},[59,60,61],"strong",{},"Deployment",": In your case, it means that a bunch of HTML files should appear on your\nS3 bucket (which is already configured for\n",[17,64,67],{"href":65,"rel":66},"http://docs.aws.amazon.com/AmazonS3/latest/dev/HowDoIWebsiteConfiguration.html?shortFooter=true",[],"static website hosting",").",[13,70,71,72,77],{},"There are a million ways to do it. We’ll use the\n",[17,73,76],{"href":74,"rel":75},"http://docs.aws.amazon.com/cli/latest/reference/s3/cp.html#examples",[],"awscli"," library,\nprovided by Amazon.",[13,79,80],{},"The full command looks like this:",[82,83,88],"pre",{"className":84,"code":85,"language":86,"meta":87,"style":87},"language-shell shiki shiki-themes github-light","aws s3 cp ./ s3://yourbucket/ --recursive --exclude \"*\" --include \"*.html\"\n","shell","",[89,90,91],"code",{"__ignoreMap":87},[92,93,96,100,104,107,110,113,117,120,123,126],"span",{"class":94,"line":95},"line",1,[92,97,99],{"class":98},"s7eDp","aws",[92,101,103],{"class":102},"sYBdl"," s3",[92,105,106],{"class":102}," cp",[92,108,109],{"class":102}," ./",[92,111,112],{"class":102}," s3://yourbucket/",[92,114,116],{"class":115},"sYu0t"," --recursive",[92,118,119],{"class":115}," --exclude",[92,121,122],{"class":102}," \"*\"",[92,124,125],{"class":115}," --include",[92,127,128],{"class":102}," \"*.html\"\n",[13,130,131,136],{},[132,133],"img",{"alt":134,"src":135},"Manual deployment","https://res.cloudinary.com/about-gitlab-com/image/upload/v1782398048/blog/Content%20Images/ci-deployment-and-environments/13.jpg","\nPushing code to repository and deploying are separate processes.",[13,138,139,140,145,146,149,150,153,154,39],{},"Important detail: The command\n",[17,141,144],{"href":142,"rel":143},"http://docs.aws.amazon.com/cli/latest/userguide/cli-chap-getting-started.html#config-settings-and-precedence",[],"expects you","\nto provide ",[89,147,148],{},"AWS_ACCESS_KEY_ID"," and  ",[89,151,152],{},"AWS_SECRET_ACCESS_KEY"," environment\nvariables. Also you might need to specify ",[89,155,156],{},"AWS_DEFAULT_REGION",[13,158,159,160,39],{},"Let’s try to automate it using ",[17,161,46],{"href":45},[52,163,165],{"id":164},"the-first-automated-deployment","The first automated deployment",[13,167,168,169,172,173,177],{},"With GitLab, there's no difference on what commands to run.\nYou can set up GitLab CI in a way that tailors to your specific needs, as if it was your local terminal on your computer. As long as you execute commands there, you can tell CI to do the same for you in GitLab.\nPut your script to ",[89,170,171],{},".gitlab-ci.yml"," and push your code – that’s it: CI triggers\na ",[174,175,176],"em",{},"job"," and your commands are executed.",[13,179,180,181,39],{},"Now, let's add some context to our story: Our website is small, there is 20-30 daily\nvisitors and the code repository has only one default branch: ",[89,182,183],{},"main",[13,185,186,187,189,190,192],{},"Let's start by specifying a ",[174,188,176],{}," with the command from above in the ",[89,191,171],{}," file:",[82,194,198],{"className":195,"code":196,"language":197,"meta":87,"style":87},"language-yaml shiki shiki-themes github-light","deploy:\n  script: aws s3 cp ./ s3://yourbucket/ --recursive --exclude \"*\" --include \"*.html\"\n\n","yaml",[89,199,200,210],{"__ignoreMap":87},[92,201,202,206],{"class":94,"line":95},[92,203,205],{"class":204},"shJU0","deploy",[92,207,209],{"class":208},"sgsFI",":\n",[92,211,213,216,219],{"class":94,"line":212},2,[92,214,215],{"class":204},"  script",[92,217,218],{"class":208},": ",[92,220,85],{"class":102},[13,222,223,224],{},"No luck:\n",[132,225],{"alt":226,"src":227},"Failed command","https://res.cloudinary.com/about-gitlab-com/image/upload/v1782398050/blog/Content%20Images/ci-deployment-and-environments/fail1.png",[13,229,230,231,233,234,236,237,239,240,243,244,246],{},"It is our ",[174,232,176],{}," to ensure that there is an ",[89,235,99],{}," executable.\nTo install ",[89,238,76],{}," we need ",[89,241,242],{},"pip",", which is a tool for Python packages installation.\nLet's specify Docker image with preinstalled Python, which should contain ",[89,245,242],{}," as well:",[82,248,250],{"className":195,"code":249,"language":197,"meta":87,"style":87},"deploy:\n  image: python:latest\n  script:\n  - pip install awscli\n  - aws s3 cp ./ s3://yourbucket/ --recursive --exclude \"*\" --include \"*.html\"\n\n",[89,251,252,258,268,275,284],{"__ignoreMap":87},[92,253,254,256],{"class":94,"line":95},[92,255,205],{"class":204},[92,257,209],{"class":208},[92,259,260,263,265],{"class":94,"line":212},[92,261,262],{"class":204},"  image",[92,264,218],{"class":208},[92,266,267],{"class":102},"python:latest\n",[92,269,271,273],{"class":94,"line":270},3,[92,272,215],{"class":204},[92,274,209],{"class":208},[92,276,278,281],{"class":94,"line":277},4,[92,279,280],{"class":208},"  - ",[92,282,283],{"class":102},"pip install awscli\n",[92,285,287,289],{"class":94,"line":286},5,[92,288,280],{"class":208},[92,290,85],{"class":102},[13,292,293,297,298,300,301,306,307,309],{},[132,294],{"alt":295,"src":296},"Automated deployment","https://res.cloudinary.com/about-gitlab-com/image/upload/v1782398050/blog/Content%20Images/ci-deployment-and-environments/14.jpg","\nYou push your code to GitLab, and it is automatically deployed by CI.\nThe installation of ",[89,299,76],{}," extends the job execution time, but that is not a big\ndeal for now. If you need to speed up the process, you can always ",[17,302,305],{"href":303,"rel":304},"https://hub.docker.com/explore/",[],"look for\na Docker image"," with preinstalled ",[89,308,76],{},",\nor create an image by yourself.",[13,311,312,313,318],{},"Also, let’s not forget about these environment variables, which you've just grabbed\nfrom ",[17,314,317],{"href":315,"rel":316},"https://console.aws.amazon.com/",[],"AWS Console",":",[82,320,322],{"className":195,"code":321,"language":197,"meta":87,"style":87},"variables:\n  AWS_ACCESS_KEY_ID: \"AKIAIOSFODNN7EXAMPLE\"\n  AWS_SECRET_ACCESS_KEY: \"wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY\"\ndeploy:\n  image: python:latest\n  script:\n  - pip install awscli\n  - aws s3 cp ./ s3://yourbucket/ --recursive --exclude \"*\" --include \"*.html\"\n\n",[89,323,324,331,341,351,357,365,372,379],{"__ignoreMap":87},[92,325,326,329],{"class":94,"line":95},[92,327,328],{"class":204},"variables",[92,330,209],{"class":208},[92,332,333,336,338],{"class":94,"line":212},[92,334,335],{"class":204},"  AWS_ACCESS_KEY_ID",[92,337,218],{"class":208},[92,339,340],{"class":102},"\"AKIAIOSFODNN7EXAMPLE\"\n",[92,342,343,346,348],{"class":94,"line":270},[92,344,345],{"class":204},"  AWS_SECRET_ACCESS_KEY",[92,347,218],{"class":208},[92,349,350],{"class":102},"\"wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY\"\n",[92,352,353,355],{"class":94,"line":277},[92,354,205],{"class":204},[92,356,209],{"class":208},[92,358,359,361,363],{"class":94,"line":286},[92,360,262],{"class":204},[92,362,218],{"class":208},[92,364,267],{"class":102},[92,366,368,370],{"class":94,"line":367},6,[92,369,215],{"class":204},[92,371,209],{"class":208},[92,373,375,377],{"class":94,"line":374},7,[92,376,280],{"class":208},[92,378,283],{"class":102},[92,380,382,384],{"class":94,"line":381},8,[92,383,280],{"class":208},[92,385,85],{"class":102},[13,387,388],{},"It should work, but keeping secret keys open, even in a private repository,\nis not a good idea. Let's see how to deal with this situation.",[390,391,393],"h3",{"id":392},"keeping-secret-things-secret","Keeping secret things secret",[13,395,396,397],{},"GitLab has a special place for secret variables: ",[59,398,399],{},"Settings > CI/CD > Variables",[13,401,402],{},[132,403],{"alt":404,"src":405},"Picture of Variables page","https://res.cloudinary.com/about-gitlab-com/image/upload/v1749674076/Blog/Content%20Images/add-variable-updated.png",[13,407,408,409,412],{},"Whatever you put there will be turned into ",[59,410,411],{},"environment variables",".\nChecking the \"Mask variable\" checkbox will obfuscate the variable in job logs. Also, checking the \"Protect variable\" checkbox will export the variable to only pipelines running on protected branches and tags. Users with Owner or Maintainer permissions to a project will have access to this section.",[13,414,415,416,418],{},"We could remove ",[89,417,328],{}," section from our CI configuration. However, let’s use it for another purpose.",[390,420,422],{"id":421},"how-to-specify-and-use-variables-that-are-not-secret","How to specify and use variables that are not secret",[13,424,425,426,433],{},"When your configuration gets bigger, it is convenient to keep some of the\nparameters as variables at the beginning of your configuration. Especially if you\nuse them in more than one place. Although it is not the case in our situation yet,\nlet's set the S3 bucket name as a ",[17,427,430],{"href":428,"rel":429},"https://docs.gitlab.com/ci/variables/",[],[59,431,432],{},"variable"," for the purpose of this demonstration:",[82,435,437],{"className":195,"code":436,"language":197,"meta":87,"style":87},"variables:\n  S3_BUCKET_NAME: \"yourbucket\"\ndeploy:\n  image: python:latest\n  script:\n  - pip install awscli\n  - aws s3 cp ./ s3://$S3_BUCKET_NAME/ --recursive --exclude \"*\" --include \"*.html\"\n\n",[89,438,439,445,455,461,469,475,481],{"__ignoreMap":87},[92,440,441,443],{"class":94,"line":95},[92,442,328],{"class":204},[92,444,209],{"class":208},[92,446,447,450,452],{"class":94,"line":212},[92,448,449],{"class":204},"  S3_BUCKET_NAME",[92,451,218],{"class":208},[92,453,454],{"class":102},"\"yourbucket\"\n",[92,456,457,459],{"class":94,"line":270},[92,458,205],{"class":204},[92,460,209],{"class":208},[92,462,463,465,467],{"class":94,"line":277},[92,464,262],{"class":204},[92,466,218],{"class":208},[92,468,267],{"class":102},[92,470,471,473],{"class":94,"line":286},[92,472,215],{"class":204},[92,474,209],{"class":208},[92,476,477,479],{"class":94,"line":367},[92,478,280],{"class":208},[92,480,283],{"class":102},[92,482,483,485],{"class":94,"line":374},[92,484,280],{"class":208},[92,486,487],{"class":102},"aws s3 cp ./ s3://$S3_BUCKET_NAME/ --recursive --exclude \"*\" --include \"*.html\"\n",[13,489,490],{},"So far so good:",[13,492,493],{},[132,494],{"alt":495,"src":496},"Successful build","https://res.cloudinary.com/about-gitlab-com/image/upload/v1782398050/blog/Content%20Images/ci-deployment-and-environments/build.png",[13,498,499],{},"In our hypothetical scenario, the audience of your website has grown, so you've hired a developer to help you.\nNow you have a team. Let's see how teamwork changes the GitLab CI workflow.",[52,501,503],{"id":502},"how-to-use-gitlab-ci-with-a-team","How to use GitLab CI with a team",[13,505,506,507,509,510,512],{},"Now, that there are two users working in the same repository, it is no longer convenient\nto use the ",[89,508,183],{}," branch for development. You decide to use separate branches\nfor both new features and new articles and merge them into ",[89,511,183],{}," when they are ready.",[13,514,515],{},"The problem is that your current CI config doesn’t care about branches at all.\nWhenever you push anything to GitLab, it will be deployed to S3.",[13,517,518,519,522,523,525],{},"Preventing this problem is straightforward. Just add ",[89,520,521],{},"only: main"," to your ",[89,524,205],{}," job.",[13,527,528,532],{},[132,529],{"alt":530,"src":531},"Automated deployment of main branch","https://res.cloudinary.com/about-gitlab-com/image/upload/v1749674076/Blog/Content%20Images/15-updated.png","\nYou don't want to deploy every branch to the production website but it would also be nice to preview your changes from feature-branches somehow.",[390,534,536],{"id":535},"how-to-set-up-a-separate-place-for-testing-code","How to set up a separate place for testing code",[13,538,539,540,545],{},"The person you recently hired, let's call him Patrick, reminds you that there is a featured called\n",[17,541,544],{"href":542,"rel":543},"https://docs.gitlab.com/user/project/pages/",[],"GitLab Pages",". It looks like a perfect candidate for\na place to preview your work in progress.",[13,547,548,549,553],{},"To ",[17,550,552],{"href":551},"/blog/gitlab-pages-setup/","host websites on GitLab Pages"," your CI configuration file should satisfy three simple rules:",[555,556,557,567,578],"ul",{},[558,559,560,561,563,564],"li",{},"The ",[174,562,176],{}," should be named ",[89,565,566],{},"pages",[558,568,569,570,573,574,577],{},"There should be an ",[89,571,572],{},"artifacts"," section with folder ",[89,575,576],{},"public"," in it",[558,579,580,581,583],{},"Everything you want to host should be in this ",[89,582,576],{}," folder",[13,585,586,587],{},"The contents of the public folder will be hosted at ",[89,588,589],{},"http://\u003Cusername>.gitlab.io/\u003Cprojectname>/",[13,591,592,593,598],{},"After applying the ",[17,594,597],{"href":595,"rel":596},"https://gitlab.com/pages/plain-html/blob/master/.gitlab-ci.yml",[],"example config for plain-html websites",",\nthe full CI configuration looks like this:",[82,600,602],{"className":195,"code":601,"language":197,"meta":87,"style":87},"variables:\n  S3_BUCKET_NAME: \"yourbucket\"\n\ndeploy:\n  image: python:latest\n  script:\n  - pip install awscli\n  - aws s3 cp ./ s3://$S3_BUCKET_NAME/ --recursive --exclude \"*\" --include \"*.html\"\n  only:\n  - main\n\npages:\n  image: alpine:latest\n  script:\n  - mkdir -p ./public\n  - cp ./*.html ./public/\n  artifacts:\n    paths:\n    - public\n  except:\n  - main\n\n",[89,603,604,610,618,624,630,638,644,650,656,664,672,677,684,694,701,709,717,725,733,742,750],{"__ignoreMap":87},[92,605,606,608],{"class":94,"line":95},[92,607,328],{"class":204},[92,609,209],{"class":208},[92,611,612,614,616],{"class":94,"line":212},[92,613,449],{"class":204},[92,615,218],{"class":208},[92,617,454],{"class":102},[92,619,620],{"class":94,"line":270},[92,621,623],{"emptyLinePlaceholder":622},true,"\n",[92,625,626,628],{"class":94,"line":277},[92,627,205],{"class":204},[92,629,209],{"class":208},[92,631,632,634,636],{"class":94,"line":286},[92,633,262],{"class":204},[92,635,218],{"class":208},[92,637,267],{"class":102},[92,639,640,642],{"class":94,"line":367},[92,641,215],{"class":204},[92,643,209],{"class":208},[92,645,646,648],{"class":94,"line":374},[92,647,280],{"class":208},[92,649,283],{"class":102},[92,651,652,654],{"class":94,"line":381},[92,653,280],{"class":208},[92,655,487],{"class":102},[92,657,659,662],{"class":94,"line":658},9,[92,660,661],{"class":204},"  only",[92,663,209],{"class":208},[92,665,667,669],{"class":94,"line":666},10,[92,668,280],{"class":208},[92,670,671],{"class":102},"main\n",[92,673,675],{"class":94,"line":674},11,[92,676,623],{"emptyLinePlaceholder":622},[92,678,680,682],{"class":94,"line":679},12,[92,681,566],{"class":204},[92,683,209],{"class":208},[92,685,687,689,691],{"class":94,"line":686},13,[92,688,262],{"class":204},[92,690,218],{"class":208},[92,692,693],{"class":102},"alpine:latest\n",[92,695,697,699],{"class":94,"line":696},14,[92,698,215],{"class":204},[92,700,209],{"class":208},[92,702,704,706],{"class":94,"line":703},15,[92,705,280],{"class":208},[92,707,708],{"class":102},"mkdir -p ./public\n",[92,710,712,714],{"class":94,"line":711},16,[92,713,280],{"class":208},[92,715,716],{"class":102},"cp ./*.html ./public/\n",[92,718,720,723],{"class":94,"line":719},17,[92,721,722],{"class":204},"  artifacts",[92,724,209],{"class":208},[92,726,728,731],{"class":94,"line":727},18,[92,729,730],{"class":204},"    paths",[92,732,209],{"class":208},[92,734,736,739],{"class":94,"line":735},19,[92,737,738],{"class":208},"    - ",[92,740,741],{"class":102},"public\n",[92,743,745,748],{"class":94,"line":744},20,[92,746,747],{"class":204},"  except",[92,749,209],{"class":208},[92,751,753,755],{"class":94,"line":752},21,[92,754,280],{"class":208},[92,756,671],{"class":102},[13,758,759,760,762,763,765],{},"We specified two jobs. One job deploys the website for your customers to S3 (",[89,761,205],{},").\nThe other one (",[89,764,566],{},") deploys the website to GitLab Pages.\nWe can name them \"Production environment\" and \"Staging environment\", respectively.",[13,767,768,772],{},[132,769],{"alt":770,"src":771},"Deployment to two places","https://res.cloudinary.com/about-gitlab-com/image/upload/v1749674076/Blog/Content%20Images/16-updated.png","\nAll branches, except main, will be deployed to GitLab Pages.",[52,774,776],{"id":775},"introducing-environments","Introducing environments",[13,778,779,780,785,786,318],{},"GitLab offers\n",[17,781,784],{"href":782,"rel":783},"https://docs.gitlab.com/ci/environments/",[],"support for environments"," (including dynamic environments and static environments),\nand all you need to do it to specify the corresponding environment for each deployment ",[174,787,176],{},[82,789,791],{"className":195,"code":790,"language":197,"meta":87,"style":87},"variables:\n  S3_BUCKET_NAME: \"yourbucket\"\n\ndeploy to production:\n  environment: production\n  image: python:latest\n  script:\n  - pip install awscli\n  - aws s3 cp ./ s3://$S3_BUCKET_NAME/ --recursive --exclude \"*\" --include \"*.html\"\n  only:\n  - main\n\npages:\n  image: alpine:latest\n  environment: staging\n  script:\n  - mkdir -p ./public\n  - cp ./*.html ./public/\n  artifacts:\n    paths:\n    - public\n  except:\n  - main\n\n",[89,792,793,799,807,811,818,828,836,842,848,854,860,866,870,876,884,893,899,905,911,917,923,929,936],{"__ignoreMap":87},[92,794,795,797],{"class":94,"line":95},[92,796,328],{"class":204},[92,798,209],{"class":208},[92,800,801,803,805],{"class":94,"line":212},[92,802,449],{"class":204},[92,804,218],{"class":208},[92,806,454],{"class":102},[92,808,809],{"class":94,"line":270},[92,810,623],{"emptyLinePlaceholder":622},[92,812,813,816],{"class":94,"line":277},[92,814,815],{"class":204},"deploy to production",[92,817,209],{"class":208},[92,819,820,823,825],{"class":94,"line":286},[92,821,822],{"class":204},"  environment",[92,824,218],{"class":208},[92,826,827],{"class":102},"production\n",[92,829,830,832,834],{"class":94,"line":367},[92,831,262],{"class":204},[92,833,218],{"class":208},[92,835,267],{"class":102},[92,837,838,840],{"class":94,"line":374},[92,839,215],{"class":204},[92,841,209],{"class":208},[92,843,844,846],{"class":94,"line":381},[92,845,280],{"class":208},[92,847,283],{"class":102},[92,849,850,852],{"class":94,"line":658},[92,851,280],{"class":208},[92,853,487],{"class":102},[92,855,856,858],{"class":94,"line":666},[92,857,661],{"class":204},[92,859,209],{"class":208},[92,861,862,864],{"class":94,"line":674},[92,863,280],{"class":208},[92,865,671],{"class":102},[92,867,868],{"class":94,"line":679},[92,869,623],{"emptyLinePlaceholder":622},[92,871,872,874],{"class":94,"line":686},[92,873,566],{"class":204},[92,875,209],{"class":208},[92,877,878,880,882],{"class":94,"line":696},[92,879,262],{"class":204},[92,881,218],{"class":208},[92,883,693],{"class":102},[92,885,886,888,890],{"class":94,"line":703},[92,887,822],{"class":204},[92,889,218],{"class":208},[92,891,892],{"class":102},"staging\n",[92,894,895,897],{"class":94,"line":711},[92,896,215],{"class":204},[92,898,209],{"class":208},[92,900,901,903],{"class":94,"line":719},[92,902,280],{"class":208},[92,904,708],{"class":102},[92,906,907,909],{"class":94,"line":727},[92,908,280],{"class":208},[92,910,716],{"class":102},[92,912,913,915],{"class":94,"line":735},[92,914,722],{"class":204},[92,916,209],{"class":208},[92,918,919,921],{"class":94,"line":744},[92,920,730],{"class":204},[92,922,209],{"class":208},[92,924,925,927],{"class":94,"line":752},[92,926,738],{"class":208},[92,928,741],{"class":102},[92,930,932,934],{"class":94,"line":931},22,[92,933,747],{"class":204},[92,935,209],{"class":208},[92,937,939,941],{"class":94,"line":938},23,[92,940,280],{"class":208},[92,942,671],{"class":102},[13,944,945],{},"GitLab keeps track of your deployments, so you always know what is currently being deployed on your servers:",[13,947,948],{},[132,949],{"alt":950,"src":951},"List of environments","https://res.cloudinary.com/about-gitlab-com/image/upload/v1749674076/Blog/Content%20Images/envs-updated.png",[13,953,954],{},"GitLab provides full history of your deployments for each of your current environments:",[13,956,957],{},[132,958],{"alt":959,"src":960},"List of deployments to staging environment","https://res.cloudinary.com/about-gitlab-com/image/upload/v1749674077/Blog/Content%20Images/staging-env-detail-updated.png",[13,962,963],{},[132,964],{"alt":965,"src":966},"Environments","https://res.cloudinary.com/about-gitlab-com/image/upload/v1749674077/Blog/Content%20Images/17-updated.png",[13,968,969],{},"Now, with everything automated and set up, we’re ready for the new challenges that are just around the corner.",[52,971,973],{"id":972},"how-to-troubleshoot-deployments","How to troubleshoot deployments",[13,975,976],{},"It has just happened again.\nYou've pushed your feature-branch to preview it on staging and a minute later Patrick pushed\nhis branch, so the staging environment was rewritten with his work. Aargh!! It was the third time today!",[13,978,979],{},"Idea!  Let's use Slack to notify us of deployments, so that people will not push their stuff if another one has been just deployed!",[981,982,983],"blockquote",{},[13,984,985,986,39],{},"Learn how to ",[17,987,990],{"href":988,"rel":989},"https://docs.gitlab.com/user/project/integrations/gitlab_slack_application/",[],"integrate GitLab with Slack",[52,992,994],{"id":993},"teamwork-at-scale","Teamwork at scale",[13,996,997],{},"As the time passed, your website became really popular, and your team has grown from two people to eight people.\nPeople develop in parallel, so the situation when people wait for each other to\npreview something on Staging has become pretty common. \"Deploy every branch to staging\" stopped working.",[13,999,1000],{},[132,1001],{"alt":1002,"src":1003},"Queue of branches for review on Staging","https://res.cloudinary.com/about-gitlab-com/image/upload/v1782398050/blog/Content%20Images/ci-deployment-and-environments/queue.jpg",[13,1005,1006],{},"It's time to modify the process one more time. You and your team agreed that if\nsomeone wants to see their changes on the staging\nserver, they should first merge the changes to the \"staging\" branch.",[13,1008,1009,1010,1012],{},"The change of ",[89,1011,171],{}," is minimal:",[82,1014,1016],{"className":195,"code":1015,"language":197,"meta":87,"style":87},"except:\n- main\n",[89,1017,1018,1025],{"__ignoreMap":87},[92,1019,1020,1023],{"class":94,"line":95},[92,1021,1022],{"class":204},"except",[92,1024,209],{"class":208},[92,1026,1027,1030],{"class":94,"line":212},[92,1028,1029],{"class":208},"- ",[92,1031,671],{"class":102},[13,1033,1034],{},"is now changed to",[82,1036,1038],{"className":195,"code":1037,"language":197,"meta":87,"style":87},"only:\n- staging\n",[89,1039,1040,1047],{"__ignoreMap":87},[92,1041,1042,1045],{"class":94,"line":95},[92,1043,1044],{"class":204},"only",[92,1046,209],{"class":208},[92,1048,1049,1051],{"class":94,"line":212},[92,1050,1029],{"class":208},[92,1052,892],{"class":102},[13,1054,1055,1059],{},[132,1056],{"alt":1057,"src":1058},"Staging branch","https://res.cloudinary.com/about-gitlab-com/image/upload/v1749674077/Blog/Content%20Images/18-updated.png","\nPeople have to merge their feature branches before preview on the staging server.",[13,1061,1062],{},"Of course, it requires additional time and effort for merging, but everybody agreed that it is better than waiting.",[390,1064,1066],{"id":1065},"how-to-handle-emergencies","How to handle emergencies",[13,1068,1069],{},"You can't control everything, so sometimes things go wrong. Someone merged branches incorrectly and\npushed the result straight to production exactly when your site was on top of HackerNews.\nThousands of people saw your completely broken layout instead of your shiny main page.",[13,1071,1072,1073,1076],{},"Luckily, someone found the ",[59,1074,1075],{},"Rollback"," button, so the\nwebsite was fixed a minute after the problem was discovered.",[13,1078,1079,1082],{},[132,1080],{"alt":950,"src":1081},"https://res.cloudinary.com/about-gitlab-com/image/upload/v1749674077/Blog/Content%20Images/prod-env-rollback-arrow-updated.png","\nRollback relaunches the previous job with the previous commit",[13,1084,1085,1086,522,1089,39],{},"Anyway, you felt that you needed to react to the problem and decided to turn off\nauto-deployment to Production and switch to manual deployment.\nTo do that, you needed to add ",[89,1087,1088],{},"when: manual",[174,1090,176],{},[13,1092,1093,1094,1097],{},"As you expected, there will be no automatic deployment to Production after that.\nTo deploy manually go to ",[59,1095,1096],{},"CI/CD > Pipelines",", and click the button:",[13,1099,1100],{},[132,1101],{"alt":1102,"src":1103},"Skipped job is available for manual launch","https://res.cloudinary.com/about-gitlab-com/image/upload/v1749674076/Blog/Content%20Images/manual-pipeline-arrow-updated.png",[13,1105,1106],{},"Fast forward in time. Finally, your company has turned into a corporation. Now, you have hundreds of people working on the website,\nso all the previous compromises no longer work.",[390,1108,1110],{"id":1109},"time-to-start-using-review-apps","Time to start using Review Apps",[13,1112,1113],{},"The next logical step is to boot up a temporary instance of the application per feature branch for review.",[13,1115,1116],{},"In our case, we set up another bucket on S3 for that. The only difference is that\nwe copy the contents of our website to a \"folder\" with the name of the\nthe development branch, so that the URL looks like this:",[13,1118,1119],{},[89,1120,1121],{},"http://\u003CREVIEW_S3_BUCKET_NAME>.s3-website-us-east-1.amazonaws.com/\u003Cbranchname>/",[13,1123,1124,1125,1127,1128,1130],{},"Here's the replacement for the ",[89,1126,566],{}," ",[174,1129,176],{}," we used before:",[82,1132,1134],{"className":195,"code":1133,"language":197,"meta":87,"style":87},"review apps:\n  variables:\n    S3_BUCKET_NAME: \"reviewbucket\"\n  image: python:latest\n  environment: review\n  script:\n  - pip install awscli\n  - mkdir -p ./$CI_BUILD_REF_NAME\n  - cp ./*.html ./$CI_BUILD_REF_NAME/\n  - aws s3 cp ./ s3://$S3_BUCKET_NAME/ --recursive --exclude \"*\" --include \"*.html\"\n\n",[89,1135,1136,1143,1150,1160,1168,1177,1183,1189,1196,1203],{"__ignoreMap":87},[92,1137,1138,1141],{"class":94,"line":95},[92,1139,1140],{"class":204},"review apps",[92,1142,209],{"class":208},[92,1144,1145,1148],{"class":94,"line":212},[92,1146,1147],{"class":204},"  variables",[92,1149,209],{"class":208},[92,1151,1152,1155,1157],{"class":94,"line":270},[92,1153,1154],{"class":204},"    S3_BUCKET_NAME",[92,1156,218],{"class":208},[92,1158,1159],{"class":102},"\"reviewbucket\"\n",[92,1161,1162,1164,1166],{"class":94,"line":277},[92,1163,262],{"class":204},[92,1165,218],{"class":208},[92,1167,267],{"class":102},[92,1169,1170,1172,1174],{"class":94,"line":286},[92,1171,822],{"class":204},[92,1173,218],{"class":208},[92,1175,1176],{"class":102},"review\n",[92,1178,1179,1181],{"class":94,"line":367},[92,1180,215],{"class":204},[92,1182,209],{"class":208},[92,1184,1185,1187],{"class":94,"line":374},[92,1186,280],{"class":208},[92,1188,283],{"class":102},[92,1190,1191,1193],{"class":94,"line":381},[92,1192,280],{"class":208},[92,1194,1195],{"class":102},"mkdir -p ./$CI_BUILD_REF_NAME\n",[92,1197,1198,1200],{"class":94,"line":658},[92,1199,280],{"class":208},[92,1201,1202],{"class":102},"cp ./*.html ./$CI_BUILD_REF_NAME/\n",[92,1204,1205,1207],{"class":94,"line":666},[92,1206,280],{"class":208},[92,1208,487],{"class":102},[13,1210,1211,1212,1215,1216,1221],{},"The interesting thing is where we got this ",[89,1213,1214],{},"$CI_BUILD_REF_NAME"," variable from.\nGitLab predefines ",[17,1217,1220],{"href":1218,"rel":1219},"https://docs.gitlab.com/ci/variables/predefined_variables/",[],"many environment variables"," so that you can use them in your jobs.",[13,1223,1224,1225,1228,1229,1231],{},"Note that we defined the ",[89,1226,1227],{},"S3_BUCKET_NAME"," variable inside the ",[174,1230,176],{},". You can do this to rewrite top-level definitions.",[13,1233,1234,1235],{},"Visual representation of this configuration:\n![Review apps]",[132,1236],{"alt":1237,"src":1238},"How to use GitLab CI - update - 19 - updated","https://res.cloudinary.com/about-gitlab-com/image/upload/v1749674077/Blog/Content%20Images/19-updated.png",[13,1240,1241],{},"The details of the Review Apps implementation varies widely, depending upon your real technology\nstack and on your deployment process, which is outside the scope of this blog post.",[13,1243,1244],{},"It will not be that straightforward, as it is with our static HTML website.\nFor example, you had to make these instances temporary, and booting up these instances\nwith all required software and services automatically on the fly is not a trivial task.\nHowever, it is doable, especially if you use Docker containers, or at least Chef or Ansible.",[13,1246,1247,1248],{},"We'll cover deployment with Docker in a future blog post.\nI feel a bit guilty for simplifying the deployment process to a simple HTML files copying, and not\nadding some hardcore scenarios. If you need some right now, I recommend you read the article ",[17,1249,1251],{"href":1250},"/blog/building-an-elixir-release-into-docker-image-using-gitlab-ci-part-1/","\"Building an Elixir Release into a Docker image using GitLab CI.\"",[13,1253,1254],{},"For now, let's talk about one final thing.",[390,1256,1258],{"id":1257},"deploying-to-different-platforms","Deploying to different platforms",[13,1260,1261],{},"In real life, we are not limited to S3 and GitLab Pages. We host, and therefore,\ndeploy our apps and packages to various services.",[13,1263,1264,1265,1268],{},"Moreover, at some point, you could decide to move to a new platform and will need to rewrite all your deployment scripts.\nYou can use a gem called ",[89,1266,1267],{},"dpl"," to minimize the damage.",[13,1270,1271,1272,1274],{},"In the examples above we used ",[89,1273,76],{}," as a tool to deliver code to an example\nservice (Amazon S3).\nHowever, no matter what tool and what destination system you use, the principle is the same:\nYou run a command with some parameters and somehow pass a secret key for authentication purposes.",[13,1276,560,1277,1279,1280,39],{},[89,1278,1267],{}," deployment tool utilizes this principle and provides a\nunified interface for ",[17,1281,1284],{"href":1282,"rel":1283},"https://github.com/travis-ci/dpl#supported-providers",[],"this list of providers",[13,1286,1287,1288,1290,1291,318],{},"Here's how a production deployment ",[174,1289,176],{}," would look if we use ",[89,1292,1267],{},[82,1294,1296],{"className":195,"code":1295,"language":197,"meta":87,"style":87},"variables:\n  S3_BUCKET_NAME: \"yourbucket\"\n\ndeploy to production:\n  environment: production\n  image: ruby:latest\n  script:\n  - gem install dpl\n  - dpl --provider=s3 --bucket=$S3_BUCKET_NAME\n  only:\n  - main\n\n",[89,1297,1298,1304,1312,1316,1322,1330,1339,1345,1352,1359,1365],{"__ignoreMap":87},[92,1299,1300,1302],{"class":94,"line":95},[92,1301,328],{"class":204},[92,1303,209],{"class":208},[92,1305,1306,1308,1310],{"class":94,"line":212},[92,1307,449],{"class":204},[92,1309,218],{"class":208},[92,1311,454],{"class":102},[92,1313,1314],{"class":94,"line":270},[92,1315,623],{"emptyLinePlaceholder":622},[92,1317,1318,1320],{"class":94,"line":277},[92,1319,815],{"class":204},[92,1321,209],{"class":208},[92,1323,1324,1326,1328],{"class":94,"line":286},[92,1325,822],{"class":204},[92,1327,218],{"class":208},[92,1329,827],{"class":102},[92,1331,1332,1334,1336],{"class":94,"line":367},[92,1333,262],{"class":204},[92,1335,218],{"class":208},[92,1337,1338],{"class":102},"ruby:latest\n",[92,1340,1341,1343],{"class":94,"line":374},[92,1342,215],{"class":204},[92,1344,209],{"class":208},[92,1346,1347,1349],{"class":94,"line":381},[92,1348,280],{"class":208},[92,1350,1351],{"class":102},"gem install dpl\n",[92,1353,1354,1356],{"class":94,"line":658},[92,1355,280],{"class":208},[92,1357,1358],{"class":102},"dpl --provider=s3 --bucket=$S3_BUCKET_NAME\n",[92,1360,1361,1363],{"class":94,"line":666},[92,1362,661],{"class":204},[92,1364,209],{"class":208},[92,1366,1367,1369],{"class":94,"line":674},[92,1368,280],{"class":208},[92,1370,671],{"class":102},[13,1372,1373,1374,1376],{},"If you deploy to different systems or change destination platform frequently, consider\nusing ",[89,1375,1267],{}," to make your deployment scripts look uniform.",[52,1378,1380],{"id":1379},"five-key-takeaways","Five key takeaways",[1382,1383,1384,1387,1392,1395,1398],"ol",{},[558,1385,1386],{},"Deployment is just a command (or a set of commands) that is regularly executed. Therefore it can run inside GitLab CI.",[558,1388,1389,1390,39],{},"Most times you'll need to provide some secret key(s) to the command you execute. Store these secret keys in ",[59,1391,399],{},[558,1393,1394],{},"With GitLab CI, you can flexibly specify which branches to deploy to.",[558,1396,1397],{},"If you deploy to multiple environments, GitLab will conserve the history of deployments,\nwhich allows you to rollback to any previous version.",[558,1399,1400],{},"For critical parts of your infrastructure, you can enable manual deployment from GitLab interface, instead of automated deployment.",[1402,1403,1404],"style",{},"\nimg.illustration {\n  padding-left: 12%;\n  padding-right: 12%;\n\n}\n@media (max-width: 760px) {\n  img.illustration {\n    padding-left: 0px;\n    padding-right: 0px;\n  }\n}\n",[1402,1406,1407],{},"html pre.shiki code .s7eDp, html code.shiki .s7eDp{--shiki-default:#6F42C1}html pre.shiki code .sYBdl, html code.shiki .sYBdl{--shiki-default:#032F62}html pre.shiki code .sYu0t, html code.shiki .sYu0t{--shiki-default:#005CC5}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 .shJU0, html code.shiki .shJU0{--shiki-default:#22863A}html pre.shiki code .sgsFI, html code.shiki .sgsFI{--shiki-default:#24292E}",{"title":87,"searchDepth":212,"depth":212,"links":1409},[1410,1411,1415,1418,1419,1420,1425],{"id":54,"depth":212,"text":55},{"id":164,"depth":212,"text":165,"children":1412},[1413,1414],{"id":392,"depth":270,"text":393},{"id":421,"depth":270,"text":422},{"id":502,"depth":212,"text":503,"children":1416},[1417],{"id":535,"depth":270,"text":536},{"id":775,"depth":212,"text":776},{"id":972,"depth":212,"text":973},{"id":993,"depth":212,"text":994,"children":1421},[1422,1423,1424],{"id":1065,"depth":270,"text":1066},{"id":1109,"depth":270,"text":1110},{"id":1257,"depth":270,"text":1258},{"id":1379,"depth":212,"text":1380},"engineering","2021-02-05","We walk you through different scenarios to demonstrate the versatility and power of GitLab CI.","md",null,false,"https://res.cloudinary.com/about-gitlab-com/image/upload/v1749662033/Blog/Hero%20Images/intro.jpg",{},"/en-us/blog/ci-deployment-and-environments",{"title":5,"description":1428,"ogTitle":5,"ogDescription":1428,"noIndex":1431,"ogImage":1432,"ogUrl":1436,"ogSiteName":1437,"ogType":1438,"canonicalUrls":1436},"https://about.gitlab.com/blog/ci-deployment-and-environments","https://about.gitlab.com","article","ci-deployment-and-environments","en-us/blog/ci-deployment-and-environments",[1442,1443,1444],"CI","CD","tutorial","BlogPost","2024-07-22","VcLx1zmtOFPatcvFSXuyn4MqmiEF2yyopU3mb_j4QQ0",{"logo":1449,"freeTrial":1454,"sales":1459,"login":1464,"items":1469,"search":1795,"minimal":1826,"duo":1845,"switchNav":1854,"pricingDeployment":1865},{"config":1450},{"href":1451,"dataGaName":1452,"dataGaLocation":1453},"/","gitlab logo","header",{"text":1455,"config":1456},"Get free trial",{"href":1457,"dataGaName":1458,"dataGaLocation":1453},"https://gitlab.com/-/trial_registrations/new?glm_source=about.gitlab.com&glm_content=default-saas-trial/","free trial",{"text":1460,"config":1461},"Request a demo",{"href":1462,"dataGaName":1463,"dataGaLocation":1453},"/sales/?contact-topic=request-demo","sales",{"text":1465,"config":1466},"Sign in",{"href":1467,"dataGaName":1468,"dataGaLocation":1453},"https://gitlab.com/users/sign_in/","sign in",[1470,1499,1598,1603,1717,1773],{"text":1471,"config":1472,"menu":1474},"Platform",{"dataNavLevelOne":1473},"platform",{"type":1475,"columns":1476},"cards",[1477,1483,1491],{"title":1471,"description":1478,"link":1479},"The intelligent orchestration platform for DevSecOps",{"text":1480,"config":1481},"Explore our Platform",{"href":1482,"dataGaName":1473,"dataGaLocation":1453},"/platform/",{"title":1484,"description":1485,"link":1486},"GitLab Duo Agent Platform","Agentic AI for the entire software lifecycle",{"text":1487,"config":1488},"Meet GitLab Duo",{"href":1489,"dataGaName":1490,"dataGaLocation":1453},"/gitlab-duo-agent-platform/","gitlab duo agent platform",{"title":1492,"description":1493,"link":1494},"Why GitLab","See the top reasons enterprises choose GitLab",{"text":1495,"config":1496},"Learn more",{"href":1497,"dataGaName":1498,"dataGaLocation":1453},"/why-gitlab/","why gitlab",{"text":1500,"left":622,"config":1501,"menu":1503},"Product",{"dataNavLevelOne":1502},"solutions",{"type":1504,"link":1505,"columns":1509,"feature":1577},"lists",{"text":1506,"config":1507},"View all Solutions",{"href":1508,"dataGaName":1502,"dataGaLocation":1453},"/solutions/",[1510,1533,1556],{"title":1511,"description":1512,"link":1513,"items":1518},"Automation","CI/CD and automation to accelerate deployment",{"config":1514},{"icon":1515,"href":1516,"dataGaName":1517,"dataGaLocation":1453},"AutomatedCodeAlt","/solutions/delivery-automation/","automated software delivery",[1519,1522,1525,1529],{"text":1520,"config":1521},"CI/CD",{"href":45,"dataGaLocation":1453,"dataGaName":1520},{"text":1484,"config":1523},{"href":1489,"dataGaLocation":1453,"dataGaName":1524},"gitlab duo agent platform - product menu",{"text":1526,"config":1527},"Source Code Management",{"href":1528,"dataGaLocation":1453,"dataGaName":1526},"/solutions/source-code-management/",{"text":1530,"config":1531},"Automated Software Delivery",{"href":1516,"dataGaLocation":1453,"dataGaName":1532},"Automated software delivery",{"title":1534,"description":1535,"link":1536,"items":1541},"Security","Deliver code faster without compromising security",{"config":1537},{"href":1538,"dataGaName":1539,"dataGaLocation":1453,"icon":1540},"/solutions/application-security-testing/","security and compliance","ShieldCheckLight",[1542,1546,1551],{"text":1543,"config":1544},"Application Security Testing",{"href":1538,"dataGaName":1545,"dataGaLocation":1453},"Application security testing",{"text":1547,"config":1548},"Software Supply Chain Security",{"href":1549,"dataGaLocation":1453,"dataGaName":1550},"/solutions/supply-chain/","Software supply chain security",{"text":1552,"config":1553},"Software Compliance",{"href":1554,"dataGaName":1555,"dataGaLocation":1453},"/solutions/software-compliance/","software compliance",{"title":1557,"link":1558,"items":1563},"Measurement",{"config":1559},{"icon":1560,"href":1561,"dataGaName":1562,"dataGaLocation":1453},"DigitalTransformation","/solutions/visibility-measurement/","visibility and measurement",[1564,1568,1572],{"text":1565,"config":1566},"Visibility & Measurement",{"href":1561,"dataGaLocation":1453,"dataGaName":1567},"Visibility and Measurement",{"text":1569,"config":1570},"Value Stream Management",{"href":1571,"dataGaLocation":1453,"dataGaName":1569},"/solutions/value-stream-management/",{"text":1573,"config":1574},"Analytics & Insights",{"href":1575,"dataGaLocation":1453,"dataGaName":1576},"/solutions/analytics-and-insights/","Analytics and insights",{"title":1578,"type":1504,"items":1579},"GitLab for",[1580,1586,1592],{"text":1581,"config":1582},"Enterprise",{"icon":1583,"href":1584,"dataGaLocation":1453,"dataGaName":1585},"Building","/enterprise/","enterprise",{"text":1587,"config":1588},"Small Business",{"icon":1589,"href":1590,"dataGaLocation":1453,"dataGaName":1591},"Work","/small-business/","small business",{"text":1593,"config":1594},"Public Sector",{"icon":1595,"href":1596,"dataGaLocation":1453,"dataGaName":1597},"Organization","/solutions/public-sector/","public sector",{"text":1599,"config":1600},"Pricing",{"href":1601,"dataGaName":1602,"dataGaLocation":1453,"dataNavLevelOne":1602},"/pricing/","pricing",{"text":1604,"config":1605,"menu":1607},"Resources",{"dataNavLevelOne":1606},"resources",{"type":1504,"link":1608,"columns":1612,"feature":1706},{"text":1609,"config":1610},"View all resources",{"href":1611,"dataGaName":1606,"dataGaLocation":1453},"/resources/",[1613,1646,1673],{"title":1614,"items":1615},"Getting started",[1616,1621,1626,1631,1636,1641],{"text":1617,"config":1618},"Install",{"href":1619,"dataGaName":1620,"dataGaLocation":1453},"/install/","install",{"text":1622,"config":1623},"Quick start guides",{"href":1624,"dataGaName":1625,"dataGaLocation":1453},"/get-started/","quick setup checklists",{"text":1627,"config":1628},"Learn",{"href":1629,"dataGaLocation":1453,"dataGaName":1630},"https://university.gitlab.com/","learn",{"text":1632,"config":1633},"Product documentation",{"href":1634,"dataGaName":1635,"dataGaLocation":1453},"https://docs.gitlab.com/","product documentation",{"text":1637,"config":1638},"Best practice videos",{"href":1639,"dataGaName":1640,"dataGaLocation":1453},"/getting-started-videos/","best practice videos",{"text":1642,"config":1643},"Integrations",{"href":1644,"dataGaName":1645,"dataGaLocation":1453},"/integrations/","integrations",{"title":1647,"items":1648},"Discover",[1649,1654,1659,1664,1668],{"text":1650,"config":1651},"Customer success stories",{"href":1652,"dataGaName":1653,"dataGaLocation":1453},"/customers/","customer success stories",{"text":1655,"config":1656},"Blog",{"href":1657,"dataGaName":1658,"dataGaLocation":1453},"/blog/","blog",{"text":1660,"config":1661},"Demo Hub",{"href":1662,"dataGaName":1663,"dataGaLocation":1453},"/demo-hub/","demo hub",{"text":1665,"config":1666},"The Source",{"href":1667,"dataGaName":1658,"dataGaLocation":1453},"/the-source/",{"text":1669,"config":1670},"Remote",{"href":1671,"dataGaName":1672,"dataGaLocation":1453},"https://handbook.gitlab.com/handbook/company/culture/all-remote/","remote",{"title":1674,"items":1675},"Connect",[1676,1681,1686,1691,1696,1701],{"text":1677,"config":1678},"GitLab Services",{"href":1679,"dataGaName":1680,"dataGaLocation":1453},"/services/","services",{"text":1682,"config":1683},"Contribute",{"href":1684,"dataGaName":1685,"dataGaLocation":1453},"https://contributors.gitlab.com","contribute",{"text":1687,"config":1688},"Community",{"href":1689,"dataGaName":1690,"dataGaLocation":1453},"/community/","community",{"text":1692,"config":1693},"Forum",{"href":1694,"dataGaName":1695,"dataGaLocation":1453},"https://forum.gitlab.com/","forum",{"text":1697,"config":1698},"Events",{"href":1699,"dataGaName":1700,"dataGaLocation":1453},"/events/","events",{"text":1702,"config":1703},"Partners",{"href":1704,"dataGaName":1705,"dataGaLocation":1453},"/partners/","partners",{"config":1707,"title":1710,"text":1711,"link":1712},{"background":1708,"textColor":1709},"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":1713,"config":1714},"Read the latest",{"href":1715,"dataGaName":1716,"dataGaLocation":1453},"/whats-new/","whats new",{"text":1718,"config":1719,"menu":1721},"Company",{"dataNavLevelOne":1720},"company",{"type":1504,"columns":1722},[1723],{"items":1724},[1725,1730,1736,1738,1743,1748,1753,1758,1763,1768],{"text":1726,"config":1727},"About",{"href":1728,"dataGaName":1729,"dataGaLocation":1453},"/company/","about",{"text":1731,"config":1732,"footerGa":1735},"Jobs",{"href":1733,"dataGaName":1734,"dataGaLocation":1453},"/jobs/","jobs",{"dataGaName":1734},{"text":1697,"config":1737},{"href":1699,"dataGaName":1700,"dataGaLocation":1453},{"text":1739,"config":1740},"Leadership",{"href":1741,"dataGaName":1742,"dataGaLocation":1453},"/company/team/e-group/","leadership",{"text":1744,"config":1745},"Handbook",{"href":1746,"dataGaName":1747,"dataGaLocation":1453},"https://handbook.gitlab.com/","handbook",{"text":1749,"config":1750},"Investor relations",{"href":1751,"dataGaName":1752,"dataGaLocation":1453},"https://ir.gitlab.com/overview/default.aspx","investor relations",{"text":1754,"config":1755},"Trust Center",{"href":1756,"dataGaName":1757,"dataGaLocation":1453},"/security/","trust center",{"text":1759,"config":1760},"AI Transparency Center",{"href":1761,"dataGaName":1762,"dataGaLocation":1453},"/ai-transparency-center/","ai transparency center",{"text":1764,"config":1765},"Newsletter",{"href":1766,"dataGaName":1767,"dataGaLocation":1453},"/company/contact/#contact-forms","newsletter",{"text":1769,"config":1770},"Press",{"href":1771,"dataGaName":1772,"dataGaLocation":1453},"/press/","press",{"text":1774,"config":1775,"menu":1776},"Contact us",{"dataNavLevelOne":1720},{"type":1504,"columns":1777},[1778],{"items":1779},[1780,1785,1790],{"text":1781,"config":1782},"Talk to sales",{"href":1783,"dataGaName":1784,"dataGaLocation":1453},"/sales/","talk to sales",{"text":1786,"config":1787},"Support portal",{"href":1788,"dataGaName":1789,"dataGaLocation":1453},"https://support.gitlab.com/hc/en-us","support portal",{"text":1791,"config":1792},"Customer portal",{"href":1793,"dataGaName":1794,"dataGaLocation":1453},"https://customers.gitlab.com/customers/sign_in/","customer portal",{"close":1796,"login":1797,"suggestions":1804},"Close",{"text":1798,"link":1799},"To search repositories and projects, login to",{"text":1800,"config":1801},"gitlab.com",{"href":1467,"dataGaName":1802,"dataGaLocation":1803},"search login","search",{"text":1805,"default":1806},"Suggestions",[1807,1809,1813,1815,1819,1823],{"text":1484,"config":1808},{"href":1489,"dataGaName":1484,"dataGaLocation":1803},{"text":1810,"config":1811},"Code Suggestions (AI)",{"href":1812,"dataGaName":1810,"dataGaLocation":1803},"/solutions/code-suggestions/",{"text":1520,"config":1814},{"href":45,"dataGaName":1520,"dataGaLocation":1803},{"text":1816,"config":1817},"GitLab on AWS",{"href":1818,"dataGaName":1816,"dataGaLocation":1803},"/partners/technology-partners/aws/",{"text":1820,"config":1821},"GitLab on Google Cloud",{"href":1822,"dataGaName":1820,"dataGaLocation":1803},"/partners/technology-partners/google-cloud-platform/",{"text":1824,"config":1825},"Why GitLab?",{"href":1497,"dataGaName":1824,"dataGaLocation":1803},{"freeTrial":1827,"mobileIcon":1832,"desktopIcon":1837,"secondaryButton":1840},{"text":1828,"config":1829},"Start free trial",{"href":1830,"dataGaName":1458,"dataGaLocation":1831},"https://gitlab.com/-/trials/new/","nav",{"altText":1833,"config":1834},"Gitlab Icon",{"src":1835,"dataGaName":1836,"dataGaLocation":1831},"https://res.cloudinary.com/about-gitlab-com/image/upload/v1758203874/jypbw1jx72aexsoohd7x.svg","gitlab icon",{"altText":1833,"config":1838},{"src":1839,"dataGaName":1836,"dataGaLocation":1831},"https://res.cloudinary.com/about-gitlab-com/image/upload/v1758203875/gs4c8p8opsgvflgkswz9.svg",{"text":1841,"config":1842},"Get Started",{"href":1843,"dataGaName":1844,"dataGaLocation":1831},"https://gitlab.com/-/trial_registrations/new?glm_source=about.gitlab.com/get-started/","get started",{"freeTrial":1846,"mobileIcon":1850,"desktopIcon":1852},{"text":1847,"config":1848},"Learn more about GitLab Duo",{"href":1489,"dataGaName":1849,"dataGaLocation":1831},"gitlab duo",{"altText":1833,"config":1851},{"src":1835,"dataGaName":1836,"dataGaLocation":1831},{"altText":1833,"config":1853},{"src":1839,"dataGaName":1836,"dataGaLocation":1831},{"button":1855,"mobileIcon":1860,"desktopIcon":1862},{"text":1856,"config":1857},"/switch",{"href":1858,"dataGaName":1859,"dataGaLocation":1831},"#contact","switch",{"altText":1833,"config":1861},{"src":1835,"dataGaName":1836,"dataGaLocation":1831},{"altText":1833,"config":1863},{"src":1864,"dataGaName":1836,"dataGaLocation":1831},"https://res.cloudinary.com/about-gitlab-com/image/upload/v1773335277/ohhpiuoxoldryzrnhfrh.png",{"freeTrial":1866,"mobileIcon":1871,"desktopIcon":1873},{"text":1867,"config":1868},"Back to pricing",{"href":1601,"dataGaName":1869,"dataGaLocation":1831,"icon":1870},"back to pricing","GoBack",{"altText":1833,"config":1872},{"src":1835,"dataGaName":1836,"dataGaLocation":1831},{"altText":1833,"config":1874},{"src":1839,"dataGaName":1836,"dataGaLocation":1831},{"title":1876,"titleMobile":1877,"button":1878,"config":1883},"Duo Agent Platform delivers 400% ROI, per new Forrester Consulting study.","400% ROI: Forrester TEI for GitLab Duo",{"text":1495,"config":1879},{"href":1880,"dataGaName":1881,"dataGaLocation":1882},"https://about.gitlab.com/blog/gitlab-duo-agent-platform-delivers-400-percent-roi/","forrester-tei-dap-banner","global-banner",{"layout":1884,"disabled":1431},"release",{"data":1886},{"text":1887,"source":1888,"edit":1894,"contribute":1899,"config":1904,"items":1909,"minimal":2119},"Git is a trademark of Software Freedom Conservancy and our use of 'GitLab' is under license",{"text":1889,"config":1890},"View page source",{"href":1891,"dataGaName":1892,"dataGaLocation":1893},"https://gitlab.com/gitlab-com/marketing/digital-experience/about-gitlab-com/","page source","footer",{"text":1895,"config":1896},"Edit this page",{"href":1897,"dataGaName":1898,"dataGaLocation":1893},"https://gitlab.com/gitlab-com/marketing/digital-experience/about-gitlab-com/-/blob/main/content/","web ide",{"text":1900,"config":1901},"Please contribute",{"href":1902,"dataGaName":1903,"dataGaLocation":1893},"https://gitlab.com/gitlab-com/marketing/digital-experience/about-gitlab-com/-/blob/main/CONTRIBUTING.md/","please contribute",{"twitter":1905,"facebook":1906,"youtube":1907,"linkedin":1908},"https://twitter.com/gitlab","https://www.facebook.com/gitlab","https://www.youtube.com/channel/UCnMGQ8QHMAnVIsI3xJrihhg","https://www.linkedin.com/company/gitlab-com",[1910,1957,2011,2055,2087],{"title":1599,"links":1911,"subMenu":1926},[1912,1916,1921],{"text":1913,"config":1914},"View plans",{"href":1601,"dataGaName":1915,"dataGaLocation":1893},"view plans",{"text":1917,"config":1918},"Why Premium?",{"href":1919,"dataGaName":1920,"dataGaLocation":1893},"/pricing/premium/","why premium",{"text":1922,"config":1923},"Why Ultimate?",{"href":1924,"dataGaName":1925,"dataGaLocation":1893},"/pricing/ultimate/","why ultimate",[1927],{"title":1928,"links":1929},"Contact Us",[1930,1933,1935,1937,1942,1947,1952],{"text":1931,"config":1932},"Contact sales",{"href":1783,"dataGaName":1463,"dataGaLocation":1893},{"text":1786,"config":1934},{"href":1788,"dataGaName":1789,"dataGaLocation":1893},{"text":1791,"config":1936},{"href":1793,"dataGaName":1794,"dataGaLocation":1893},{"text":1938,"config":1939},"Status",{"href":1940,"dataGaName":1941,"dataGaLocation":1893},"https://status.gitlab.com/","status",{"text":1943,"config":1944},"Terms of use",{"href":1945,"dataGaName":1946,"dataGaLocation":1893},"/terms/","terms of use",{"text":1948,"config":1949},"Privacy statement",{"href":1950,"dataGaName":1951,"dataGaLocation":1893},"/privacy/","privacy statement",{"text":1953,"config":1954},"Cookie preferences",{"dataGaName":1955,"dataGaLocation":1893,"id":1956,"isOneTrustButton":622},"cookie preferences","ot-sdk-btn",{"title":1500,"links":1958,"subMenu":1967},[1959,1963],{"text":1960,"config":1961},"DevSecOps platform",{"href":1482,"dataGaName":1962,"dataGaLocation":1893},"devsecops platform",{"text":1964,"config":1965},"AI-Assisted Development",{"href":1489,"dataGaName":1966,"dataGaLocation":1893},"ai-assisted development",[1968],{"title":1969,"links":1970},"Topics",[1971,1976,1981,1986,1991,1996,2001,2006],{"text":1972,"config":1973},"CICD",{"href":1974,"dataGaName":1975,"dataGaLocation":1893},"/topics/ci-cd/","cicd",{"text":1977,"config":1978},"GitOps",{"href":1979,"dataGaName":1980,"dataGaLocation":1893},"/topics/gitops/","gitops",{"text":1982,"config":1983},"DevOps",{"href":1984,"dataGaName":1985,"dataGaLocation":1893},"/topics/devops/","devops",{"text":1987,"config":1988},"Version Control",{"href":1989,"dataGaName":1990,"dataGaLocation":1893},"/topics/version-control/","version control",{"text":1992,"config":1993},"DevSecOps",{"href":1994,"dataGaName":1995,"dataGaLocation":1893},"/topics/devsecops/","devsecops",{"text":1997,"config":1998},"Cloud Native",{"href":1999,"dataGaName":2000,"dataGaLocation":1893},"/topics/cloud-native/","cloud native",{"text":2002,"config":2003},"AI for Coding",{"href":2004,"dataGaName":2005,"dataGaLocation":1893},"/topics/devops/ai-for-coding/","ai for coding",{"text":2007,"config":2008},"Agentic AI",{"href":2009,"dataGaName":2010,"dataGaLocation":1893},"/topics/agentic-ai/","agentic ai",{"title":2012,"links":2013},"Solutions",[2014,2016,2018,2023,2027,2030,2034,2037,2039,2042,2045,2050],{"text":1543,"config":2015},{"href":1538,"dataGaName":1543,"dataGaLocation":1893},{"text":1532,"config":2017},{"href":1516,"dataGaName":1517,"dataGaLocation":1893},{"text":2019,"config":2020},"Agile development",{"href":2021,"dataGaName":2022,"dataGaLocation":1893},"/solutions/agile-delivery/","agile delivery",{"text":2024,"config":2025},"SCM",{"href":1528,"dataGaName":2026,"dataGaLocation":1893},"source code management",{"text":1972,"config":2028},{"href":45,"dataGaName":2029,"dataGaLocation":1893},"continuous integration & delivery",{"text":2031,"config":2032},"Value stream management",{"href":1571,"dataGaName":2033,"dataGaLocation":1893},"value stream management",{"text":1977,"config":2035},{"href":2036,"dataGaName":1980,"dataGaLocation":1893},"/solutions/gitops/",{"text":1581,"config":2038},{"href":1584,"dataGaName":1585,"dataGaLocation":1893},{"text":2040,"config":2041},"Small business",{"href":1590,"dataGaName":1591,"dataGaLocation":1893},{"text":2043,"config":2044},"Public sector",{"href":1596,"dataGaName":1597,"dataGaLocation":1893},{"text":2046,"config":2047},"Education",{"href":2048,"dataGaName":2049,"dataGaLocation":1893},"/solutions/education/","education",{"text":2051,"config":2052},"Financial services",{"href":2053,"dataGaName":2054,"dataGaLocation":1893},"/solutions/finance/","financial services",{"title":1604,"links":2056},[2057,2059,2061,2063,2066,2068,2071,2073,2075,2077,2079,2081,2083,2085],{"text":1617,"config":2058},{"href":1619,"dataGaName":1620,"dataGaLocation":1893},{"text":1622,"config":2060},{"href":1624,"dataGaName":1625,"dataGaLocation":1893},{"text":1627,"config":2062},{"href":1629,"dataGaName":1630,"dataGaLocation":1893},{"text":1632,"config":2064},{"href":1634,"dataGaName":2065,"dataGaLocation":1893},"docs",{"text":1655,"config":2067},{"href":1657,"dataGaName":1658,"dataGaLocation":1893},{"text":2069,"config":2070},"What's new",{"href":1715,"dataGaName":1716,"dataGaLocation":1893},{"text":1650,"config":2072},{"href":1652,"dataGaName":1653,"dataGaLocation":1893},{"text":1669,"config":2074},{"href":1671,"dataGaName":1672,"dataGaLocation":1893},{"text":1677,"config":2076},{"href":1679,"dataGaName":1680,"dataGaLocation":1893},{"text":1682,"config":2078},{"href":1684,"dataGaName":1685,"dataGaLocation":1893},{"text":1687,"config":2080},{"href":1689,"dataGaName":1690,"dataGaLocation":1893},{"text":1692,"config":2082},{"href":1694,"dataGaName":1695,"dataGaLocation":1893},{"text":1697,"config":2084},{"href":1699,"dataGaName":1700,"dataGaLocation":1893},{"text":1702,"config":2086},{"href":1704,"dataGaName":1705,"dataGaLocation":1893},{"title":1718,"links":2088},[2089,2091,2093,2095,2097,2099,2103,2108,2110,2112,2114],{"text":1726,"config":2090},{"href":1728,"dataGaName":1720,"dataGaLocation":1893},{"text":1731,"config":2092},{"href":1733,"dataGaName":1734,"dataGaLocation":1893},{"text":1739,"config":2094},{"href":1741,"dataGaName":1742,"dataGaLocation":1893},{"text":1744,"config":2096},{"href":1746,"dataGaName":1747,"dataGaLocation":1893},{"text":1749,"config":2098},{"href":1751,"dataGaName":1752,"dataGaLocation":1893},{"text":2100,"config":2101},"Sustainability",{"href":2102,"dataGaName":2100,"dataGaLocation":1893},"/sustainability/",{"text":2104,"config":2105},"Diversity, inclusion and belonging (DIB)",{"href":2106,"dataGaName":2107,"dataGaLocation":1893},"/diversity-inclusion-belonging/","Diversity, inclusion and belonging",{"text":1754,"config":2109},{"href":1756,"dataGaName":1757,"dataGaLocation":1893},{"text":1764,"config":2111},{"href":1766,"dataGaName":1767,"dataGaLocation":1893},{"text":1769,"config":2113},{"href":1771,"dataGaName":1772,"dataGaLocation":1893},{"text":2115,"config":2116},"Modern Slavery Transparency Statement",{"href":2117,"dataGaName":2118,"dataGaLocation":1893},"https://handbook.gitlab.com/handbook/legal/modern-slavery-act-transparency-statement/","modern slavery transparency statement",{"items":2120},[2121,2124,2127],{"text":2122,"config":2123},"Terms",{"href":1945,"dataGaName":1946,"dataGaLocation":1893},{"text":2125,"config":2126},"Cookies",{"dataGaName":1955,"dataGaLocation":1893,"id":1956,"isOneTrustButton":622},{"text":2128,"config":2129},"Privacy",{"href":1950,"dataGaName":1951,"dataGaLocation":1893},[2131,2145],{"id":2132,"title":7,"body":1430,"config":2133,"content":2135,"description":1430,"extension":2139,"meta":2140,"navigation":622,"path":2141,"seo":2142,"stem":2143,"__hash__":2144},"blogAuthors/en-us/blog/authors/ivan-nemytchenko.yml",{"template":2134},"BlogAuthor",{"name":7,"config":2136},{"headshot":2137,"ctfId":2138},"https://res.cloudinary.com/about-gitlab-com/image/upload/v1749659488/Blog/Author%20Headshots/gitlab-logo-extra-whitespace.png","Ivan-Nemytchenko","yml",{},"/en-us/blog/authors/ivan-nemytchenko",{},"en-us/blog/authors/ivan-nemytchenko","LIveI3a-Zqab8RyZk3ijJwKPTck0i-FN_JanwQMOZr4",{"id":2146,"title":8,"body":1430,"config":2147,"content":2148,"description":1430,"extension":2139,"meta":2152,"navigation":622,"path":2153,"seo":2154,"stem":2155,"__hash__":2156},"blogAuthors/en-us/blog/authors/cesar-saavedra.yml",{"template":2134},{"name":8,"config":2149},{"headshot":2150,"ctfId":2151},"https://res.cloudinary.com/about-gitlab-com/image/upload/v1749659600/Blog/Author%20Headshots/csaavedra1-headshot.jpg","csaavedra1",{},"/en-us/blog/authors/cesar-saavedra",{},"en-us/blog/authors/cesar-saavedra","SMqRf-z0W5m5GROz_dXGjmuIb3YaOwm_n_RfeK16GcA",[2158,2167,2175],{"title":2159,"description":2160,"heroImage":2161,"category":1426,"date":2162,"authors":2163,"slug":2166,"externalUrl":1430},"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",[2164,2165],"Mathias Ewald","Martin Paloncy, Edgeless Systems","confidential-ai-for-gitlab-self-hosted",{"title":2168,"description":2169,"heroImage":2170,"category":1426,"date":2171,"authors":2172,"slug":2174,"externalUrl":1430},"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",[2173],"Lysanne Pinto","green-devops-carbon-measurement-cicd-pipeline",{"title":2176,"description":2177,"heroImage":2178,"category":1426,"date":2179,"authors":2180,"slug":2182,"externalUrl":1430},"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",[2181],"Paul Meresanu","how-to-build-ci-cd-observability-at-scale",{"promotions":2184},[2185,2199,2211,2223],{"id":2186,"categories":2187,"header":2189,"text":2190,"button":2191,"image":2196},"ai-modernization",[2188],"ai","Is AI achieving its promise at scale?","Quiz will take 5 minutes or less",{"text":2192,"config":2193},"Get your AI maturity score",{"href":2194,"dataGaName":2195,"dataGaLocation":1658},"/assessments/ai-modernization-assessment/","modernization assessment",{"config":2197},{"src":2198},"https://res.cloudinary.com/about-gitlab-com/image/upload/v1772138786/qix0m7kwnd8x2fh1zq49.png",{"id":2200,"categories":2201,"header":2203,"text":2190,"button":2204,"image":2208},"devops-modernization",[2202,1995],"product","Are you just managing tools or shipping innovation?",{"text":2205,"config":2206},"Get your DevOps maturity score",{"href":2207,"dataGaName":2195,"dataGaLocation":1658},"/assessments/devops-modernization-assessment/",{"config":2209},{"src":2210},"https://res.cloudinary.com/about-gitlab-com/image/upload/v1772138785/eg818fmakweyuznttgid.png",{"id":2212,"categories":2213,"header":2215,"text":2190,"button":2216,"image":2220},"security-modernization",[2214],"security","Are you trading speed for security?",{"text":2217,"config":2218},"Get your security maturity score",{"href":2219,"dataGaName":2195,"dataGaLocation":1658},"/assessments/security-modernization-assessment/",{"config":2221},{"src":2222},"https://res.cloudinary.com/about-gitlab-com/image/upload/v1772138786/p4pbqd9nnjejg5ds6mdk.png",{"id":2224,"paths":2225,"header":2228,"text":2229,"button":2230,"image":2235},"github-azure-migration",[2226,2227],"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":2231,"config":2232},"See how GitLab compares to GitHub",{"href":2233,"dataGaName":2234,"dataGaLocation":1658},"/compare/gitlab-vs-github/github-azure-migration/","github azure migration",{"config":2236},{"src":2210},{"header":2238,"blurb":2239,"button":2240,"secondaryButton":2245},"Start building faster today","See what your team can do with the intelligent orchestration platform for DevSecOps.\n",{"text":2241,"config":2242},"Get your free trial",{"href":2243,"dataGaName":1458,"dataGaLocation":2244},"https://gitlab.com/-/trial_registrations/new?glm_content=default-saas-trial&glm_source=about.gitlab.com/","feature",{"text":1931,"config":2246},{"href":1783,"dataGaName":1463,"dataGaLocation":2244},1786803767283]