[{"data":1,"prerenderedAt":1225},["ShallowReactive",2],{"/blog/behind-the-scenes-how-we-built-review-apps":3,"navigation-en-us":439,"banner-en-us":866,"footer-en-us":876,"blog-post-authors-en-us-Mark Pundsack":1120,"blog-related-posts-en-us-behind-the-scenes-how-we-built-review-apps":1135,"blog-promotions-en-us":1161,"next-steps-en-us":1215},{"id":4,"title":5,"authors":6,"body":8,"category":415,"date":416,"description":417,"extension":418,"externalUrl":419,"faq":419,"featured":420,"heroImage":421,"meta":422,"navigation":264,"path":430,"seo":431,"slug":435,"stem":436,"tags":419,"template":437,"updatedDate":419,"__hash__":438},"blogPosts/en-us/blog/behind-the-scenes-how-we-built-review-apps.md","Behind the scenes: How we built Review Apps",[7],"Mark Pundsack",{"type":9,"value":10,"toc":409},"minimark",[11,22,43,56,59,62,65,143,148,159,212,216,222,323,327,330,338,342,345,348,354,383,405],[12,13,14,15,21],"p",{},"A bunch of us on the GitLab team have known for a while just how important review apps are. Even though this wasn’t something that a lot of customers asked for, we knew we had to tackle it because of how we'd seen it transform a developer's flow. We also knew that tightly integrating it into GitLab would make it even better. Although our aspirations for the feature started out gigantic and magical, we ultimately constrained them to the practical and concrete. Here's a behind-the-scenes look at how we iterated and shipped ",[16,17,20],"a",{"href":18,"rel":19},"https://docs.gitlab.com/ci/review_apps/",[],"Review Apps"," over the last 3 releases.",[12,23,24,25,30,31,36,37,42],{},"Full disclosure: I used to work at Heroku on the team that shipped ",[16,26,29],{"href":27,"rel":28},"https://devcenter.heroku.com/articles/github-integration-review-apps",[],"Heroku Review Apps",", and some of that work was inspired by a tool called ",[16,32,35],{"href":33,"rel":34},"https://github.com/rainforestapp/fourchette",[],"Fourchette",", which was created by the great folks at ",[16,38,41],{"href":39,"rel":40},"https://www.rainforestqa.com/",[],"Rainforest QA",". Even outside of my personal bias, our CEO, CI Lead and others had seen things like this elsewhere and saw how transformative it could be.",[44,45,48,49],"figure",{"className":46},[47],"video_container","\n  ",[50,51,55],"iframe",{"src":52,"frameBorder":53,"allowFullScreen":54},"https://www.youtube.com/embed/CteZol_7pxo?start=1713","0","true"," ",[12,57,58],{},"There are a ton of different ways we could have shipped it. We started months ago, mostly discussing asynchronously on GitLab issues, with big ideas that made Review Apps seem kind of daunting. We had ideas for black magic to detect Kubernetes settings, configure all the review app stuff for you, make them work only for merge requests rather than for every branch, etc. It felt like something that might not ship for months, if not years, because of all the complexity and dependencies.",[12,60,61],{},"But then a few of us got together to see how we could simplify, starting with a written proposal, then collaborating in a Google Doc, then a live chat over Google Hangouts, and we came up with what we felt would be the smallest thing we could do to enable the functionality. We shared that proposal back on the public issue. After a couple days, we pushed it even further and really cut the scope.",[12,63,64],{},"Here are a few links to some of the issues we went through, starting with a large meta issue, down to a concrete proposal and then counterproposal, until finally, the winning proposal emerged:",[66,67,68,86,97,111,121,132],"ul",{},[69,70,71,76,77,81,82],"li",{},[16,72,75],{"href":73,"rel":74},"https://gitlab.com/gitlab-org/gitlab-ce/issues/3286",[],"#3286"," - ",[78,79,80],"span",{},"Epic"," GitLab Deploy, ",[83,84,85],"em",{},"opened 1 year ago",[69,87,88,93,94],{},[16,89,92],{"href":90,"rel":91},"https://gitlab.com/gitlab-org/gitlab-ce/issues/14698",[],"#14698"," - Container scheduler for 4 use cases, ",[83,95,96],{},"9 months ago",[69,98,99,76,104,107,108],{},[16,100,103],{"href":101,"rel":102},"https://gitlab.com/gitlab-org/gitlab-ce/issues/20255",[],"#20255",[78,105,106],{},"Meta"," Review Apps, ",[83,109,110],{},"5 months ago",[69,112,113,118,119],{},[16,114,117],{"href":115,"rel":116},"https://gitlab.com/gitlab-org/gitlab-ce/issues/20054",[],"#20054"," - Review Apps as Runner job, ",[83,120,110],{},[69,122,123,128,129],{},[16,124,127],{"href":125,"rel":126},"https://gitlab.com/gitlab-org/gitlab-ce/issues/21411",[],"#21411"," - How do we do deploys, ",[83,130,131],{},"4 months ago",[69,133,134,139,140],{},[16,135,138],{"href":136,"rel":137},"https://gitlab.com/gitlab-org/gitlab-ce/issues/21971",[],"#21971"," - Dynamic environments aka Review Apps, ",[83,141,142],{},"3 months ago",[144,145,147],"h2",{"id":146},"_812","8.12",[12,149,150,151,155,156,158],{},"We initially offered experimental support for Review Apps in GitLab 8.12. At that point, we had reduced it to just one or two seemingly small changes to the ",[152,153,154],"code",{},".gitlab-ci.yml"," format. Specifically, we let you specify the URL of an environment in ",[152,157,154],{}," (rather than just in the web UI), and we let you use variables within the environment name and URL. Trivial, right? One extra keyword and another small change enabled environments to now be “dynamic,” which is the core of Review Apps.",[160,161,166],"pre",{"className":162,"code":163,"language":164,"meta":165,"style":165},"language-yaml shiki shiki-themes github-light","review_apps:\n  environment:\n    name: review/$CI_BUILD_REF_NAME\n    url: http://$CI_BUILD_REF_NAME.review.gitlab.com/\n\n","yaml","",[152,167,168,180,188,201],{"__ignoreMap":165},[78,169,172,176],{"class":170,"line":171},"line",1,[78,173,175],{"class":174},"shJU0","review_apps",[78,177,179],{"class":178},"sgsFI",":\n",[78,181,183,186],{"class":170,"line":182},2,[78,184,185],{"class":174},"  environment",[78,187,179],{"class":178},[78,189,191,194,197],{"class":170,"line":190},3,[78,192,193],{"class":174},"    name",[78,195,196],{"class":178},": ",[78,198,200],{"class":199},"sYBdl","review/$CI_BUILD_REF_NAME\n",[78,202,204,207,209],{"class":170,"line":203},4,[78,205,206],{"class":174},"    url",[78,208,196],{"class":178},[78,210,211],{"class":199},"http://$CI_BUILD_REF_NAME.review.gitlab.com/\n",[144,213,215],{"id":214},"_813","8.13",[12,217,218,219,221],{},"Then in 8.13 we implemented another key piece: the ability to delete or stop apps. Again, there were all sorts of complex ideas for how to solve this, but we settled on the smallest change possible that enabled the feature. In this case, that was reusing our existing concept of manual actions, or jobs that run in a pipeline only when a user triggers them manually from the web UI. So we said, if you can script how to delete your app, just create a manual action job for it. Then we added a new keyword in ",[152,220,154],{}," so you could identify which of these jobs stopped the environment, and we displayed a different UI for that - now you get a little square stop button instead of the triangle play button. Again, pretty trivial.",[160,223,225],{"className":162,"code":224,"language":164,"meta":165,"style":165},"review:\n  environment:\n    name: review/$app\n    on_stop: stop_review\n\nstop_review:\n  script: echo Delete My App\n  when: manual\n  environment:\n    name: review/$app\n    action: stop\n\n",[152,226,227,234,240,249,259,266,274,285,296,303,312],{"__ignoreMap":165},[78,228,229,232],{"class":170,"line":171},[78,230,231],{"class":174},"review",[78,233,179],{"class":178},[78,235,236,238],{"class":170,"line":182},[78,237,185],{"class":174},[78,239,179],{"class":178},[78,241,242,244,246],{"class":170,"line":190},[78,243,193],{"class":174},[78,245,196],{"class":178},[78,247,248],{"class":199},"review/$app\n",[78,250,251,254,256],{"class":170,"line":203},[78,252,253],{"class":174},"    on_stop",[78,255,196],{"class":178},[78,257,258],{"class":199},"stop_review\n",[78,260,262],{"class":170,"line":261},5,[78,263,265],{"emptyLinePlaceholder":264},true,"\n",[78,267,269,272],{"class":170,"line":268},6,[78,270,271],{"class":174},"stop_review",[78,273,179],{"class":178},[78,275,277,280,282],{"class":170,"line":276},7,[78,278,279],{"class":174},"  script",[78,281,196],{"class":178},[78,283,284],{"class":199},"echo Delete My App\n",[78,286,288,291,293],{"class":170,"line":287},8,[78,289,290],{"class":174},"  when",[78,292,196],{"class":178},[78,294,295],{"class":199},"manual\n",[78,297,299,301],{"class":170,"line":298},9,[78,300,185],{"class":174},[78,302,179],{"class":178},[78,304,306,308,310],{"class":170,"line":305},10,[78,307,193],{"class":174},[78,309,196],{"class":178},[78,311,248],{"class":199},[78,313,315,318,320],{"class":170,"line":314},11,[78,316,317],{"class":174},"    action",[78,319,196],{"class":178},[78,321,322],{"class":199},"stop\n",[144,324,326],{"id":325},"_814","8.14",[12,328,329],{},"Most recently, in the 8.14 release, we made it so that we automatically detect when a branch is deleted, and run that manual action automatically for you. We also realized that with tons of Review Apps, your environments list might get unmanageable. To mitigate this, we came up with the convention that if you named your review app starting with a common name and then a slash, we’d treat that like a folder by which to group your apps, so the interface can show a bunch of Review Apps behind a collapsed folder. Once again, these are relatively small changes.",[66,331,332,335],{},[69,333,334],{},"Auto-stop on branch delete",[69,336,337],{},"Folders in environment list",[144,339,341],{"id":340},"wrap-up","Wrap up",[12,343,344],{},"Ultimately this really complex, life changing feature was broken down into 3 releases of the minimal viable change.",[12,346,347],{},"While we say Review Apps is now complete, it’s not finished. In fact, we have a saying that nothing is ever finished because we’re always looking for the minimal change, and then iterating. By shipping smaller pieces, we not only deliver faster, but we learn from what’s been shipped, and then iterate smarter.",[12,349,350,351,353],{},"We’ve now got follow-on issues to look at simplifying the ",[152,352,154],{}," syntax for Review Apps, and even adding back some of that magic we originally envisioned. We’ll continue to iterate, and your feedback is key to us shipping better.",[66,355,356,367,375],{},[69,357,358,363,364,366],{},[16,359,362],{"href":360,"rel":361},"https://gitlab.com/gitlab-org/gitlab-ce/issues/25138",[],"#25138"," - Simplify ",[152,365,154],{}," syntax for stopping Review Apps",[69,368,369,374],{},[16,370,373],{"href":371,"rel":372},"https://gitlab.com/gitlab-org/gitlab-ce/issues/24197",[],"#24197"," - Smart deploy",[69,376,377,382],{},[16,378,381],{"href":379,"rel":380},"https://gitlab.com/gitlab-org/gitlab-ce/issues/23580",[],"#23580"," - Auto deploy",[12,384,385],{},[83,386,387,388,393,394,398,399,404],{},"Tweet us ",[16,389,392],{"href":390,"rel":391},"https://twitter.com/gitlab",[],"@GitLab",", check out our ",[16,395,397],{"href":396},"/jobs/","job openings",", or add your questions and suggestions to our ",[16,400,403],{"href":401,"rel":402},"https://gitlab.com/gitlab-org/gitlab-ce/issues",[],"issue tracker","!",[406,407,408],"style",{},"html pre.shiki code .shJU0, html code.shiki .shJU0{--shiki-default:#22863A}html pre.shiki code .sgsFI, html code.shiki .sgsFI{--shiki-default:#24292E}html pre.shiki code .sYBdl, html code.shiki .sYBdl{--shiki-default:#032F62}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);}",{"title":165,"searchDepth":182,"depth":182,"links":410},[411,412,413,414],{"id":146,"depth":182,"text":147},{"id":214,"depth":182,"text":215},{"id":325,"depth":182,"text":326},{"id":340,"depth":182,"text":341},"engineering","2017-01-04","GitLab's Head of Product shares an inside look at iterating on one of our latest features","md",null,false,"https://res.cloudinary.com/about-gitlab-com/image/upload/v1749668402/Blog/Hero%20Images/code-gitlab-tanuki.png",{"excerpt":423},{"type":9,"value":424},[425],[12,426,14,427,21],{},[16,428,20],{"href":18,"rel":429},[],"/en-us/blog/behind-the-scenes-how-we-built-review-apps",{"title":5,"description":417,"ogTitle":5,"ogDescription":417,"noIndex":420,"ogImage":421,"ogUrl":432,"ogSiteName":433,"ogType":434,"canonicalUrls":432},"https://about.gitlab.com/blog/behind-the-scenes-how-we-built-review-apps","https://about.gitlab.com","article","behind-the-scenes-how-we-built-review-apps","en-us/blog/behind-the-scenes-how-we-built-review-apps","BlogPost","dSlyeaLOdDFuQjp64-V2Ix5c1w47yZJALNTcOcctsU4",{"logo":440,"freeTrial":445,"sales":450,"login":455,"items":460,"search":786,"minimal":817,"duo":836,"switchNav":845,"pricingDeployment":856},{"config":441},{"href":442,"dataGaName":443,"dataGaLocation":444},"/","gitlab logo","header",{"text":446,"config":447},"Get free trial",{"href":448,"dataGaName":449,"dataGaLocation":444},"https://gitlab.com/-/trial_registrations/new?glm_source=about.gitlab.com&glm_content=default-saas-trial/","free trial",{"text":451,"config":452},"Request a demo",{"href":453,"dataGaName":454,"dataGaLocation":444},"/sales/?contact-topic=request-demo","sales",{"text":456,"config":457},"Sign in",{"href":458,"dataGaName":459,"dataGaLocation":444},"https://gitlab.com/users/sign_in/","sign in",[461,490,590,595,709,764],{"text":462,"config":463,"menu":465},"Platform",{"dataNavLevelOne":464},"platform",{"type":466,"columns":467},"cards",[468,474,482],{"title":462,"description":469,"link":470},"The intelligent orchestration platform for DevSecOps",{"text":471,"config":472},"Explore our Platform",{"href":473,"dataGaName":464,"dataGaLocation":444},"/platform/",{"title":475,"description":476,"link":477},"GitLab Duo Agent Platform","Agentic AI for the entire software lifecycle",{"text":478,"config":479},"Meet GitLab Duo",{"href":480,"dataGaName":481,"dataGaLocation":444},"/gitlab-duo-agent-platform/","gitlab duo agent platform",{"title":483,"description":484,"link":485},"Why GitLab","See the top reasons enterprises choose GitLab",{"text":486,"config":487},"Learn more",{"href":488,"dataGaName":489,"dataGaLocation":444},"/why-gitlab/","why gitlab",{"text":491,"left":264,"config":492,"menu":494},"Product",{"dataNavLevelOne":493},"solutions",{"type":495,"link":496,"columns":500,"feature":569},"lists",{"text":497,"config":498},"View all Solutions",{"href":499,"dataGaName":493,"dataGaLocation":444},"/solutions/",[501,525,548],{"title":502,"description":503,"link":504,"items":509},"Automation","CI/CD and automation to accelerate deployment",{"config":505},{"icon":506,"href":507,"dataGaName":508,"dataGaLocation":444},"AutomatedCodeAlt","/solutions/delivery-automation/","automated software delivery",[510,514,517,521],{"text":511,"config":512},"CI/CD",{"href":513,"dataGaLocation":444,"dataGaName":511},"/solutions/continuous-integration/",{"text":475,"config":515},{"href":480,"dataGaLocation":444,"dataGaName":516},"gitlab duo agent platform - product menu",{"text":518,"config":519},"Source Code Management",{"href":520,"dataGaLocation":444,"dataGaName":518},"/solutions/source-code-management/",{"text":522,"config":523},"Automated Software Delivery",{"href":507,"dataGaLocation":444,"dataGaName":524},"Automated software delivery",{"title":526,"description":527,"link":528,"items":533},"Security","Deliver code faster without compromising security",{"config":529},{"href":530,"dataGaName":531,"dataGaLocation":444,"icon":532},"/solutions/application-security-testing/","security and compliance","ShieldCheckLight",[534,538,543],{"text":535,"config":536},"Application Security Testing",{"href":530,"dataGaName":537,"dataGaLocation":444},"Application security testing",{"text":539,"config":540},"Software Supply Chain Security",{"href":541,"dataGaLocation":444,"dataGaName":542},"/solutions/supply-chain/","Software supply chain security",{"text":544,"config":545},"Software Compliance",{"href":546,"dataGaName":547,"dataGaLocation":444},"/solutions/software-compliance/","software compliance",{"title":549,"link":550,"items":555},"Measurement",{"config":551},{"icon":552,"href":553,"dataGaName":554,"dataGaLocation":444},"DigitalTransformation","/solutions/visibility-measurement/","visibility and measurement",[556,560,564],{"text":557,"config":558},"Visibility & Measurement",{"href":553,"dataGaLocation":444,"dataGaName":559},"Visibility and Measurement",{"text":561,"config":562},"Value Stream Management",{"href":563,"dataGaLocation":444,"dataGaName":561},"/solutions/value-stream-management/",{"text":565,"config":566},"Analytics & Insights",{"href":567,"dataGaLocation":444,"dataGaName":568},"/solutions/analytics-and-insights/","Analytics and insights",{"title":570,"type":495,"items":571},"GitLab for",[572,578,584],{"text":573,"config":574},"Enterprise",{"icon":575,"href":576,"dataGaLocation":444,"dataGaName":577},"Building","/enterprise/","enterprise",{"text":579,"config":580},"Small Business",{"icon":581,"href":582,"dataGaLocation":444,"dataGaName":583},"Work","/small-business/","small business",{"text":585,"config":586},"Public Sector",{"icon":587,"href":588,"dataGaLocation":444,"dataGaName":589},"Organization","/solutions/public-sector/","public sector",{"text":591,"config":592},"Pricing",{"href":593,"dataGaName":594,"dataGaLocation":444,"dataNavLevelOne":594},"/pricing/","pricing",{"text":596,"config":597,"menu":599},"Resources",{"dataNavLevelOne":598},"resources",{"type":495,"link":600,"columns":604,"feature":698},{"text":601,"config":602},"View all resources",{"href":603,"dataGaName":598,"dataGaLocation":444},"/resources/",[605,638,665],{"title":606,"items":607},"Getting started",[608,613,618,623,628,633],{"text":609,"config":610},"Install",{"href":611,"dataGaName":612,"dataGaLocation":444},"/install/","install",{"text":614,"config":615},"Quick start guides",{"href":616,"dataGaName":617,"dataGaLocation":444},"/get-started/","quick setup checklists",{"text":619,"config":620},"Learn",{"href":621,"dataGaLocation":444,"dataGaName":622},"https://university.gitlab.com/","learn",{"text":624,"config":625},"Product documentation",{"href":626,"dataGaName":627,"dataGaLocation":444},"https://docs.gitlab.com/","product documentation",{"text":629,"config":630},"Best practice videos",{"href":631,"dataGaName":632,"dataGaLocation":444},"/getting-started-videos/","best practice videos",{"text":634,"config":635},"Integrations",{"href":636,"dataGaName":637,"dataGaLocation":444},"/integrations/","integrations",{"title":639,"items":640},"Discover",[641,646,651,656,660],{"text":642,"config":643},"Customer success stories",{"href":644,"dataGaName":645,"dataGaLocation":444},"/customers/","customer success stories",{"text":647,"config":648},"Blog",{"href":649,"dataGaName":650,"dataGaLocation":444},"/blog/","blog",{"text":652,"config":653},"Demo Hub",{"href":654,"dataGaName":655,"dataGaLocation":444},"/demo-hub/","demo hub",{"text":657,"config":658},"The Source",{"href":659,"dataGaName":650,"dataGaLocation":444},"/the-source/",{"text":661,"config":662},"Remote",{"href":663,"dataGaName":664,"dataGaLocation":444},"https://handbook.gitlab.com/handbook/company/culture/all-remote/","remote",{"title":666,"items":667},"Connect",[668,673,678,683,688,693],{"text":669,"config":670},"GitLab Services",{"href":671,"dataGaName":672,"dataGaLocation":444},"/services/","services",{"text":674,"config":675},"Contribute",{"href":676,"dataGaName":677,"dataGaLocation":444},"https://contributors.gitlab.com","contribute",{"text":679,"config":680},"Community",{"href":681,"dataGaName":682,"dataGaLocation":444},"/community/","community",{"text":684,"config":685},"Forum",{"href":686,"dataGaName":687,"dataGaLocation":444},"https://forum.gitlab.com/","forum",{"text":689,"config":690},"Events",{"href":691,"dataGaName":692,"dataGaLocation":444},"/events/","events",{"text":694,"config":695},"Partners",{"href":696,"dataGaName":697,"dataGaLocation":444},"/partners/","partners",{"config":699,"title":702,"text":703,"link":704},{"background":700,"textColor":701},"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":705,"config":706},"Read the latest",{"href":707,"dataGaName":708,"dataGaLocation":444},"/whats-new/","whats new",{"text":710,"config":711,"menu":713},"Company",{"dataNavLevelOne":712},"company",{"type":495,"columns":714},[715],{"items":716},[717,722,727,729,734,739,744,749,754,759],{"text":718,"config":719},"About",{"href":720,"dataGaName":721,"dataGaLocation":444},"/company/","about",{"text":723,"config":724,"footerGa":726},"Jobs",{"href":396,"dataGaName":725,"dataGaLocation":444},"jobs",{"dataGaName":725},{"text":689,"config":728},{"href":691,"dataGaName":692,"dataGaLocation":444},{"text":730,"config":731},"Leadership",{"href":732,"dataGaName":733,"dataGaLocation":444},"/company/team/e-group/","leadership",{"text":735,"config":736},"Handbook",{"href":737,"dataGaName":738,"dataGaLocation":444},"https://handbook.gitlab.com/","handbook",{"text":740,"config":741},"Investor relations",{"href":742,"dataGaName":743,"dataGaLocation":444},"https://ir.gitlab.com/overview/default.aspx","investor relations",{"text":745,"config":746},"Trust Center",{"href":747,"dataGaName":748,"dataGaLocation":444},"/security/","trust center",{"text":750,"config":751},"AI Transparency Center",{"href":752,"dataGaName":753,"dataGaLocation":444},"/ai-transparency-center/","ai transparency center",{"text":755,"config":756},"Newsletter",{"href":757,"dataGaName":758,"dataGaLocation":444},"/company/contact/#contact-forms","newsletter",{"text":760,"config":761},"Press",{"href":762,"dataGaName":763,"dataGaLocation":444},"/press/","press",{"text":765,"config":766,"menu":767},"Contact us",{"dataNavLevelOne":712},{"type":495,"columns":768},[769],{"items":770},[771,776,781],{"text":772,"config":773},"Talk to sales",{"href":774,"dataGaName":775,"dataGaLocation":444},"/sales/","talk to sales",{"text":777,"config":778},"Support portal",{"href":779,"dataGaName":780,"dataGaLocation":444},"https://support.gitlab.com/hc/en-us","support portal",{"text":782,"config":783},"Customer portal",{"href":784,"dataGaName":785,"dataGaLocation":444},"https://customers.gitlab.com/customers/sign_in/","customer portal",{"close":787,"login":788,"suggestions":795},"Close",{"text":789,"link":790},"To search repositories and projects, login to",{"text":791,"config":792},"gitlab.com",{"href":458,"dataGaName":793,"dataGaLocation":794},"search login","search",{"text":796,"default":797},"Suggestions",[798,800,804,806,810,814],{"text":475,"config":799},{"href":480,"dataGaName":475,"dataGaLocation":794},{"text":801,"config":802},"Code Suggestions (AI)",{"href":803,"dataGaName":801,"dataGaLocation":794},"/solutions/code-suggestions/",{"text":511,"config":805},{"href":513,"dataGaName":511,"dataGaLocation":794},{"text":807,"config":808},"GitLab on AWS",{"href":809,"dataGaName":807,"dataGaLocation":794},"/partners/technology-partners/aws/",{"text":811,"config":812},"GitLab on Google Cloud",{"href":813,"dataGaName":811,"dataGaLocation":794},"/partners/technology-partners/google-cloud-platform/",{"text":815,"config":816},"Why GitLab?",{"href":488,"dataGaName":815,"dataGaLocation":794},{"freeTrial":818,"mobileIcon":823,"desktopIcon":828,"secondaryButton":831},{"text":819,"config":820},"Start free trial",{"href":821,"dataGaName":449,"dataGaLocation":822},"https://gitlab.com/-/trials/new/","nav",{"altText":824,"config":825},"Gitlab Icon",{"src":826,"dataGaName":827,"dataGaLocation":822},"https://res.cloudinary.com/about-gitlab-com/image/upload/v1758203874/jypbw1jx72aexsoohd7x.svg","gitlab icon",{"altText":824,"config":829},{"src":830,"dataGaName":827,"dataGaLocation":822},"https://res.cloudinary.com/about-gitlab-com/image/upload/v1758203875/gs4c8p8opsgvflgkswz9.svg",{"text":832,"config":833},"Get Started",{"href":834,"dataGaName":835,"dataGaLocation":822},"https://gitlab.com/-/trial_registrations/new?glm_source=about.gitlab.com/get-started/","get started",{"freeTrial":837,"mobileIcon":841,"desktopIcon":843},{"text":838,"config":839},"Learn more about GitLab Duo",{"href":480,"dataGaName":840,"dataGaLocation":822},"gitlab duo",{"altText":824,"config":842},{"src":826,"dataGaName":827,"dataGaLocation":822},{"altText":824,"config":844},{"src":830,"dataGaName":827,"dataGaLocation":822},{"button":846,"mobileIcon":851,"desktopIcon":853},{"text":847,"config":848},"/switch",{"href":849,"dataGaName":850,"dataGaLocation":822},"#contact","switch",{"altText":824,"config":852},{"src":826,"dataGaName":827,"dataGaLocation":822},{"altText":824,"config":854},{"src":855,"dataGaName":827,"dataGaLocation":822},"https://res.cloudinary.com/about-gitlab-com/image/upload/v1773335277/ohhpiuoxoldryzrnhfrh.png",{"freeTrial":857,"mobileIcon":862,"desktopIcon":864},{"text":858,"config":859},"Back to pricing",{"href":593,"dataGaName":860,"dataGaLocation":822,"icon":861},"back to pricing","GoBack",{"altText":824,"config":863},{"src":826,"dataGaName":827,"dataGaLocation":822},{"altText":824,"config":865},{"src":830,"dataGaName":827,"dataGaLocation":822},{"title":867,"titleMobile":868,"button":869,"config":874},"Duo Agent Platform delivers 400% ROI, per new Forrester Consulting study.","400% ROI: Forrester TEI for GitLab Duo",{"text":486,"config":870},{"href":871,"dataGaName":872,"dataGaLocation":873},"https://about.gitlab.com/blog/gitlab-duo-agent-platform-delivers-400-percent-roi/","forrester-tei-dap-banner","global-banner",{"layout":875,"disabled":420},"release",{"data":877},{"text":878,"source":879,"edit":885,"contribute":890,"config":895,"items":899,"minimal":1109},"Git is a trademark of Software Freedom Conservancy and our use of 'GitLab' is under license",{"text":880,"config":881},"View page source",{"href":882,"dataGaName":883,"dataGaLocation":884},"https://gitlab.com/gitlab-com/marketing/digital-experience/about-gitlab-com/","page source","footer",{"text":886,"config":887},"Edit this page",{"href":888,"dataGaName":889,"dataGaLocation":884},"https://gitlab.com/gitlab-com/marketing/digital-experience/about-gitlab-com/-/blob/main/content/","web ide",{"text":891,"config":892},"Please contribute",{"href":893,"dataGaName":894,"dataGaLocation":884},"https://gitlab.com/gitlab-com/marketing/digital-experience/about-gitlab-com/-/blob/main/CONTRIBUTING.md/","please contribute",{"twitter":390,"facebook":896,"youtube":897,"linkedin":898},"https://www.facebook.com/gitlab","https://www.youtube.com/channel/UCnMGQ8QHMAnVIsI3xJrihhg","https://www.linkedin.com/company/gitlab-com",[900,947,1001,1045,1077],{"title":591,"links":901,"subMenu":916},[902,906,911],{"text":903,"config":904},"View plans",{"href":593,"dataGaName":905,"dataGaLocation":884},"view plans",{"text":907,"config":908},"Why Premium?",{"href":909,"dataGaName":910,"dataGaLocation":884},"/pricing/premium/","why premium",{"text":912,"config":913},"Why Ultimate?",{"href":914,"dataGaName":915,"dataGaLocation":884},"/pricing/ultimate/","why ultimate",[917],{"title":918,"links":919},"Contact Us",[920,923,925,927,932,937,942],{"text":921,"config":922},"Contact sales",{"href":774,"dataGaName":454,"dataGaLocation":884},{"text":777,"config":924},{"href":779,"dataGaName":780,"dataGaLocation":884},{"text":782,"config":926},{"href":784,"dataGaName":785,"dataGaLocation":884},{"text":928,"config":929},"Status",{"href":930,"dataGaName":931,"dataGaLocation":884},"https://status.gitlab.com/","status",{"text":933,"config":934},"Terms of use",{"href":935,"dataGaName":936,"dataGaLocation":884},"/terms/","terms of use",{"text":938,"config":939},"Privacy statement",{"href":940,"dataGaName":941,"dataGaLocation":884},"/privacy/","privacy statement",{"text":943,"config":944},"Cookie preferences",{"dataGaName":945,"dataGaLocation":884,"id":946,"isOneTrustButton":264},"cookie preferences","ot-sdk-btn",{"title":491,"links":948,"subMenu":957},[949,953],{"text":950,"config":951},"DevSecOps platform",{"href":473,"dataGaName":952,"dataGaLocation":884},"devsecops platform",{"text":954,"config":955},"AI-Assisted Development",{"href":480,"dataGaName":956,"dataGaLocation":884},"ai-assisted development",[958],{"title":959,"links":960},"Topics",[961,966,971,976,981,986,991,996],{"text":962,"config":963},"CICD",{"href":964,"dataGaName":965,"dataGaLocation":884},"/topics/ci-cd/","cicd",{"text":967,"config":968},"GitOps",{"href":969,"dataGaName":970,"dataGaLocation":884},"/topics/gitops/","gitops",{"text":972,"config":973},"DevOps",{"href":974,"dataGaName":975,"dataGaLocation":884},"/topics/devops/","devops",{"text":977,"config":978},"Version Control",{"href":979,"dataGaName":980,"dataGaLocation":884},"/topics/version-control/","version control",{"text":982,"config":983},"DevSecOps",{"href":984,"dataGaName":985,"dataGaLocation":884},"/topics/devsecops/","devsecops",{"text":987,"config":988},"Cloud Native",{"href":989,"dataGaName":990,"dataGaLocation":884},"/topics/cloud-native/","cloud native",{"text":992,"config":993},"AI for Coding",{"href":994,"dataGaName":995,"dataGaLocation":884},"/topics/devops/ai-for-coding/","ai for coding",{"text":997,"config":998},"Agentic AI",{"href":999,"dataGaName":1000,"dataGaLocation":884},"/topics/agentic-ai/","agentic ai",{"title":1002,"links":1003},"Solutions",[1004,1006,1008,1013,1017,1020,1024,1027,1029,1032,1035,1040],{"text":535,"config":1005},{"href":530,"dataGaName":535,"dataGaLocation":884},{"text":524,"config":1007},{"href":507,"dataGaName":508,"dataGaLocation":884},{"text":1009,"config":1010},"Agile development",{"href":1011,"dataGaName":1012,"dataGaLocation":884},"/solutions/agile-delivery/","agile delivery",{"text":1014,"config":1015},"SCM",{"href":520,"dataGaName":1016,"dataGaLocation":884},"source code management",{"text":962,"config":1018},{"href":513,"dataGaName":1019,"dataGaLocation":884},"continuous integration & delivery",{"text":1021,"config":1022},"Value stream management",{"href":563,"dataGaName":1023,"dataGaLocation":884},"value stream management",{"text":967,"config":1025},{"href":1026,"dataGaName":970,"dataGaLocation":884},"/solutions/gitops/",{"text":573,"config":1028},{"href":576,"dataGaName":577,"dataGaLocation":884},{"text":1030,"config":1031},"Small business",{"href":582,"dataGaName":583,"dataGaLocation":884},{"text":1033,"config":1034},"Public sector",{"href":588,"dataGaName":589,"dataGaLocation":884},{"text":1036,"config":1037},"Education",{"href":1038,"dataGaName":1039,"dataGaLocation":884},"/solutions/education/","education",{"text":1041,"config":1042},"Financial services",{"href":1043,"dataGaName":1044,"dataGaLocation":884},"/solutions/finance/","financial services",{"title":596,"links":1046},[1047,1049,1051,1053,1056,1058,1061,1063,1065,1067,1069,1071,1073,1075],{"text":609,"config":1048},{"href":611,"dataGaName":612,"dataGaLocation":884},{"text":614,"config":1050},{"href":616,"dataGaName":617,"dataGaLocation":884},{"text":619,"config":1052},{"href":621,"dataGaName":622,"dataGaLocation":884},{"text":624,"config":1054},{"href":626,"dataGaName":1055,"dataGaLocation":884},"docs",{"text":647,"config":1057},{"href":649,"dataGaName":650,"dataGaLocation":884},{"text":1059,"config":1060},"What's new",{"href":707,"dataGaName":708,"dataGaLocation":884},{"text":642,"config":1062},{"href":644,"dataGaName":645,"dataGaLocation":884},{"text":661,"config":1064},{"href":663,"dataGaName":664,"dataGaLocation":884},{"text":669,"config":1066},{"href":671,"dataGaName":672,"dataGaLocation":884},{"text":674,"config":1068},{"href":676,"dataGaName":677,"dataGaLocation":884},{"text":679,"config":1070},{"href":681,"dataGaName":682,"dataGaLocation":884},{"text":684,"config":1072},{"href":686,"dataGaName":687,"dataGaLocation":884},{"text":689,"config":1074},{"href":691,"dataGaName":692,"dataGaLocation":884},{"text":694,"config":1076},{"href":696,"dataGaName":697,"dataGaLocation":884},{"title":710,"links":1078},[1079,1081,1083,1085,1087,1089,1093,1098,1100,1102,1104],{"text":718,"config":1080},{"href":720,"dataGaName":712,"dataGaLocation":884},{"text":723,"config":1082},{"href":396,"dataGaName":725,"dataGaLocation":884},{"text":730,"config":1084},{"href":732,"dataGaName":733,"dataGaLocation":884},{"text":735,"config":1086},{"href":737,"dataGaName":738,"dataGaLocation":884},{"text":740,"config":1088},{"href":742,"dataGaName":743,"dataGaLocation":884},{"text":1090,"config":1091},"Sustainability",{"href":1092,"dataGaName":1090,"dataGaLocation":884},"/sustainability/",{"text":1094,"config":1095},"Diversity, inclusion and belonging (DIB)",{"href":1096,"dataGaName":1097,"dataGaLocation":884},"/diversity-inclusion-belonging/","Diversity, inclusion and belonging",{"text":745,"config":1099},{"href":747,"dataGaName":748,"dataGaLocation":884},{"text":755,"config":1101},{"href":757,"dataGaName":758,"dataGaLocation":884},{"text":760,"config":1103},{"href":762,"dataGaName":763,"dataGaLocation":884},{"text":1105,"config":1106},"Modern Slavery Transparency Statement",{"href":1107,"dataGaName":1108,"dataGaLocation":884},"https://handbook.gitlab.com/handbook/legal/modern-slavery-act-transparency-statement/","modern slavery transparency statement",{"items":1110},[1111,1114,1117],{"text":1112,"config":1113},"Terms",{"href":935,"dataGaName":936,"dataGaLocation":884},{"text":1115,"config":1116},"Cookies",{"dataGaName":945,"dataGaLocation":884,"id":946,"isOneTrustButton":264},{"text":1118,"config":1119},"Privacy",{"href":940,"dataGaName":941,"dataGaLocation":884},[1121],{"id":1122,"title":7,"body":419,"config":1123,"content":1125,"description":419,"extension":1129,"meta":1130,"navigation":264,"path":1131,"seo":1132,"stem":1133,"__hash__":1134},"blogAuthors/en-us/blog/authors/mark-pundsack.yml",{"template":1124},"BlogAuthor",{"name":7,"config":1126},{"headshot":1127,"ctfId":1128},"https://res.cloudinary.com/about-gitlab-com/image/upload/v1749659488/Blog/Author%20Headshots/gitlab-logo-extra-whitespace.png","markpundsack","yml",{},"/en-us/blog/authors/mark-pundsack",{},"en-us/blog/authors/mark-pundsack","0XkJx2hq2Kagj8C3zTex7xC4ZA-fT8eE1Uo1cML1zhU",[1136,1145,1153],{"title":1137,"description":1138,"heroImage":1139,"category":415,"date":1140,"authors":1141,"slug":1144,"externalUrl":419},"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",[1142,1143],"Mathias Ewald","Martin Paloncy, Edgeless Systems","confidential-ai-for-gitlab-self-hosted",{"title":1146,"description":1147,"heroImage":1148,"category":415,"date":1149,"authors":1150,"slug":1152,"externalUrl":419},"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",[1151],"Lysanne Pinto","green-devops-carbon-measurement-cicd-pipeline",{"title":1154,"description":1155,"heroImage":1156,"category":415,"date":1157,"authors":1158,"slug":1160,"externalUrl":419},"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",[1159],"Paul Meresanu","how-to-build-ci-cd-observability-at-scale",{"promotions":1162},[1163,1177,1189,1201],{"id":1164,"categories":1165,"header":1167,"text":1168,"button":1169,"image":1174},"ai-modernization",[1166],"ai","Is AI achieving its promise at scale?","Quiz will take 5 minutes or less",{"text":1170,"config":1171},"Get your AI maturity score",{"href":1172,"dataGaName":1173,"dataGaLocation":650},"/assessments/ai-modernization-assessment/","modernization assessment",{"config":1175},{"src":1176},"https://res.cloudinary.com/about-gitlab-com/image/upload/v1772138786/qix0m7kwnd8x2fh1zq49.png",{"id":1178,"categories":1179,"header":1181,"text":1168,"button":1182,"image":1186},"devops-modernization",[1180,985],"product","Are you just managing tools or shipping innovation?",{"text":1183,"config":1184},"Get your DevOps maturity score",{"href":1185,"dataGaName":1173,"dataGaLocation":650},"/assessments/devops-modernization-assessment/",{"config":1187},{"src":1188},"https://res.cloudinary.com/about-gitlab-com/image/upload/v1772138785/eg818fmakweyuznttgid.png",{"id":1190,"categories":1191,"header":1193,"text":1168,"button":1194,"image":1198},"security-modernization",[1192],"security","Are you trading speed for security?",{"text":1195,"config":1196},"Get your security maturity score",{"href":1197,"dataGaName":1173,"dataGaLocation":650},"/assessments/security-modernization-assessment/",{"config":1199},{"src":1200},"https://res.cloudinary.com/about-gitlab-com/image/upload/v1772138786/p4pbqd9nnjejg5ds6mdk.png",{"id":1202,"paths":1203,"header":1206,"text":1207,"button":1208,"image":1213},"github-azure-migration",[1204,1205],"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":1209,"config":1210},"See how GitLab compares to GitHub",{"href":1211,"dataGaName":1212,"dataGaLocation":650},"/compare/gitlab-vs-github/github-azure-migration/","github azure migration",{"config":1214},{"src":1188},{"header":1216,"blurb":1217,"button":1218,"secondaryButton":1223},"Start building faster today","See what your team can do with the intelligent orchestration platform for DevSecOps.\n",{"text":1219,"config":1220},"Get your free trial",{"href":1221,"dataGaName":449,"dataGaLocation":1222},"https://gitlab.com/-/trial_registrations/new?glm_content=default-saas-trial&glm_source=about.gitlab.com/","feature",{"text":921,"config":1224},{"href":774,"dataGaName":454,"dataGaLocation":1222},1786803745353]