[{"data":1,"prerenderedAt":1432},["ShallowReactive",2],{"/blog/this-sre-attempted-to-roll-out-an-haproxy-change":3,"navigation-en-us":644,"banner-en-us":1072,"footer-en-us":1082,"blog-post-authors-en-us-Igor Wiedler":1327,"blog-related-posts-en-us-this-sre-attempted-to-roll-out-an-haproxy-change":1342,"blog-promotions-en-us":1368,"next-steps-en-us":1422},{"id":4,"title":5,"authors":6,"body":8,"category":622,"date":623,"description":624,"extension":625,"externalUrl":626,"faq":626,"featured":627,"heroImage":628,"meta":629,"navigation":630,"path":631,"seo":632,"slug":637,"stem":638,"tags":639,"template":642,"updatedDate":626,"__hash__":643},"blogPosts/en-us/blog/this-sre-attempted-to-roll-out-an-haproxy-change.md","This SRE attempted to roll out an HAProxy config change. You won't believe what happened next...",[7],"Igor Wiedler",{"type":9,"value":10,"toc":611},"minimark",[11,15,20,48,52,62,65,75,84,90,101,136,139,143,150,157,163,170,173,218,229,235,247,250,254,257,289,292,302,305,309,317,320,331,334,342,349,355,363,366,369,373,379,390,412,418,425,431,434,440,446,456,464,467,521,524,528,534,537,560,568,576,586,592,595,601,604,607],[12,13,14],"p",{},"This blog post was originally published on the GitLab Unfiltered blog. It was reviewed and republished on 2021-02-12.",[16,17,19],"h2",{"id":18},"tldr","TL;DR",[21,22,23,32,35,42,45],"ul",{},[24,25,26,27,31],"li",{},"HAProxy has a ",[28,29,30],"code",{},"server-state-file"," directive that persists some of its state across restarts.",[24,33,34],{},"This state file contains the port of each backend server.",[24,36,37,38,41],{},"If an ",[28,39,40],{},"haproxy.cfg"," change modifies the port, the new port will be overwritten with the previous one from the state file.",[24,43,44],{},"A workaround is to change the backend server name, so that it is considered to be a separate server that does not match what is in the state file.",[24,46,47],{},"This has implications for the rollout procedure we use on HAProxy.",[16,49,51],{"id":50},"background","Background",[12,53,54,55,61],{},"All of this occurred in the context of ",[56,57,60],"a",{"href":58,"rel":59},"https://gitlab.com/gitlab-com/gl-infra/infrastructure/-/issues/11902",[],"the gitlab-pages PROXYv2\nproject",".",[12,63,64],{},"The rollout to staging involves changing the request flow from TCP proxying...",[66,67,73],"pre",{"className":68,"code":70,"language":71,"meta":72},[69],"language-text","\n                   443                   443                        1443\n[ client ] -> [ google lb ] -> [ fe-pages-01-lb-gstg ] -> [ web-pages-01-sv-gstg ]\n      tcp,tls,http         tcp                        tcp            tcp,tls,http\n\n","text","",[28,74,70],{"__ignoreMap":72},[12,76,77,78,83],{},"... to using the ",[56,79,82],{"href":80,"rel":81},"https://www.haproxy.org/download/1.8/doc/proxy-protocol.txt",[],"PROXY protocol",":",[66,85,88],{"className":86,"code":87,"language":71,"meta":72},[69],"\n                   443                   443                        2443\n[ client ] -> [ google lb ] -> [ fe-pages-01-lb-gstg ] -> [ web-pages-01-sv-gstg ]\n      tcp,tls,http         tcp                     proxyv2,tcp       proxyv2,tcp,tls,http\n\n",[28,89,87],{"__ignoreMap":72},[12,91,92,93,96,97,100],{},"This is done through this change to ",[28,94,95],{},"/etc/haproxy/haproxy.cfg"," on\n",[28,98,99],{},"fe-pages-01-lb-gstg"," (note the port change):",[66,102,106],{"className":103,"code":104,"language":105,"meta":72,"style":72},"language-diff shiki shiki-themes github-light","-    server web-pages-01-sv-gstg web-pages-01-sv-gstg.c.gitlab-staging-1.internal:1443 check inter 3s fastinter 1s downinter 5s fall 3 port 1080\n-    server web-pages-02-sv-gstg web-pages-02-sv-gstg.c.gitlab-staging-1.internal:1443 check inter 3s fastinter 1s downinter 5s fall 3 port 1080\n+    server web-pages-01-sv-gstg web-pages-01-sv-gstg.c.gitlab-staging-1.internal:2443 check inter 3s fastinter 1s downinter 5s fall 3 port 1080 send-proxy-v2\n+    server web-pages-02-sv-gstg web-pages-02-sv-gstg.c.gitlab-staging-1.internal:2443 check inter 3s fastinter 1s downinter 5s fall 3 port 1080 send-proxy-v2\n","diff",[28,107,108,117,123,130],{"__ignoreMap":72},[109,110,113],"span",{"class":111,"line":112},"line",1,[109,114,116],{"class":115},"s9hmT","-    server web-pages-01-sv-gstg web-pages-01-sv-gstg.c.gitlab-staging-1.internal:1443 check inter 3s fastinter 1s downinter 5s fall 3 port 1080\n",[109,118,120],{"class":111,"line":119},2,[109,121,122],{"class":115},"-    server web-pages-02-sv-gstg web-pages-02-sv-gstg.c.gitlab-staging-1.internal:1443 check inter 3s fastinter 1s downinter 5s fall 3 port 1080\n",[109,124,126],{"class":111,"line":125},3,[109,127,129],{"class":128},"shJU0","+    server web-pages-01-sv-gstg web-pages-01-sv-gstg.c.gitlab-staging-1.internal:2443 check inter 3s fastinter 1s downinter 5s fall 3 port 1080 send-proxy-v2\n",[109,131,133],{"class":111,"line":132},4,[109,134,135],{"class":128},"+    server web-pages-02-sv-gstg web-pages-02-sv-gstg.c.gitlab-staging-1.internal:2443 check inter 3s fastinter 1s downinter 5s fall 3 port 1080 send-proxy-v2\n",[12,137,138],{},"Seems straightforward enough, let's go ahead and apply that change.",[16,140,142],{"id":141},"the-brokenness","The brokenness",[12,144,145,146,149],{},"After applying this change on one of the two ",[28,147,148],{},"fe-pages"," nodes, the requests to\nthat node start failing.",[12,151,152,153,156],{},"By retrying a few times via ",[28,154,155],{},"curl"," on the command line, we see this error:",[66,158,161],{"className":159,"code":160,"language":71,"meta":72},[69],"➜  ~ curl -vvv https://jarv.staging.gitlab.io/pages-test/\n*   Trying 35.229.69.78...\n* TCP_NODELAY set\n* Connected to jarv.staging.gitlab.io (35.229.69.78) port 443 (#0)\n* ALPN, offering h2\n* ALPN, offering http/1.1\n* successfully set certificate verify locations:\n*   CAfile: /etc/ssl/cert.pem\n  CApath: none\n* TLSv1.2 (OUT), TLS handshake, Client hello (1):\n* LibreSSL SSL_connect: SSL_ERROR_SYSCALL in connection to jarv.staging.gitlab.io:443\n* Closing connection 0\ncurl: (35) LibreSSL SSL_connect: SSL_ERROR_SYSCALL in connection to jarv.staging.gitlab.io:443\n",[28,162,160],{"__ignoreMap":72},[12,164,165,166,169],{},"This looks like some issue in the TLS stack, or possibly with the underlying\nconnection. It turns out that ",[28,167,168],{},"LibreSSL"," does not give us much insight into the\nunderlying issue here.",[12,171,172],{},"So to get a better idea, let's capture a traffic dump on the HAProxy node:",[66,174,178],{"className":175,"code":176,"language":177,"meta":72,"style":72},"language-shell shiki shiki-themes github-light","sudo tcpdump -v -w \"$(pwd)/$(hostname).$(date +%Y%m%d_%H%M%S).pcap\"\n","shell",[28,179,180],{"__ignoreMap":72},[109,181,182,186,190,194,197,200,203,206,209,212,215],{"class":111,"line":112},[109,183,185],{"class":184},"s7eDp","sudo",[109,187,189],{"class":188},"sYBdl"," tcpdump",[109,191,193],{"class":192},"sYu0t"," -v",[109,195,196],{"class":192}," -w",[109,198,199],{"class":188}," \"$(",[109,201,202],{"class":192},"pwd",[109,204,205],{"class":188},")/$(",[109,207,208],{"class":184},"hostname",[109,210,211],{"class":188},").$(",[109,213,214],{"class":184},"date",[109,216,217],{"class":188}," +%Y%m%d_%H%M%S).pcap\"\n",[12,219,220,221,224,225,228],{},"While ",[28,222,223],{},"tcpdump"," is running, we can generate some traffic, then ctrl+c and pull\nthe dump down for further analysis. That ",[28,226,227],{},"pcap"," file can be opened in Wireshark, and this allows the data to be\nexplored and filtered interactively. Here, the first really surprising thing happens:",[12,230,231],{},[232,233,234],"strong",{},"We do not see any traffic on port 2443.",[12,236,237,238,242,243,246],{},"At the same time, we ",[239,240,241],"em",{},"do"," see some traffic on port 1443. But we came here to look at what underlies the LibreSSL error, and what we find\nis the following (by filtering for ",[28,244,245],{},"ip.addr == \u003Cmy external ip>","). We have a TCP SYN/ACK, establishing the connection. Followed by the client\nsending a TLS \"hello\". After which the server closes the connection with a FIN.",[12,248,249],{},"In other words, the server is closing the connection on the client.",[16,251,253],{"id":252},"the-early-hypotheses","The early hypotheses",[12,255,256],{},"So here come the usual suspects:",[21,258,259,262,265,268,271,274,277,280,283,286],{},[24,260,261],{},"Did we modify the correct place in the config file?",[24,263,264],{},"Did we catch all places we need to update in the config?",[24,266,267],{},"Did the HAProxy process parse th econfig successfully?",[24,269,270],{},"Did HAProxy actually reload?",[24,272,273],{},"Is there a difference between reload and restart?",[24,275,276],{},"Did we modify the correct config file?",[24,278,279],{},"Are there old lingering HAProxy processes on the box?",[24,281,282],{},"Are we actually sending traffic to this node?",[24,284,285],{},"Are backend health checks failing?",[24,287,288],{},"Is there anything in the HAProxy logs?",[12,290,291],{},"None of these gave any insights whatsoever.",[12,293,294,295,298,299,61],{},"In an effort to reproduce the issue, I ran HAProxy on my local machine with a\nsimilar config, proxying traffic to ",[28,296,297],{},"web-pages-01-sv-gstg",". To my surprise, this\nworked correctly. I tested with different HAProxy versions. It worked locally, but not on\n",[28,300,301],{},"fe-pages-01",[12,303,304],{},"At this point I'm stumped. The local config is not identical to gstg, but quite\nsimilar. What could possibly be the difference?",[16,306,308],{"id":307},"digging-deeper","Digging deeper",[12,310,311,312,316],{},"This is when I reached out to ",[56,313,315],{"href":314},"/company/#/msmiley","Matt Smiley"," to help with the investigation.",[12,318,319],{},"We started off by repeating the experiment. We saw the same results:",[21,321,322,325,328],{},[24,323,324],{},"Server closes connection after client sends TLS hello",[24,326,327],{},"No traffic from fe-pages to web-pages on port 2443",[24,329,330],{},"Traffic from fe-pages to web-pages on port 1443",[12,332,333],{},"The first lead was to look at the packets going to port 1443. What do they\ncontain? We see this:",[12,335,336,341],{},[337,338],"img",{"alt":339,"src":340},"Traffic capture in wireshark showing a TCP FIN and the string QUIT in the stream","https://res.cloudinary.com/about-gitlab-com/image/upload/v1782398574/blog/Content%20Images/infra-proxy-protocol-wireshark.png","\nTraffic capture in Wireshark showing a TCP FIN and the string QUIT in the stream",[12,343,344,345,348],{},"There is mention of ",[28,346,347],{},"jarv.staging.gitlab.io"," which does match what the client sent. And before that there is some really weird preamble:",[66,350,353],{"className":351,"code":352,"language":71,"meta":72},[69],"\"\\r\\n\\r\\n\\0\\r\\nQUIT\\n\"\n",[28,354,352],{"__ignoreMap":72},[12,356,357,358,362],{},"What on earth is this? Is it from the PROXY protocol? Let's search ",[56,359,361],{"href":80,"rel":360},[],"the\nspec"," for the word\n\"QUIT.\" Nothing.",[12,364,365],{},"Is this something in the HAProxy source? Searching for \"QUIT\" in the code\nreveals some hits, but none that explain this.",[12,367,368],{},"So this is a mystery. We leave it for now, and probe in a different direction.",[16,370,372],{"id":371},"honing-in","Honing in",[12,374,375,376,378],{},"How come we are sending traffic to port 1443, when that port is not mentioned in\n",[28,377,40],{},"? Where on earth is HAProxy getting that information from?",[12,380,381,382,385,386,389],{},"I suggested running ",[28,383,384],{},"strace"," on HAProxy startup, so that we can see which files\nare being ",[28,387,388],{},"open","ed. This is a bit tricky to do though, because the process is\nsystemd-managed.",[12,391,392,393,398,399,404,405,407,408,411],{},"It turns out that thanks to BPF and ",[56,394,397],{"href":395,"rel":396},"https://github.com/iovisor/bcc",[],"BCC",", we\ncan actually listen on open events system-wide using the wonderful\n",[56,400,403],{"href":401,"rel":402},"https://github.com/iovisor/bcc/blob/master/tools/opensnoop.py",[],"opensnoop",". So we run ",[28,406,403],{}," and restart ",[28,409,410],{},"haproxy",", and this is what we see, highlighting the relevant bit:",[66,413,416],{"className":414,"code":415,"language":71,"meta":72},[69],"iwiedler@fe-pages-01-lb-gstg.c.gitlab-staging-1.internal:~$ sudo /usr/share/bcc/tools/opensnoop  -T --name haproxy\n\n...\n\n24.117171000  16702  haproxy             3   0 /etc/haproxy/haproxy.cfg\n...\n24.118099000  16702  haproxy             4   0 /etc/haproxy/errors/400.http\n...\n24.118333000  16702  haproxy             4   0 /etc/haproxy/cloudflare_ips_v4.lst\n...\n24.119109000  16702  haproxy             3   0 /etc/haproxy/state/global\n",[28,417,415],{"__ignoreMap":72},[12,419,420,421,424],{},"What do we have here? ",[28,422,423],{},"/etc/haproxy/state/global",", this seems oddly suspicious.\nWhat could it possibly be? Let's see what this file contains.",[66,426,429],{"className":427,"code":428,"language":71,"meta":72},[69],"iwiedler@fe-pages-01-lb-gstg.c.gitlab-staging-1.internal:~$ sudo cat /etc/haproxy/state/global\n\n1\n# be_id be_name srv_id srv_name srv_addr srv_op_state srv_admin_state srv_uweight srv_iweight srv_time_since_last_change srv_check_status srv_check_result srv_check_health srv_check_state srv_agent_state bk_f_forced_id srv_f_forced_id srv_fqdn srv_port srvrecord\n5 pages_http 1 web-pages-01-sv-gstg 10.224.26.2 2 0 1 1 21134 15 3 4 6 0 0 0 web-pages-01-sv-gstg.c.gitlab-staging-1.internal 1080 -\n5 pages_http 2 web-pages-02-sv-gstg 10.224.26.3 2 0 1 1 20994 15 3 4 6 0 0 0 web-pages-02-sv-gstg.c.gitlab-staging-1.internal 1080 -\n6 pages_https 1 web-pages-01-sv-gstg 10.224.26.2 2 0 1 1 21134 15 3 4 6 0 0 0 web-pages-01-sv-gstg.c.gitlab-staging-1.internal 1443 -\n6 pages_https 2 web-pages-02-sv-gstg 10.224.26.3 2 0 1 1 20994 15 3 4 6 0 0 0 web-pages-02-sv-gstg.c.gitlab-staging-1.internal 1443 -\n",[28,430,428],{"__ignoreMap":72},[12,432,433],{},"It appears we are storing some metadata for each backend server, including its old port number!",[12,435,436,437,439],{},"Now, looking again in ",[28,438,40],{},", we see:",[66,441,444],{"className":442,"code":443,"language":71,"meta":72},[69],"global\n    ...\n    server-state-file /etc/haproxy/state/global\n\n",[28,445,443],{"__ignoreMap":72},[12,447,448,449,455],{},"So we are using the\n",[56,450,453],{"href":451,"rel":452},"https://cbonte.github.io/haproxy-dconv/1.8/configuration.html#server-state-file",[],[28,454,30],{},"\ndirective. This will persist server state across HAProxy restarts. That is\nuseful to keep metadata consistent, such as whether a server was marked as\nMAINT.",[12,457,458],{},[232,459,460,461,463],{},"However, it appears to be clobbering the port from ",[28,462,40],{},"!",[12,465,466],{},"The suspected behavior is:",[21,468,469,478,491,494,500,512],{},[24,470,471,472,474,475],{},"HAProxy is running with the old config: ",[28,473,297],{},", ",[28,476,477],{},"1443",[24,479,480,482,483,474,485,474,488],{},[28,481,40],{}," is updated with the new config: ",[28,484,297],{},[28,486,487],{},"2443",[28,489,490],{},"send-proxy-v2",[24,492,493],{},"HAProxy reload is initiated",[24,495,496,497,499],{},"HAProxy writes out the state to ",[28,498,423],{}," (including the old port of each backend server)",[24,501,502,503,505,506,474,508,474,510],{},"HAProxy starts up, reads ",[28,504,40],{},", initializes itself with the new config: ",[28,507,297],{},[28,509,487],{},[28,511,490],{},[24,513,514,515,517,518,520],{},"HAProxy reads the state from ",[28,516,423],{},", matches on the backend server ",[28,519,297],{},", and overrides all values, including the port!",[12,522,523],{},"The result is that we are now attempting to send PROXYv2 traffic to the TLS port.",[16,525,527],{"id":526},"the-workaround","The workaround",[12,529,530,531,533],{},"To validate the theory and develop a potential workaround, we modify\n",[28,532,40],{}," to use a different backend server name.",[12,535,536],{},"The new diff is:",[66,538,540],{"className":103,"code":539,"language":105,"meta":72,"style":72},"-    server web-pages-01-sv-gstg web-pages-01-sv-gstg.c.gitlab-staging-1.internal:1443 check inter 3s fastinter 1s downinter 5s fall 3 port 1080\n-    server web-pages-02-sv-gstg web-pages-02-sv-gstg.c.gitlab-staging-1.internal:1443 check inter 3s fastinter 1s downinter 5s fall 3 port 1080\n+    server web-pages-01-sv-gstg-proxyv2 web-pages-01-sv-gstg.c.gitlab-staging-1.internal:2443 check inter 3s fastinter 1s downinter 5s fall 3 port 1080 send-proxy-v2\n+    server web-pages-02-sv-gstg-proxyv2 web-pages-02-sv-gstg.c.gitlab-staging-1.internal:2443 check inter 3s fastinter 1s downinter 5s fall 3 port 1080 send-proxy-v2\n",[28,541,542,546,550,555],{"__ignoreMap":72},[109,543,544],{"class":111,"line":112},[109,545,116],{"class":115},[109,547,548],{"class":111,"line":119},[109,549,122],{"class":115},[109,551,552],{"class":111,"line":125},[109,553,554],{"class":128},"+    server web-pages-01-sv-gstg-proxyv2 web-pages-01-sv-gstg.c.gitlab-staging-1.internal:2443 check inter 3s fastinter 1s downinter 5s fall 3 port 1080 send-proxy-v2\n",[109,556,557],{"class":111,"line":132},[109,558,559],{"class":128},"+    server web-pages-02-sv-gstg-proxyv2 web-pages-02-sv-gstg.c.gitlab-staging-1.internal:2443 check inter 3s fastinter 1s downinter 5s fall 3 port 1080 send-proxy-v2\n",[12,561,562,563,61],{},"With this config change in place, we reload HAProxy and indeed, it is now\nserving traffic correctly. See ",[56,564,567],{"href":565,"rel":566},"https://gitlab.com/gitlab-cookbooks/gitlab-haproxy/-/merge_requests/261",[],"the merge request fixing it",[16,569,571,572,575],{"id":570},"a-follow-up-on-those-quit-bytes","A follow-up on those ",[28,573,574],{},"QUIT"," bytes",[12,577,578,579,581,582,585],{},"Now, what is up with that ",[28,580,574],{}," message? Is it part of the PROXY protocol? Remember, searching ",[56,583,361],{"href":80,"rel":584},[]," for that\nstring did not find any matches. However, Matt actually read the spec, and found this section on version 2 of\nthe protocol:",[66,587,590],{"className":588,"code":589,"language":71,"meta":72},[69],"The binary header format starts with a constant 12 bytes block containing the\nprotocol signature :\n\n   \\x0D \\x0A \\x0D \\x0A \\x00 \\x0D \\x0A \\x51 \\x55 \\x49 \\x54 \\x0A\n\n",[28,591,589],{"__ignoreMap":72},[12,593,594],{},"Those are indeed the bytes that make up \"\\r\\n\\r\\n\\0\\r\\nQUIT\\n\". Slightly less mnemonic than the header from text-based version 1 of the protocol:",[66,596,599],{"className":597,"code":598,"language":71,"meta":72},[69],"- a string identifying the protocol : \"PROXY\" ( \\x50 \\x52 \\x4F \\x58 \\x59 )\n  Seeing this string indicates that this is version 1 of the protocol.\n\n",[28,600,598],{"__ignoreMap":72},[12,602,603],{},"Well, I suppose that explains it.",[12,605,606],{},"I believe our work here is done. Don't forget to like and subscribe!",[608,609,610],"style",{},"html pre.shiki code .s9hmT, html code.shiki .s9hmT{--shiki-default:#B31D28}html pre.shiki code .shJU0, html code.shiki .shJU0{--shiki-default:#22863A}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .s7eDp, html code.shiki .s7eDp{--shiki-default:#6F42C1}html pre.shiki code .sYBdl, html code.shiki .sYBdl{--shiki-default:#032F62}html pre.shiki code .sYu0t, html code.shiki .sYu0t{--shiki-default:#005CC5}",{"title":72,"searchDepth":119,"depth":119,"links":612},[613,614,615,616,617,618,619,620],{"id":18,"depth":119,"text":19},{"id":50,"depth":119,"text":51},{"id":141,"depth":119,"text":142},{"id":252,"depth":119,"text":253},{"id":307,"depth":119,"text":308},{"id":371,"depth":119,"text":372},{"id":526,"depth":119,"text":527},{"id":570,"depth":119,"text":621},"A follow-up on those QUIT bytes","engineering","2021-01-14","This post is about a wild discovery made while investigating strange behavior from HAProxy. We dive into the pathology, describe how we found it, and share some investigative techniques used along the way.","md",null,false,"https://res.cloudinary.com/about-gitlab-com/image/upload/v1749681844/Blog/Hero%20Images/infra-proxy-protocol-wireshark-header.png",{},true,"/en-us/blog/this-sre-attempted-to-roll-out-an-haproxy-change",{"title":633,"description":624,"ogTitle":633,"ogDescription":624,"noIndex":627,"ogImage":628,"ogUrl":634,"ogSiteName":635,"ogType":636,"canonicalUrls":634},"This SRE's HAProxy Config Change: An Unexpected Journey","https://about.gitlab.com/blog/this-sre-attempted-to-roll-out-an-haproxy-change","https://about.gitlab.com","article","this-sre-attempted-to-roll-out-an-haproxy-change","en-us/blog/this-sre-attempted-to-roll-out-an-haproxy-change",[640,641],"production","inside GitLab","BlogPost","9ex6WtfohQTrgy7AtLf4LP86nn4hUSBU2kWjwQSQsa8",{"logo":645,"freeTrial":650,"sales":655,"login":660,"items":665,"search":992,"minimal":1023,"duo":1042,"switchNav":1051,"pricingDeployment":1062},{"config":646},{"href":647,"dataGaName":648,"dataGaLocation":649},"/","gitlab logo","header",{"text":651,"config":652},"Get free trial",{"href":653,"dataGaName":654,"dataGaLocation":649},"https://gitlab.com/-/trial_registrations/new?glm_source=about.gitlab.com&glm_content=default-saas-trial/","free trial",{"text":656,"config":657},"Request a demo",{"href":658,"dataGaName":659,"dataGaLocation":649},"/sales/?contact-topic=request-demo","sales",{"text":661,"config":662},"Sign in",{"href":663,"dataGaName":664,"dataGaLocation":649},"https://gitlab.com/users/sign_in/","sign in",[666,695,795,800,914,970],{"text":667,"config":668,"menu":670},"Platform",{"dataNavLevelOne":669},"platform",{"type":671,"columns":672},"cards",[673,679,687],{"title":667,"description":674,"link":675},"The intelligent orchestration platform for DevSecOps",{"text":676,"config":677},"Explore our Platform",{"href":678,"dataGaName":669,"dataGaLocation":649},"/platform/",{"title":680,"description":681,"link":682},"GitLab Duo Agent Platform","Agentic AI for the entire software lifecycle",{"text":683,"config":684},"Meet GitLab Duo",{"href":685,"dataGaName":686,"dataGaLocation":649},"/gitlab-duo-agent-platform/","gitlab duo agent platform",{"title":688,"description":689,"link":690},"Why GitLab","See the top reasons enterprises choose GitLab",{"text":691,"config":692},"Learn more",{"href":693,"dataGaName":694,"dataGaLocation":649},"/why-gitlab/","why gitlab",{"text":696,"left":630,"config":697,"menu":699},"Product",{"dataNavLevelOne":698},"solutions",{"type":700,"link":701,"columns":705,"feature":774},"lists",{"text":702,"config":703},"View all Solutions",{"href":704,"dataGaName":698,"dataGaLocation":649},"/solutions/",[706,730,753],{"title":707,"description":708,"link":709,"items":714},"Automation","CI/CD and automation to accelerate deployment",{"config":710},{"icon":711,"href":712,"dataGaName":713,"dataGaLocation":649},"AutomatedCodeAlt","/solutions/delivery-automation/","automated software delivery",[715,719,722,726],{"text":716,"config":717},"CI/CD",{"href":718,"dataGaLocation":649,"dataGaName":716},"/solutions/continuous-integration/",{"text":680,"config":720},{"href":685,"dataGaLocation":649,"dataGaName":721},"gitlab duo agent platform - product menu",{"text":723,"config":724},"Source Code Management",{"href":725,"dataGaLocation":649,"dataGaName":723},"/solutions/source-code-management/",{"text":727,"config":728},"Automated Software Delivery",{"href":712,"dataGaLocation":649,"dataGaName":729},"Automated software delivery",{"title":731,"description":732,"link":733,"items":738},"Security","Deliver code faster without compromising security",{"config":734},{"href":735,"dataGaName":736,"dataGaLocation":649,"icon":737},"/solutions/application-security-testing/","security and compliance","ShieldCheckLight",[739,743,748],{"text":740,"config":741},"Application Security Testing",{"href":735,"dataGaName":742,"dataGaLocation":649},"Application security testing",{"text":744,"config":745},"Software Supply Chain Security",{"href":746,"dataGaLocation":649,"dataGaName":747},"/solutions/supply-chain/","Software supply chain security",{"text":749,"config":750},"Software Compliance",{"href":751,"dataGaName":752,"dataGaLocation":649},"/solutions/software-compliance/","software compliance",{"title":754,"link":755,"items":760},"Measurement",{"config":756},{"icon":757,"href":758,"dataGaName":759,"dataGaLocation":649},"DigitalTransformation","/solutions/visibility-measurement/","visibility and measurement",[761,765,769],{"text":762,"config":763},"Visibility & Measurement",{"href":758,"dataGaLocation":649,"dataGaName":764},"Visibility and Measurement",{"text":766,"config":767},"Value Stream Management",{"href":768,"dataGaLocation":649,"dataGaName":766},"/solutions/value-stream-management/",{"text":770,"config":771},"Analytics & Insights",{"href":772,"dataGaLocation":649,"dataGaName":773},"/solutions/analytics-and-insights/","Analytics and insights",{"title":775,"type":700,"items":776},"GitLab for",[777,783,789],{"text":778,"config":779},"Enterprise",{"icon":780,"href":781,"dataGaLocation":649,"dataGaName":782},"Building","/enterprise/","enterprise",{"text":784,"config":785},"Small Business",{"icon":786,"href":787,"dataGaLocation":649,"dataGaName":788},"Work","/small-business/","small business",{"text":790,"config":791},"Public Sector",{"icon":792,"href":793,"dataGaLocation":649,"dataGaName":794},"Organization","/solutions/public-sector/","public sector",{"text":796,"config":797},"Pricing",{"href":798,"dataGaName":799,"dataGaLocation":649,"dataNavLevelOne":799},"/pricing/","pricing",{"text":801,"config":802,"menu":804},"Resources",{"dataNavLevelOne":803},"resources",{"type":700,"link":805,"columns":809,"feature":903},{"text":806,"config":807},"View all resources",{"href":808,"dataGaName":803,"dataGaLocation":649},"/resources/",[810,843,870],{"title":811,"items":812},"Getting started",[813,818,823,828,833,838],{"text":814,"config":815},"Install",{"href":816,"dataGaName":817,"dataGaLocation":649},"/install/","install",{"text":819,"config":820},"Quick start guides",{"href":821,"dataGaName":822,"dataGaLocation":649},"/get-started/","quick setup checklists",{"text":824,"config":825},"Learn",{"href":826,"dataGaLocation":649,"dataGaName":827},"https://university.gitlab.com/","learn",{"text":829,"config":830},"Product documentation",{"href":831,"dataGaName":832,"dataGaLocation":649},"https://docs.gitlab.com/","product documentation",{"text":834,"config":835},"Best practice videos",{"href":836,"dataGaName":837,"dataGaLocation":649},"/getting-started-videos/","best practice videos",{"text":839,"config":840},"Integrations",{"href":841,"dataGaName":842,"dataGaLocation":649},"/integrations/","integrations",{"title":844,"items":845},"Discover",[846,851,856,861,865],{"text":847,"config":848},"Customer success stories",{"href":849,"dataGaName":850,"dataGaLocation":649},"/customers/","customer success stories",{"text":852,"config":853},"Blog",{"href":854,"dataGaName":855,"dataGaLocation":649},"/blog/","blog",{"text":857,"config":858},"Demo Hub",{"href":859,"dataGaName":860,"dataGaLocation":649},"/demo-hub/","demo hub",{"text":862,"config":863},"The Source",{"href":864,"dataGaName":855,"dataGaLocation":649},"/the-source/",{"text":866,"config":867},"Remote",{"href":868,"dataGaName":869,"dataGaLocation":649},"https://handbook.gitlab.com/handbook/company/culture/all-remote/","remote",{"title":871,"items":872},"Connect",[873,878,883,888,893,898],{"text":874,"config":875},"GitLab Services",{"href":876,"dataGaName":877,"dataGaLocation":649},"/services/","services",{"text":879,"config":880},"Contribute",{"href":881,"dataGaName":882,"dataGaLocation":649},"https://contributors.gitlab.com","contribute",{"text":884,"config":885},"Community",{"href":886,"dataGaName":887,"dataGaLocation":649},"/community/","community",{"text":889,"config":890},"Forum",{"href":891,"dataGaName":892,"dataGaLocation":649},"https://forum.gitlab.com/","forum",{"text":894,"config":895},"Events",{"href":896,"dataGaName":897,"dataGaLocation":649},"/events/","events",{"text":899,"config":900},"Partners",{"href":901,"dataGaName":902,"dataGaLocation":649},"/partners/","partners",{"config":904,"title":907,"text":908,"link":909},{"background":905,"textColor":906},"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":910,"config":911},"Read the latest",{"href":912,"dataGaName":913,"dataGaLocation":649},"/whats-new/","whats new",{"text":915,"config":916,"menu":918},"Company",{"dataNavLevelOne":917},"company",{"type":700,"columns":919},[920],{"items":921},[922,927,933,935,940,945,950,955,960,965],{"text":923,"config":924},"About",{"href":925,"dataGaName":926,"dataGaLocation":649},"/company/","about",{"text":928,"config":929,"footerGa":932},"Jobs",{"href":930,"dataGaName":931,"dataGaLocation":649},"/jobs/","jobs",{"dataGaName":931},{"text":894,"config":934},{"href":896,"dataGaName":897,"dataGaLocation":649},{"text":936,"config":937},"Leadership",{"href":938,"dataGaName":939,"dataGaLocation":649},"/company/team/e-group/","leadership",{"text":941,"config":942},"Handbook",{"href":943,"dataGaName":944,"dataGaLocation":649},"https://handbook.gitlab.com/","handbook",{"text":946,"config":947},"Investor relations",{"href":948,"dataGaName":949,"dataGaLocation":649},"https://ir.gitlab.com/overview/default.aspx","investor relations",{"text":951,"config":952},"Trust Center",{"href":953,"dataGaName":954,"dataGaLocation":649},"/security/","trust center",{"text":956,"config":957},"AI Transparency Center",{"href":958,"dataGaName":959,"dataGaLocation":649},"/ai-transparency-center/","ai transparency center",{"text":961,"config":962},"Newsletter",{"href":963,"dataGaName":964,"dataGaLocation":649},"/company/contact/#contact-forms","newsletter",{"text":966,"config":967},"Press",{"href":968,"dataGaName":969,"dataGaLocation":649},"/press/","press",{"text":971,"config":972,"menu":973},"Contact us",{"dataNavLevelOne":917},{"type":700,"columns":974},[975],{"items":976},[977,982,987],{"text":978,"config":979},"Talk to sales",{"href":980,"dataGaName":981,"dataGaLocation":649},"/sales/","talk to sales",{"text":983,"config":984},"Support portal",{"href":985,"dataGaName":986,"dataGaLocation":649},"https://support.gitlab.com/hc/en-us","support portal",{"text":988,"config":989},"Customer portal",{"href":990,"dataGaName":991,"dataGaLocation":649},"https://customers.gitlab.com/customers/sign_in/","customer portal",{"close":993,"login":994,"suggestions":1001},"Close",{"text":995,"link":996},"To search repositories and projects, login to",{"text":997,"config":998},"gitlab.com",{"href":663,"dataGaName":999,"dataGaLocation":1000},"search login","search",{"text":1002,"default":1003},"Suggestions",[1004,1006,1010,1012,1016,1020],{"text":680,"config":1005},{"href":685,"dataGaName":680,"dataGaLocation":1000},{"text":1007,"config":1008},"Code Suggestions (AI)",{"href":1009,"dataGaName":1007,"dataGaLocation":1000},"/solutions/code-suggestions/",{"text":716,"config":1011},{"href":718,"dataGaName":716,"dataGaLocation":1000},{"text":1013,"config":1014},"GitLab on AWS",{"href":1015,"dataGaName":1013,"dataGaLocation":1000},"/partners/technology-partners/aws/",{"text":1017,"config":1018},"GitLab on Google Cloud",{"href":1019,"dataGaName":1017,"dataGaLocation":1000},"/partners/technology-partners/google-cloud-platform/",{"text":1021,"config":1022},"Why GitLab?",{"href":693,"dataGaName":1021,"dataGaLocation":1000},{"freeTrial":1024,"mobileIcon":1029,"desktopIcon":1034,"secondaryButton":1037},{"text":1025,"config":1026},"Start free trial",{"href":1027,"dataGaName":654,"dataGaLocation":1028},"https://gitlab.com/-/trials/new/","nav",{"altText":1030,"config":1031},"Gitlab Icon",{"src":1032,"dataGaName":1033,"dataGaLocation":1028},"https://res.cloudinary.com/about-gitlab-com/image/upload/v1758203874/jypbw1jx72aexsoohd7x.svg","gitlab icon",{"altText":1030,"config":1035},{"src":1036,"dataGaName":1033,"dataGaLocation":1028},"https://res.cloudinary.com/about-gitlab-com/image/upload/v1758203875/gs4c8p8opsgvflgkswz9.svg",{"text":1038,"config":1039},"Get Started",{"href":1040,"dataGaName":1041,"dataGaLocation":1028},"https://gitlab.com/-/trial_registrations/new?glm_source=about.gitlab.com/get-started/","get started",{"freeTrial":1043,"mobileIcon":1047,"desktopIcon":1049},{"text":1044,"config":1045},"Learn more about GitLab Duo",{"href":685,"dataGaName":1046,"dataGaLocation":1028},"gitlab duo",{"altText":1030,"config":1048},{"src":1032,"dataGaName":1033,"dataGaLocation":1028},{"altText":1030,"config":1050},{"src":1036,"dataGaName":1033,"dataGaLocation":1028},{"button":1052,"mobileIcon":1057,"desktopIcon":1059},{"text":1053,"config":1054},"/switch",{"href":1055,"dataGaName":1056,"dataGaLocation":1028},"#contact","switch",{"altText":1030,"config":1058},{"src":1032,"dataGaName":1033,"dataGaLocation":1028},{"altText":1030,"config":1060},{"src":1061,"dataGaName":1033,"dataGaLocation":1028},"https://res.cloudinary.com/about-gitlab-com/image/upload/v1773335277/ohhpiuoxoldryzrnhfrh.png",{"freeTrial":1063,"mobileIcon":1068,"desktopIcon":1070},{"text":1064,"config":1065},"Back to pricing",{"href":798,"dataGaName":1066,"dataGaLocation":1028,"icon":1067},"back to pricing","GoBack",{"altText":1030,"config":1069},{"src":1032,"dataGaName":1033,"dataGaLocation":1028},{"altText":1030,"config":1071},{"src":1036,"dataGaName":1033,"dataGaLocation":1028},{"title":1073,"titleMobile":1074,"button":1075,"config":1080},"Duo Agent Platform delivers 400% ROI, per new Forrester Consulting study.","400% ROI: Forrester TEI for GitLab Duo",{"text":691,"config":1076},{"href":1077,"dataGaName":1078,"dataGaLocation":1079},"https://about.gitlab.com/blog/gitlab-duo-agent-platform-delivers-400-percent-roi/","forrester-tei-dap-banner","global-banner",{"layout":1081,"disabled":627},"release",{"data":1083},{"text":1084,"source":1085,"edit":1091,"contribute":1096,"config":1101,"items":1106,"minimal":1316},"Git is a trademark of Software Freedom Conservancy and our use of 'GitLab' is under license",{"text":1086,"config":1087},"View page source",{"href":1088,"dataGaName":1089,"dataGaLocation":1090},"https://gitlab.com/gitlab-com/marketing/digital-experience/about-gitlab-com/","page source","footer",{"text":1092,"config":1093},"Edit this page",{"href":1094,"dataGaName":1095,"dataGaLocation":1090},"https://gitlab.com/gitlab-com/marketing/digital-experience/about-gitlab-com/-/blob/main/content/","web ide",{"text":1097,"config":1098},"Please contribute",{"href":1099,"dataGaName":1100,"dataGaLocation":1090},"https://gitlab.com/gitlab-com/marketing/digital-experience/about-gitlab-com/-/blob/main/CONTRIBUTING.md/","please contribute",{"twitter":1102,"facebook":1103,"youtube":1104,"linkedin":1105},"https://twitter.com/gitlab","https://www.facebook.com/gitlab","https://www.youtube.com/channel/UCnMGQ8QHMAnVIsI3xJrihhg","https://www.linkedin.com/company/gitlab-com",[1107,1154,1208,1252,1284],{"title":796,"links":1108,"subMenu":1123},[1109,1113,1118],{"text":1110,"config":1111},"View plans",{"href":798,"dataGaName":1112,"dataGaLocation":1090},"view plans",{"text":1114,"config":1115},"Why Premium?",{"href":1116,"dataGaName":1117,"dataGaLocation":1090},"/pricing/premium/","why premium",{"text":1119,"config":1120},"Why Ultimate?",{"href":1121,"dataGaName":1122,"dataGaLocation":1090},"/pricing/ultimate/","why ultimate",[1124],{"title":1125,"links":1126},"Contact Us",[1127,1130,1132,1134,1139,1144,1149],{"text":1128,"config":1129},"Contact sales",{"href":980,"dataGaName":659,"dataGaLocation":1090},{"text":983,"config":1131},{"href":985,"dataGaName":986,"dataGaLocation":1090},{"text":988,"config":1133},{"href":990,"dataGaName":991,"dataGaLocation":1090},{"text":1135,"config":1136},"Status",{"href":1137,"dataGaName":1138,"dataGaLocation":1090},"https://status.gitlab.com/","status",{"text":1140,"config":1141},"Terms of use",{"href":1142,"dataGaName":1143,"dataGaLocation":1090},"/terms/","terms of use",{"text":1145,"config":1146},"Privacy statement",{"href":1147,"dataGaName":1148,"dataGaLocation":1090},"/privacy/","privacy statement",{"text":1150,"config":1151},"Cookie preferences",{"dataGaName":1152,"dataGaLocation":1090,"id":1153,"isOneTrustButton":630},"cookie preferences","ot-sdk-btn",{"title":696,"links":1155,"subMenu":1164},[1156,1160],{"text":1157,"config":1158},"DevSecOps platform",{"href":678,"dataGaName":1159,"dataGaLocation":1090},"devsecops platform",{"text":1161,"config":1162},"AI-Assisted Development",{"href":685,"dataGaName":1163,"dataGaLocation":1090},"ai-assisted development",[1165],{"title":1166,"links":1167},"Topics",[1168,1173,1178,1183,1188,1193,1198,1203],{"text":1169,"config":1170},"CICD",{"href":1171,"dataGaName":1172,"dataGaLocation":1090},"/topics/ci-cd/","cicd",{"text":1174,"config":1175},"GitOps",{"href":1176,"dataGaName":1177,"dataGaLocation":1090},"/topics/gitops/","gitops",{"text":1179,"config":1180},"DevOps",{"href":1181,"dataGaName":1182,"dataGaLocation":1090},"/topics/devops/","devops",{"text":1184,"config":1185},"Version Control",{"href":1186,"dataGaName":1187,"dataGaLocation":1090},"/topics/version-control/","version control",{"text":1189,"config":1190},"DevSecOps",{"href":1191,"dataGaName":1192,"dataGaLocation":1090},"/topics/devsecops/","devsecops",{"text":1194,"config":1195},"Cloud Native",{"href":1196,"dataGaName":1197,"dataGaLocation":1090},"/topics/cloud-native/","cloud native",{"text":1199,"config":1200},"AI for Coding",{"href":1201,"dataGaName":1202,"dataGaLocation":1090},"/topics/devops/ai-for-coding/","ai for coding",{"text":1204,"config":1205},"Agentic AI",{"href":1206,"dataGaName":1207,"dataGaLocation":1090},"/topics/agentic-ai/","agentic ai",{"title":1209,"links":1210},"Solutions",[1211,1213,1215,1220,1224,1227,1231,1234,1236,1239,1242,1247],{"text":740,"config":1212},{"href":735,"dataGaName":740,"dataGaLocation":1090},{"text":729,"config":1214},{"href":712,"dataGaName":713,"dataGaLocation":1090},{"text":1216,"config":1217},"Agile development",{"href":1218,"dataGaName":1219,"dataGaLocation":1090},"/solutions/agile-delivery/","agile delivery",{"text":1221,"config":1222},"SCM",{"href":725,"dataGaName":1223,"dataGaLocation":1090},"source code management",{"text":1169,"config":1225},{"href":718,"dataGaName":1226,"dataGaLocation":1090},"continuous integration & delivery",{"text":1228,"config":1229},"Value stream management",{"href":768,"dataGaName":1230,"dataGaLocation":1090},"value stream management",{"text":1174,"config":1232},{"href":1233,"dataGaName":1177,"dataGaLocation":1090},"/solutions/gitops/",{"text":778,"config":1235},{"href":781,"dataGaName":782,"dataGaLocation":1090},{"text":1237,"config":1238},"Small business",{"href":787,"dataGaName":788,"dataGaLocation":1090},{"text":1240,"config":1241},"Public sector",{"href":793,"dataGaName":794,"dataGaLocation":1090},{"text":1243,"config":1244},"Education",{"href":1245,"dataGaName":1246,"dataGaLocation":1090},"/solutions/education/","education",{"text":1248,"config":1249},"Financial services",{"href":1250,"dataGaName":1251,"dataGaLocation":1090},"/solutions/finance/","financial services",{"title":801,"links":1253},[1254,1256,1258,1260,1263,1265,1268,1270,1272,1274,1276,1278,1280,1282],{"text":814,"config":1255},{"href":816,"dataGaName":817,"dataGaLocation":1090},{"text":819,"config":1257},{"href":821,"dataGaName":822,"dataGaLocation":1090},{"text":824,"config":1259},{"href":826,"dataGaName":827,"dataGaLocation":1090},{"text":829,"config":1261},{"href":831,"dataGaName":1262,"dataGaLocation":1090},"docs",{"text":852,"config":1264},{"href":854,"dataGaName":855,"dataGaLocation":1090},{"text":1266,"config":1267},"What's new",{"href":912,"dataGaName":913,"dataGaLocation":1090},{"text":847,"config":1269},{"href":849,"dataGaName":850,"dataGaLocation":1090},{"text":866,"config":1271},{"href":868,"dataGaName":869,"dataGaLocation":1090},{"text":874,"config":1273},{"href":876,"dataGaName":877,"dataGaLocation":1090},{"text":879,"config":1275},{"href":881,"dataGaName":882,"dataGaLocation":1090},{"text":884,"config":1277},{"href":886,"dataGaName":887,"dataGaLocation":1090},{"text":889,"config":1279},{"href":891,"dataGaName":892,"dataGaLocation":1090},{"text":894,"config":1281},{"href":896,"dataGaName":897,"dataGaLocation":1090},{"text":899,"config":1283},{"href":901,"dataGaName":902,"dataGaLocation":1090},{"title":915,"links":1285},[1286,1288,1290,1292,1294,1296,1300,1305,1307,1309,1311],{"text":923,"config":1287},{"href":925,"dataGaName":917,"dataGaLocation":1090},{"text":928,"config":1289},{"href":930,"dataGaName":931,"dataGaLocation":1090},{"text":936,"config":1291},{"href":938,"dataGaName":939,"dataGaLocation":1090},{"text":941,"config":1293},{"href":943,"dataGaName":944,"dataGaLocation":1090},{"text":946,"config":1295},{"href":948,"dataGaName":949,"dataGaLocation":1090},{"text":1297,"config":1298},"Sustainability",{"href":1299,"dataGaName":1297,"dataGaLocation":1090},"/sustainability/",{"text":1301,"config":1302},"Diversity, inclusion and belonging (DIB)",{"href":1303,"dataGaName":1304,"dataGaLocation":1090},"/diversity-inclusion-belonging/","Diversity, inclusion and belonging",{"text":951,"config":1306},{"href":953,"dataGaName":954,"dataGaLocation":1090},{"text":961,"config":1308},{"href":963,"dataGaName":964,"dataGaLocation":1090},{"text":966,"config":1310},{"href":968,"dataGaName":969,"dataGaLocation":1090},{"text":1312,"config":1313},"Modern Slavery Transparency Statement",{"href":1314,"dataGaName":1315,"dataGaLocation":1090},"https://handbook.gitlab.com/handbook/legal/modern-slavery-act-transparency-statement/","modern slavery transparency statement",{"items":1317},[1318,1321,1324],{"text":1319,"config":1320},"Terms",{"href":1142,"dataGaName":1143,"dataGaLocation":1090},{"text":1322,"config":1323},"Cookies",{"dataGaName":1152,"dataGaLocation":1090,"id":1153,"isOneTrustButton":630},{"text":1325,"config":1326},"Privacy",{"href":1147,"dataGaName":1148,"dataGaLocation":1090},[1328],{"id":1329,"title":7,"body":626,"config":1330,"content":1332,"description":626,"extension":1336,"meta":1337,"navigation":630,"path":1338,"seo":1339,"stem":1340,"__hash__":1341},"blogAuthors/en-us/blog/authors/igor-wiedler.yml",{"template":1331},"BlogAuthor",{"name":7,"config":1333},{"headshot":1334,"ctfId":1335},"https://res.cloudinary.com/about-gitlab-com/image/upload/v1749681841/Blog/Author%20Headshots/igorwwwwwwwwwwwwwwwwwwww-headshot.png","igorwwwwwwwwwwwwwwwwwwww","yml",{},"/en-us/blog/authors/igor-wiedler",{},"en-us/blog/authors/igor-wiedler","UjB3CK9iFhUnSs_R6AJ6Q5U3BYKHSDHJl3NejTQp1tU",[1343,1352,1360],{"title":1344,"description":1345,"heroImage":1346,"category":622,"date":1347,"authors":1348,"slug":1351,"externalUrl":626},"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",[1349,1350],"Mathias Ewald","Martin Paloncy, Edgeless Systems","confidential-ai-for-gitlab-self-hosted",{"title":1353,"description":1354,"heroImage":1355,"category":622,"date":1356,"authors":1357,"slug":1359,"externalUrl":626},"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",[1358],"Lysanne Pinto","green-devops-carbon-measurement-cicd-pipeline",{"title":1361,"description":1362,"heroImage":1363,"category":622,"date":1364,"authors":1365,"slug":1367,"externalUrl":626},"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",[1366],"Paul Meresanu","how-to-build-ci-cd-observability-at-scale",{"promotions":1369},[1370,1384,1396,1408],{"id":1371,"categories":1372,"header":1374,"text":1375,"button":1376,"image":1381},"ai-modernization",[1373],"ai","Is AI achieving its promise at scale?","Quiz will take 5 minutes or less",{"text":1377,"config":1378},"Get your AI maturity score",{"href":1379,"dataGaName":1380,"dataGaLocation":855},"/assessments/ai-modernization-assessment/","modernization assessment",{"config":1382},{"src":1383},"https://res.cloudinary.com/about-gitlab-com/image/upload/v1772138786/qix0m7kwnd8x2fh1zq49.png",{"id":1385,"categories":1386,"header":1388,"text":1375,"button":1389,"image":1393},"devops-modernization",[1387,1192],"product","Are you just managing tools or shipping innovation?",{"text":1390,"config":1391},"Get your DevOps maturity score",{"href":1392,"dataGaName":1380,"dataGaLocation":855},"/assessments/devops-modernization-assessment/",{"config":1394},{"src":1395},"https://res.cloudinary.com/about-gitlab-com/image/upload/v1772138785/eg818fmakweyuznttgid.png",{"id":1397,"categories":1398,"header":1400,"text":1375,"button":1401,"image":1405},"security-modernization",[1399],"security","Are you trading speed for security?",{"text":1402,"config":1403},"Get your security maturity score",{"href":1404,"dataGaName":1380,"dataGaLocation":855},"/assessments/security-modernization-assessment/",{"config":1406},{"src":1407},"https://res.cloudinary.com/about-gitlab-com/image/upload/v1772138786/p4pbqd9nnjejg5ds6mdk.png",{"id":1409,"paths":1410,"header":1413,"text":1414,"button":1415,"image":1420},"github-azure-migration",[1411,1412],"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":1416,"config":1417},"See how GitLab compares to GitHub",{"href":1418,"dataGaName":1419,"dataGaLocation":855},"/compare/gitlab-vs-github/github-azure-migration/","github azure migration",{"config":1421},{"src":1395},{"header":1423,"blurb":1424,"button":1425,"secondaryButton":1430},"Start building faster today","See what your team can do with the intelligent orchestration platform for DevSecOps.\n",{"text":1426,"config":1427},"Get your free trial",{"href":1428,"dataGaName":654,"dataGaLocation":1429},"https://gitlab.com/-/trial_registrations/new?glm_content=default-saas-trial&glm_source=about.gitlab.com/","feature",{"text":1128,"config":1431},{"href":980,"dataGaName":659,"dataGaLocation":1429},1786803775723]