[{"data":1,"prerenderedAt":2903},["ShallowReactive",2],{"/en-us/blog/tags/ci/":3,"navigation-en-us":20,"banner-en-us":450,"footer-en-us":467,"CI-tag-page-en-us":677},{"_path":4,"_dir":5,"_draft":6,"_partial":6,"_locale":7,"content":8,"config":11,"_id":13,"_type":14,"title":15,"_source":16,"_file":17,"_stem":18,"_extension":19},"/en-us/blog/tags/ci","tags",false,"",{"tag":9,"tagSlug":10},"CI","ci",{"template":12},"BlogTag","content:en-us:blog:tags:ci.yml","yaml","Ci","content","en-us/blog/tags/ci.yml","en-us/blog/tags/ci","yml",{"_path":21,"_dir":22,"_draft":6,"_partial":6,"_locale":7,"data":23,"_id":446,"_type":14,"title":447,"_source":16,"_file":448,"_stem":449,"_extension":19},"/shared/en-us/main-navigation","en-us",{"logo":24,"freeTrial":29,"sales":34,"login":39,"items":44,"search":377,"minimal":408,"duo":427,"pricingDeployment":436},{"config":25},{"href":26,"dataGaName":27,"dataGaLocation":28},"/","gitlab logo","header",{"text":30,"config":31},"Get free trial",{"href":32,"dataGaName":33,"dataGaLocation":28},"https://gitlab.com/-/trial_registrations/new?glm_source=about.gitlab.com&glm_content=default-saas-trial/","free trial",{"text":35,"config":36},"Talk to sales",{"href":37,"dataGaName":38,"dataGaLocation":28},"/sales/","sales",{"text":40,"config":41},"Sign in",{"href":42,"dataGaName":43,"dataGaLocation":28},"https://gitlab.com/users/sign_in/","sign in",[45,89,187,192,298,358],{"text":46,"config":47,"cards":49,"footer":72},"Platform",{"dataNavLevelOne":48},"platform",[50,56,64],{"title":46,"description":51,"link":52},"The most comprehensive AI-powered DevSecOps Platform",{"text":53,"config":54},"Explore our Platform",{"href":55,"dataGaName":48,"dataGaLocation":28},"/platform/",{"title":57,"description":58,"link":59},"GitLab Duo (AI)","Build software faster with AI at every stage of development",{"text":60,"config":61},"Meet GitLab Duo",{"href":62,"dataGaName":63,"dataGaLocation":28},"/gitlab-duo/","gitlab duo ai",{"title":65,"description":66,"link":67},"Why GitLab","10 reasons why Enterprises choose GitLab",{"text":68,"config":69},"Learn more",{"href":70,"dataGaName":71,"dataGaLocation":28},"/why-gitlab/","why gitlab",{"title":73,"items":74},"Get started with",[75,80,85],{"text":76,"config":77},"Platform Engineering",{"href":78,"dataGaName":79,"dataGaLocation":28},"/solutions/platform-engineering/","platform engineering",{"text":81,"config":82},"Developer Experience",{"href":83,"dataGaName":84,"dataGaLocation":28},"/developer-experience/","Developer experience",{"text":86,"config":87},"MLOps",{"href":88,"dataGaName":86,"dataGaLocation":28},"/topics/devops/the-role-of-ai-in-devops/",{"text":90,"left":91,"config":92,"link":94,"lists":98,"footer":169},"Product",true,{"dataNavLevelOne":93},"solutions",{"text":95,"config":96},"View all Solutions",{"href":97,"dataGaName":93,"dataGaLocation":28},"/solutions/",[99,124,148],{"title":100,"description":101,"link":102,"items":107},"Automation","CI/CD and automation to accelerate deployment",{"config":103},{"icon":104,"href":105,"dataGaName":106,"dataGaLocation":28},"AutomatedCodeAlt","/solutions/delivery-automation/","automated software delivery",[108,112,116,120],{"text":109,"config":110},"CI/CD",{"href":111,"dataGaLocation":28,"dataGaName":109},"/solutions/continuous-integration/",{"text":113,"config":114},"AI-Assisted Development",{"href":62,"dataGaLocation":28,"dataGaName":115},"AI assisted development",{"text":117,"config":118},"Source Code Management",{"href":119,"dataGaLocation":28,"dataGaName":117},"/solutions/source-code-management/",{"text":121,"config":122},"Automated Software Delivery",{"href":105,"dataGaLocation":28,"dataGaName":123},"Automated software delivery",{"title":125,"description":126,"link":127,"items":132},"Security","Deliver code faster without compromising security",{"config":128},{"href":129,"dataGaName":130,"dataGaLocation":28,"icon":131},"/solutions/security-compliance/","security and compliance","ShieldCheckLight",[133,138,143],{"text":134,"config":135},"Application Security Testing",{"href":136,"dataGaName":137,"dataGaLocation":28},"/solutions/application-security-testing/","Application security testing",{"text":139,"config":140},"Software Supply Chain Security",{"href":141,"dataGaLocation":28,"dataGaName":142},"/solutions/supply-chain/","Software supply chain security",{"text":144,"config":145},"Software Compliance",{"href":146,"dataGaName":147,"dataGaLocation":28},"/solutions/software-compliance/","software compliance",{"title":149,"link":150,"items":155},"Measurement",{"config":151},{"icon":152,"href":153,"dataGaName":154,"dataGaLocation":28},"DigitalTransformation","/solutions/visibility-measurement/","visibility and measurement",[156,160,164],{"text":157,"config":158},"Visibility & Measurement",{"href":153,"dataGaLocation":28,"dataGaName":159},"Visibility and Measurement",{"text":161,"config":162},"Value Stream Management",{"href":163,"dataGaLocation":28,"dataGaName":161},"/solutions/value-stream-management/",{"text":165,"config":166},"Analytics & Insights",{"href":167,"dataGaLocation":28,"dataGaName":168},"/solutions/analytics-and-insights/","Analytics and insights",{"title":170,"items":171},"GitLab for",[172,177,182],{"text":173,"config":174},"Enterprise",{"href":175,"dataGaLocation":28,"dataGaName":176},"/enterprise/","enterprise",{"text":178,"config":179},"Small Business",{"href":180,"dataGaLocation":28,"dataGaName":181},"/small-business/","small business",{"text":183,"config":184},"Public Sector",{"href":185,"dataGaLocation":28,"dataGaName":186},"/solutions/public-sector/","public sector",{"text":188,"config":189},"Pricing",{"href":190,"dataGaName":191,"dataGaLocation":28,"dataNavLevelOne":191},"/pricing/","pricing",{"text":193,"config":194,"link":196,"lists":200,"feature":285},"Resources",{"dataNavLevelOne":195},"resources",{"text":197,"config":198},"View all resources",{"href":199,"dataGaName":195,"dataGaLocation":28},"/resources/",[201,234,257],{"title":202,"items":203},"Getting started",[204,209,214,219,224,229],{"text":205,"config":206},"Install",{"href":207,"dataGaName":208,"dataGaLocation":28},"/install/","install",{"text":210,"config":211},"Quick start guides",{"href":212,"dataGaName":213,"dataGaLocation":28},"/get-started/","quick setup checklists",{"text":215,"config":216},"Learn",{"href":217,"dataGaLocation":28,"dataGaName":218},"https://university.gitlab.com/","learn",{"text":220,"config":221},"Product documentation",{"href":222,"dataGaName":223,"dataGaLocation":28},"https://docs.gitlab.com/","product documentation",{"text":225,"config":226},"Best practice videos",{"href":227,"dataGaName":228,"dataGaLocation":28},"/getting-started-videos/","best practice videos",{"text":230,"config":231},"Integrations",{"href":232,"dataGaName":233,"dataGaLocation":28},"/integrations/","integrations",{"title":235,"items":236},"Discover",[237,242,247,252],{"text":238,"config":239},"Customer success stories",{"href":240,"dataGaName":241,"dataGaLocation":28},"/customers/","customer success stories",{"text":243,"config":244},"Blog",{"href":245,"dataGaName":246,"dataGaLocation":28},"/blog/","blog",{"text":248,"config":249},"Remote",{"href":250,"dataGaName":251,"dataGaLocation":28},"https://handbook.gitlab.com/handbook/company/culture/all-remote/","remote",{"text":253,"config":254},"TeamOps",{"href":255,"dataGaName":256,"dataGaLocation":28},"/teamops/","teamops",{"title":258,"items":259},"Connect",[260,265,270,275,280],{"text":261,"config":262},"GitLab Services",{"href":263,"dataGaName":264,"dataGaLocation":28},"/services/","services",{"text":266,"config":267},"Community",{"href":268,"dataGaName":269,"dataGaLocation":28},"/community/","community",{"text":271,"config":272},"Forum",{"href":273,"dataGaName":274,"dataGaLocation":28},"https://forum.gitlab.com/","forum",{"text":276,"config":277},"Events",{"href":278,"dataGaName":279,"dataGaLocation":28},"/events/","events",{"text":281,"config":282},"Partners",{"href":283,"dataGaName":284,"dataGaLocation":28},"/partners/","partners",{"backgroundColor":286,"textColor":287,"text":288,"image":289,"link":293},"#2f2a6b","#fff","Insights for the future of software development",{"altText":290,"config":291},"the source promo card",{"src":292},"https://res.cloudinary.com/about-gitlab-com/image/upload/v1758208064/dzl0dbift9xdizyelkk4.svg",{"text":294,"config":295},"Read the latest",{"href":296,"dataGaName":297,"dataGaLocation":28},"/the-source/","the source",{"text":299,"config":300,"lists":302},"Company",{"dataNavLevelOne":301},"company",[303],{"items":304},[305,310,316,318,323,328,333,338,343,348,353],{"text":306,"config":307},"About",{"href":308,"dataGaName":309,"dataGaLocation":28},"/company/","about",{"text":311,"config":312,"footerGa":315},"Jobs",{"href":313,"dataGaName":314,"dataGaLocation":28},"/jobs/","jobs",{"dataGaName":314},{"text":276,"config":317},{"href":278,"dataGaName":279,"dataGaLocation":28},{"text":319,"config":320},"Leadership",{"href":321,"dataGaName":322,"dataGaLocation":28},"/company/team/e-group/","leadership",{"text":324,"config":325},"Team",{"href":326,"dataGaName":327,"dataGaLocation":28},"/company/team/","team",{"text":329,"config":330},"Handbook",{"href":331,"dataGaName":332,"dataGaLocation":28},"https://handbook.gitlab.com/","handbook",{"text":334,"config":335},"Investor relations",{"href":336,"dataGaName":337,"dataGaLocation":28},"https://ir.gitlab.com/","investor relations",{"text":339,"config":340},"Trust Center",{"href":341,"dataGaName":342,"dataGaLocation":28},"/security/","trust center",{"text":344,"config":345},"AI Transparency Center",{"href":346,"dataGaName":347,"dataGaLocation":28},"/ai-transparency-center/","ai transparency center",{"text":349,"config":350},"Newsletter",{"href":351,"dataGaName":352,"dataGaLocation":28},"/company/contact/","newsletter",{"text":354,"config":355},"Press",{"href":356,"dataGaName":357,"dataGaLocation":28},"/press/","press",{"text":359,"config":360,"lists":361},"Contact us",{"dataNavLevelOne":301},[362],{"items":363},[364,367,372],{"text":35,"config":365},{"href":37,"dataGaName":366,"dataGaLocation":28},"talk to sales",{"text":368,"config":369},"Get help",{"href":370,"dataGaName":371,"dataGaLocation":28},"/support/","get help",{"text":373,"config":374},"Customer portal",{"href":375,"dataGaName":376,"dataGaLocation":28},"https://customers.gitlab.com/customers/sign_in/","customer portal",{"close":378,"login":379,"suggestions":386},"Close",{"text":380,"link":381},"To search repositories and projects, login to",{"text":382,"config":383},"gitlab.com",{"href":42,"dataGaName":384,"dataGaLocation":385},"search login","search",{"text":387,"default":388},"Suggestions",[389,391,395,397,401,405],{"text":57,"config":390},{"href":62,"dataGaName":57,"dataGaLocation":385},{"text":392,"config":393},"Code Suggestions (AI)",{"href":394,"dataGaName":392,"dataGaLocation":385},"/solutions/code-suggestions/",{"text":109,"config":396},{"href":111,"dataGaName":109,"dataGaLocation":385},{"text":398,"config":399},"GitLab on AWS",{"href":400,"dataGaName":398,"dataGaLocation":385},"/partners/technology-partners/aws/",{"text":402,"config":403},"GitLab on Google Cloud",{"href":404,"dataGaName":402,"dataGaLocation":385},"/partners/technology-partners/google-cloud-platform/",{"text":406,"config":407},"Why GitLab?",{"href":70,"dataGaName":406,"dataGaLocation":385},{"freeTrial":409,"mobileIcon":414,"desktopIcon":419,"secondaryButton":422},{"text":410,"config":411},"Start free trial",{"href":412,"dataGaName":33,"dataGaLocation":413},"https://gitlab.com/-/trials/new/","nav",{"altText":415,"config":416},"Gitlab Icon",{"src":417,"dataGaName":418,"dataGaLocation":413},"https://res.cloudinary.com/about-gitlab-com/image/upload/v1758203874/jypbw1jx72aexsoohd7x.svg","gitlab icon",{"altText":415,"config":420},{"src":421,"dataGaName":418,"dataGaLocation":413},"https://res.cloudinary.com/about-gitlab-com/image/upload/v1758203875/gs4c8p8opsgvflgkswz9.svg",{"text":423,"config":424},"Get Started",{"href":425,"dataGaName":426,"dataGaLocation":413},"https://gitlab.com/-/trial_registrations/new?glm_source=about.gitlab.com/compare/gitlab-vs-github/","get started",{"freeTrial":428,"mobileIcon":432,"desktopIcon":434},{"text":429,"config":430},"Learn more about GitLab Duo",{"href":62,"dataGaName":431,"dataGaLocation":413},"gitlab duo",{"altText":415,"config":433},{"src":417,"dataGaName":418,"dataGaLocation":413},{"altText":415,"config":435},{"src":421,"dataGaName":418,"dataGaLocation":413},{"freeTrial":437,"mobileIcon":442,"desktopIcon":444},{"text":438,"config":439},"Back to pricing",{"href":190,"dataGaName":440,"dataGaLocation":413,"icon":441},"back to pricing","GoBack",{"altText":415,"config":443},{"src":417,"dataGaName":418,"dataGaLocation":413},{"altText":415,"config":445},{"src":421,"dataGaName":418,"dataGaLocation":413},"content:shared:en-us:main-navigation.yml","Main Navigation","shared/en-us/main-navigation.yml","shared/en-us/main-navigation",{"_path":451,"_dir":22,"_draft":6,"_partial":6,"_locale":7,"title":452,"button":453,"image":458,"config":462,"_id":464,"_type":14,"_source":16,"_file":465,"_stem":466,"_extension":19},"/shared/en-us/banner","is now in public beta!",{"text":454,"config":455},"Try the Beta",{"href":456,"dataGaName":457,"dataGaLocation":28},"/gitlab-duo/agent-platform/","duo banner",{"altText":459,"config":460},"GitLab Duo Agent Platform",{"src":461},"https://res.cloudinary.com/about-gitlab-com/image/upload/v1753720689/somrf9zaunk0xlt7ne4x.svg",{"layout":463},"release","content:shared:en-us:banner.yml","shared/en-us/banner.yml","shared/en-us/banner",{"_path":468,"_dir":22,"_draft":6,"_partial":6,"_locale":7,"data":469,"_id":673,"_type":14,"title":674,"_source":16,"_file":675,"_stem":676,"_extension":19},"/shared/en-us/main-footer",{"text":470,"source":471,"edit":477,"contribute":482,"config":487,"items":492,"minimal":665},"Git is a trademark of Software Freedom Conservancy and our use of 'GitLab' is under license",{"text":472,"config":473},"View page source",{"href":474,"dataGaName":475,"dataGaLocation":476},"https://gitlab.com/gitlab-com/marketing/digital-experience/about-gitlab-com/","page source","footer",{"text":478,"config":479},"Edit this page",{"href":480,"dataGaName":481,"dataGaLocation":476},"https://gitlab.com/gitlab-com/marketing/digital-experience/about-gitlab-com/-/blob/main/content/","web ide",{"text":483,"config":484},"Please contribute",{"href":485,"dataGaName":486,"dataGaLocation":476},"https://gitlab.com/gitlab-com/marketing/digital-experience/about-gitlab-com/-/blob/main/CONTRIBUTING.md/","please contribute",{"twitter":488,"facebook":489,"youtube":490,"linkedin":491},"https://twitter.com/gitlab","https://www.facebook.com/gitlab","https://www.youtube.com/channel/UCnMGQ8QHMAnVIsI3xJrihhg","https://www.linkedin.com/company/gitlab-com",[493,516,572,601,635],{"title":46,"links":494,"subMenu":499},[495],{"text":496,"config":497},"DevSecOps platform",{"href":55,"dataGaName":498,"dataGaLocation":476},"devsecops platform",[500],{"title":188,"links":501},[502,506,511],{"text":503,"config":504},"View plans",{"href":190,"dataGaName":505,"dataGaLocation":476},"view plans",{"text":507,"config":508},"Why Premium?",{"href":509,"dataGaName":510,"dataGaLocation":476},"/pricing/premium/","why premium",{"text":512,"config":513},"Why Ultimate?",{"href":514,"dataGaName":515,"dataGaLocation":476},"/pricing/ultimate/","why ultimate",{"title":517,"links":518},"Solutions",[519,524,526,528,533,538,542,545,549,554,556,559,562,567],{"text":520,"config":521},"Digital transformation",{"href":522,"dataGaName":523,"dataGaLocation":476},"/topics/digital-transformation/","digital transformation",{"text":134,"config":525},{"href":136,"dataGaName":134,"dataGaLocation":476},{"text":123,"config":527},{"href":105,"dataGaName":106,"dataGaLocation":476},{"text":529,"config":530},"Agile development",{"href":531,"dataGaName":532,"dataGaLocation":476},"/solutions/agile-delivery/","agile delivery",{"text":534,"config":535},"Cloud transformation",{"href":536,"dataGaName":537,"dataGaLocation":476},"/topics/cloud-native/","cloud transformation",{"text":539,"config":540},"SCM",{"href":119,"dataGaName":541,"dataGaLocation":476},"source code management",{"text":109,"config":543},{"href":111,"dataGaName":544,"dataGaLocation":476},"continuous integration & delivery",{"text":546,"config":547},"Value stream management",{"href":163,"dataGaName":548,"dataGaLocation":476},"value stream management",{"text":550,"config":551},"GitOps",{"href":552,"dataGaName":553,"dataGaLocation":476},"/solutions/gitops/","gitops",{"text":173,"config":555},{"href":175,"dataGaName":176,"dataGaLocation":476},{"text":557,"config":558},"Small business",{"href":180,"dataGaName":181,"dataGaLocation":476},{"text":560,"config":561},"Public sector",{"href":185,"dataGaName":186,"dataGaLocation":476},{"text":563,"config":564},"Education",{"href":565,"dataGaName":566,"dataGaLocation":476},"/solutions/education/","education",{"text":568,"config":569},"Financial services",{"href":570,"dataGaName":571,"dataGaLocation":476},"/solutions/finance/","financial services",{"title":193,"links":573},[574,576,578,580,583,585,587,589,591,593,595,597,599],{"text":205,"config":575},{"href":207,"dataGaName":208,"dataGaLocation":476},{"text":210,"config":577},{"href":212,"dataGaName":213,"dataGaLocation":476},{"text":215,"config":579},{"href":217,"dataGaName":218,"dataGaLocation":476},{"text":220,"config":581},{"href":222,"dataGaName":582,"dataGaLocation":476},"docs",{"text":243,"config":584},{"href":245,"dataGaName":246,"dataGaLocation":476},{"text":238,"config":586},{"href":240,"dataGaName":241,"dataGaLocation":476},{"text":248,"config":588},{"href":250,"dataGaName":251,"dataGaLocation":476},{"text":261,"config":590},{"href":263,"dataGaName":264,"dataGaLocation":476},{"text":253,"config":592},{"href":255,"dataGaName":256,"dataGaLocation":476},{"text":266,"config":594},{"href":268,"dataGaName":269,"dataGaLocation":476},{"text":271,"config":596},{"href":273,"dataGaName":274,"dataGaLocation":476},{"text":276,"config":598},{"href":278,"dataGaName":279,"dataGaLocation":476},{"text":281,"config":600},{"href":283,"dataGaName":284,"dataGaLocation":476},{"title":299,"links":602},[603,605,607,609,611,613,615,619,624,626,628,630],{"text":306,"config":604},{"href":308,"dataGaName":301,"dataGaLocation":476},{"text":311,"config":606},{"href":313,"dataGaName":314,"dataGaLocation":476},{"text":319,"config":608},{"href":321,"dataGaName":322,"dataGaLocation":476},{"text":324,"config":610},{"href":326,"dataGaName":327,"dataGaLocation":476},{"text":329,"config":612},{"href":331,"dataGaName":332,"dataGaLocation":476},{"text":334,"config":614},{"href":336,"dataGaName":337,"dataGaLocation":476},{"text":616,"config":617},"Sustainability",{"href":618,"dataGaName":616,"dataGaLocation":476},"/sustainability/",{"text":620,"config":621},"Diversity, inclusion and belonging (DIB)",{"href":622,"dataGaName":623,"dataGaLocation":476},"/diversity-inclusion-belonging/","Diversity, inclusion and belonging",{"text":339,"config":625},{"href":341,"dataGaName":342,"dataGaLocation":476},{"text":349,"config":627},{"href":351,"dataGaName":352,"dataGaLocation":476},{"text":354,"config":629},{"href":356,"dataGaName":357,"dataGaLocation":476},{"text":631,"config":632},"Modern Slavery Transparency Statement",{"href":633,"dataGaName":634,"dataGaLocation":476},"https://handbook.gitlab.com/handbook/legal/modern-slavery-act-transparency-statement/","modern slavery transparency statement",{"title":636,"links":637},"Contact Us",[638,641,643,645,650,655,660],{"text":639,"config":640},"Contact an expert",{"href":37,"dataGaName":38,"dataGaLocation":476},{"text":368,"config":642},{"href":370,"dataGaName":371,"dataGaLocation":476},{"text":373,"config":644},{"href":375,"dataGaName":376,"dataGaLocation":476},{"text":646,"config":647},"Status",{"href":648,"dataGaName":649,"dataGaLocation":476},"https://status.gitlab.com/","status",{"text":651,"config":652},"Terms of use",{"href":653,"dataGaName":654,"dataGaLocation":476},"/terms/","terms of use",{"text":656,"config":657},"Privacy statement",{"href":658,"dataGaName":659,"dataGaLocation":476},"/privacy/","privacy statement",{"text":661,"config":662},"Cookie preferences",{"dataGaName":663,"dataGaLocation":476,"id":664,"isOneTrustButton":91},"cookie preferences","ot-sdk-btn",{"items":666},[667,669,671],{"text":651,"config":668},{"href":653,"dataGaName":654,"dataGaLocation":476},{"text":656,"config":670},{"href":658,"dataGaName":659,"dataGaLocation":476},{"text":661,"config":672},{"dataGaName":663,"dataGaLocation":476,"id":664,"isOneTrustButton":91},"content:shared:en-us:main-footer.yml","Main Footer","shared/en-us/main-footer.yml","shared/en-us/main-footer",{"allPosts":678,"featuredPost":2880,"totalPagesCount":2901,"initialPosts":2902},[679,704,727,749,770,793,815,835,859,879,899,923,945,969,990,1014,1036,1056,1075,1097,1117,1140,1160,1181,1201,1221,1241,1262,1281,1301,1321,1341,1363,1385,1403,1423,1443,1464,1485,1504,1524,1545,1565,1584,1604,1624,1644,1663,1682,1701,1720,1741,1761,1781,1800,1821,1841,1861,1880,1900,1918,1939,1959,1978,1997,2017,2037,2055,2075,2095,2114,2134,2153,2173,2191,2210,2231,2250,2270,2289,2308,2329,2347,2368,2387,2405,2427,2446,2466,2486,2505,2524,2544,2563,2581,2601,2621,2641,2661,2682,2700,2721,2741,2761,2781,2801,2820,2840,2861],{"_path":680,"_dir":246,"_draft":6,"_partial":6,"_locale":7,"seo":681,"content":689,"config":697,"_id":700,"_type":14,"title":701,"_source":16,"_file":702,"_stem":703,"_extension":19},"/en-us/blog/a-ci-component-builders-journey",{"title":682,"description":683,"ogTitle":682,"ogDescription":683,"noIndex":6,"ogImage":684,"ogUrl":685,"ogSiteName":686,"ogType":687,"canonicalUrls":685,"schema":688},"A CI/CD component builder's journey","Learn how a creator of shared, includable templates upskilled by migrating the templates to GitLab CI/CD components and the CI/CD Catalog.","https://res.cloudinary.com/about-gitlab-com/image/upload/v1749663857/Blog/Hero%20Images/blog-image-template-1800x945__12_.png","https://about.gitlab.com/blog/a-ci-component-builders-journey","https://about.gitlab.com","article","\n                        {\n        \"@context\": \"https://schema.org\",\n        \"@type\": \"Article\",\n        \"headline\": \"A CI/CD component builder's journey\",\n        \"author\": [{\"@type\":\"Person\",\"name\":\"Darwin Sanoy\"}],\n        \"datePublished\": \"2024-06-04\",\n      }",{"title":682,"description":683,"authors":690,"heroImage":684,"date":692,"body":693,"category":694,"tags":695},[691],"Darwin Sanoy","2024-06-04","I've always found it fascinating that my father, a heavy-duty mechanic by trade, would make his own tools for challenging jobs for which his industry had not yet built a fit-to-purpose tool. Little did I realize I'd become a tool builder in IT, which has been one of my loves for many years now.\n\nI have been building GitLab CI/CD includable, shared templates since starting with GitLab over four years ago. They were designed in a specific way for others to depend directly on them – similar to the dependency managers you see in application languages like Node.js NPM, Python Pypi, and .NET NuGet.\n\nGitLab itself has had long experience in building these shared CI dependencies through [Auto DevOps](https://docs.gitlab.com/ee/topics/autodevops/) and all of our security scanning suite of tools.\n\nWith the introduction of [GitLab CI/CD Catalog](https://about.gitlab.com/blog/ci-cd-catalog-goes-ga-no-more-building-pipelines-from-scratch/), this long-running approach is formalized into a way for everyone to publish GitLab CI/CD components for use by anyone in the world.\n\nSome of they key upgrades compared to the shared templates approach include:\n\n- **Independent component versions** are a new versioning mechanism that no longer relies on inheriting containers versions. GitLab CI/CD component versions bundle together the CI code and any number of containers (or no containers) behind a single CI/CD component version. The concepts of stable, production-grade DevSecOps require the ability to peg dependency versions in automation – for exactly the same reasons and benefits that this is done in production-grade application code.\n\n- **Global visibility (with control)** is available through the catalog at GitLab.com (or global to your company on a self-managed instance). Individual component visibility is also subject to the security settings of it's source project - so you can publish components to secure groups.\n\n- **Catalog metadata,** like most code-sharing mechanisms, is needed data to make decisions about which components to use.\n\n## Let's show some code\n\nI much prefer to show than tell, so let's look at a few component examples - all of which also publish their sources publicly (click on the title to access the component).\n\n### 1. [Hello World](https://gitlab.com/explore/catalog/guided-explorations/ci-components/hello-world)\nI noticed that there was not yet a Hello World component that could show the minimum viable component, both the results and the source. This particular example shows how to \"componentize\" just CI code.\n\n### 2. [Hello World Container](https://gitlab.com/explore/catalog/guided-explorations/ci-components/hello-world-container)\nFrequently a CI/CD component will require a container to be fully functional. This example includes a container that is published in the same project as the component itself.\n\n### 3. [GitVersion Ultimate Auto Semversioning](https://gitlab.com/explore/catalog/guided-explorations/ci-components/ultimate-auto-semversioning)\n\nThis component automates the venerable \"GitVersion\" utility, which completely automates selecting the next semversion for your software without having to store the last version – even for busy repositories where many production-possible candidates are being worked on at once. One of the building principles this component follows is the principle of \"least configuration\" or \"default to doing the most useful thing with zero configuration.\" In this case, if your project does not contain a `GitVersion.yml`, the component creates the one that an individual unfamiliar with GitVersion might find to be the most useful starting point.\n\n### 4. [Amazon CodeGuru Secure SAST Scanner](https://gitlab.com/explore/catalog/guided-explorations/ci-components/aws/amazon-codeguru-secure-sast)\nThis component is a security scanner and, as such, follows some security scanning best practices I have implemented during recent years. For instance, if it detects that you are licensed for GitLab Ultimate, it has the scanner output GitLab's SAST JSON format, which integrates the findings just like native GitLab scanner findings. The findings appear in MRs and dashboards and can be the target of security policy merge approvals. If, however, you are not licensed for GitLab Ultimate, the scanner outputs JUNIT XML so that you have some basic, non-diffed findings visualization in the pipeline \"Test Results\" tab. It also only activates if there are file types it can scan and disables if the GitLab SAST_DISABLED property is turned on.\n\n### 5. [Checkov IaC SAST](https://gitlab.com/explore/catalog/guided-explorations/ci-components/checkov-iac-sast)\nCheckov IaC SAST is another security scanner component that also follows the above security scanner principles, but specifically for the file types it is capable of scanning. A critical best practice of many of these components is pegging container tags for stability - but doing so through a \"component input\" with a default value. This allows component users to test with and peg to a newer or older version than you last tested with. So your shared dependency then offers stability, but with flexibility.\n\n### 6. [Super-Linter](https://gitlab.com/guided-explorations/ci-components/super-linter)\nSuper-Linter is a community-driven conglomeration of many linters for many languages. It originally started life as a GitHub Action, so this particular example demonstrates some of the ease of porting open source GitHub Actions to GitLab CI/CD components. A best practice aspect to many of my components is to always link to working example code with the component in action. This also allows you to do easy testing when performing updates.\n\n### 7. [Kaniko](https://gitlab.com/explore/catalog/guided-explorations/ci-components/kaniko)\nKaniko is a container that can build containers without Docker-in-Docker (DinD) privileged mode requirement. This component supports many OpenContainers labels and multi-arch builds.\n\n### 8. [CI Component Publishing Utilities](https://gitlab.com/explore/catalog/guided-explorations/ci-components/ci-component-pub)\nAs I built more components, I noticed that my \"component publishing CI code\" was being duplicated many times - and that makes it a candidate for becoming a component itself. All the other components here leverage this component. It also uses components itself, so it uses **GitVersion Ultimate Auto Semversioning** to get the next version.\n\nAnd if you're wondering, yes, CI Component Publishing Utilities publishes itself. In many of my components I have expanded the standard \"Inputs\" README section to \"Inputs and Configuration\" and I have added a column to show whether configurations are happening via inputs or variables. While you generally want to favor inputs, there are times when variables give more flexibility or you just want to document that the user can get perform key configurations of the underlying utilities via environment variables that the utility already supports. CI Component Publishing Utilities also uses the **Kaniko** CI component to build a container with the same version if it finds a Dockerfile at the root of your project (or you tell it where one is with a variable). This synchronizes the version of components and containers that support them. It also handles multi-arch container builds - see the documentation linked above to learn more!\n\n## Getting started with component templates\n\nThe Hello World components function as my own personal templates for starting a new component. They incorporate the CI Component Publishing Utilities and a reasonably good README.\n\nFor components that contain only CI code, I start by copying the source of [Hello World](https://gitlab.com/explore/catalog/guided-explorations/ci-components/hello-world) and for ones that require a container, I start with [Hello World Container](https://gitlab.com/explore/catalog/guided-explorations/ci-components/hello-world-container). I generally copy just the source into a new project so that I have a clean commit history.\n\nWhen I feel the component is stable and well developed I do a manual pipeline run and force the version to 1.1.0 or greater. The CI Component Publishing Utilities will then auto-increment the version from there.\n\n## CI component Builders Guides and practices\n\n[Darwins CI Component Builders Guide](https://gitlab.com/guided-explorations/ci-components/gitlab-profile) - I was also interested in publishing my approach to building components and what better way to get visibility than as a CI/CD component? BTW, the [GitLab Pipeline Authoring](https://about.gitlab.com/direction/verify/pipeline_composition/) team that created the CI/CD component architecture and [CI/CD Catalog](https://about.gitlab.com/blog/ci-cd-catalog-goes-ga-no-more-building-pipelines-from-scratch/) has some great best practices published at [CI components best practices](https://docs.gitlab.com/ee/ci/components/#best-practices). The practices I publish reference these ones, but I also have quite a few I follow that are specific to my own lessons learned.\n\n## Finding the CI/CD components and their sources\n\nThe [GitLab CI/CD Catalog](https://about.gitlab.com/blog/ci-cd-catalog-goes-ga-no-more-building-pipelines-from-scratch/) is still undergoing innovation in searchability. However, the description from the source project is free-form searchable, so by including standard text in the descriptions of all my component source projects, I have created the ability for users to [find all of the ones I've created in the catalog](https://gitlab.com/explore/catalog?search=Part+of+the+DarwinJS+Builder+Component+Library).\n\nTo make my component source findable regardless of its location on GitLab.com:\n- I add a repository topic to all the projects called [DarwinJS Component Builder Library](https://gitlab.com/explore/projects/topics/DarwinJS+Component+Builder+Libary).\n- I tag with the organic tag I found called [`GitLab CICD Components`](https://gitlab.com/explore/projects/topics/GitLab+CICD+Components).\n\nBoth of the above techniques can help you provide an index to your components and their source if you are inclined to do so.\n\nI hope that my CI/CD component building journey will be helpful to you now and in the future.\n\n> Learn more about the CI/CD Catalog and components:\n>  \n> - [CI/CD Catalog goes GA: No more building pipelines from scratch](https://about.gitlab.com/blog/ci-cd-catalog-goes-ga-no-more-building-pipelines-from-scratch/)\n> \n> - [FAQ: GitLab CI/CD Catalog](https://about.gitlab.com/blog/faq-gitlab-ci-cd-catalog/)\n>\n> - [Documentation: CI/CD components and CI/CD Catalog](https://docs.gitlab.com/ee/ci/components/)\n> \n> - [Introducing CI/CD components and how to use them in GitLab](https://about.gitlab.com/blog/introducing-ci-components/)\n>","open-source",[109,9,696],"CD",{"slug":698,"featured":6,"template":699},"a-ci-component-builders-journey","BlogPost","content:en-us:blog:a-ci-component-builders-journey.yml","A Ci Component Builders Journey","en-us/blog/a-ci-component-builders-journey.yml","en-us/blog/a-ci-component-builders-journey",{"_path":705,"_dir":246,"_draft":6,"_partial":6,"_locale":7,"seo":706,"content":712,"config":721,"_id":723,"_type":14,"title":724,"_source":16,"_file":725,"_stem":726,"_extension":19},"/en-us/blog/a-story-of-runner-scaling",{"title":707,"description":708,"ogTitle":707,"ogDescription":708,"noIndex":6,"ogImage":709,"ogUrl":710,"ogSiteName":686,"ogType":687,"canonicalUrls":710,"schema":711},"An SA story about hyperscaling GitLab Runner workloads using Kubernetes","It is important to have the complete picture of scaled effects in view when designing automation.","https://res.cloudinary.com/about-gitlab-com/image/upload/v1749669897/Blog/Hero%20Images/kaleidico-26MJGnCM0Wc-unsplash.jpg","https://about.gitlab.com/blog/a-story-of-runner-scaling","\n                        {\n        \"@context\": \"https://schema.org\",\n        \"@type\": \"Article\",\n        \"headline\": \"An SA story about hyperscaling GitLab Runner workloads using Kubernetes\",\n        \"author\": [{\"@type\":\"Person\",\"name\":\"Darwin Sanoy\"},{\"@type\":\"Person\",\"name\":\"Brian Wald\"}],\n        \"datePublished\": \"2022-06-29\",\n      }",{"title":707,"description":708,"authors":713,"heroImage":709,"date":715,"body":716,"category":717,"tags":718},[691,714],"Brian Wald","2022-06-29","\n\nThe following *fictional story*\u003Csup>1\u003C/sup> reflects a repeating pattern that Solutions Architects at GitLab encounter frequently. In the analysis of this story we intend to demonstrate three things: (a) Why one should be thoughtful in leveraging Kubernetes for scaling, (b) How unintended consequences of an approach to automation can create a net productivity loss for an organization (reversal of ROI) and (c) How solutions architecture perspectives can help find anti-patterns - retrospectively or when applied during a development process.\n\n### A DevOps transformation story snippet\n\nGild Investment Trust went through a DevOps transformational effort to build efficiency in their development process through automation with GitLab. Dakota, the application development director, knew that their current system handled about 80 pipelines with 600 total tasks and over 30,000 CI minutes so they knew that scaled CI was needed. Since development occurred primarily during European business hours, they were interested in reducing compute costs outside of peak work hours. Cloud compute was also a target due to acquring the pay per use model combined with elastic scaling.\n\nIngrid was the infrastructure engineer for developer productivity who was tasked with building out the shared GitLab Runner fleet to meet the needs of the development teams. At the beginning of the project she made a successful bid to leverage Kubernetes to scale CI and CD to take advantage of the elastic scaling and high availability all with the efficiency of containers. Ingrid had recently achieved the Certified Kubernetes Administrator (CKA) certification and she was eager to put her knowledge to practical use. She did some additional reading around applications running on Kubernetes and noted the strong emphasis on minimizing the resource profile of microservices to achieve efficiency in the form of compute density. She defined runner containers with 2GB of memory and 750millicores (about three quarters of a CPU) had good results from running some test CI pipelines. She also decided to leverage the Kubernetes Cluster Autoscaler which would use the overall cluster utilization and scheduling to automatically add and remove Kubernetes worker nodes for smooth elastic scaling in response to demand.\n\nAbout 3 months into the proof of concept implementation, Sasha, a developer team lead, noted that many of their new job types were failing with strange error messages. The same jobs ran fine on quickly provisioned GitLab shell runners. Since the primary difference between the environments was the liberal allocation of machine resources in a shell runner, Sasha reasoned that the failures were likely due to the constrained CPU and memory resources of the Kubernetes pods.\n\nTo test this hypothesis, Ingrid decided to add a new pod definition. She found it was difficult to discern which of the job types were failing due to CPU constraints, which ones due to memory constraints and which ones due to the combination of both. She knew it could be a lot of her time to discern the answer. She decided to simply define a pod that was more liberal on both CPU and memory and have it be selectable by runner tagging when more resources were needed for certain CI jobs. She created a GitLab Runner pod definition with 4GB of memory and 1750 millicores of CPU to cover the failing job types. Developers could then use these larger containers when the smaller ones failed by adding the ‘large-container’ tag to their GitLab job.\n\nSasha redid the CI testing and was delighted to find that the new resourcing made all the troubling jobs work fine. Sasha created a guide for developers to try to help discern when mysterious error messages and failed CI jobs were probably the fault of resourcing and then how to add a runner tag to the job to expand the resources.\n\nSome weeks later two of the key jobs that were fixed by the new container resourcing started intermittently failing on NPM package creation jobs for just 3 pipelines on 2 different teams. Of course Sasha tried to understand what the differences were and found that these particular pipelines were packaging notably large file sets because they were actually packaging testing data and the NPM format was a convenient way to provide testing data during automated QA testing.\n\nSasha brought this information to Ingrid and together they did testing to figure out that a 6GB container with 2500 millicores would be sufficient for creating an NPM package out of the current test dataset size. They also discussed whether the development team might want to use a dedicated test data management solution, but it turned out that the teams needs were very simple and that their familiarity with NPM packaging meant that bending NPM packaging to suit their purpose was actually more efficient than acquiring, deploying, learning and maintaining a special system for this purpose. So a new pod resourcing profile was defined and could be accessed with the runner tag ‘xlarge’.\n\nSasha updated the guide for finding the optimal container size through failure testing of CI jobs - but they were not happy with how large the document was getting and how imprecise the process was for determining when a CI job failure was, most likely due to container resource constraints. They were concerned that developers would not go through the process and instead simply pick the largest container resourcing profile in order to avoid the effort of optimizing and they shared this concern with Ingrid. In fact, Sasha noted, they were hard pressed themselves to follow their own guidelines and not to simply choose the largest container for all jobs themselves.\n\nThe potential for this cycle to repeat was halted several months later when Dakota, the app dev director, generated a report that showed a 2% increase in developer time spent optimizing CI jobs using failure testing for container size optimization. Dakota considered this work to be a net new increase because when the company was not using container-based CI, the developers did not have to manage this concern at all. Across 298 developers this amounted to around $840,000/yr dollars of total benefits per month\u003Csup>2\u003C/sup>. It was also thought to add about 2 hours (and growing) to developer onboarding training. It was noted that the report did not attempt to account for the opportunity cost tax - what would these people be doing to solve customer problems with that time? It also did not account for the \"critical moments tax\" (when complexity has an outsized frustration effect and business impact on high pressure, high risk situations).\n\n### Solution architecture retrospective: What went wrong?\n\nThis story reflects a classic antipattern we see at GitLab, not only with regard to Kubernetes runner optimization, but also across other areas, such as overly minimalized build containers and the potential for resultant pipeline complexity as was discussed in a previous blog called [When the pursuit of simplicity creates complexity in container-based CI pipelines](/blog/second-law-of-complexity-dynamics/). Frequently this result comes from inadvertent adherance to heuristics of a small part of the problem as though they were applicable to the entirety of the problem (a type of a logical “fallacy of composition”).\n\nThankfully the emergence of the anti-pattern follows a pattern itself :). Let’s apply a little retrospective solution architecture to the \"what happened\" in order to learn what might be done proactively next time to create better iterations on the next automation project.\n\nThere is a certain approach to landscaping shared greenspaces where, rather than shame people into compliance with signs about not cutting across the grass in key locations, the paths that humans naturally take are interpreted as the signal “there should be a path here.” Humans love beauty and detail in the environments they move through, but depending on the space, they can also value the efficiency of the shortest possible route slightly higher than aesthetics. A wise approach to landscaping holds these factors in a balance that reflects the efficiency versus aesthetic appeal balance of the space user. The space stays beautiful without any shaming required.\n\nIn our story Sasha and Ingrid had exactly this kind of cue where the developers were likely to walk across the grass. If that cue is taken to be a signal that reflects efficiency, we can quickly see what can be done to avoid the antipattern when it starts to occur.\n\nThe signal was the observation that developers might simply choose the largest container all the time to avoid the fussy process of optimizing the compute resources being consumed. Some would consider that laziness and not a good signal to heed. However, most human laziness is deeply rooted in efficiency trade-offs. The developers intuitively understand that their time fussing with failure testing to optimize job containers and their time diagnosing intermittent failures due to the varying content of those jobs, is not worth the amount of compute saved. That is especially true given the opportunity cost of not spending that time innovating the core software solution for the revenue generating application.\n\nIngrid and Sasha’s collaboration has initially missed the scaled human toil factor that was introduced to keep container resources at the minimum tolerable levels. They failed to factor in the escalating cost of scaled human toil to have a comprehensive efficiency measurement. They were following a microservices resourcing pattern which assumes the compute is purpose designed around minimal and well known workloads. When taken as a whole in a shared CI cluster, CI compute follows generalized compute patterns where the needs for CPU, Memory, Disk IO and Network IO can vary wildly from one moment to the next.\n\nIn the broadest analysis, the infrastructure team over indexed to the “team local” optimization of compute efficiency and unintentionally created a global de-optimization of scaled human toil for another team.\n\n## How can this antipattern be avoided?\n\nOne way to combat over indexing on a criteria is to have balancing objectives. This need is covered in \"Measure What Matters\" with the concept of counter balancing objectives. There are some counter balancing questions that can be asked of almost any automation effort. When solution architecture is functioning well these counter balancing questions are asked during the iterative process of building out a solution. Here are some applicable ones for this effort:\n\n**Approporiate Rules: Does the primary compute optimization heuristic match the characteristics of the actual compute workload being optimized?**\n\nThe main benefits of container compute for CI are dependency isolation, dependency encapsulation and a clean build environment for every job. None of these benefits has to do with the extreme resource optimizations available to engineer microservices architected applications. As a whole, CI compute reflects generalized compute, not the ultra-specialized compute of a 12 factor architected micro-service.\n\n**Appropriate granularity: Does optimization need to be applied at every level?**\n\nThe fact that the cluster itself has elastic scaling at the Kubernetes node level is a higher order optimization that will generate significant savings. Another possible optimization that would not require continuous fussing by developers is having a node group running on spot compute (as long as the spot compute runners self-identify their compute as spot so pipeline engineers can select appropriate jobs for spot). These optimizations can create huge savings, without creating scaled human toil.\n\n**People and processes counter check: Does the approach to optimization create scaled human toil by its intensity and/or frequency and/or lack of predictability for any people anywhere in the organization?**\n\nAutomation is all about moving human toil into the world of machines. While optimizing machine resources must always be a primary consideration, it is a lower priority objective than creating a net increase in human toil anywhere in your company. Machines can efficiently and elastically scale, while human workforces respond to scaling needs in months or even years.\n\n### Avoid scaled human toil\n\nNotice that neither the story, nor the qualifying questions, imply there is never a valid reason to have specialized runners that developers might need to select using tags. If a given attribute of runners could be selected once and with confidence then the antipattern would not be in play. One example would be selecting spot compute backed runners for workloads that can tolerate termination. It is the potential for repeated needed attention to calibrate container sizing - made worse by the possibility of intermittent failure based on job content - that pushes this specific scenario into the potential realm of “scaled human toil.” The ability to leverage elastic cluster autoscaling is also a huge help to managing compute resources more efficiently.\n\nIf the risk of scaled human toil could be removed then some of this approach may be able to be preserved. For example, having very large minimum pod resourcing and then a super-size for stuff that breaks the standard pod size just once. Caution is still warranted because it is still possible that developers have to fuss a lot to get a two pod approach working in practice.\n\n### Beware of scaled human toil of an individual\n\nOne thing the story did not highlight is that even if we were able to move all the fussing of such a design to the Infrastructure Engineer persona (perhaps by building an AI tuning mechanism that guesses at pod resourcing for a given job), the cumulative taxes on their role are frequently still not worth the expense. This is, in part, because they have a leveraged role - they help with all the automation of the scaled developer workforce and any time they spend on one activity can’t be spent on another. We humans are generally bad at accounting for opportunity costs - what else could that specific engineer be innovating on to make a stronger overall impact to the organization’s productivity or bottom line? Given the very tight IT labor market, a given function may not be able to add headcount, so opportunity costs take on an outsized importance.\n\n### Unlike people’s time, cloud compute does not carry opportunity cost\n\nA long time ago people had to schedule time on shared computing resources. If the time was used for low-value compute activities it could be taking away time from higher value activities. In this model compute time has an opportunity cost - the cost of what it could be using that time for if it wasn’t doing a lower value activity. Cloud compute has changed this because when compute is not being used, it is not being paid for. Additionally, elastic scaling eliminates the costs of over provisioning hardware and completely eliminates the administrative overhead of procuring capacity - if you need lots for a short period of time it is immediately available. In contrast, people time is not elastically scalable nor pay per use. This means that the opportunity cost question “What could this time be used for if it didn’t have to be spent on low value activities?” is still relevant for anything that creates activities for people.\n\n### The first corollary to the Second Law of Complexity Dynamics\n\nThe Second Law of Complexity Dynamics was introduced in an earlier blog. The essence is that complexity is never destroyed - it is only reformed - and primarily it is moved across a boundary line that dictates whether the management of the complexity is in our domain or externalized. For instance, if you write a function for md5 hashing in your code, you are managing the complexity of that code. If you install a dependency package that contains a premade md5 hash function that you simply use, then the complexity is externalized and managed for you by someone else.\n\nIn this story we are introducing the corollary to that “Law” that “**Exchanging Raw Machine Resources for Complexity Management is Generally a Reasonable Trade-off.**” In this case our scaled human toil is created due to the complexity of unending, daily management of optimizing compute efficiency. This does not mean that burning thousands of dollars of inefficient compute is OK because it saved someone 20 minutes of fussing. It is scoped in the following way:\n\n- scoped to “complexity management” (which is creating the “scaled human toil” in our story) - many minutes of toil that increases proportionally or compounds with more of the activity.\n- scoped to “raw machine resources” - meaning that there is not additional logistics nor human toil to gain the resources. In the cloud raw machine resources are generally available via configuration tweaks.\n- scoped to “generally reasonable” - this indicates a disposition of being very cautious about increasing human toil with an automatoin solution - but it still makes sense to use models or calculations to check if the rule actually holds in a given case.\n\nSo if we can externalize complexity management that is great (The Second Law of Complexity Dynamics). If we can trade complexity management for raw computing resource, that is likely still better than managing it ourselves (The First Corollary).\n\n### Iterating SA: Experimental improvements for your next project\n\nThis post contains specifics that can be used to avoid antipatterns in building out a Kubernetes cluster for GitLab CI. However, in the qualifying questions we’ve attempted to kick it up to one meta-level higher to help assess whether any automation effort may have an “overly local” optimization focus which can inadvertently create a net loss of efficiency across the more global “company context.” It is our opinion that automation efforts that create a net loss in human productivity should not be classified as automation at all. While it’s strong medicine to apply to one’s work, we feel that doing so causes appropriate innovation pressure to ensure that individual automation efforts truly deliver on their inherent promise of higher human productivity and efficiency. So simply ask “Does this way of solving a problem cause recurring work for anyone?”\n\n### DevOps transformation and solution architecture perspectives\n\nA technology architecture focus rightfully hones in on the technology choices for a solution build. However, if it is the only lens, it can result in scenarios like our story. Solutions architecture steps back to a broader perspective to sanity-check that solution iterations account for a more complete picture of both the positive and negative impacts across all three of people, processes and technology. As an organizational competency, DevOps emphasis solution architecture perspectives when it is defined as a collaborative and cultural approach to people, processes and technology.\n\nFootnotes:\n\n1. This fictional story was devised specifically for this article and does not knowingly reflect the details of any other published story or an actual situation. The names used in the story are from [GitLab’s list of personas](https://handbook.gitlab.com/handbook/product/personas/).\n2. Across a team of 300 full time developers. 9.6min/workday x 250 workdays / year = 2400mins / 8hrs/workday  = 5 workdays x $560 per day (140K Total Comp/250days) = $2800/dev/year x 300 developers = $840,000/yr\n\nCover image by [Kaleidico](https://unsplash.com/@kaleidico?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText) on [Unsplash](https://unsplash.com/)\n","engineering",[9,696,719,720],"performance","solutions architecture",{"slug":722,"featured":6,"template":699},"a-story-of-runner-scaling","content:en-us:blog:a-story-of-runner-scaling.yml","A Story Of Runner Scaling","en-us/blog/a-story-of-runner-scaling.yml","en-us/blog/a-story-of-runner-scaling",{"_path":728,"_dir":246,"_draft":6,"_partial":6,"_locale":7,"seo":729,"content":735,"config":743,"_id":745,"_type":14,"title":746,"_source":16,"_file":747,"_stem":748,"_extension":19},"/en-us/blog/a-visual-guide-to-gitlab-ci-caching",{"title":730,"description":731,"ogTitle":730,"ogDescription":731,"noIndex":6,"ogImage":732,"ogUrl":733,"ogSiteName":686,"ogType":687,"canonicalUrls":733,"schema":734},"A visual guide to GitLab CI/CD caching","Learn cache types, as well as when and how to use them.","https://res.cloudinary.com/about-gitlab-com/image/upload/v1749682443/Blog/Hero%20Images/cover.jpg","https://about.gitlab.com/blog/a-visual-guide-to-gitlab-ci-caching","\n                        {\n        \"@context\": \"https://schema.org\",\n        \"@type\": \"Article\",\n        \"headline\": \"A visual guide to GitLab CI/CD caching\",\n        \"author\": [{\"@type\":\"Person\",\"name\":\"Matthieu Fronton\"}],\n        \"datePublished\": \"2022-09-12\",\n      }",{"title":730,"description":731,"authors":736,"heroImage":732,"date":738,"body":739,"category":717,"tags":740},[737],"Matthieu Fronton","2022-09-12","If you've ever worked with GitLab CI/CD you may have needed, at some point,\nto use a cache to share content between jobs. The decentralized nature of\nGitLab CI/CD is a strength that can confuse the understanding of even the\nbest of us when we want to connect wires all together. For instance, we need\nto know critical information such as the difference between artifacts and\ncache and where/how to place setups.\n\n\nThis visual guide will help with both challenges.\n\n\n## Cache vs. artifacts\n\n\nThe concepts _may_ seem to overlap because they are about sharing content\nbetween jobs, but they actually are fundamentally different:\n\n\n- If your job does not rely on the the previous one (i.e. can produce it by\nitself but if content already exists the job will run faster), then use\ncache.\n\n- If your job does rely on the output of the previous one (i.e. cannot\nproduce it by itself), then use artifacts and dependencies.\n\n\nHere is a simple sentence to remember if you struggle between choosing cache\nor artifact:\n\n> Cache is here to speed up your job but it may not exist, so don't rely on\nit.\n\n\nThis article will focus on **cache**.\n\n\n## Initial setup\n\n\nWe'll go with a simple representation of the GitLab CI/CD pipelining model\nand ignore (for now) that the jobs can be executed on any runners and hosts.\nIt will help get the basics.\n\n\nLet's say you have:\n\n- 1 project with 3 branches\n\n- 1 host running 2 docker runners\n\n\n![Initial\nsetup](https://about.gitlab.com/images/blogimages/visual-guide-caching/vgc-1.png){:\n.shadow.center}\n\n\n## Local cache: Docker volume\n\n\nIf you want a [local\ncache](https://docs.gitlab.com/ee/ci/caching/index.html#where-the-caches-are-stored)\nbetween all your jobs running on the same runner, use the [cache\nstatement](https://docs.gitlab.com/ee/ci/yaml/#cache) in your\n`.gitlab-ci.yml`:\n\n\n```yaml\n\ndefault:\n  cache:\n    path:\n      - relative/path/to/folder/*.ext\n      - relative/path/to/another_folder/\n      - relative/path/to/file\n```\n\n\n![local / container / all branches / all\njobs](https://about.gitlab.com/images/blogimages/visual-guide-caching/vgc-2.png){:\n.shadow.center}\n\n\nUsing the [predefined\nvariable](https://docs.gitlab.com/ee/ci/variables/predefined_variables.html)\n`CI_COMMIT_REF_NAME` as the [cache\nkey](https://docs.gitlab.com/ee/ci/yaml/index.html#cachekey), you can ensure\nthe cache is tied to a specific branch:\n\n\n```yaml\n\ndefault:\n  cache:\n    key: $CI_COMMIT_REF_NAME\n    path:\n      - relative/path/to/folder/*.ext\n      - relative/path/to/another_folder/\n      - relative/path/to/file\n```\n\n\n![local / container / one branch / all\njobs](https://about.gitlab.com/images/blogimages/visual-guide-caching/vgc-3.png){:\n.shadow.center}\n\n\nUsing the [predefined\nvariable](https://docs.gitlab.com/ee/ci/variables/predefined_variables.html)\n`CI_JOB_NAME` as the [cache\nkey](https://docs.gitlab.com/ee/ci/yaml/index.html#cachekey), you can ensure\nthe cache is tied to a specific job:\n\n\n![local / container / all branch / one\njobs](https://about.gitlab.com/images/blogimages/visual-guide-caching/vgc-4.png){:\n.shadow.center}\n\n\n## Local cache: Bind mount\n\n\nIf you don't want to use a volume for caching purposes (debugging purpose,\ncleanup disk space more easily, etc.), you can configure a [bind mount for\nDocker volumes](https://docs.docker.com/storage/bind-mounts/) while\nregistering the runner. With this setup, you do not need to set up the\n[cache statement](https://docs.gitlab.com/ee/ci/yaml/#cache) in your\n`.gitlab-ci.yml`:\n\n\n```yaml\n\n#!/bin/bash\n\n\ngitlab-runner register                             \\\n  --name=\"Bind-Mount Runner\"                       \\\n  --docker-volumes=\"/host/path:/container/path:rw\" \\\n...\n\n```\n\n\n![local / one runners / one host / all branch / all\njobs](https://about.gitlab.com/images/blogimages/visual-guide-caching/vgc-5.png){:\n.shadow.center}\n\n\nIn fact, this setup even allows you to share a cache between jobs running on\nthe same host without requiring you to set up a distributed cache (which\nwe'll talk about later):\n\n\n```yaml\n\n#!/bin/bash\n\n\ngitlab-runner register                             \\\n  --name=\"Bind-Mount Runner X\"                     \\\n  --docker-volumes=\"/host/path:/container/path:rw\" \\\n...\n\n\ngitlab-runner register                                 \\\n  --name=\"Bind-Mount Runner Y\"                         \\\n  --docker-volumes=\"/host/path:/container/alt/path:rw\" \\\n...\n\n```\n\n\n![local / multiple runners / one host / all branch / all\njobs](https://about.gitlab.com/images/blogimages/visual-guide-caching/vgc-6.png){:\n.shadow.center}\n\n\n## Distributed cache\n\n\nIf you want to have a [shared\ncache](https://docs.gitlab.com/runner/configuration/autoscale.html#distributed-runners-caching)\nbetween all your jobs running on multiple runners and hosts, use the \u003Ca\nhref=\"https://docs.gitlab.com/runner/configuration/advanced-configuration.html#the-runnerscache-section\">[runner.cache]\u003Ca>\nsection in your `config.toml`:\n\n\n```yaml\n\n[[runners]]\n  name = \"Distributed-Cache Runner\"\n...\n  [runners.cache]\n    Type = \"s3\"\n    Path = \"bucket/path/prefix\"\n    Shared = true\n    [runners.cache.s3]\n      ServerAddress = \"s3.amazonaws.com\"\n      AccessKey = \"\u003Cchangeme>\"\n      SecretKey = \"\u003Cchangeme>\"\n      BucketName = \"foobar\"\n      BucketLocation = \"us-east-1\"\n```\n\n\n![remote / multiple runners / multiple hosts / all branch / all\njobs](https://about.gitlab.com/images/blogimages/visual-guide-caching/vgc-7.png){:\n.shadow.center}\n\n\nUsing the predefined variable `CI_COMMIT_REF_NAME` as the cache key you can\nensure the cache is tied to a specific branch between multiple runners and\nhosts:\n\n\n![remote / multiple runners / multiple hosts / one branch / all\njobs](https://about.gitlab.com/images/blogimages/visual-guide-caching/vgc-8.png){:\n.shadow.center}\n\n\n## Real-life setup\n\n\nThe above assumptions allowed you to harness your understanding of the\nconcepts and possibilities.\n\n\nIn real life, you'll face more complex wiring and we hope this article will\nhelp you as a visual cheatsheet along with the reference documentation.\n\n\nJust to give you a sneak peek, here is an exercise for you:\n\n\n- Set up a cache between all the jobs of a specific stage, running on any\nrunner and any hosts, but only between pipeline of the same branches:\n\n\n![Real-life test\nassignment](https://about.gitlab.com/images/blogimages/visual-guide-caching/vgc-9.png){:\n.shadow.center}\n\n\nHappy caching, folks!\n\n\n\n\nCover image by [Alina Grubnyak](https://unsplash.com/@alinnnaaaa) on\n[Unsplash](https://unsplash.com)\n\n{: .note}\n",[9,696,741,742],"DevOps","tutorial",{"slug":744,"featured":6,"template":699},"a-visual-guide-to-gitlab-ci-caching","content:en-us:blog:a-visual-guide-to-gitlab-ci-caching.yml","A Visual Guide To Gitlab Ci Caching","en-us/blog/a-visual-guide-to-gitlab-ci-caching.yml","en-us/blog/a-visual-guide-to-gitlab-ci-caching",{"_path":750,"_dir":246,"_draft":6,"_partial":6,"_locale":7,"seo":751,"content":757,"config":764,"_id":766,"_type":14,"title":767,"_source":16,"_file":768,"_stem":769,"_extension":19},"/en-us/blog/achieving-23-cost-savings-and-36-performance-gain-using-gitlab-and-gitlab-runner-on-arm-neoverse-based-aws-graviton2-processor",{"title":752,"description":753,"ogTitle":752,"ogDescription":753,"noIndex":6,"ogImage":754,"ogUrl":755,"ogSiteName":686,"ogType":687,"canonicalUrls":755,"schema":756},"GitLab on Graviton2: 23% cheaper, 36% higher performance","GitLab and GitLab Runner Performance Gains on Arm based AWS Graviton2","https://res.cloudinary.com/about-gitlab-com/image/upload/v1749669673/Blog/Hero%20Images/engineering.png","https://about.gitlab.com/blog/achieving-23-cost-savings-and-36-performance-gain-using-gitlab-and-gitlab-runner-on-arm-neoverse-based-aws-graviton2-processor","\n                        {\n        \"@context\": \"https://schema.org\",\n        \"@type\": \"Article\",\n        \"headline\": \"23% Cost savings and 36% performance gain by deploying GitLab on Arm-based AWS Graviton2\",\n        \"author\": [{\"@type\":\"Person\",\"name\":\"Pranay Bakre\"}],\n        \"datePublished\": \"2021-08-05\",\n      }",{"title":758,"description":753,"authors":759,"heroImage":754,"date":761,"body":762,"category":717,"tags":763},"23% Cost savings and 36% performance gain by deploying GitLab on Arm-based AWS Graviton2",[760],"Pranay Bakre","2021-08-05","\n\nCompanies in all industries and sectors have significantly invested in digital transformation and increased their software development capabilities. GitLab delivers modern DevOps with a complete DevOps platform. However, some organizations require self-managed GitLab and GitLab Runners, which creates added costs for hosting and running GitLab infrastructure. Our latest cost analysis and performance benchmarks show that customers can realize cost savings of up to 23% and performance gains of up to 36% by deploying the GitLab application and GitLab Runner on the Arm-based Graviton2 when compared to the x86 based M5/C5 EC2 instances.\n\n[Arm](https://www.arm.com/) is a leading provider of silicon intellectual property (IP) for intelligent systems-on-chip (SoC) that power billions of devices. [GitLab and Arm have collaborated](/blog/gitlab-arm-aws-graviton2-solution/) closely to make GitLab tools available for devices based on Arm architecture. AWS is the first major public cloud provider to offer Arm-based EC2 compute instances powered by Graviton2 processors built upon Arm Neoverse N1 IP cores.\n\n## Performance benchmarks for GitLab 10,000 reference architecture\n\nGitLab is a highly scalable and modular application and can accomodate 10 users to 10,000 as a business scales. Today, the [GitLab 10,000 reference architecture](https://docs.gitlab.com/ee/administration/reference_architectures/10k_users.html) provides users with a blueprint for hosting GitLab on x86_64-backed compute on leading cloud platforms providers. Building upon our collaboration from last year, the next step was to include Arm64 backed compute in the reference architecture.\n\nFor the research, we first ran the performance benchmarks comparing the cost of hosting GitLab's [Reference Architecture](https://docs.gitlab.com/ee/administration/reference_architectures/) for up to 10,000 users on Arm64 and x86 environments on AWS. We found that GitLab customers can realize up to 23% cost savings on their AWS bill by deploying GitLab on Graviton2-based EC2 instances over comparable x86-based EC2 instances for about the same level of performance. See the monthly AWS cost for running this scenario on the [Arm64 environment](https://calculator.s3.amazonaws.com/index.html#r=IAD&s=EC2&key=files/calc-4f854bec29723ed3fa0f209ca0fddf3495447e8f&v=ver20210322c7) and [x86 environment](https://calculator.s3.amazonaws.com/index.html#r=IAD&s=EC2&key=files/calc-8c66ad5bfb008a1f0f21c779fcc336418ae1e83a&v=ver20210322c7).\n\nThe figure below shows the components that make up the GitLab 10,000 reference architecture:\n\n![GitLab architecture](https://about.gitlab.com/images/blogimages/gitlab_arch.png){: .shadow.medium.center}\nAn example of components that make up a 10,000 user GitLab architecture.\n{: .note.text-center}\n\nRead more about the [components required to set up the 10,000 architecture](https://docs.gitlab.com/ee/administration/reference_architectures/10k_users.html).\n\nFor testing, we used the [GitLab Performance Tool](https://gitlab.com/gitlab-org/quality/performance) developed in-house by GitLab to test the performance of GitLab. Below is a high-level view of the different kinds of tests generated by GPT.\n\n![GPT tests](https://about.gitlab.com/images/blogimages/gpt_tests.png){: .shadow.medium.center}\nThe different types of tests created by the GitLab Performance Tool.\n{: .note.text-center}\n\nAll data was generated under a group named `gpt` and split into two areas: vertical and horizontal. The vertical area consists of one or more large projects that are considered a good and representative size for performance testing. The horizontal area consists of a large number of subgroups that have a large number of projects. All of these subgroups are saved under the parent subgroup `gpt/many_groups_and_projects`.\n\nWe also used the GitLab Environment Toolkit (GET), a provisioning and configuration toolkit, for deploying GitLab's Reference Architectures with Terraform and Ansible.\n\n## About performance benchmarking for the self-managed GitLab Runner\n\nGitLab Runner is the open source application that runs GitLab CI/CD jobs on various computing platforms and operating systems. The GitLab Runner has supported Arm architecture since [GitLab 12.6](/releases/2019/12/22/gitlab-12-6-released/), which allows users to run CI/CD jobs natively on Arm.\n\nWe ran the performance benchmark results for the GitLab Runner by compiling a standard Linux kernel on M6g and M5 instances. In this case, we demonstrated 36% performance gain on M6g instances under 100% CPU utilization. For example, it took 7 minutes and 53 seconds to compile Linux kernel on M5.xlarge (4 core) instances, whereas it only took 5 minutes 47 seconds on M6g.xlarge (4 core) instances.\n\nThe figure below shows that the architecture of the test setup for the GitLab runner we used to benchmark the performance based on the [GitLab Runner stress test repository](https://gitlab.com/gitlab-org/ci-cd/gitlab-runner-stress).\n\n![GitLab Runner test configuration](https://about.gitlab.com/images/blogimages/gl_runner_test_config.JPG){: .shadow.medium.center}\nThe architecture of the GitLab Runner used to benchmark performance.\n{: .note.text-center}\n\nWe used Prometheus and Grafana to obtain the CPU utilization graphs for both M6g and M5 instances from the Runner. The diagrams below show we have 100% CPU utilization on both Arm and x86 environments, and we are still able to achieve a 36% performance gain with the GitLab Runners on Arm-based M6g instances.\n\n![Runner on ARM - CPU utilization](https://about.gitlab.com/images/blogimages/runner_arm_cpu_perf_1.png){: .shadow.medium.center}\nInside CPU use on Arm environment.\n{: .note.text-center}\n\n![Runner on x86-64 - CPU utilization](https://about.gitlab.com/images/blogimages/runner_x86_cpu_perf_1.png){: .shadow.medium.center}\nCPU use on x86 environments.\n{: .note.text-center}\n\nUsers can benefit from the 36% performance gain for CI job execution and the roughly 23% per month in cost savings for executing CI jobs. The savings can be significant for customers that consume about 500,000 CI compute minutes per month.\n\n## GitLab customers increase performance and decrease cost by moving to Arm\n\nGitLab enterprise customers can gain 36% in performance improvements and 23% cost savings by deploying GitLab and GitLab Runner on AWS Graviton2-based EC2 instance. If your company's cloud infrastructure is on AWS, then you should consider whether moving workloads to Arm-based Graviton2 instances is suitable for your organization.\n\nCheck out this [repository for resources for getting started with AWS Graviton processors](https://github.com/aws/aws-graviton-getting-started) and information on supported operating systems and software. Feel free to open an [issue](https://github.com/aws/aws-graviton-getting-started/issues) if you have questions or need more help.\n\n_Join us at [Arm DevSummit 2021](https://devsummit.arm.com/en) to learn more about GitLab performance benchmarking and other topics._\n",[9],{"slug":765,"featured":6,"template":699},"achieving-23-cost-savings-and-36-performance-gain-using-gitlab-and-gitlab-runner-on-arm-neoverse-based-aws-graviton2-processor","content:en-us:blog:achieving-23-cost-savings-and-36-performance-gain-using-gitlab-and-gitlab-runner-on-arm-neoverse-based-aws-graviton2-processor.yml","Achieving 23 Cost Savings And 36 Performance Gain Using Gitlab And Gitlab Runner On Arm Neoverse Based Aws Graviton2 Processor","en-us/blog/achieving-23-cost-savings-and-36-performance-gain-using-gitlab-and-gitlab-runner-on-arm-neoverse-based-aws-graviton2-processor.yml","en-us/blog/achieving-23-cost-savings-and-36-performance-gain-using-gitlab-and-gitlab-runner-on-arm-neoverse-based-aws-graviton2-processor",{"_path":771,"_dir":246,"_draft":6,"_partial":6,"_locale":7,"seo":772,"content":778,"config":787,"_id":789,"_type":14,"title":790,"_source":16,"_file":791,"_stem":792,"_extension":19},"/en-us/blog/actioning-security-vulnerabilities-in-gitlab-premium",{"title":773,"description":774,"ogTitle":773,"ogDescription":774,"noIndex":6,"ogImage":775,"ogUrl":776,"ogSiteName":686,"ogType":687,"canonicalUrls":776,"schema":777},"How to action security vulnerabilities in GitLab Premium","Learn step-by-step how to process detected vulnerabilities and spawn merge request approval rules from critical vulnerabilities.","https://res.cloudinary.com/about-gitlab-com/image/upload/v1750099637/Blog/Hero%20Images/Blog/Hero%20Images/security-pipelines_security-pipelines.jpg_1750099637178.jpg","https://about.gitlab.com/blog/actioning-security-vulnerabilities-in-gitlab-premium","\n                        {\n        \"@context\": \"https://schema.org\",\n        \"@type\": \"Article\",\n        \"headline\": \"How to action security vulnerabilities in GitLab Premium\",\n        \"author\": [{\"@type\":\"Person\",\"name\":\"Sam Morris\"},{\"@type\":\"Person\",\"name\":\"Noah Ing\"}],\n        \"datePublished\": \"2023-03-13\",\n      }",{"title":773,"description":774,"authors":779,"heroImage":775,"date":782,"body":783,"category":784,"tags":785},[780,781],"Sam Morris","Noah Ing","2023-03-13","GitLab Premium features several security scanners you can leverage to detect\nvulnerabilities. However, when you incorporate the scanners into your\nproject pipelines and the scanning job succeeds, you'll want feedback on\nwhether you are introducing vulnerabilities into the codebase. This tutorial\nprovides a mechanism to require a merge request approval if a scanner\navailable on GitLab Premium finds a critical vulnerability.\n\n\n*While this tutorial shows how to add some process around actioning\nvulnerabilities, we have more robust, governed, and user-friendly\nfunctionality available in GitLab Ultimate called a [Scan Result\nPolicy](https://docs.gitlab.com/ee/user/application_security/policies/scan-result-policies.html).\nThe solution outlined here does not seek to replace that functionality, but\nrather augment the scan results available in GitLab Premium. If you are an\nUltimate user or if you want to compare the two experiences, then you should\ncheck out [this video\nintroduction](https://www.youtube.com/watch?v=w5I9gcUgr9U&ab_channel=GitLabUnfiltered)\ninstead.*\n\n\nLearn how to do the following:\n\n\n1. Set up a .gitlab-ci.yml\n\n2. Add in a vulnerability processing script\n\n3. Require approval if vulnerabilities are found \n\n\n### Prerequisites\n\n\n- A project with GitLab Premium\n\n- A gitlab-ci.yml\n\n- A project access token\n\n- Basic knowledge of Python\n\n- 5 minutes (or less)\n\n\n## Setup the gitlab-ci.yml \n\n\nThis is how the GitLab CI pipeline of our test project looks visually. Below\nwe will break down the individual stages.\n\n\nAdd the following to your .gitlab-ci.yml:\n\n\n```yaml\n\nsecret_detection:\n  artifacts:\n    paths:\n      - gl-secret-detection-report.json\n\nprocess_secret_detection:\n   image: python:3.7-alpine3.9\n   stage: process_vulns\n   needs:\n    - job: secret_detection\n      artifacts: true\n   before_script:\n      pip install python-gitlab\n   script:\n     - python3 process_vulns.py gl-secret-detection-report.json $PROJECT_ACCESS_TOKEN $CI_PROJECT_ID $CI_COMMIT_SHA\n```\n\n\nA breakdown of what is going on above:\n\n- gl-secret-detection-report.json needs to be overriden so it’s being stored\nas an artifact in the secret_detection job.\n\n- The process_secret_detection job is dependent on secret_detection's\nartifact so we have added a needs keyword requiring successful completion of\nthe secret_detection job.\n\n- pip installs the python-gitlab dependency so that the process_vulns.py can\nleverage GitLab API calls.\n\n- The process_vulns.py is taking in four arguments:\n   - gl-secret-detection-report.json is the JSON report produced from the secret_detection scanner. If you would like to take in another report this will need to be modified.\n   - $PROJECT_ACCESS_TOKEN needs to be added; review the instructions on creating a project access token in the next step.\n   - $CI_PROJECT_ID and $CI_COMMIT_SHA are both GitLab CI environment variables that will automatically be inferred.\n\n### Create a project access token\n\n\nTo create a project access token:\n\n1. On the top bar, select Main menu > Projects and find your project.\n\n2. On the left sidebar, select Settings > Access Tokens.\n\n3. Enter a name. The token name is visible to any user with permissions to\nview the project.\n\n4. Optional. Enter an expiry date for the token. The token expires on that\ndate at midnight UTC. An instance-wide maximum lifetime setting can limit\nthe maximum allowable lifetime in self-managed instances.\n\n5. Select a role for the token.\n\n6. Select the desired scopes.\n\n7. Select Create project access token.\n\n8. Add this newly created project access token to your CI/CD variables in\nyour project settings!\n\n\n## Add in the vulnerability processing script\n\n\n[The process_vulns.py script can be found\nhere.]((https://gitlab.com/gl-demo-premium-smorris/secure-premium-app/-/blob/main/process_vulns.py)\nCopy that file into your project.\n\n\nThe goal of this script is to require approval from an author (or group of\nauthors) if a critical vulnerability is found.\n\n\n**Note:** You will need to [change the user ID in the\nprocess_vulns.py](https://gitlab.com/gl-demo-premium-smorris/secure-premium-app/-/blame/main/process_vulns.py#L40)\nto match the user ID of your designated Approver at your organization.\n\n\nThe following is a breakdown of what the script is doing:\n\n\n- JSON security reports are loaded in, if there any vulnerabilities they are\nparsed.\n\n- An authentication with GitLab is run using the project access token to\ninteract with the project.\n\n- If vulnerabilities are not found, then it will print to the GitLab CI\nLogs: “No vulnerabilities are found.”\n\n- If a critical vulnerability is found, then it will require an approval.\n\n\nRun the pipeline and voila! Your pipeline now requires approvers if a\ncritical vulnerability is found!\n\n\n### Demo\n\n\nWatch a video demonstration of how to action security vulnerabilities in\nGitLab Premium, presented by Sam Morris:\n\n\n\u003Ciframe width=\"560\" height=\"315\"\nsrc=\"https://www.youtube.com/embed/Cld36OZrLFo\" title=\"YouTube video player\"\nframeborder=\"0\" allow=\"accelerometer; autoplay; clipboard-write;\nencrypted-media; gyroscope; picture-in-picture; web-share\"\nallowfullscreen>\u003C/iframe>\n\n\n#### Caveats\n\n- This is mimicking a Scan Result Policy; it is not a replacement.\n\n- This currently only requires approval for a critical vulnerability, and\neach new rule would have to be added to the script.\n\n- This script lives within the same location as your project, so there is no\nrestriction on who can modify the script, breaking separation of duties at\nscale.\n\n- Approval rules are not removed once the vulnerability is fixed.\n\n- Approvers' IDs need to be hardcoded and maintained in the script file.\n\n- Since there is no vulnerability record generated, you cannot track the\nvulnerabilities over time in your application.\n\n- Vulnerabilities are not fed into a report or security dashboard, so this\nonly reports merge request vulnerabilities.\n\n\n## References\n\n- [Create a project access\ntoken](https://docs.gitlab.com/ee/user/project/settings/project_access_tokens.html#create-a-project-access-token)\n\n- [Setting up CI/CD\nvariables](https://docs.gitlab.com/ee/ci/variables/#define-a-cicd-variable-in-the-ui)\n\n- [Secure Premium app\nproject](https://gitlab.com/gl-demo-premium-smorris/secure-premium-app/-/blob/main/process_vulns.py)\n\n\n## Related posts\n\n- [GitLab's commitment to enhanced application security in the modern DevOps\nworld](/blog/security-gitlab-15/)\n\n- [How to become more productive with Gitlab\nCI](/blog/how-to-become-more-productive-with-gitlab-ci/)\n\n- [GitLab CI DRY\nDevelopment](/blog/keeping-your-development-dry/)\n\n\n_Cover image by [Christopher\nBurns](https://unsplash.com/@christopher__burns?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText)\non [Unsplash](https://www.unsplash.com)._\n","security",[784,786,9,696,742],"DevSecOps",{"slug":788,"featured":6,"template":699},"actioning-security-vulnerabilities-in-gitlab-premium","content:en-us:blog:actioning-security-vulnerabilities-in-gitlab-premium.yml","Actioning Security Vulnerabilities In Gitlab Premium","en-us/blog/actioning-security-vulnerabilities-in-gitlab-premium.yml","en-us/blog/actioning-security-vulnerabilities-in-gitlab-premium",{"_path":794,"_dir":246,"_draft":6,"_partial":6,"_locale":7,"seo":795,"content":801,"config":809,"_id":811,"_type":14,"title":812,"_source":16,"_file":813,"_stem":814,"_extension":19},"/en-us/blog/amazon-linux-2-support-and-distro-specific-packages",{"title":796,"description":797,"ogTitle":796,"ogDescription":797,"noIndex":6,"ogImage":798,"ogUrl":799,"ogSiteName":686,"ogType":687,"canonicalUrls":799,"schema":800},"Amazon Linux 2 support and distro-specific packages for GitLab","Learn how to do early testing as well as how to peg your automation to the EL 7 packages until you are able to properly integrate the changes into your automation.","https://res.cloudinary.com/about-gitlab-com/image/upload/v1749682299/Blog/Hero%20Images/gitlab-blog-banner.png","https://about.gitlab.com/blog/amazon-linux-2-support-and-distro-specific-packages","\n                        {\n        \"@context\": \"https://schema.org\",\n        \"@type\": \"Article\",\n        \"headline\": \"Amazon Linux 2 support and distro-specific packages for GitLab\",\n        \"author\": [{\"@type\":\"Person\",\"name\":\"Darwin Sanoy\"}],\n        \"datePublished\": \"2022-05-02\",\n      }",{"title":796,"description":797,"authors":802,"heroImage":798,"date":803,"body":804,"category":805,"tags":806},[691],"2022-05-02","GitLab’s Distribution Engineering team has been hard at work getting Amazon\nLinux 2 distro-specific packages ready in preparation for GitLab’s official\nsupport of Amazon Linux 2. Starting with Version 15.0 of GitLab, Amazon\nLinux 2 is a supported distro and packages are available for both x86 and\nGraviton ARM architectures.\n\n\n## What is Amazon Linux 2?\n\n\nAmazon Linux 2 is the next-generation Amazon Linux operating system that\nprovides a modern application environment with the most recent enhancements\nfrom the Linux community alongside long-term support. Amazon Linux 2 is\naccessible as a virtual machine image for on-premises development and\ntesting. This lets you easily develop, test, and certify your applications\nright from your local development environment. \n\n\nAccording to the AWS FAQ page for Amazon Linux 2, the primary elements of\nthis latest version of the operating system include:\n\n\n1. A Linux kernel tuned for performance on Amazon EC2.\n\n\n2. A set of core packages including systemd, GCC 7.3, Glibc 2.26, Binutils\n2.29.1 that receive Long Term Support (LTS) from\n[AWS](/blog/deploy-aws/).\n\n\n3. An extras channel for rapidly evolving technologies that are likely to be\nupdated frequently and outside the Long Term Support (LTS) model.\n\n\nAmazon Linux 2 has a support lifespan through June 20, 2024, to allow enough\ntime for users to migrate to Amazon Linux 2022.\n\n\n\n## Safely moving forward to Amazon Linux 2 packages from EL7\n\n\nWhile Amazon Linux 2 has not been officially supported before 15.0, as a\nconvenience to customers who wanted to use yum and RPM packages to install\nthe EL7 packages, GitLab configured a workaround in our packaging services\nto direct Amazon Linux 2 yum requests to the EL7 packages. If you’ve been\nusing GitLab’s yum repo registration script, you many not know that you were\nusing EL7 packages and not distro-specific packages.\n\n\nThis workaround will be deprecated and requests from Amazon Linux 2 will get\nthe distro-specific packages with the release of GitLab 15.3.0 on August 22,\n2022.\n\n\nAs a convenience for those of you who have automation that depends directly\non this workaround, we wanted to provide you with information on how to do\nearly testing as well as how to peg your automation to the EL 7 packages\nuntil you are able to properly integrate the changes into your automation.\n\n\nGitLab documentation demonstrates how to call our managed yum repository\nsetup scripts by downloading the latest copy and running it directly in [the\ninstructions for installing\ninstances](https://about.gitlab.com/install/#centos-7) and [the instructions\nfor installing\nrunners](https://docs.gitlab.com/runner/install/linux-repository.html).\n\n\nAny organization using GitLab’s EL 7 packages for Amazon Linux 2 will want\nto test with - and update to - the distro-specific packages as soon as\npossible as GitLab will only be testing Amazon Linux 2 with the Amazon Linux\n2 specific packages going forward.\n\n\nWe also understand that the timing of the testing and migration to these\npackages must be done in a coordinated cutover so that the package type does\nnot change in your existing stacks without you having made any changes. This\ncan be more important if a GitLab stack has undergone platform qualification\nfor compliance purposes.\n\n\nAmazon Linux 2 specific packages are only available for GitLab 14.9.0 and\nlater. If your automation depends directly on GitLab’s repo configuration\nscript and it is still pegged to a GitLab version prior to 14.9.0 when this\nchange becomes GA, then action must be taken to prevent breaking that\nautomation. We have devised an idempotent two-line script solution that you\ncan put in place now to prevent disruption if you are still on a pre-14.9.0\nversion at the time the new behavior of `script.rpm.sh` becomes GA on August\n22, 2022 with the release of GitLab 15.3.0.\n\n\nGitLab rake-based backup and restore will continue to work seamlessly across\nthe distro-specific package changes if you have to restore to your Amazon\nLinux 2 built stack from an EL7 backup. If you are using third-party backup,\nyou may wish to trigger a new backup immediately after transitioning to the\nnew distro packages to avoid the scenario altogether.\n\n\n## Amazon Linux 2 packages for building GitLab instances before 15.3.0\n\n\nThe following code inserts two lines of code between those originally\noutlined in [the instructions for installing using RPM\npackages](/install/#centos-7). The first one (starts with `sed`) splices in\nthe Amazon Linux 2 yum repo endpoint edits into the repository configuration\nfile created by script.rpm.sh. The second one (starts with `if yum`) cleans\nthe yum cache if the package was already installed so that the new location\nwill be used.\n\n\n> Sudo note: If you are using these commands interactively under the default\nSSH or SSM session manager user, then using `sudo su` before running this\ncode is necessary. If you are using these commands in Infrastructure as Code\n(e.g. CloudFormation userdata scripts), then sudo may cause ‘command not\nfound’ errors when the user running automation is already root equivalent.\nBe mindful about using interactively tested commands directly in your\nautomation.\n\n\n```bash\n\n#Existing packaging script from https://about.gitlab.com/install/#centos-7\n\ncurl\nhttps://packages.gitlab.com/install/repositories/gitlab/gitlab-ee/script.rpm.sh\n| sudo bash\n\n\n#Patch to preview and/or peg Amazon Linux 2 specific packages\n\nsed -i \"s/\\/el\\/7/\\/amazon\\/2/g\" /etc/yum.repos.d/gitlab_gitlab*.repo\n\n\n#Reset the cache if the package was previously installed (not needed for\ninstalls onto a clean machine)\n\nif yum list installed gitlab-ee; then yum clean all ; yum makecache; fi\n\n\n#Existing install command (remove \"-y\" to validate package and arch mapping\nbefore install)\n\nyum install gitlab-ee -y\n\n```\n\n\n> Notice in this output that the **Version** ends in `.amazon2`. In this\ncase the **Arch** is `aarch64` - indicating 64-bit Graviton ARM.\n\n\n![Resolved GitLab\nDependencies](https://about.gitlab.com/images/blogimages/2022-04-amazon-linux-2/gl-instance-dependencies-resolved.png)\n\n\n### Moving to Amazon Linux 2 packages early for a seamless post-GA\ntransition\n\n\nWhen the script.rpm.sh script is cut over to always point Amazon Linux 2 to\nthe new distro-specific packages, the sed command will no longer be\nnecessary. However, sed is also idempotent and will not make edits if the\nsearch text is not found. This means you can use the sed command to switch\nover early, but not have to worry about a breaking change when the\n`script.rpm.sh` is updated.\n\n\n### Pegging EL 7 and/or a GitLab version prior to 14.9.0 for a seamless\npost-GA transition\n\n\nIf your automation is pegged to an earlier version of GitLab, you will need\nto keep using EL7 packages, and, in fact, after the cutover you would need\nto implement the opposite command (which is also idempotent to be\nimplemented now).\n\n\n```bash\n\n#Patch to peg GitLab Version to EL 7 Packages (only does something after GA\nof gitlab repo script)\n\nsed -i \"s/\\/amazon\\/2/\\/el\\/7/g\" /etc/yum.repos.d/gitlab_gitlab*.repo\n\n\n#Reset the cache if the package was previously installed (not needed for\ninstalls onto a clean machine)\n\nif yum list installed gitlab-ee; then yum clean all ; yum makecache; fi\n\n```\n\n\nJust like the sed command for taking distro-specific packages early, this\ncommand can be implemented immediately with no bad effects - which will\nseamlessly keeping your automation pegged to the EL 7 packages when\n`script.rpm.sh` is updated.\n\n\n## Amazon Linux 2 package for building GitLab Runners before 15.3.0\n\n\nThe following code inserts two lines of code between those originally\n[outlined in the\ninstructions](https://docs.gitlab.com/runner/install/linux-repository.html).\nThe first one (starts with `sed`) splices in the Amazon Linux 2 yum repo\nendpoint edits into the repository configuration file created by\nscript.rpm.sh. The second one (starts with `if yum`) cleans the yum cache if\nthe package was already installed so that the new location will be used.\n\n\n> Sudo note: If you are using these commands interactively under the default\nSSH or SSM session manager user, then using `sudo su` before running this\ncode is necessary. If you are using these commands in Infrastructure as Code\n(e.g. CloudFormation userdata scripts), then sudo may cause ‘command not\nfound’ errors when the user running automation is already root equivalent.\nBe mindful about using interactively tested commands directly in your\nautomation.\n\n\n```bash\n\n#Existing packaging script from\nhttps://docs.gitlab.com/runner/install/linux-repository.html\n\ncurl -L\n\"https://packages.gitlab.com/install/repositories/runner/gitlab-runner/script.rpm.sh\"\n| sudo bash\n\n\n#Patch to test or peg Amazon Linux 2 specific packages\n\nsed -i \"s/\\/el\\/7/\\/amazon\\/2/g\" /etc/yum.repos.d/runner_gitlab*.repo\n\n\n#Reset the cache if the package was previously installed (not needed for\ninstalls onto a clean machine)\n\nif yum list installed gitlab-runner; then yum clean all ; yum makecache; fi\n\n\n#Existing install command (remove \"-y\" to validate package and arch mapping\nbefore install)\n\nyum install gitlab-runner -y\n\n```\n\n\n> Notice in this output that **Version** is not distro-specific. In this\ncase the **Arch** is `aarch64` - indicating 64-bit Graviton ARM.\n\n\n![Resolved GitLab Runner\nDependencies](https://about.gitlab.com/images/blogimages/2022-04-amazon-linux-2/gl-runner-dependencies-resolved.png)\n\n\n## Pegging to EL 7 and/or a GitLab Runner version prior to 14.9.1 for a\nseamless post-GA transition\n\n\nThe underlying package for EL 7 and Amazon Linux 2 is literally a copy of\nthe same package. However, the Amazon Linux 2 endpoint for Runner RPM\npackages have only been uploaded from GitLab Runner 14.9.1 and later, so if\nyou have runners that need to be on an earlier version, you would need to\nstay pointed at EL 7 for those packages to continue to resolve as available.\nThe following code shows how to do that for GitLab Runner.\n\n\n```bash\n\n#Patch to peg GitLab Version to EL 7 Packages (only does something after GA\nof gitlab repo script)\n\nsed -i \"s/\\/amazon\\/2/\\/el\\/7/g\" /etc/yum.repos.d/runner_gitlab*.repo\n\n\n#Reset the cache if the package was previously installed (not needed for\ninstalls onto a clean machine)\n\nif yum list installed gitlab-runner; then yum clean all ; yum makecache; fi\n\n```\n\n\n## Need-to-know takeaways\n\n\n- Amazon Linux 2 is a supported distro for GitLab instances and runner as of\nthe release of version 15.0 on May 22, 2022.\n\n- Amazon Linux 2 packages are available for x86 and ARM for GitLab Version\n14.9.0 and higher. (Prior to 14.9.0 the EL7 packages must be used as they\nhave a long version history).\n\n- This is the first availability of ARM RPM packages of GitLab for Amazon\nLinux 2.\n\n- In 15.3 (August 22, 2022), the script.rpm.sh will automatically start\ndirecting to the Amazon Linux 2 packages where it had previously directed\nAmazon Linux 2 yum requests to the EL7 packages.\n\n- It is common to have taken a dependency directly on the latest version of\nthis GitLab script in other automation.\n\n- Before the GA cutover date of August 22, 2022 (15.3.0 GitLab Release), for\nthese scripts, you have the opportunity to pre-test these packages and\ndetermine whether they create any issues with your automation or GitLab\nconfiguration.\n\n- You can also peg to the Amazon Linux 2 packages early or peg to the EL7\npackages in advance if you find problems that you need more time to resolve.\nBoth of these pegging types are idempotent, meaning the code changes do not\ndo anything that causes problems after the change over happens.\n\n- Existing Amazon Linux 2 installations that were installed using the EL7\npackages can use a regular yum upgrade command to start using the new Amazon\nLinux 2 packages. This operation may also be an upgrade of the product\nversion at the same time. For existing installations you will need to patch\nthe yum repo files as explained in this article in order to upgrade directly\nto Amazon Linux 2 from EL7 using packages. \n\n\n> **Note**\n\n> This blog post and linked pages contain information related to upcoming\nproducts, features, and functionality. It is important to note that the\ninformation presented is for informational purposes only. Please do not rely\non this information for purchasing or planning purposes. As with all\nprojects, the items mentioned in this blog post and linked pages are subject\nto change or delay. The development, release, and timing of any products,\nfeatures, or functionality remain at the sole discretion of GitLab Inc.\n\n\n![AWS Partner\nLogo](https://about.gitlab.com/images/blogimages/2022-04-amazon-linux-2/awsgravitonready.png){:\n.right}\n","news",[807,9,696,742,808],"releases","AWS",{"slug":810,"featured":6,"template":699},"amazon-linux-2-support-and-distro-specific-packages","content:en-us:blog:amazon-linux-2-support-and-distro-specific-packages.yml","Amazon Linux 2 Support And Distro Specific Packages","en-us/blog/amazon-linux-2-support-and-distro-specific-packages.yml","en-us/blog/amazon-linux-2-support-and-distro-specific-packages",{"_path":816,"_dir":246,"_draft":6,"_partial":6,"_locale":7,"seo":817,"content":823,"config":829,"_id":831,"_type":14,"title":832,"_source":16,"_file":833,"_stem":834,"_extension":19},"/en-us/blog/application-modernization-best-practices",{"title":818,"description":819,"ogTitle":818,"ogDescription":819,"noIndex":6,"ogImage":820,"ogUrl":821,"ogSiteName":686,"ogType":687,"canonicalUrls":821,"schema":822},"7 Best practices for application modernization","Use these best practices to avoid common pitfalls on the application modernization journey.","https://res.cloudinary.com/about-gitlab-com/image/upload/v1749671258/Blog/Hero%20Images/just-commit-blog-cover.png","https://about.gitlab.com/blog/application-modernization-best-practices","\n                        {\n        \"@context\": \"https://schema.org\",\n        \"@type\": \"Article\",\n        \"headline\": \"7 Best practices for application modernization\",\n        \"author\": [{\"@type\":\"Person\",\"name\":\"Chrissie Buchanan\"}],\n        \"datePublished\": \"2019-03-27\",\n      }",{"title":818,"description":819,"authors":824,"heroImage":820,"date":826,"body":827,"category":717,"tags":828},[825],"Chrissie Buchanan","2019-03-27","\n\nA journey starts with a single step, any motivational poster can you tell you that, but what about all the steps after?\nEven if you know where you're going, are you getting there in the most efficient way possible?\nBefore you start an application modernization quest of your own, it helps to get an idea of the road ahead.\n\nYou don't have to have everything mapped out from the start, and chances are high your plans will change.\nEnterprises can learn a lot from [teams that modernized their legacy systems successfully](/blog/application-modernization-examples/), but there are also valuable lessons from those that failed.\n\n## Why legacy modernization projects fail\n\nEnterprises that dive into the application modernization process are trying to solve big problems, but great intentions rarely guarantee success.\nIn 1999, Carnegie Mellon researchers dove into [10 reasons why legacy re-engineering efforts fail](https://www.cs.cmu.edu/~aldrich/courses/654-sp05/readings/Bergey99.pdf) that are still very relevant today:\n\n1. The organization adopts a flawed strategy from the start.\n2. The organization relies too heavily on outside consultants.\n3. The team is tied down to old technologies and inadequate training.\n4. The organization thinks it has its legacy system under control (it doesn't).\n5. The needs of the organization are oversimplified.\n6. The overall software architecture isn't given enough consideration.\n7. There is no defined application modernization process.\n8. Inadequate planning and follow through.\n9. Lack of long-term commitment to the strategy.\n10. Leaders pre-determine technical decisions.\n\nEvery team faces legacy modernization challenges.\nCommitting to the process is the first step to meeting those challenges head on.\nAs teams go through the modernization journey, use these best practices to avoid common pitfalls and ensure long-term success.\n\n### 1. Create a modernization team\n\nGroups can learn a lot from each other and a variety of voices at the table can point out weaknesses and improve the modernization process.\nWhen choosing a team or developing an innovation group, avoid thinking along legacy lines which divide teams by stages of the software lifecycle.\n[Think about building a cross-functional team of 8–12 people](/blog/beyond-application-modernization-trends/) who can focus on developing the culture, process, and tools needed to continuously deliver software.\n\n### 2. Disagree, commit, and disagree\n\nWith more voices come more opinions.\nIt's a powerful way to innovate and generate great ideas, but it's also the most effective way to be ineffective.\nDecisions sometimes have to be made without a 100 percent buy-in.\nEverything can be questioned but as long as a decision is in place, we should expect people to commit to executing it.\n\"Disagree and commit\" is [one of GitLab's core values](https://handbook.gitlab.com/handbook/values/#disagree-commit-and-disagree) and it's a common business principle that keeps projects moving forward.\n\nWhether decisions are left to one individual or distributed will largely depend on the size of the organization.\nFor all final decision-makers during the application modernization process: listen to other points of view, thank those who contribute ideas and feedback, consider options carefully, and commit to a course of action.\n\n### 3. Map the development workflow\n\nMany organizations have been bogged down by the sheer number of tools, plug-ins, and platforms they use to accommodate everyday tasks.\nSome workflows have more in common with a Rube Goldberg device than a logical order of operation, but mapping out the development workflow is a necessity when undertaking a legacy modernization project.\nThis step is usually when the headaches of toolchain complexity come to light.\n\nLook at every tool being used across teams and identify dependencies.\nMore handoffs present more opportunities for single points failure, and any new applications added to the mix need to be able to play well with others.\nEven if you don't mind teams finding their own solutions so that they can work creatively, it's a good rule of thumb to [identify all privately used tools](https://www.pluralsight.com/blog/career/shadow-it-security-threat) that might be in the mix.\n\n### 4. Set small modernization goals\n\nHaving an entire timeline mapped out months in advance sets you up for failure.\nWhy? Projects inevitably change once they get started.\nTrying to map moving targets months in advance is an exercise in futility that ends in projects that are rushed, incomplete, or late anyway. Reducing the cycle time and focusing instead on iterating towards smaller goals will have a much higher likelihood of success.\nTeams that master iteration respond to feedback faster, adapt more quickly, and complete their projects faster than their large-scale counterparts.\nBy shortening the timeframe and reducing the scope of each goal, you're able to respond to changing needs, adjust your long-term plans with the feedback you receive along the way, and radically reduce engineering risk.\n\nWhen planning for major milestones (when certain tools will be retired or migrated, when updates will occur, team training, etc), focus instead on the many small steps between them.\nA smaller deploy introduces less changes that can potentially introduce issues, ensuring that each steps moves smoothly.\n\n### 5. Prioritize legacy data\n\nPreventing data loss is a key priority during the modernization process.\nEvaluate the data being processed, moved, and stored and put it into categories.\nWhether it's \"high, moderate, or low\" or \"green, yellow, and red,\" make sure the team understands each data category and what safeguards to have in place for each.\n\n### 6. Don't modernize bad habits\n\nMany organizations have squandered a clean slate by infusing new tools with old habits.\nTake a close look at your development workflow and identify instances of duplicated data, manual tasks, inefficiencies, and other habits that could derail your application modernization process in its tracks.\nMany of these practices are due to a lack of training or documentation – both easily fixable problems.\nA new tool doesn't solve bad habits, but bad habits can derail new tools.\n\n### 7. Close the skill gap\n\nThe number of programming languages, tools, systems, and methodologies that developers have to know is immense.\nIt's a challenge for teams to develop the knowledge they need to work quickly, and adding a new system to the mix should be considered carefully.\nKeeping teams in the loop on changes and then dedicating resources to make sure they understand how to navigate the new workflows will be the _most important part of the application modernization process_.\nMake this an ongoing, long-term commitment to organizational success and continue to document best practices long after legacy systems are turned off. Tools are only as good as the people who use them.\n\nAre you ready to tackle application modernization? [Just commit.](/blog/application-modernization-best-practices/)\n",[9,741],{"slug":830,"featured":6,"template":699},"application-modernization-best-practices","content:en-us:blog:application-modernization-best-practices.yml","Application Modernization Best Practices","en-us/blog/application-modernization-best-practices.yml","en-us/blog/application-modernization-best-practices",{"_path":836,"_dir":246,"_draft":6,"_partial":6,"_locale":7,"seo":837,"content":843,"config":853,"_id":855,"_type":14,"title":856,"_source":16,"_file":857,"_stem":858,"_extension":19},"/en-us/blog/automate-to-accelerate-webcast-recap",{"title":838,"description":839,"ogTitle":838,"ogDescription":839,"noIndex":6,"ogImage":840,"ogUrl":841,"ogSiteName":686,"ogType":687,"canonicalUrls":841,"schema":842},"Testing & release automation: Accelerate development","If you’re not using automated testing, your competitors almost certainly are – catch up on our recent webcast to get started.","https://res.cloudinary.com/about-gitlab-com/image/upload/v1749671288/Blog/Hero%20Images/gitlab-live-event.png","https://about.gitlab.com/blog/automate-to-accelerate-webcast-recap","\n                        {\n        \"@context\": \"https://schema.org\",\n        \"@type\": \"Article\",\n        \"headline\": \"Automate to accelerate: What you need to know about test and release automation\",\n        \"author\": [{\"@type\":\"Person\",\"name\":\"Rebecca Dodd\"}],\n        \"datePublished\": \"2017-12-08\",\n      }",{"title":844,"description":839,"authors":845,"heroImage":840,"date":847,"body":848,"category":849,"tags":850},"Automate to accelerate: What you need to know about test and release automation",[846],"Rebecca Dodd","2017-12-08","\n\nBuild better software, faster, with test and release automation. Check out our recent webcast to discover why it's critical to your software development process.\n\n\u003C!-- more -->\n\nIt's been six years since Marc Andreessen's landmark \"[software is eating the world](https://www.wsj.com/articles/SB10001424053111903480904576512250915629460)\" claim, and we know now that he was on the money. Whether or not you consider yourself to be in the business of software, you are. Virtually all products and services today contain digital elements, and some component of your user experience will absolutely be online.\n\nWe've moved beyond software for manufacturing, to where 61 percent of financial services jobs are expected to be replaced by software in the 2030s. Every sort of job has the potential to be consumed by software — robo-advisors, truck drivers, grocery stockers, cashiers, and the list goes on.\n\nConsider [this statement made earlier this year by the Nvidia CEO](https://www.technologyreview.com/s/607831/nvidia-ceo-software-is-eating-the-world-but-ai-is-going-to-eat-software/): “Software is eating the world, but AI is eating software” – this puts a new software development issue in play just to stay competitive. The power of AI, when harnessed correctly, will change the landscape entirely yet again. Your key to effective AI may just well be adaptive Continuous Integration functionality.\n\nTo keep up, your release cycle needs to be efficient – we’re talking about when and how you distribute updates to your product. Enter release management.\n\nIn this webcast GitLab Senior Solutions Architect [Joel Krooswyk](/company/team/#JoelKroos) talks about:\n\n- Release management and how it's changing\n- Why automation is critical to test and release processes\n- Challenges of adopting test and release automation and how to overcome them\n- Unified continuous integration and continuous delivery\n\nAnd he demonstrates how to get started with test automation in no time at all with [Auto DevOps](https://docs.gitlab.com/ee/topics/autodevops/).\n\n## Watch the recording\n\n\u003C!-- blank line -->\n\u003Cfigure class=\"video_container\">\n  \u003Ciframe src=\"https://www.youtube.com/embed/dvayJWwzfPY\" frameborder=\"0\" allowfullscreen=\"true\"> \u003C/iframe>\n\u003C/figure>\n\u003C!-- blank line -->\n\n## Grab the slides\n\n\u003Cfigure class=\"video_container\">\n\u003Ciframe src=\"https://docs.google.com/presentation/d/e/2PACX-1vTIAQe2m4mheFhuanNFJzqlY4TdVY3f2wR1wg7L1jVdYF5tL3D1ewo0a5DzUotdAZp5X16ypME200Ev/embed?start=false&loop=false&delayms=3000\" frameborder=\"0\" width=\"960\" height=\"569\" allowfullscreen=\"true\" mozallowfullscreen=\"true\" webkitallowfullscreen=\"true\">\u003C/iframe>\n\u003C/figure>\n","insights",[741,851,9,852],"testing","webcast",{"slug":854,"featured":6,"template":699},"automate-to-accelerate-webcast-recap","content:en-us:blog:automate-to-accelerate-webcast-recap.yml","Automate To Accelerate Webcast Recap","en-us/blog/automate-to-accelerate-webcast-recap.yml","en-us/blog/automate-to-accelerate-webcast-recap",{"_path":860,"_dir":246,"_draft":6,"_partial":6,"_locale":7,"seo":861,"content":867,"config":873,"_id":875,"_type":14,"title":876,"_source":16,"_file":877,"_stem":878,"_extension":19},"/en-us/blog/automating-a-twitter-bot-using-gitlab-cicd",{"title":862,"description":863,"ogTitle":862,"ogDescription":863,"noIndex":6,"ogImage":864,"ogUrl":865,"ogSiteName":686,"ogType":687,"canonicalUrls":865,"schema":866},"How to automate a Twitter bot using GitLab CI/CD","This tutorial shows how to use the DevSecOps platform to create a set-and-forget Twitter bot.","https://res.cloudinary.com/about-gitlab-com/image/upload/v1749661856/Blog/Hero%20Images/ci-cd-demo.jpg","https://about.gitlab.com/blog/automating-a-twitter-bot-using-gitlab-cicd","\n                        {\n        \"@context\": \"https://schema.org\",\n        \"@type\": \"Article\",\n        \"headline\": \"How to automate a Twitter bot using GitLab CI/CD\",\n        \"author\": [{\"@type\":\"Person\",\"name\":\"Siddharth Mathur\"}],\n        \"datePublished\": \"2023-03-21\",\n      }",{"title":862,"description":863,"authors":868,"heroImage":864,"date":870,"body":871,"category":717,"tags":872},[869],"Siddharth Mathur","2023-03-21","\n\nGitLab's CI/CD pipelines are great for automating many things, like deployments to Google Kubernetes Engine and security scans. But did you know that you could use GitLab CI/CD pipelines to run a set-and-forget Twitter bot?\n\nMany organizations today are leveraging Twitter's API to [understand customer sentiment](https://developer.twitter.com/en/blog/success-stories/target), [track public health data](https://developer.twitter.com/en/blog/success-stories/penn), [perform financial analysis](https://developer.twitter.com/en/blog/success-stories/likefolio), and more. While these bots may be running on self-managed infrastrucuture or external services, you can simplify and consolidate your tooling by leveraging GitLab instead, making your bot easier to manage.\n\nWith GitLab's [Free tier](/pricing/), you can leverage 400 minutes of CI/CD run time per month to automatically analyze and post tweets. With GitLab [Premium](/pricing/premium) and [Ultimate](/pricing/ultimate), you'll get even more pipeline minutes to tweet more, run longer natural language processing analyses, or for other projects.\n\nSetting up a Twitter bot using GitLab is pretty simple. At the end of this blog, you'll have a project that looks like [this](https://gitlab.com/smathur/twitter-bot), and a Twitter account that automatically posts a simple tweet.\n\nTo get started, you'll need these prerequisites:\n- GitLab account (self-hosted with GitLab Runner(s) set up or on GitLab.com)\n- Twitter API credentials\n\nOnce you've generated your Twitter API credentials, we can start building out our bot in GitLab. In this blog, we'll leverage GitLab's Web IDE based on Visual Studio Code, but feel free to use a code editor of your choice.\n\n## Step 1: Write a Python script to post tweets\n\n![Navigate to the Web IDE](https://about.gitlab.com/images/blogimages/2023-03-10-automating-a-twitter-bot-using-gitlab-cicd/web-ide.png){: .shadow}\n\nCreate a new blank project in GitLab, and click the \"Web IDE\" button to start writing some code. In the Web IDE, create a new file called `run_bot.py`, and paste the following code (this is where you interact with the Twitter API):\n\n```python\nimport tweepy\nimport config\n\ndef set_up():\n\tauth = tweepy.OAuthHandler(config.consumer_key, config.consumer_secret_key)\n\tauth.set_access_token(config.access_token, config.access_token_secret)\n\tapi = tweepy.API(auth)\n\treturn api\n\ndef run(tweet):\n\tapi = set_up()\n\tapi.update_status(tweet)\n\nrun('It\\'s Tanuki time')\n```\n\n**Note:** If you're familiar with Python, you'll notice that we're importing a file called `config` with some variables that we're using. This `config` file doesn't exist yet, but we'll create it from within a GitLab pipeline, leveraging CI/CD variables to securely store and use our Twitter API credentials.\n\nCreate another file called `requirements.txt`, and paste the following line:\n\n```\ntweepy\n```\n\nChanges to files in the Web IDE will be automatically saved, so switch to the Git tab and commit your changes.\n\n## Step 2: Create a CI/CD pipeline to run your Python script\n\nNext, we'll create a CI/CD pipeline script to run our Twitter bot and post a tweet every time the pipeline is run. To do this, you can:\n1. Create a new file using the Web IDE called `.gitlab-ci.yml`, or\n2. Head to your GitLab project, and from the sidebar, click CI/CD > Editor.\n\nIf you see some default text in the pipeline configuration, delete everything to start with a clean slate.\n\nIn the pipeline YAML file, we'll first specify the Docker image we want to run the bot on:\n\n```yaml\nimage: python:latest\n```\n\n**Note:** Normally in a pipeline, we would define stages first and then write jobs that are each assigned to a specific stage. Since we're only running one job in this pipeline, we don't need to specify stages at the top of our pipeline configuration file.\n\nNext, we'll add a job called `run` that runs the Python script we created in the previous step. Inside this job, we'll add a `script` section to run some commands that will execute our Python script.\n\n```yaml\nrun:\n  script:\n    - echo \"consumer_key = '$CONSUMER_KEY'\" >> config.py\n    - echo \"consumer_secret_key = '$CONSUMER_SECRET'\" >> config.py\n    - echo \"access_token = '$ACCESS_TOKEN'\" >> config.py\n    - echo \"access_token_secret = '$ACCESS_SECRET'\" >> config.py\n    - pip install -r requirements.txt\n    - python3 run_bot.py\n```\n\nCommit your changes. The pipeline will automatically run, since you just made a change to the project files, but it will fail. This is because we are calling some CI/CD variables in the pipeline, which we haven't set yet. Let's go ahead and do that!\n\n## Step 3: Set CI/CD variables to store API tokens\n\nHead to your GitLab project and from the sidebar, go to Settings > CI/CD.\n\nExpand the \"Variables\" section and add the `ACCESS_SECRET`, `ACCESS_TOKEN`, `CONSUMER_KEY`, and `CONSUMER_SECRET` variables as shown below (these are your Twitter API credentials):\n\n![CI/CD variables](https://about.gitlab.com/images/blogimages/2023-03-10-automating-a-twitter-bot-using-gitlab-cicd/ci-cd-variables.png){: .shadow}\n\nNote that the secrets are masked to prevent them from showing up in job logs (check the \"Mask variable\" box when creating/editing the variable).\n\n## Step 4: Test and schedule your Twitter bot\n\nNow that we've got everything set up, all we need to do is run the bot. Go to CI/CD > Pipelines, and click \"Run pipeline\". Click \"Run pipeline\" again, and wait for the `run` job to finish. If you've set up your Twitter credentials correctly, you should see that the pipeline successfully ran, and a tweet was posted on your bot account!\n\n![Schedule a pipeline](https://about.gitlab.com/images/blogimages/2023-03-10-automating-a-twitter-bot-using-gitlab-cicd/schedule-pipeline.png){: .shadow}\n\nOnce you've verified that your pipeline runs successfully, schedule your pipeline to automatically run at a regular interval. Go to CI/CD > Schedules, and click \"New schedule\". Feel free to use one of the default provided intervals, or use cron to set a custom schedule. Specify a timezone, and ensure that the \"Active\" checkbox is checked. Finally, click \"Save pipeline schedule\". You'll see that your pipeline has been scheduled to run, and when it will run next.\n\nAnd that's it! You now have a fully-functional Twitter bot running on GitLab, using CI/CD pipelines to automatically post tweets. While this demo Twitter bot simply posts a specified text message, you can add your own logic to [generate sentences using AI](https://linguatools.org/language-apis/sentence-generating-api/), [perform sentiment analysis on other users' tweets](https://www.analyticsvidhya.com/blog/2021/06/twitter-sentiment-analysis-a-nlp-use-case-for-beginners/), and more. Running a Twitter bot is just one of the many ways you can leverage pipelines in GitLab, and you can also check out some other [interesting use cases](https://docs.gitlab.com/ee/ci/examples/).\n",[786,9,696,742],{"slug":874,"featured":6,"template":699},"automating-a-twitter-bot-using-gitlab-cicd","content:en-us:blog:automating-a-twitter-bot-using-gitlab-cicd.yml","Automating A Twitter Bot Using Gitlab Cicd","en-us/blog/automating-a-twitter-bot-using-gitlab-cicd.yml","en-us/blog/automating-a-twitter-bot-using-gitlab-cicd",{"_path":880,"_dir":246,"_draft":6,"_partial":6,"_locale":7,"seo":881,"content":887,"config":893,"_id":895,"_type":14,"title":896,"_source":16,"_file":897,"_stem":898,"_extension":19},"/en-us/blog/autoscale-continuous-deployment-gitlab-runner-digital-ocean",{"title":882,"description":883,"ogTitle":882,"ogDescription":883,"noIndex":6,"ogImage":884,"ogUrl":885,"ogSiteName":686,"ogType":687,"canonicalUrls":885,"schema":886},"How to autoscale continuous deployment with GitLab Runner on DigitalOcean","Our friends over at DigitalOcean share how to configure a highly scalable, responsive and cost-effective GitLab infrastructure.","https://res.cloudinary.com/about-gitlab-com/image/upload/v1749680042/Blog/Hero%20Images/gitlab-digitalocean-cover.jpg","https://about.gitlab.com/blog/autoscale-continuous-deployment-gitlab-runner-digital-ocean","\n                        {\n        \"@context\": \"https://schema.org\",\n        \"@type\": \"Article\",\n        \"headline\": \"How to autoscale continuous deployment with GitLab Runner on DigitalOcean\",\n        \"author\": [{\"@type\":\"Person\",\"name\":\"Owen Williams\"}],\n        \"datePublished\": \"2018-06-19\",\n      }",{"title":882,"description":883,"authors":888,"heroImage":884,"date":890,"body":891,"category":717,"tags":892},[889],"Owen Williams","2018-06-19","[GitLab CI/CD](/solutions/continuous-integration/) is an effective way to\nbuild the habit of testing all code before it’s deployed. GitLab CI/CD is\nalso highly scalable thanks to an additional tool, GitLab Runner, which\nautomates scaling your build queue in order to avoid long wait times for\ndevelopment teams trying to release code.\n\n\nIn this guide, we will demonstrate how to configure a highly scalable GitLab\ninfrastructure that manages its own costs, and automatically responds to\nload by increasing and decreasing available server capacity.\n\n\n## Goals\n\n\nWe’re going to build a scalable CI/CD process on DigitalOcean that\nautomatically responds to demand by creating new servers on the platform and\ndestroys them when the queue is empty.\n\n\nThese reusable servers are spawned by the GitLab Runner process and are\nautomatically deleted when no jobs are running, reducing costs and\nadministration overhead for your team.\n\n\nAs we’ll explain in this tutorial, you are in control of how many machines\nare created at any given time, as well as the length of time they’re\nretained before being destroyed.\n\n\nWe’ll be using three separate servers to build this project, so let’s go\nover terminology first:\n\n\n* **GitLab**: Your hosted GitLab instance or self-managed instance where\nyour code repositories are stored.\n\n\n* **GitLab Bastion**: The *bastion* server or Droplet is the core of what\nwe’ll be configuring. It is the control instance that is used to interact\nwith the DigitalOcean API to create Droplets and destroy them when\nnecessary. No jobs are executed on this server.\n\n\n* **GitLab Runner**: Your *runners* are transient servers or Droplets that\nare created on the fly by the *bastion* server when needed to execute a\nCI/CD job in your build queue. These servers are disposable, and are where\nyour code is executed or tested before your build is marked as passing or\nfailing.\n\n\n![GitLab Runners\nDiagram](https://assets.digitalocean.com/articles/gitlab-runner/Autoscaling-GitLab-Runners.png){:\n.medium.center}\n\n\nBy leveraging each of the GitLab components, the CI/CD process will enable\nyou to scale responsively based on demands. With these goals in mind, we are\nready to begin setting up our [continuous deployment](/topics/ci-cd/) with\nGitLab and DigitalOcean.\n\n\n## Prerequisites\n\n\nThis tutorial will assume you have already configured GitLab on your own\nserver or through the hosted service, and that you have an existing\nDigitalOcean account.\n\n\nTo set this up on an Ubuntu 16.04 Droplet, you can use the DigitalOcean\none-click image, or follow our guide: “[How To Install and Configure GitLab\non Ubuntu\n16.04](https://www.digitalocean.com/community/tutorials/how-to-install-and-configure-gitlab-on-ubuntu-16-04).”\n\n\nFor the purposes of this tutorial, we assume you have private networking\nenabled on this Droplet, which you can achieve by following our guide on\n“[How To Enable DigitalOcean Private Networking on Existing\nDroplets](https://www.digitalocean.com/community/tutorials/how-to-enable-digitalocean-private-networking-on-existing-droplets),”\nbut it is not compulsory.\n\n\nThroughout this tutorial, we’ll be using non-root users with admin\nprivileges on our Droplets.\n\n\n## Step 1: Import JavaScript project\n\nTo begin, we will create a new example project in your existing GitLab\ninstance containing a sample Node.js application.\n\n\n![GitLab\ninterface](https://assets.digitalocean.com/articles/gitlab-runner/gitlab.jpg){:\n.shadow.large.center}\n\n\nLog into your GitLab instance and click the **plus icon**, then select **New\nproject** from the dropdown menu.\n\n\nOn the new project screen, select the **Import project** tag, then click\n**Repo by URL** to import our example project directly from GitHub.\n\n\nPaste the below clone URL into the Git repository URL:\n\n\n```bash\n\nhttps://github.com/do-community/hello_hapi.git\n\n```\n\n\nThis repository is a basic JavaScript application for the purposes of\ndemonstration, which we won’t be running in production. To complete the\nimport, click the **New Project** button.\n\n\nYour new project will now be in GitLab and we can get started setting up our\nCI pipeline.\n\n\n## Step 2: Set up infrastructure\n\n\nOur GitLab Code Runner requires specific configuration as we’re planning to\nprogrammatically create Droplets to handle CI load as it grows and shrinks.\n\n\nWe will create two types of machines in this tutorial: a **bastion**\ninstance, which controls and spawns new machines, and our **runner**\ninstances, which are temporary servers spawned by the bastion Droplet to\nbuild code when required. The bastion instance uses Docker to create your\nrunners.\n\n\nHere are the DigitalOcean products we’ll use, and what each component is\nused for:\n\n\n* **Flexible Droplets** — We will create memory-optimized Droplets for our\nGitLab Runners as it’s a memory-intensive process which will run using\nDocker for containerization. You can shrink or grow this Droplet in the\nfuture as needed, however we recommend the flexible Droplet option as a\nstarting point to understand how your pipeline will perform under load.\n\n\n* **DigitalOcean Spaces (Object Storage)** — We will use [DigitalOcean\nSpaces](https://www.digitalocean.com/products/spaces/) to persist cached\nbuild components across your runners as they’re created and destroyed. This\nreduces the time required to set up a new runner when the CI pipeline is\nbusy, and allows new runners to pick up where others left off immediately.\n\n\n* **Private Networking** — We will create a private network for your bastion\nDroplet and GitLab runners to ensure secure code compilation and to reduce\nfirewall configuration required.\n\n\nTo start, we’ll create the bastion Droplet. Create a [new\nDroplet](https://cloud.digitalocean.com/droplets/new), then under **choose\nan image**, select the **One-click apps** tab. From there, select **Docker\n17.12.0-ce on 16.04** (note that this version is current at the time of\nwriting), then choose the smallest Droplet size available, as our bastion\nDroplet will manage the creation of other Droplets rather than actually\nperform tests.\n\n\nIt is recommended that you create your server in a data center that\nincludes  [DigitalOcean\nSpaces](https://www.digitalocean.com/community/tutorials/an-introduction-to-digitalocean-spaces)\nin order to use the object storage caching features mentioned earlier.\n\n\nSelect both the **Private networking** and **Monitoring** options, then\nclick **Create Droplet**.\n\n\nWe also need to set up our storage space which will be used for caching.\nFollow the steps in “[How To Create a DigitalOcean Space and API\nKey](https://www.digitalocean.com/community/tutorials/how-to-create-a-digitalocean-space-and-api-key)”\nto create a new Space in the same or nearest data center as your hosted\nGitLab instance, along with an API Key.\n\n\nNote this key down, as we’ll need it later in the tutorial.\n\n\nNow it’s time to get our CI started!\n\n\n## Step 3: Configure the GitLab Runner Bastion Server\n\n\nWith the fresh Droplet ready, we can now configure GitLab Runner. We’ll be\ninstalling scripts from GitLab and GitHub repositories.\n\n\nAs a best practice, be sure to inspect scripts to confirm what you will be\ninstalling prior to running the full commands below.\n\n\nConnect to the Droplet using SSH, move into the `/tmp` directory, then add\nthe [official GitLab Runner\nrepository](https://docs.gitlab.com/runner/install/linux-repository.html) to\nUbuntu’s package manager:\n\n\n```bash\n\ncd /tmp\n\ncurl -L\nhttps://packages.gitlab.com/install/repositories/runner/gitlab-runner/script.deb.sh\n| sudo bash\n\n```\n\n\nOnce added, install the GitLab Runner application:\n\n\n```bash\n\nsudo apt-get install gitlab-runner\n\n```\n\n\nWe also need to install **[Docker\nMachine](https://docs.docker.com/machine/install-machine/#install-machine-directly)**,\nwhich is an additional Docker tool that assists with automating the\ndeployment of containers on cloud providers:\n\n\n```bash\n\ncurl -L\nhttps://github.com/docker/machine/releases/download/v0.14.0/docker-machine-`uname\n-s`-`uname -m` >/tmp/docker-machine && \\\n\nsudo install /tmp/docker-machine /usr/local/bin/docker-machine\n\n```\n\n\nWith these installations complete, we can move on to connecting our GitLab\nRunner to our GitLab install.\n\n\n## Step 4: Obtain Runner registration token\n\n\nTo link GitLab Runner to your existing GitLab install, we need to link the\ntwo instances together by obtaining a token that authenticates your runner\nto your code repositories.\n\n\nLogin to your existing GitLab instance as the admin user, then click the\nwrench icon to enter the admin settings area.\n\n\nOn the left of your screen, hover over **Overview** and select **Runners**\nfrom the list that appears.\n\n\nOn the Runners page under the **How to set up a shared Runner for a new\nproject** section, copy the token shown in Step 3, and make a note of it\nalong with the publicly accessible URL of your GitLab instance from Step 2.\nIf you are using HTTPS for Gitlab, make sure it is not a self-signed\ncertificate, or GitLab Runner will fail to start.\n\n\n## Step 5: Configure GitLab on the Bastion Droplet\n\n\nBack in your SSH connection with your bastion Droplet, run the following\ncommand:\n\n\n```bash\n\nsudo gitlab-runner register\n\n```\n\n\nThis will initiate the linking process, and you will be asked a series of\nquestions.\n\n\nOn the next step, enter the **GitLab instance URL** from the previous step:\n\n\n```bash\n\nPlease enter the gitlab-ci coordinator URL (e.g. https://gitlab.com)\n\nhttps://example.digitalocean.com\n\n```\n\n\nEnter the token you obtained from your GitLab instance:\n\n\n```bash\n\nPlease enter the gitlab-ci token for this runner\n\nsample-gitlab-ci-token\n\n```\n\n\nEnter a description that will help you recognize it in the GitLab web\ninterface. We recommend naming this instance something unique, like\n`runner-bastion` for clarity.\n\n\n```bash\n\nPlease enter the gitlab-ci description for this runner\n\n[yourhostname] runner-bastion\n\n```\n\n\nIf relevant, you may enter the tags for code you will build with your\nrunner. However, we recommend this is left blank at this stage. This can\neasily be changed from the GitLab interface later.\n\n\n```bash\n\nPlease enter the gitlab-ci tags for this runner (comma separated):\n\ncode-tag\n\n```\n\n\nChoose whether or not your runner should be able to run untagged jobs. This\nsetting allows you to choose whether your runner should build repositories\nwith no tags at all, or require specific tags. Select true in this case, so\nyour runner can execute all repositories.\n\n\n```bash\n\nWhether to run untagged jobs [true/false]: true\n\n```\n\n\nChoose if this runner should be shared among your projects, or locked to the\ncurrent one, which blocks it from building any code other than those\nspecified. Select false for now, as this can be changed later in GitLab’s\ninterface:\n\n\n```bash\n\nWhether to lock Runner to current project [true/false]: false\n\n```\n\n\nChoose the executor which will build your machines. Because we’ll be\ncreating new Droplets using Docker, we’ll choose `docker+machine` here, but\nyou can read more about the advantages of each approach in this\n[compatibility\nchart](https://docs.gitlab.com/runner/executors/README.html#compatibility-chart):\n\n\n```bash\n\nPlease enter the executor: ssh, docker+machine, docker-ssh+machine,\nkubernetes, docker, parallels, virtualbox, docker-ssh, shell:\n\ndocker+machine\n\n```\n\n\nYou’ll be asked which image to use for projects that don’t explicitly define\none. We’ll choose a basic, secure default:\n\n\n```bash\n\nPlease enter the Docker image (e.g. ruby:2.1):\n\nalpine:latest\n\n```\n\n\nNow you’re done configuring the core bastion runner! At this point it should\nappear within the GitLab Runner page of your GitLab admin settings, which we\naccessed to obtain the token.\n\n\nIf you encounter any issues with these steps, the [GitLab Runner\ndocumentation](https://docs.gitlab.com/runner/register/index.html) includes\noptions for troubleshooting.\n\n\n## Step 6: Configure Docker caching and Docker Machine\n\nTo speed up Droplet creation when the build queue is busy, we’ll leverage\nDocker’s caching tools on the Bastion Droplet to store the images for your\ncommonly used containers on DigitalOcean Spaces.\n\n\nTo do so, upgrade Docker Machine on your SSH shell using the following\ncommand:\n\n\n```bash\n\ncurl -L\nhttps://github.com/docker/machine/releases/download/v0.14.0/docker-machine-`uname\n-s`-`uname -m` >/tmp/docker-machine && sudo install /tmp/docker-machine\n/usr/local/bin/docker-machine\n\n```\n\n\nWith Docker Machine upgraded, we can move on to setting up our access tokens\nfor GitLab Runner to use.\n\n\n## Step 7: Gather DigitalOcean credentials\n\n\nNow we need to create the credentials that GitLab Runner will use to create\nnew Droplets using your DigitalOcean account.\n\n\nVisit your DigitalOcean [dashboard](https://cloud.digitalocean.com) and\nclick **API**. On the next screen, look for **Personal access tokens** and\nclick **Generate New Token**.\n\n\nGive the new token a name you will recognize such as `GitLab Runner Access`\nand ensure that both the read and write scopes are enabled, as we need the\nDroplet to create new machines without human intervention.\n\n\nCopy the token somewhere safe as we’ll use it in the next step. You can’t\nretrieve this token again without regenerating it, so be sure it’s stored\nsecurely.\n\n\n## Step 8: Edit GitLab Runner configuration files\n\nTo bring all of these components together, we need to finish configuring our\nbastion Droplet to communicate with your DigitalOcean account.\n\n\nIn your SSH connection to your bastion Droplet, use your favorite text\neditor, such as nano, to open the GitLab Runner configuration file for\nediting:\n\n\n```bash\n\nnano /etc/gitlab-runner/config.toml\n\n```\n\n\nThis configuration file is responsible for the rules your CI setup uses to\nscale up and down on demand. To configure the bastion to autoscale on\ndemand, you need to add the following lines:\n\n\n```bash\n\nconcurrent = 50   # All registered Runners can run up to 50 concurrent\nbuilds\n\n\n[[runners]]\n  url = \"https://example.digitalocean.com\"\n  token = \"existinggitlabtoken\"             # Note this is different from the registration token used by `gitlab-runner register`\n  name = \"example-runner\"\n  executor = \"docker+machine\"        # This Runner is using the 'docker+machine' executor\n  limit = 10                         # This Runner can execute up to 10 builds (created machines)\n  [runners.docker]\n    image = \"alpine:latest\"               # Our secure image\n  [runners.machine]\n    IdleCount = 1                    # The amount of idle machines we require for CI if build queue is empty\n    IdleTime = 600                   # Each machine can be idle for up to 600 seconds, then destroyed\n    MachineName = \"gitlab-runner-autoscale-%s\"    # Each machine will have a unique name ('%s' is required and generates a random number)\n    MachineDriver = \"digitalocean\"   # Docker Machine is using the 'digitalocean' driver\n    MachineOptions = [\n        \"digitalocean-image=coreos-stable\", # The DigitalOcean system image to use by default\n        \"digitalocean-ssh-user=core\", # The default SSH user\n        \"digitalocean-access-token=DO_ACCESS_TOKEN\", # Access token from Step 7\n        \"digitalocean-region=nyc3\", # The data center to spawn runners in\n        \"digitalocean-size=1gb\", # The size (and price category) of your spawned runners\n        \"digitalocean-private-networking\" # Enable private networking on runners\n    ]\n  [runners.cache]\n    Type = \"s3\"   # The Runner is using a distributed cache with the S3-compatible Spaces service\n    ServerAddress = \"nyc3.spaces.digitaloceanspaces.com\"\n    AccessKey = \"YOUR_SPACES_KEY\"\n    SecretKey = \"YOUR_SPACES_SECRET\"\n    BucketName = \"your_bucket_name\"\n    Insecure = true # We do not have a SSL certificate, as we are only running locally\n```\n\n\nOnce you’ve added the new lines, customize the access token, region and\nDroplet size based on  your setup. For the purposes of this tutorial, we’ve\nused the smallest Droplet size of 1GB and created our Droplets in NYC3. Be\nsure to use the information that is relevant in your case.\n\n\nYou also need to customize the cache component, and enter your Space’s\nserver address from the infrastructure configuration step, access key,\nsecret key and the name of the Space that you created.\n\n\nWhen completed, restart GitLab Runner to make sure the configuration is\nbeing used:\n\n\n```bash\n\ngitlab-runner restart\n\n```\n\n\nIf you would like to learn about more all available options, including\noff-peak hours, you can read [GitLab’s advanced\ndocumentation](https://docs.gitlab.com/runner/configuration/autoscale.html).\n\n\n## Step 9 — Test Your GitLab Runner\n\n\nAt this point, our GitLab Runner bastion Droplet is configured and is able\nto create DigitalOcean Droplets on demand, as the CI queue fills up. We’ll\nneed to test it to be sure it works by heading to your GitLab instance and\nthe project we imported in Step 1.\n\n\nTo trigger a build, edit the `readme.md` file by clicking on it, then\nclicking **edit**, and add any relevant testing text to the file, then click\n**Commit changes**.\n\n\nNow a build will be automatically triggered, which can be found under the\nproject’s **CI/CD** option in the left navigation.\n\n\nOn this page you should see a pipeline entry with the status of **running**.\nIn your DigitalOcean account, you’ll see a number of Droplets automatically\ncreated by GitLab Runner in order to build this change.\n\n\nCongratulations! Your CI pipeline is cloud scalable and now manages its own\nresource usage. After the specified idle time, the machines should be\nautomatically destroyed, but we recommend verifying this manually to ensure\nyou aren’t unexpectedly billed.\n\n\n## Troubleshooting\n\n\nIn some cases, GitLab may report that the runner is unreachable and as a\nresult perform no actions, including deploying new runners. You can\ntroubleshoot this by stopping GitLab Runner, then starting it again in debug\nmode:\n\n\n```bash\n\ngitlab-runner stop\n\ngitlab-runner --debug start\n\n```\n\n\nThe output should throw an error, which will be helpful in determining which\nconfiguration is causing the issue.\n\n\nIf your configuration creates too many machines, and you wish to remove them\nall at the same time, you can run this command to destroy them all:\n\n\n```bash\n\ndocker-machine rm $(docker-machine ls -q)\n\n```\n\nFor more troubleshooting steps and additional configuration options, you can\nrefer to [GitLab’s documentation](https://docs.gitlab.com/runner/).\n\n\n## Conclusion\n\n\nYou've successfully set up an automated CI/CD pipeline using GitLab Runner\nand Docker. From here, you could configure higher levels of caching with\nDocker Registry to optimize performance or explore the use of tagging code\nbuilds to specific GitLab code runners.\n\n\nFor more on GitLab Runner, [see the detailed\ndocumentation](https://docs.gitlab.com/runner/), or to learn more, you can\nread [GitLab’s series of blog posts](https://docs.gitlab.com/ee/ci/) on how\nto make the most of your continuous integration pipeline.\n\n\n[This post was originally published by\nDigitalOcean](https://www.digitalocean.com/community/tutorials/how-to-autoscale-gitlab-continuous-deployment-with-gitlab-runner-on-digitalocean)\nand is licensed under [CC BY-NC-SA\n4.0](https://creativecommons.org/licenses/by-nc-sa/4.0/).\n\n{: .note}\n",[9,233],{"slug":894,"featured":6,"template":699},"autoscale-continuous-deployment-gitlab-runner-digital-ocean","content:en-us:blog:autoscale-continuous-deployment-gitlab-runner-digital-ocean.yml","Autoscale Continuous Deployment Gitlab Runner Digital Ocean","en-us/blog/autoscale-continuous-deployment-gitlab-runner-digital-ocean.yml","en-us/blog/autoscale-continuous-deployment-gitlab-runner-digital-ocean",{"_path":900,"_dir":246,"_draft":6,"_partial":6,"_locale":7,"seo":901,"content":907,"config":917,"_id":919,"_type":14,"title":920,"_source":16,"_file":921,"_stem":922,"_extension":19},"/en-us/blog/aws-reinvent-recap",{"title":902,"description":903,"ogTitle":902,"ogDescription":903,"noIndex":6,"ogImage":904,"ogUrl":905,"ogSiteName":686,"ogType":687,"canonicalUrls":905,"schema":906},"Highlights from AWS re:Invent 2018","Catch up on what GitLab got up to at AWS re:Invent last week! Reinventing pipelines, emerging as a single application, theCUBE interviews, and more.","https://res.cloudinary.com/about-gitlab-com/image/upload/v1749679994/Blog/Hero%20Images/aws_booth_2018.jpg","https://about.gitlab.com/blog/aws-reinvent-recap","\n                        {\n        \"@context\": \"https://schema.org\",\n        \"@type\": \"Article\",\n        \"headline\": \"Highlights from AWS re:Invent 2018\",\n        \"author\": [{\"@type\":\"Person\",\"name\":\"Priyanka Sharma\"}],\n        \"datePublished\": \"2018-12-06\",\n      }",{"title":902,"description":903,"authors":908,"heroImage":904,"date":910,"body":911,"category":301,"tags":912},[909],"Priyanka Sharma","2018-12-06","\n\nLast week GitLab was at AWS re:Invent 2018, the marquee event for cloud computing in the US. As the frontrunner in the space, Amazon has built re:Invent to be a juggernaut. This year it commanded most of the Las Vegas strip and had over 50,000 attendees. As a first-time visitor myself, I was impressed by the sheer scale and efficiency of the event. I was also thrilled to achieve my personal goal of giving my first talk with a live demo using code and GitLab. As for GitLab, we saw that our company emerged as a leader in the DevOps space with a single application for the whole software development lifecycle.\n\n## Highlights\n\n### Reinventing CI/CD pipelines\n\nOur CEO [Sid Sijbrandij](/company/team/#sytses) and I did a talk and live demo about reinventing CI/CD pipelines using GitLab, [Kubernetes](/solutions/kubernetes/), and EKS. This was our first hint that this re:Invent was going to be special. The talk was bursting at the seams with attendees, as we shared both the challenges of the toolchain crisis engulfing our ecosystem, and about how a single application for the entire DevOps lifecycle can make an improvement of over 200 percent in cycle times. You can [check out the presentation here](https://docs.google.com/presentation/d/1x1g4pfpoaav9lhcYkjAJylLMl-9S0JFTeKXlNF98O-I/edit?usp=sharing).\n\n![Sid Sijbrandij and Priyanka Sharma on stage at AWS re:Invent](https://about.gitlab.com/images/blogimages/aws-2018/aws_2018_sid_talk_stage.jpeg){: .shadow.medium.center}\n\nThe demo, which showed us running a CI/CD pipeline and deploying code to Kubernetes on EKS, is an example of the [cloud native workflows](/topics/cloud-native/) users can push via GitLab. It is such competency that makes Kubernetes on EKS a breeze and is the reason GitLab was awarded the [AWS Partner DevOps Competency Certification](/blog/gitlab-achieves-aws-devops-competency-certification/) to confirm our viability and excellence as a DevOps solution for companies using AWS Cloud.\n\n### Validation for our vision\n\nOur experience at re:Invent was one of validation and emergence. As a company, we saw that our efforts to build the first single application for the entire DevOps lifecycle have paid off and our users resonated with our message. Most folks who came to our booth were aware that GitLab played a part in multiple stages (if not all) of their workflow and many were avid [GitLab CI](/solutions/continuous-integration/) fans. Gone are the days when [version control](https://docs.gitlab.com/ee/topics/gitlab_flow.html) was the only thing GitLab was associated with.\n\n![Collage from GitLab at AWS re:Invent](https://about.gitlab.com/images/blogimages/aws-2018/aws_booth_collage.jpeg){: .medium.center}\n\nOur VP of Alliances, [Brandon Jung](/company/team/#brandoncjung), [appeared on theCUBE](https://www.youtube.com/watch?v=Ejs5xGAhL8s) with a company called Beacon. As the former head of partnerships at Google Cloud, Brandon has a long history with GitLab. He has seen the company grow over the years and shared how our rocketship ascent across the DevOps lifecycle convinced him of the potential. He said, \"In just over two years, [GitLab became the frontrunner for continuous integration](/blog/gitlab-leader-continuous-integration-forrester-wave/), according to Forrester. That's impressive.\"\n\n### Livestream with The New Stack\n\nI also represented GitLab on [a livestream podcast](https://www.pscp.tv/w/1eaJbODAepnxX) with [The New Stack](https://thenewstack.io/), [Matt Biilmann](https://twitter.com/biilmann?lang=en), CEO of [Netlify](/blog/netlify-launches-gitlab-support/), and [Joe Beda](https://twitter.com/jbeda), founder of [Heptio](https://heptio.com/) and creator of Kubernetes. We discussed GitOps, NoOps, and the toolchain crisis. As Matt wisely said, \"Trust in open source is critical to cloud computing and the ecosystem. Companies like GitLab will keep the players honest.\"\n\n{::options parse_block_html=\"false\" /}\n\n\u003Cdiv class=\"center\">\n\n  \u003Cblockquote class=\"twitter-tweet\" data-lang=\"en\">\u003Cp lang=\"en\" dir=\"ltr\">GitOps, NoOps and the tool chain crisis. \u003Ca href=\"https://t.co/mtfm8OaYYD\">https://t.co/mtfm8OaYYD\u003C/a>\u003C/p>&mdash; The New Stack (@thenewstack) \u003Ca href=\"https://twitter.com/thenewstack/status/1067881587214184448?ref_src=twsrc%5Etfw\">November 28, 2018\u003C/a>\u003C/blockquote>\n  \u003Cscript async src=\"https://platform.twitter.com/widgets.js\" charset=\"utf-8\">\u003C/script>\n\n\u003C/div>\n\nWe thank AWS for creating this amazing ecosystem of end users and practitioners who came together in Vegas last week. Next year will be bigger, better. Until then, see you all at [KubeCon](/events/)! 😃\n",[9,269,913,279,805,914,915,916],"demo","kubernetes","inside GitLab","open source",{"slug":918,"featured":6,"template":699},"aws-reinvent-recap","content:en-us:blog:aws-reinvent-recap.yml","Aws Reinvent Recap","en-us/blog/aws-reinvent-recap.yml","en-us/blog/aws-reinvent-recap",{"_path":924,"_dir":246,"_draft":6,"_partial":6,"_locale":7,"seo":925,"content":931,"config":939,"_id":941,"_type":14,"title":942,"_source":16,"_file":943,"_stem":944,"_extension":19},"/en-us/blog/basics-of-gitlab-ci-updated",{"title":926,"description":927,"ogTitle":926,"ogDescription":927,"noIndex":6,"ogImage":928,"ogUrl":929,"ogSiteName":686,"ogType":687,"canonicalUrls":929,"schema":930},"Running CI jobs in sequential, parallel, and custom orders","New to continuous integration? Learn how to build your first CI pipeline with GitLab.","https://res.cloudinary.com/about-gitlab-com/image/upload/v1749662061/Blog/Hero%20Images/cicdcover.png","https://about.gitlab.com/blog/basics-of-gitlab-ci-updated","\n                        {\n        \"@context\": \"https://schema.org\",\n        \"@type\": \"Article\",\n        \"headline\": \"The basics of CI: How to run jobs sequentially, in parallel, or out of order\",\n        \"author\": [{\"@type\":\"Person\",\"name\":\"Itzik Gan Baruch\"}],\n        \"datePublished\": \"2020-12-10\",\n      }",{"title":932,"description":927,"authors":933,"heroImage":928,"date":935,"body":936,"category":717,"tags":937,"updatedDate":938},"The basics of CI: How to run jobs sequentially, in parallel, or out of order",[934],"Itzik Gan Baruch","2020-12-10","Let's assume that you don't know anything about [continuous integration\n(CI)](/topics/ci-cd/) and [why it's\nneeded](/blog/how-to-keep-up-with-ci-cd-best-practices/) in the software\ndevelopment lifecycle.\n\n\nImagine that you work on a project, where all the code consists of two text\nfiles. Moreover, it is super critical that the concatenation of these two\nfiles contains the phrase \"Hello, world.\"\n\n\nIf it's not there, the whole development team won't get paid that month.\nYeah, it is that serious!\n\n\nThe most responsible software developer wrote a small script to run every\ntime we are about to send our code to customers.\n\n\nThe code is pretty sophisticated:\n\n\n```bash\n\ncat file1.txt file2.txt | grep -q \"Hello world\"\n\n```\n\n\nThe problem is that there are 10 developers on the team, and, you know,\nhuman factors can hit hard.\n\n\nA week ago, a new guy forgot to run the script and three clients got broken\nbuilds. So you decided to solve the problem once and for all. Luckily, your\ncode is already on GitLab, and you remember that there is [built-in\nCI](/solutions/continuous-integration/). Moreover, you heard at a conference\nthat people use CI to run tests...\n\n\n## Let's run our first test inside CI\n\n\nAfter taking a couple of minutes to find and read the docs, it seems like\nall we need is these two lines of code in a file called `.gitlab-ci.yml`:\n\n\n```yaml\n\ntest:\n  script: cat file1.txt file2.txt | grep -q 'Hello world'\n```\n\n\nWe commit it, and hooray! Our build is successful:\n\n\n![build\nsucceeded](https://res.cloudinary.com/about-gitlab-com/image/upload/v1749674096/Blog/Content%20Images/build_succeeded.png)\n\n\nLet's change \"world\" to \"Africa\" in the second file and check what happens:\n\n\n![build\nfailed](https://res.cloudinary.com/about-gitlab-com/image/upload/v1749674096/Blog/Content%20Images/build_failed.png)\n\n\nThe build fails as expected!\n\n\nOK, we now have automated tests here! GitLab CI will run our test script\nevery time we push new code to the source code repository in the DevOps\nenvironment.\n\n\n**Note:** In the above example, we assume that file1.txt and file2.txt exist\nin the runner host.\n\n\nTo run this example in GitLab, use the below code that first will create the\nfiles and then run the script.\n\n\n```yaml\n\ntest:\n\nbefore_script:\n      - echo \"Hello \" > | tr -d \"\\n\" | > file1.txt\n      - echo \"world\" > file2.txt\nscript: cat file1.txt file2.txt | grep -q 'Hello world'\n\n```\n\n\nFor the sake of compactness, we will assume that these files exist in the\nhost, and will not create them in the following examples.\n\n\n## Make results of builds downloadable\n\n\nThe next business requirement is to package the code before sending it to\nour customers. Let's automate that part of the software development process\nas well!\n\n\nAll we need to do is define another job for CI. Let's name the job\n\"package\":\n\n\n```yaml\n\ntest:\n  script: cat file1.txt file2.txt | grep -q 'Hello world'\n\npackage:\n  script: cat file1.txt file2.txt | gzip > package.gz\n```\n\n\nWe have two tabs now:\n\n\n![Two tabs - generated from two\njobs](https://res.cloudinary.com/about-gitlab-com/image/upload/v1749674096/Blog/Content%20Images/two_tabs.png)\n\n\nHowever, we forgot to specify that the new file is a build _artifact_, so\nthat it could be downloaded. We can fix it by adding an `artifacts` section:\n\n\n```yaml\n\ntest:\n  script: cat file1.txt file2.txt | grep -q 'Hello world'\n\npackage:\n  script: cat file1.txt file2.txt | gzip > packaged.gz\n  artifacts:\n    paths:\n    - packaged.gz\n```\n\n\nChecking... it is there:\n\n\n![Checking the download\nbutton](https://res.cloudinary.com/about-gitlab-com/image/upload/v1749674096/Blog/Content%20Images/artifacts.png)\n\n\nPerfect, it is! However, we have a problem to fix: The jobs are running in\nparallel, but we do not want to package our application if our tests fail.\n\n\n## Run jobs sequentially\n\n\nWe only want to run the 'package' job if the tests are successful. Let's\ndefine the order by specifying `stages`:\n\n\n```yaml\n\nstages:\n  - test\n  - package\n\ntest:\n  stage: test\n  script: cat file1.txt file2.txt | grep -q 'Hello world'\n\npackage:\n  stage: package\n  script: cat file1.txt file2.txt | gzip > packaged.gz\n  artifacts:\n    paths:\n    - packaged.gz\n```\n\n\nThat should be good!\n\n\nAlso, we forgot to mention, that compilation (which is represented by\nconcatenation in our case) takes a while, so we don't want to run it twice.\nLet's define a separate step for it:\n\n\n```yaml\n\nstages:\n  - compile\n  - test\n  - package\n\ncompile:\n  stage: compile\n  script: cat file1.txt file2.txt > compiled.txt\n  artifacts:\n    paths:\n    - compiled.txt\n\ntest:\n  stage: test\n  script: cat compiled.txt | grep -q 'Hello world'\n\npackage:\n  stage: package\n  script: cat compiled.txt | gzip > packaged.gz\n  artifacts:\n    paths:\n    - packaged.gz\n```\n\n\nLet's take a look at our artifacts:\n\n\n![Unnecessary\nartifact](https://about.gitlab.com/images/blogimages/the-basics-of-gitlab-ci/clean-artifacts.png)\n\n\nHmm, we do not need that \"compile\" file to be downloadable. Let's make our\ntemporary artifacts expire by setting `expire_in` to '20 minutes':\n\n\n```yaml\n\ncompile:\n  stage: compile\n  script: cat file1.txt file2.txt > compiled.txt\n  artifacts:\n    paths:\n    - compiled.txt\n    expire_in: 20 minutes\n```\n\n\nNow our config looks pretty impressive:\n\n\n- We have three sequential stages to compile, test, and package our\napplication.\n\n- We pass the compiled app to the next stages so that there's no need to run\ncompilation twice (so it will run faster).\n\n- We store a packaged version of our app in build artifacts for further\nusage.\n\n\n## Learning which Docker image to use\n\n\nSo far, so good. However, it appears our builds are still slow. Let's take a\nlook at the logs.\n\n\n![ruby3.1](https://res.cloudinary.com/about-gitlab-com/image/upload/v1749674096/Blog/Content%20Images/ruby-31.png)\n\n\nWait, what is this? Ruby 3.1?\n\n\nWhy do we need Ruby at all? Oh, GitLab.com uses Docker images to [run our\nbuilds](/blog/shared-runners/), and [by\ndefault](https://docs.gitlab.com/ee/user/gitlab_com/#shared-runners) it uses\nthe [`ruby:3.1`](https://hub.docker.com/_/ruby/) image. For sure, this image\ncontains many packages we don't need. After a minute of Googling, we figure\nout that there's an image called\n[`alpine`](https://hub.docker.com/_/alpine/), which is an almost blank Linux\nimage.\n\n\nOK, let's explicitly specify that we want to use this image by adding\n`image: alpine` to `.gitlab-ci.yml`.\n\n\nNow we're talking! We shaved nearly three minutes off:\n\n\n![Build speed\nimproved](https://about.gitlab.com/images/blogimages/the-basics-of-gitlab-ci/speed.png)\n\n\nIt looks like there are a lot of public images around:\n\n- [mysql](https://hub.docker.com/_/mysql/)\n\n- [Python](https://hub.docker.com/_/python/)\n\n- [Java](https://hub.docker.com/_/java/)\n\n- [php](https://hub.docker.com/_/php/)\n\n\nSo we can just grab one for our technology stack. It makes sense to specify\nan image that contains no extra software because it minimizes download time.\n\n\n## Dealing with complex scenarios\n\n\nSo far, so good. However, let's suppose we have a new client who wants us to\npackage our app into `.iso` image instead of `.gz`. Since CI does all the\nwork, we can just add one more job to it. ISO images can be created using\nthe [mkisofs](http://www.w3big.com/linux/linux-comm-mkisofs.html) command.\nHere's how our config should look:\n\n\n```yaml\n\nimage: alpine\n\n\nstages:\n  - compile\n  - test\n  - package\n\n# ... \"compile\" and \"test\" jobs are skipped here for the sake of compactness\n\n\npack-gz:\n  stage: package\n  script: cat compiled.txt | gzip > packaged.gz\n  artifacts:\n    paths:\n    - packaged.gz\n\npack-iso:\n  stage: package\n  script:\n  - mkisofs -o ./packaged.iso ./compiled.txt\n  artifacts:\n    paths:\n    - packaged.iso\n```\n\n\nNote that job names shouldn't necessarily be the same. In fact, if they were\nthe same, it wouldn't be possible to make the jobs run in parallel inside\nthe same stage of the software development process. Hence, think of same\nnames of jobs and stages as coincidence.\n\n\nAnyhow, the build is failing:\n\n\n![Failed build because of missing\nmkisofs](https://about.gitlab.com/images/blogimages/the-basics-of-gitlab-ci/mkisofs.png)\n\n\nThe problem is that `mkisofs` is not included in the `alpine` image, so we\nneed to install it first.\n\n\n## Dealing with missing software/packages\n\n\nAccording to the [Alpine Linux\nwebsite](https://pkgs.alpinelinux.org/contents?file=mkisofs&path=&name=&branch=edge&repo=&arch=)\n`mkisofs` is a part of the `xorriso` and `cdrkit` packages. These are the\nmagic commands that we need to run to install a package:\n\n\n```bash\n\necho \"ipv6\" >> /etc/modules  # enable networking\n\napk update                   # update packages list\n\napk add xorriso              # install package\n\n```\n\n\nFor CI, these are just like any other commands. The full list of commands we\nneed to pass to `script` section should look like this:\n\n\n```yml\n\nscript:\n\n- echo \"ipv6\" >> /etc/modules\n\n- apk update\n\n- apk add xorriso\n\n- mkisofs -o ./packaged.iso ./compiled.txt\n\n```\n\n\nHowever, to make it semantically correct, let's put commands related to\npackage installation in `before_script`. Note that if you use\n`before_script` at the top level of a configuration, then the commands will\nrun before all jobs. In our case, we just want it to run before one specific\njob.\n\n\n## Directed Acyclic Graphs: Get faster and more flexible pipelines\n\n\nWe defined stages so that the package jobs will run only if the tests\npassed. What if we want to break the stage sequencing a bit, and run a few\njobs earlier, even if they are defined in a later stage? In some cases, the\ntraditional stage sequencing might slow down the overall pipeline execution\ntime.\n\n\nImagine that our test stage includes a few more heavy tests that take a lot\nof time to execute, and that those tests are not necessarily related to the\npackage jobs. In this case, it would be more efficient if the package jobs\ndon't have to wait for those tests to complete before they can start. This\nis where Directed Acyclic Graphs (DAG) come in: To break the stage order for\nspecific jobs, you can define job dependencies which will skip the regular\nstage order.\n\n\nGitLab has a special keyword `needs`, which creates dependencies between\njobs, and allows jobs to run earlier, as soon as their dependent jobs\ncomplete.\n\n\nIn the below example, the pack jobs will start running as soon as the test\njob completes, so if, in future, someone adds more tests in the test stage,\nthe package jobs will start to run before the new test jobs complete:\n\n\n```yaml\n\npack-gz:\n  stage: package\n  script: cat compiled.txt | gzip > packaged.gz\n  needs: [\"test\"]\n  artifacts:\n    paths:\n    - packaged.gz\n\npack-iso:\n  stage: package\n  before_script:\n  - echo \"ipv6\" >> /etc/modules\n  - apk update\n  - apk add xorriso\n  script:\n  - mkisofs -o ./packaged.iso ./compiled.txt\n  needs: [\"test\"]\n  artifacts:\n    paths:\n    - packaged.iso\n```\n\n\nOur final version of `.gitlab-ci.yml`:\n\n\n```yaml\n\nimage: alpine\n\n\nstages:\n  - compile\n  - test\n  - package\n\ncompile:\n  stage: compile\n  before_script:\n      - echo \"Hello  \" | tr -d \"\\n\" > file1.txt\n      - echo \"world\" > file2.txt\n  script: cat file1.txt file2.txt > compiled.txt\n  artifacts:\n    paths:\n    - compiled.txt\n    expire_in: 20 minutes\n\ntest:\n  stage: test\n  script: cat compiled.txt | grep -q 'Hello world'\n\npack-gz:\n  stage: package\n  script: cat compiled.txt | gzip > packaged.gz\n  needs: [\"test\"]\n  artifacts:\n    paths:\n    - packaged.gz\n\npack-iso:\n  stage: package\n  before_script:\n  - echo \"ipv6\" >> /etc/modules\n  - apk update\n  - apk add xorriso\n  script:\n  - mkisofs -o ./packaged.iso ./compiled.txt\n  needs: [\"test\"]\n  artifacts:\n    paths:\n    - packaged.iso\n```\n\n\nWow, it looks like we have just created a pipeline! We have three sequential\nstages, the jobs `pack-gz` and `pack-iso`, inside the `package` stage, are\nrunning in parallel:\n\n\n![Pipelines\nillustration](https://about.gitlab.com/images/blogimages/the-basics-of-gitlab-ci/pipeline.png)\n\n\n## Elevating your pipeline\n\n\nHere is how to elevate your pipeline.\n\n\n### Incorporating automated testing into CI pipelines\n\n\nIn DevOps, a key software development strategy rule is making really great\napps with amazing user experience. So, let's add some tests in our CI\npipeline to catch bugs early in the entire process. This way, we fix issues\nbefore they get big and before we move on to work on a new project.\n\n\nGitLab makes our lives easier by offering out-of-the-box templates for\nvarious [tests](https://docs.gitlab.com/ee/ci/testing/). All we need to do\nis include these templates in our CI configuration.\n\n\nIn this example, we will include [accessibility\ntesting](https://docs.gitlab.com/ee/ci/testing/accessibility_testing.html):\n\n\n```yaml\n\nstages:\n  - accessibility\n\nvariables:\n  a11y_urls: \"https://about.gitlab.com https://www.example.com\"\n\ninclude:\n  - template: \"Verify/Accessibility.gitlab-ci.yml\"\n```\n\n\nCustomize the `a11y_urls` variable to list the URLs of the web pages to test\nwith [Pa11y](https://pa11y.org/) and [code\nquality](https://docs.gitlab.com/ee/ci/testing/code_quality.html).\n\n\n```yaml\n   include:\n   - template: Jobs/Code-Quality.gitlab-ci.yml\n```\n\n\nGitLab makes it easy to see the test report right in the merge request\nwidget area. Having the code review, pipeline status, and test results in\none spot makes everything smoother and more efficient.\n\n\n![Accessibility\nreport](https://res.cloudinary.com/about-gitlab-com/image/upload/v1749674096/Blog/Content%20Images/Screenshot_2024-04-02_at_10.56.41.png)\n\n\u003Ccenter>\u003Ci>Accessibility merge request widget\u003C/i>\u003C/center>\u003Cp>\u003C/p>\n\n\n![Code quality widget in\nMR](https://res.cloudinary.com/about-gitlab-com/image/upload/v1749674096/Blog/Content%20Images/Screenshot_2024-04-02_at_11.00.25.png)\n\n\u003Ccenter>\u003Ci>Code quality merge request widget\u003C/i>\u003C/center>\n\n\n### Matrix builds\n\n\nIn some cases, we will need to test our app in different configurations, OS\nversions, programming language versions, etc. For those cases, we'll use the\n[parallel:matrix](https://docs.gitlab.com/ee/ci/yaml/#parallelmatrix) build\nto test our application across various combinations in parallel using one\njob configuration. In this blog, we'll test our code with different Python\nversions using the matrix keyword.\n\n\n```yaml\n\npython-req:\n  image: python:$VERSION\n  stage: lint\n  script:\n    - pip install -r requirements_dev.txt\n    - chmod +x ./build_cpp.sh\n    - ./build_cpp.sh\n  parallel:\n    matrix:\n      - VERSION: ['3.8', '3.9', '3.10', '3.11']   # https://hub.docker.com/_/python\n```\n\n\nDuring pipeline execution, this job will run in parallel four times, each\ntime using different Python image as shown below:\n\n\n![Matrix job\nrunning](https://res.cloudinary.com/about-gitlab-com/image/upload/v1749674096/Blog/Content%20Images/Screenshot_2024-04-02_at_11.12.48.png)\n\n\n### Unit testing\n\n\n#### What are unit tests?\n\n\nUnit tests are small, targeted tests that check individual components or\nfunctions of software to ensure they work as expected. They are essential\nfor catching bugs early in the software development process and verifying\nthat each part of the code performs correctly in isolation.\n\n\nExample: Imagine you're developing a calculator app. A unit test for the\naddition function would check if 2 + 2 equals 4. If this test passes, it\nconfirms that the addition function is working correctly.\n\n\n#### Unit testing best practices\n\n\nIf the tests fail, the pipeline fails and users get notified. The developer\nneeds to check the job logs, which usually contain thousands of lines, and\nsee where the tests failed so that they can fix them. This check is\ntime-consuming and inefficient.\n\n\nYou can configure your job to use [unit test\nreports](https://docs.gitlab.com/ee/ci/testing/unit_test_reports.html).\nGitLab displays reports on the merge request and on the pipelines details\npage, making it easier and faster to identify the failure without having to\ncheck the entire log.\n\n\n##### JUnit test report\n\n\nThis is a sample JUnit test report:\n\n\n![pipelines JUnit test report v13\n10](https://res.cloudinary.com/about-gitlab-com/image/upload/v1749674097/Blog/Content%20Images/pipelines_junit_test_report_v13_10.png){:\n.shadow.center}\n\n\n### Integration and end-to-end testing strategies\n\n\nIn addition to our regular development routine, it's super important to set\nup a special pipeline just for integration and end-to-end testing. This\nchecks that all the different parts of our code work together smoothly,\nincluding those\n[microservices](https://about.gitlab.com/topics/microservices/), UI testing,\nand any other components.\n\n\nWe run these tests\n[nightly](https://docs.gitlab.com/ee/ci/pipelines/schedules.html). We can\nset it up so that the [results automatically get sent to a special Slack\nchannel](https://docs.gitlab.com/ee/user/project/integrations/gitlab_slack_application.html#notification-events).\nThis way, when developers come in the next day, they can quickly spot any\nissues. It's all about catching and fixing problems early on!\n\n\n### Test environment\n\n\nFor some of the tests, we may need a test environment to properly test our\napps. With GitLab CI/CD, we can automate the deployment of testing\nenvironments and save a ton of time. Since this blog mostly focuses on CI, I\nwon't elaborate on this, but you can refer to this section in the [GitLab\ndocumentation](https://docs.gitlab.com/ee/topics/release_your_application.html).\n\n\n## Implementing security scans in CI pipelines\n\n\nHere are the ways to implement security scans in CI pipelines.\n\n\n### SAST and DAST integration\n\n\nWe're all about keeping our code safe. If there are any vulnerabilities in\nour latest changes, we want to know ASAP. That's why it's a good idea to add\nsecurity scans to your pipeline. They'll check the code with every commit\nand give you a heads up about any risks. We've put together a product tour\nto walk you through adding scans, including static application security\ntesting ([SAST](https://docs.gitlab.com/ee/user/application_security/sast/))\nand dynamic application security testing\n([DAST](https://docs.gitlab.com/ee/user/application_security/dast/)), to\nyour CI pipeline.\n\n\n__Click__ the image below to start the tour.\n\n\n[![Scans product\ntour](https://res.cloudinary.com/about-gitlab-com/image/upload/v1749674096/Blog/Content%20Images/Screenshot_2024-04-14_at_13.44.42.png)](https://gitlab.navattic.com/gitlab-scans)\n\n\nPlus, with AI, we can dig even deeper into vulnerabilities and get\nsuggestions on how to fix them. Check out this demo for more info.\n\n\n__Click__ the image below to start the tour.\n\n\n[![product tour explain vulnerability\n](https://res.cloudinary.com/about-gitlab-com/image/upload/v1749674096/Blog/Content%20Images/Screenshot_2024-04-14_at_13.50.24.png)](https://tech-marketing.gitlab.io/static-demos/pt-explain-vulnerability.html)\n\n\n## Recap\n\n\nThere's much more to cover but let's stop here for now. All examples were\nmade intentionally trivial so that you could learn the concepts of GitLab CI\nwithout being distracted by an unfamiliar technology stack. Let's wrap up\nwhat we have learned:\n\n\n1. To delegate some work to GitLab CI you should define one or more\n[jobs](https://docs.gitlab.com/ee/ci/jobs/) in `.gitlab-ci.yml`.\n\n2. Jobs should have names and it's your responsibility to come up with good\nones.\n\n3. Every job contains a set of rules and instructions for GitLab CI, defined\nby [special keywords](#keywords).\n\n4. Jobs can run sequentially, in parallel, or out of order using\n[DAG](https://docs.gitlab.com/ee/ci/directed_acyclic_graph/index.html).\n\n5. You can pass files between jobs and store them in build artifacts so that\nthey can be downloaded from the interface.\n\n6. Add [tests and security\nscans](https://docs.gitlab.com/ee/development/integrations/secure.html) to\nthe CI pipeline to ensure the quality and security of your app.\n\n\nBelow are more formal descriptions of the terms and keywords we used, as\nwell as links to the relevant documentation.\n\n\n### Keyword descriptions and documentation\n\n\n{: #keywords}\n\n\n| Keyword/term       | Description |\n\n|---------------|--------------------|\n\n| [.gitlab-ci.yml](https://docs.gitlab.com/ee/ci/yaml/) | File containing\nall definitions of how your project should be built |\n\n| [script](https://docs.gitlab.com/ee/ci/yaml/#script)        | Defines a\nshell script to be executed |\n\n| [before_script](https://docs.gitlab.com/ee/ci/yaml/#before_script) | Used\nto define the command that should be run before (all) jobs |\n\n|\n[image](https://docs.gitlab.com/ee/ci/docker/using_docker_images.html#what-is-image)\n| Defines what Docker image to use |\n\n| [stages](https://docs.gitlab.com/ee/ci/yaml/#stages)         | Defines a\npipeline stage (default: `test`) |\n\n| [artifacts](https://docs.gitlab.com/ee/ci/yaml/#artifacts)     | Defines a\nlist of build artifacts |\n\n|\n[artifacts:expire_in](https://docs.gitlab.com/ee/ci/yaml/#artifactsexpire_in)\n| Used to delete uploaded artifacts after the specified time |\n\n| [needs](https://docs.gitlab.com/ee/ci/yaml/#needs) | Used to define\ndependencies between jobs and allows to run jobs out of order |\n\n| [pipelines](https://about.gitlab.com/topics/ci-cd/cicd-pipeline/) | A\npipeline is a group of builds that get executed in stages (batches) |\n\n\n## More on CI/CD\n\n\n- [GitLab’s guide to CI/CD for beginners](/blog/beginner-guide-ci-cd/)\n\n- [Get faster and more flexible pipelines with a Directed Acyclic\nGraph](/blog/directed-acyclic-graph/)\n\n- [Decrease build time with custom Docker\nimage](http://beenje.github.io/blog/posts/gitlab-ci-and-conda/)\n\n- [Introducing the GitLab CI/CD Catalog\nBeta](https://about.gitlab.com/blog/introducing-the-gitlab-ci-cd-catalog-beta/)\n\n\n## FAQ\n\n\n### How do you choose between running CI jobs sequentially vs. in parallel?\n\n\nConsiderations for choosing between running CI jobs sequentially or in\nparallel include job dependencies, resource availability, execution times,\npotential interference, test suite structure, and cost considerations. For\nexample, if you have a build job that must finish before a deployment job\ncan start, you would run these jobs sequentially to ensure the correct order\nof execution. On the other hand, tasks such as unit testing and integration\ntesting can typically run in parallel since they are independent and don't\nrely on each other's completion.\n\n\n### What are directed Acyclic Graphs in GitLab CI, and how do they improve\npipeline flexibility?\n\n\nA Directed Acyclic Graph (DAG) in GitLab CI breaks the linear order of\npipeline stages. It lets you set dependencies between jobs, so jobs in later\nstages start as soon as earlier stage jobs finish. This reduces overall\npipeline execution time, improves efficiency, and lets some jobs complete\nearlier than in a regular order.\n\n\n### What is the importance of choosing the right Docker image for CI jobs in\nGitLab?\n\n\nGitLab utilizes Docker images to execute jobs. The default image is\nruby:3.1. Depending on your job's requirements, it's crucial to choose the\nappropriate image. Note that jobs first download the specified Docker image,\nand if the image contains additional packages beyond what's necessary, it\nwill increase download and execution times. Therefore, it's important to\nensure that the chosen image contains only the packages essential for your\njob to avoid unnecessary delays in execution.\n\n\n## Next steps\n\n\nAs a next step and to further modernize your software development practice,\ncheck out the [GitLab CI/CD\nCatalog](https://docs.gitlab.com/ee/architecture/blueprints/ci_pipeline_components/)\nto learn how to standardize and reuse CI/CD components.\n",[9,742],"2024-04-24",{"slug":940,"featured":6,"template":699},"basics-of-gitlab-ci-updated","content:en-us:blog:basics-of-gitlab-ci-updated.yml","Basics Of Gitlab Ci Updated","en-us/blog/basics-of-gitlab-ci-updated.yml","en-us/blog/basics-of-gitlab-ci-updated",{"_path":946,"_dir":246,"_draft":6,"_partial":6,"_locale":7,"seo":947,"content":953,"config":963,"_id":965,"_type":14,"title":966,"_source":16,"_file":967,"_stem":968,"_extension":19},"/en-us/blog/building-gitlab-with-gitlab-a-multi-region-service-to-deliver-ai-features",{"title":948,"description":949,"ogTitle":948,"ogDescription":949,"noIndex":6,"ogImage":950,"ogUrl":951,"ogSiteName":686,"ogType":687,"canonicalUrls":951,"schema":952},"Building GitLab with GitLab: A multi-region service to deliver AI features","Discover how we built our first multi-region deployment for teams at GitLab using the platform's many features, helping create a frictionless developer experience for GitLab Duo users.","https://res.cloudinary.com/about-gitlab-com/image/upload/v1750098664/Blog/Hero%20Images/Blog/Hero%20Images/building-gitlab-with-gitlab-no-type_building-gitlab-with-gitlab-no-type.png_1750098663794.png","https://about.gitlab.com/blog/building-gitlab-with-gitlab-a-multi-region-service-to-deliver-ai-features","\n                        {\n        \"@context\": \"https://schema.org\",\n        \"@type\": \"Article\",\n        \"headline\": \"Building GitLab with GitLab: A multi-region service to deliver AI features\",\n        \"author\": [{\"@type\":\"Person\",\"name\":\"Chance Feick\"},{\"@type\":\"Person\",\"name\":\"Sam Wiskow\"}],\n        \"datePublished\": \"2024-09-12\",\n      }",{"title":948,"description":949,"authors":954,"heroImage":950,"date":957,"body":958,"category":717,"tags":959},[955,956],"Chance Feick","Sam Wiskow","2024-09-12","For GitLab Duo, real-time AI-powered capabilities like [Code\nSuggestions](https://about.gitlab.com/solutions/code-suggestions/) need\nlow-latency response times for a frictionless developer experience. Users\ndon’t want to interrupt their flow and wait for a code suggestion to show\nup. To ensure GitLab Duo can provide the right suggestion at the right time\nand meet high performance standards for critical AI infrastructure, GitLab\nrecently launched our first multi-region service to deliver AI features.\n\n\nIn this article, we will cover the benefits of multi-region services, how we\nbuilt an internal platform codenamed ‘Runway’ for provisioning and deploying\nmulti-region services using GitLab features, and the lessons learned\nmigrating to multi-region in production.\n\n\n## Background on the project\n\n\nRunway is GitLab’s internal platform as a service (PaaS) for provisioning,\ndeploying, and operating containerized services. Runway's purpose is to\nenable GitLab service owners to self-serve infrastructure needs with\nproduction readiness out of the box, so application developers can focus on\nproviding value to customers. As part of [our corporate value of\ndogfooding](https://handbook.gitlab.com/handbook/values/#results), the first\niteration was built in 2023 by the Infrastructure department on top of core\nGitLab capabilities, such as continuous integration/continuous delivery\n([CI/CD](https://about.gitlab.com/topics/ci-cd/)), environments, and\ndeployments.\n\n\nBy establishing automated GitOps best practices, Runway services use\ninfrastructure as code (IaC), merge requests (MRs), and CI/CD by default.\n\n\nGitLab Duo is primarily powered by [AI\nGateway](https://gitlab.com/gitlab-org/modelops/applied-ml/code-suggestions/ai-assist),\na satellite service written in Python outside of GitLab’s modular monolith\nwritten in Ruby. In cloud computing, a region is a geographical location of\ndata centers operated by cloud providers.\n\n\n## Defining a multi-region strategy\n\n\nDeploying in a single region is a good starting point for most services, but\ncan come with downsides when you are trying to reach a global audience.\nUsers who are geographically far from where your service is deployed may\nexperience different levels of service and responsiveness than those who are\ncloser. This can lead to a poor user experience, even if your service is\nwell built in all other respects.\n\n\nFor AI Gateway, it was important to meet global customers wherever they are\nlocated, whether on GitLab.com or self-managed instances using Cloud\nConnector. When a developer is deciding to accept or reject a code\nsuggestion, milliseconds matter and can define the user experience.\n\n\n### Goals\n\n\nMulti-region deployments require more infrastructure complexity, but for use\ncases where latency is a core component of the user experience, the benefits\noften outweigh the downsides. First, multi-region deployments offer\nincreased responsiveness to the user. By serving requests from locations\nclosest to end users, latency can be significantly reduced. Second,\nmulti-region deployments provide greater availability. With fault tolerance,\nservices can fail over during a regional outage. There is a much lower\nchance of a service failing completely, meaning users should not be\ninterrupted even in partial failures.\n\n\nBased on our goals for performance and availability, we used this\nopportunity to create a scalable multi-region strategy in Runway, which is\nbuilt leveraging GitLab features.\n\n\n### Architecture\n\n\nIn SaaS platforms, GitLab.com’s infrastructure is hosted on Google Cloud\nPlatform (GCP). As a result, Runway’s first supported platform runtime is\nCloud Run. The initial workloads deployed on Runway are stateless satellite\nservices (e.g., AI Gateway), so Cloud Run services are a good fit that\nprovide a clear migration path to more complex and flexible platform\nruntimes, e.g. Kubernetes.\n\n\nBuilding Runway on top of GCP Cloud Run using GitLab has allowed us to\niterate and tease out the right level of abstractions for service owners as\npart of a platform play in the Infrastructure department.\n\n\nTo serve traffic from multiple regions in Cloud Run, the multi-region\ndeployment strategy must support global load balancing, and the provisioning\nand configuration of regional resources. Here’s a simplified diagram of the\nproposed architecture in GCP:\n\n\n![simplified diagram of the proposed architecture in\nGCP](https://res.cloudinary.com/about-gitlab-com/image/upload/v1750098671/Blog/Content%20Images/Blog/Content%20Images/image7_aHR0cHM6_1750098671612.png)\n\n\nBy replicating Cloud Run services across multiple regions and configuring\nthe existing global load balancing with serverless network endpoint group\n(NEG) backends, we’re able to serve traffic from multiple regions. For the\nremainder of the article, we’ll focus less on specifics of Cloud Run and\nmore on how we’re building with GitLab.\n\n\n## Building a multi-region platform with GitLab\n\n\nNow that you have context about Runway, let's walk through how to build a\nmulti-region platform using GitLab features.\n\n\n### Provision\n\n\nWhen building an internal platform, the first challenge is provisioning\ninfrastructure for a service. In Runway, Provisioner is the component that\nis responsible for maintaining a service inventory and managing IaC for GCP\nresources using Terraform.\n\n\nTo provision a service, an application developer will open an MR to add a\nservice project to the inventory using git, and Provisioner will create\nrequired resources, such as service accounts and identity and access\nmanagement policies. When building this functionality with GitLab, Runway\nleverages [OpenID Connect (OIDC) with GPC Workload Identity\nFederation](https://docs.gitlab.com/ee/ci/cloud\\_services/google\\_cloud/)\nfor managing IaC.\n\n\nAdditionally, Provisioner will create a deployment project for each service\nproject. The purpose of creating separate projects for deployments is to\nensure the [principle of least\nprivilege](https://about.gitlab.com/blog/the-ultimate-guide-to-least-privilege-access-with-gitlab/)\nby authenticating as a GCP service account with restricted permissions.\nRunway leverages the [Projects\nAPI](https://docs.gitlab.com/ee/api/projects.html) for creating projects\nwith [Terraform\nprovider](https://registry.terraform.io/providers/gitlabhq/gitlab/latest/docs).\n\n\nFinally, Provisioner defines variables in the deployment project for the\nservice account, so that deployment CI jobs can authenticate to GCP. Runway\nleverages [CI/CD variables](https://docs.gitlab.com/ee/ci/variables/) and\n[Job Token\nallowlist](https://docs.gitlab.com/ee/ci/jobs/ci\\_job\\_token.html\\#add-a-group-or-project-to-the-job-token-allowlist)\nto handle authentication and authorization.\n\n\nHere’s a simplified example of provisioning a multi-region service in the\nservice inventory:\n\n\n```\n\n{\n  \"inventory\": [\n    {\n      \"name\": \"example-service\",\n      \"project_id\": 46267196,\n      \"regions\": [\n        \"europe-west1\",\n        \"us-east1\",\n        \"us-west1\"\n      ]\n    }\n  ]\n}\n\n```\n\n\nOnce provisioned, a deployment project and necessary infrastructure will be\ncreated for a service.\n\n\n### Configure\n\n\nAfter a service is provisioned, the next challenge is the configuration for\na service. In Runway,\n[Reconciler](https://gitlab.com/gitlab-com/gl-infra/platform/runway/runwayctl)\nis a component that is responsible for configuring and deploying services by\naligning the actual state with the desired state using Golang and Terraform.\n\n\nHere’s a simplified example of an application developer configuring GitLab\nCI/CD in their service project:\n\n\n```\n\n# .gitlab-ci.yml\n\nstages:\n  - validate\n  - runway_staging\n  - runway_production\n\ninclude:\n  - project: 'gitlab-com/gl-infra/platform/runway/runwayctl'\n    file: 'ci-tasks/service-project/runway.yml'\n    inputs:\n      runway_service_id: example-service\n      image: \"$CI_REGISTRY_IMAGE/${CI_PROJECT_NAME}:${CI_COMMIT_SHORT_SHA}\"\n      runway_version: v3.22.0\n\n# omitted for brevity\n\n```\n\n\nRunway provides sane default values for configuration that are based on our\nexperience in delivering stable and reliable features to customers.\nAdditionally, service owners can configure infrastructure using a service\nmanifest file hosted in a service project. The service manifest uses JSON\nSchema for validation. When building this functionality with GitLab, Runway\nleverages [Pages](https://docs.gitlab.com/ee/user/project/pages/) for schema\ndocumentation.\n\n\nTo deliver this part of the platform, Runway leverages [CI/CD\ntemplates](https://docs.gitlab.com/ee/development/cicd/templates.html),\n[Releases](https://docs.gitlab.com/ee/user/project/releases/), and\n[Container\nRegistry](https://docs.gitlab.com/ee/user/packages/container\\_registry/) for\nintegrating with service projects.\n\n\nHere’s a simplified example of a service manifest:\n\n\n```\n\n# .runway/runway-production.yml\n\napiVersion: runway/v1\n\nkind: RunwayService\n\nspec:\n container_port: 8181\n regions:\n   - us-east1\n   - us-west1\n   - europe-west1\n\n# omitted for brevity\n\n```\n\n\nFor multi-region services, Runway injects an environment variable into the\ncontainer instance runtime, e.g. RUNWAY\\_REGION, so application developers\nhave the context to make any downstream dependencies regionally-aware, e.g.\nVertex AI API.\n\n\nOnce configured, a service project will be integrated with a deployment\nproject.\n\n\n### Deploy\n\n\nAfter a service project is configured, the next challenge is deploying a\nservice. In Runway, Reconciler handles this by triggering a deployment job\nin the deployment project when an MR is merged to the main branch. When\nbuilding this functionality with GitLab, Runway leverages [Trigger\nPipelines](https://docs.gitlab.com/ee/ci/triggers/) and [Multi-Project\nPipelines](https://docs.gitlab.com/ee/ci/pipelines/downstream\\_pipelines.html\\#multi-project-pipelines)\nto trigger jobs from service project to deployment project.\n\n\n![trigger jobs from service project to deployment\nproject](https://res.cloudinary.com/about-gitlab-com/image/upload/v1750098672/Blog/Content%20Images/Blog/Content%20Images/image5_aHR0cHM6_1750098671612.png)\n\n\nOnce a pipeline is running in a deployment project, it will be deployed to\nan environment. By default, Runway will provision staging and production\nenvironments for all services. At this point, Reconciler will apply any\nTerraform resource changes for infrastructure. When building this\nfunctionality with GitLab, Runway leverages\n[Environments/Deployments](https://docs.gitlab.com/ee/ci/environments/) and\n[GitLab-managed Terraform\nstate](https://docs.gitlab.com/ee/user/infrastructure/iac/terraform\\_state.html)\nfor each service.\n\n\n![Reconciler applies any Terraform resource changes for\ninfrastructure](https://res.cloudinary.com/about-gitlab-com/image/upload/v1750098672/Blog/Content%20Images/Blog/Content%20Images/image1_aHR0cHM6_1750098671614.png)\n\n\nRunway provides default application metrics for services. Additionally,\ncustom metrics can be used by enabling a sidecar container with\nOpenTelemetry Collector configured to scrape Prometheus and remote write to\nMimir. By providing observability out of the box, Runway is able to bake\nmonitoring into CI/CD pipelines.\n\n\nExample scenarios include gradual rollouts for blue/green deployments,\npreventing promotions to production when staging is broken, or automatically\nrolling back to previous revision when elevated error rates occur in\nproduction.\n\n\n![Runway bakes monitoring into CI/CD\npipelines](https://res.cloudinary.com/about-gitlab-com/image/upload/v1750098672/Blog/Content%20Images/Blog/Content%20Images/image2_aHR0cHM6_1750098671615.png)\n\n\nOnce deployed, environments will serve the latest revision of a service. At\nthis point, you should have a good understanding of some of the challenges\nthat will be encountered, and how to solve them with GitLab features.\n\n\n## Migrating to multi-region in production\n\n\nAfter extending Runway components to support multi-region in Cloud Run, the\nfinal challenge was migrating from AI Gateway’s single-region deployment in\nproduction with zero downtime. Today, teams using Runway to deploy their\nservices can self-serve on regions making a multi-region deployment just as\nsimple as a single-region deployment. \n\n\nWe were able to iterate on building multi-region functionality without\nimpacting existing infrastructure by using semantic versioning for Runway.\nNext, we’ll share some learnings from the migration that may inform how to\noperate services for an internal multi-region platform.\n\n\n### Dry run deployments\n\n\nIn Runway, Reconciler will apply Terraform changes in CI/CD. The trade-off\nis that plans cannot be verified in advance, which could risk inadvertently\ndestroying or misconfiguring production infrastructure. To solve this\nproblem, Runway will perform a “dry run” deployment for MRs.\n\n\n![\"Dry run\"\ndeployment](https://res.cloudinary.com/about-gitlab-com/image/upload/v1750098672/Blog/Content%20Images/Blog/Content%20Images/image6_aHR0cHM6_1750098671616.png)\n\n\nFor migrating AI Gateway, dry run deployments increased confidence and\nhelped mitigate risk of downtime during rollout. When building an internal\nplatform with GitLab, we recommend supporting dry run deployments from the\nstart.\n\n\n### Regional observability\n\n\nIn Runway, existing observability was aggregated by assuming a single-region\ndeployment. To solve this problem, Runway observability was retrofitted to\ninclude a new region label for Prometheus metrics.\n\n\nOnce metrics were retrofitted, we were able to introduce service level\nindicators (SLIs) for both regional Cloud Run services and global load\nbalancing. Here’s an example dashboard screenshot for a general Runway\nservice:\n\n\n![dashboard screenshot for a general Runway\nservice](https://res.cloudinary.com/about-gitlab-com/image/upload/v1750098672/Blog/Content%20Images/Blog/Content%20Images/image3_aHR0cHM6_1750098671617.png)\n\n\n***Note:** Data is not actual production data and is only for illustration\npurposes.*\n\n\nAdditionally, we were able to update our service level objectives (SLOs) to\nsupport regions. As a result, service owners could be alerted when a\nspecific region experiences an elevated error rate, or increase in response\ntimes.\n\n\n![screenshot of\nalerts](https://res.cloudinary.com/about-gitlab-com/image/upload/v1750098672/Blog/Content%20Images/Blog/Content%20Images/image4_aHR0cHM6_1750098671617.png)\n\n\n***Note:** Data is not actual production data and is only for illustration\npurposes.*\n\n\nFor migrating AI Gateway, regional observability increased confidence and\nhelped provide more visibility into new infrastructure. When building an\ninternal platform with GitLab, we recommend supporting regional\nobservability from the start.\n\n\n### Self-service regions\n\n\nThe Infrastructure department successfully performed the initial migration\nof multi-region support for AI Gateway in production with zero downtime.\nGiven the risk associated with rolling out a large infrastructure migration,\nit was important to ensure the service continued working as expected.\n\n\nShortly afterwards, service owners began self-serving additional regions to\nmeet the growth of customers. At the time of writing, [GitLab\nDuo](https://about.gitlab.com/gitlab-duo/) is available in six regions\naround the globe and counting. Service owners are able to configure the\ndesired regions, and Runway will provide guardrails along the way in a\nscalable solution.\n\n\nAdditionally, three other internal services have already started using\nmulti-region functionality on Runway. Application developers have entirely\nself-served functionality, which validates that we’ve provided a good\nplatform experience for service owners. For a platform play, a scalable\nsolution like Runway is considered a good outcome since the Infrastructure\ndepartment is no longer a blocker.\n\n\n## What’s next for Runway\n\n\nBased on how quickly we could iterate to provide results for customers, the\nSaaS Platforms department has continued to invest in Runway. We’ve grown the\nRunway team with additional contributors, started evolving the platform\nruntime (e.g. Google Kubernetes Engine), and continue dogfooding with\ntighter integration in the product.\n\n\nIf you’re interested in learning more, feel free to check out\n[https://gitlab.com/gitlab-com/gl-infra/platform/runway](https://gitlab.com/gitlab-com/gl-infra/platform/runway).\n\n\n## More Building GitLab with GitLab\n\n- [Why there is no MLOps without\nDevSecOps](https://about.gitlab.com/blog/there-is-no-mlops-without-devsecops/)\n\n- [Stress-testing Product\nAnalytics](https://about.gitlab.com/blog/building-gitlab-with-gitlab-stress-testing-product-analytics/)\n\n- [Web API Fuzz\nTesting](https://about.gitlab.com/blog/building-gitlab-with-gitlab-api-fuzzing-workflow/)\n\n- [How GitLab.com inspired\nDedicated](https://about.gitlab.com/blog/building-gitlab-with-gitlabcom-how-gitlab-inspired-dedicated/)\n\n- [Expanding our security certification\nportfolio](https://about.gitlab.com/blog/building-gitlab-with-gitlab-expanding-our-security-certification-portfolio/)\n",[109,696,9,915,742,719,960,961,786,962],"google","git","AI/ML",{"slug":964,"featured":91,"template":699},"building-gitlab-with-gitlab-a-multi-region-service-to-deliver-ai-features","content:en-us:blog:building-gitlab-with-gitlab-a-multi-region-service-to-deliver-ai-features.yml","Building Gitlab With Gitlab A Multi Region Service To Deliver Ai Features","en-us/blog/building-gitlab-with-gitlab-a-multi-region-service-to-deliver-ai-features.yml","en-us/blog/building-gitlab-with-gitlab-a-multi-region-service-to-deliver-ai-features",{"_path":970,"_dir":246,"_draft":6,"_partial":6,"_locale":7,"seo":971,"content":977,"config":984,"_id":986,"_type":14,"title":987,"_source":16,"_file":988,"_stem":989,"_extension":19},"/en-us/blog/changes-to-the-preclonescript",{"title":972,"description":973,"ogTitle":972,"ogDescription":973,"noIndex":6,"ogImage":974,"ogUrl":975,"ogSiteName":686,"ogType":687,"canonicalUrls":975,"schema":976},"Guide to pre_clone_script changes on GitLab SaaS Linux Runners","Learn about the change from CI_PRE_CLONE_SCRIPT to pre_get_sources_script on GitLab SaaS Linux Runners.","https://res.cloudinary.com/about-gitlab-com/image/upload/v1749664087/Blog/Hero%20Images/tanukicover.jpg","https://about.gitlab.com/blog/changes-to-the-preclonescript","\n                        {\n        \"@context\": \"https://schema.org\",\n        \"@type\": \"Article\",\n        \"headline\": \"Guide to pre_clone_script changes on GitLab SaaS Linux Runners\",\n        \"author\": [{\"@type\":\"Person\",\"name\":\"Darren Eastman\"}],\n        \"datePublished\": \"2023-03-27\",\n      }",{"title":972,"description":973,"authors":978,"heroImage":974,"date":980,"body":981,"category":805,"tags":982},[979],"Darren Eastman","2023-03-27","\n\nIn GitLab 16.0, on GitLab SaaS Runners on Linux, we are removing the `CI_PRE_CLONE_SCRIPT` variable support in CI/CD workflows. If you use the `CI_PRE_CLONE_SCRIPT` variable in your GitLab SaaS CI pipelines, you must change to the new method to ensure your workflows run as expected.\n\n## What is the pre_clone_script?\n\nThe [`pre_clone_script`](https://docs.gitlab.com/runner/configuration/advanced-configuration.html#the-runners-section) configuration option is a powerful pre-build script feature that enables you to execute custom logic before a GitLab Runner clones the project repository and runs your CI jobs. For example, you could use this feature in your environment to automate the cleanup of files from the build directory that aren’t useful for subsequent builds. Other use cases include retrieving files needed for the build or running other commands before the git initialization of the build directory.\n\nTo use this feature on GitLab SaaS Runners on Linux, you must first define a project CI/CD variable, `CI_PRE_CLONE_SCRIPT`, and include that variable in the `.gitlab-ci.yml` pipeline file.\n\nWhile this Runner pre-build script hook configuration has proven helpful for our customers, we needed to devise a more straightforward solution, while introducing additional guard rails. Enter the new [`pre_get_sources_script`](https://docs.gitlab.com/ee/ci/yaml/index.html#hookspre_get_sources_script) keyword in the `.gilab-ci.yml` file syntax.\n\n## What is the pre_get_sources_script hook?\n\nThe `pre_get_sources_script` hook is a simple-to-use method that enables you to have your script executed by the GitLab Runner before the git clone, init, and CI build scripts. Using the new `pre_get_sources_script` script is as simple as entering the following syntax in your `.gitlab-ci.yml` pipeline file.\n\n``` yaml\ntest_job:\n   stage: test\n   hooks:\n      pre_get_sources_script:\n      - echo 'hello run commands here before fetching the project repository'\n   script:\n     - echo 'this is the start of my CI build job script\n\n```\n\nSince the hook now is visible as code in your pipeline, you have immediate visibility into the script the Runner will execute before running the build job.\n\n## How to prepare for `pre_get_sources_script`?\n\nTo prepare for the change to `pre_get_sources_script` in GitLab 16.0, follow these steps: \n\n1. Check your CI jobs on GitLab SaaS to confirm if the `CI_PRE_CLONE_SCRIPT` variable is used.\n1. If the `CI_PRE_CLONE_SCRIPT` is used, then replace the script definition with a `pre_get_sources_script` hook in your `.gitlab-ci.yml` file.\n1. If you have any issues during testing of your pipelines with `pre_get_sources_script`, connect with us by leaving a comment below.\n\n## What's next: Support for `post_get_source`\n\nOn self-managed GitLab Runners, the `pre_get_sources_script` hook is only one of many hooks you can use to run code in various CI/CD pipeline stages. Those hooks include `post_get_sources`, `pre_build`, and `post_build` hooks, configurable only on the Runner host. More details are available in the [`[[runners]]`](https://docs.gitlab.com/runner/configuration/advanced-configuration.html#the-runners-section) section in the advanced configuration documentation.\n\nIn the future, we plan to add support for `post_get_sources` in the YAML syntax of the `gitlab-ci.yml` pipeline.\n\n_Disclaimer: This blog contains information related to upcoming products, features, and functionality. It is important to note that the information in this blog post is for informational purposes only. Please do not rely on this information for purchasing or planning purposes. As with all projects, the items mentioned in this blog and linked pages are subject to change or delay. The development, release, and timing of any products, features, or functionality remain at the sole discretion of GitLab._\n\n",[9,696,983],"features",{"slug":985,"featured":6,"template":699},"changes-to-the-preclonescript","content:en-us:blog:changes-to-the-preclonescript.yml","Changes To The Preclonescript","en-us/blog/changes-to-the-preclonescript.yml","en-us/blog/changes-to-the-preclonescript",{"_path":991,"_dir":246,"_draft":6,"_partial":6,"_locale":7,"seo":992,"content":998,"config":1008,"_id":1010,"_type":14,"title":1011,"_source":16,"_file":1012,"_stem":1013,"_extension":19},"/en-us/blog/chris-hill-devops-enterprise-summit-talk",{"title":993,"description":994,"ogTitle":993,"ogDescription":994,"noIndex":6,"ogImage":995,"ogUrl":996,"ogSiteName":686,"ogType":687,"canonicalUrls":996,"schema":997},"How Jaguar Land Rover embraced CI to speed up their software lifecycle","Inspiration, persistence, an attitude of continuous improvement – how adopting CI helped this vehicle company implement software over the air.","https://res.cloudinary.com/about-gitlab-com/image/upload/v1749667619/Blog/Hero%20Images/chris-hill-jlr-does.jpg","https://about.gitlab.com/blog/chris-hill-devops-enterprise-summit-talk","\n                        {\n        \"@context\": \"https://schema.org\",\n        \"@type\": \"Article\",\n        \"headline\": \"How Jaguar Land Rover embraced CI to speed up their software lifecycle\",\n        \"author\": [{\"@type\":\"Person\",\"name\":\"Rebecca Dodd\"}],\n        \"datePublished\": \"2018-07-23\",\n      }",{"title":993,"description":994,"authors":999,"heroImage":995,"date":1000,"body":1001,"category":1002,"tags":1003},[846],"2018-07-23","\n\n[CI/CD](/topics/ci-cd/) gets us pretty excited anyway, but it's not often we get to talk about how it improves something as cool as a luxury car. Chris Hill, Head of Systems Engineering for Infotainment at Jaguar Land Rover, recently shared his own team's journey from feedback loops of 4-6 weeks to just 30 minutes, in this inspiring talk from [DevOps Enterprise](/stages-devops-lifecycle/) Summit London 2018.\n\n\u003C!-- blank line -->\n\u003Cfigure class=\"video_container\">\n  \u003Ciframe src=\"https://www.youtube.com/embed/CEvjB-79tOs\" frameborder=\"0\" allowfullscreen=\"true\"> \u003C/iframe>\n\u003C/figure>\n\u003C!-- blank line -->\n\n## Key takeaways from Chris' talk\n\n### What's needed for transformation\n\n\u003Cdiv class=\"panel panel-default twitter-block\"> \u003Ca class=\"twitter-block-link panel-body\" href=\"http://twitter.com/share?text=%22Driving change within an enterprise requires three qualities: inspiration, persistence, and an attitude of continuous improvement.%22 – @chillosuvia via @gitlab&amp;amp;url=https://about.gitlab.com/blog/chris-hill-devops-enterprise-summit-talk/&amp;amp;hashtags=\" rel=\"nofollow\" target=\"_blank\" title=\"Tweet!\"> \u003Cspan class=\"twitter-text pull-left\"> \"Driving change within an enterprise requires three qualities: inspiration, persistence, and an attitude of continuous improvement.\" – @chillosu via @gitlab \u003C/span> \u003Cspan class=\"click-to-tweet\"> Click to tweet! \u003Ci class=\"fab fa-twitter\">\u003C/i> \u003C/span> \u003C/a> \u003C/div>\n\n### How you respond to complaints matters\n\n> \"Equally if not more important than the complaint itself, is the response or reaction to the complaint. 'Can I bring a complaint, that I know my voice is heard and that somebody cares about resolving my issue?'\"\n\n> \"'I asked the ops team three weeks ago to add a build dependency on the build servers, and it still hasn't been added. I'm just going to go back to building on my own.' This complaint obviously is a knife right to the heart because you feel like you've started to regress. But what I like about this complaint is it led to a behavioral change as well as a technical change. We decided instead of continuing the same direction, to move to ephemeral Docker containers to run all of our builds. With ephemeral Docker containers we defined every piece of build infrastructure as code. We used packer recipes to find a Docker container, and every app developer could now change the underlying infrastructure which built their application. They were empowered. They now had the self service to do their lifecycle on their own. And you're never going to receive the ops complaint because you've handed over the keys.\"\n\n### Efficient feedback loops are critical\n\n> \"Our feedback loops were 4-6 weeks. Could you imagine writing code today and six weeks from now being told whether or not it works or is broken? I don't remember the shirt that I wore yesterday, let alone what I had for breakfast this morning, let alone what I wrote six weeks ago, and chances are I've been working on features for the last six weeks, and for me to try to unpick what I was thinking at that point could be a huge context-switch penalty.\"\n\n> \"Infotainment also had a significantly higher number of contributors – up to 1,000 contributors. And what we noticed is that contributions don't come linearly, they come in bursts. We actually found that Thursdays were the day that most of our developers committed on. And when we had manual code reviews, if we didn't have reviewers ready on a Thursday, we would create our own backlog.\"\n\n### Deployments don't have to be limited to a traditional release cycle\n\n> \"How could we change the game? Instead of ditching the combustion engine, we ditched the dealership visits, and we implemented software over the air. And this huge Linux distribution that we build upwards towards 700 times per day in a continuous integration pattern, on a dev branch or a master branch, or a release branch, we can now deliver to every vehicle in the form of small, incremental deltas. We can also deliver it to the vehicle while you're driving, and not interrupt your daily life. In fact I showed Gene yesterday, we started a download and an install while I was driving, and the entire thing happened in the background. Jeff even made the comment, 'This is blue-green deployment for vehicles.'\"\n\n> \"One of my favorite indicators is deploys per day, per developer. But I was always embarrassed to share ours because it was always below one. All of our new software wouldn't actually make it to vehicles; it was always batched together. Now I'm happy to say we can deploy, and we have been in our engineering environment, 50-70 times per day of each individual piece of software to a target or to a vehicle.\"\n\n> \"No longer are deployments limited to a traditional software release cycle. We've now skirted every single process to get a technician a new piece of software, and bother somebody else's day – one of our owners – to come into a dealership and spend an hour waiting for their vehicle to be done. We've now empowered the customer to be their own technician.\"\n","customer-stories",[741,1004,9,1005,1006,1007],"collaboration","user stories","automotive","customers",{"slug":1009,"featured":6,"template":699},"chris-hill-devops-enterprise-summit-talk","content:en-us:blog:chris-hill-devops-enterprise-summit-talk.yml","Chris Hill Devops Enterprise Summit Talk","en-us/blog/chris-hill-devops-enterprise-summit-talk.yml","en-us/blog/chris-hill-devops-enterprise-summit-talk",{"_path":1015,"_dir":246,"_draft":6,"_partial":6,"_locale":7,"seo":1016,"content":1022,"config":1030,"_id":1032,"_type":14,"title":1033,"_source":16,"_file":1034,"_stem":1035,"_extension":19},"/en-us/blog/ci-deployment-and-environments",{"title":1017,"description":1018,"ogTitle":1017,"ogDescription":1018,"noIndex":6,"ogImage":1019,"ogUrl":1020,"ogSiteName":686,"ogType":687,"canonicalUrls":1020,"schema":1021},"How to use GitLab CI to deploy to multiple environments","We walk you through different scenarios to demonstrate the versatility and power of GitLab CI.","https://res.cloudinary.com/about-gitlab-com/image/upload/v1749662033/Blog/Hero%20Images/intro.jpg","https://about.gitlab.com/blog/ci-deployment-and-environments","\n                        {\n        \"@context\": \"https://schema.org\",\n        \"@type\": \"Article\",\n        \"headline\": \"How to use GitLab CI to deploy to multiple environments\",\n        \"author\": [{\"@type\":\"Person\",\"name\":\"Ivan Nemytchenko\"},{\"@type\":\"Person\",\"name\":\"Cesar Saavedra\"}],\n        \"datePublished\": \"2021-02-05\",\n      }",{"title":1017,"description":1018,"authors":1023,"heroImage":1019,"date":1026,"body":1027,"category":717,"tags":1028,"updatedDate":1029},[1024,1025],"Ivan Nemytchenko","Cesar Saavedra","2021-02-05","This post is a success story of one imaginary news portal, and you're the\nhappy\n\nowner, the editor, and the only developer. Luckily, you already host your\nproject\n\ncode on GitLab.com and know that you can\n\n[run tests with GitLab CI/CD](https://docs.gitlab.com/ee/ci/testing/).\n\nNow you’re curious if it can be [used for\ndeployment](/blog/how-to-keep-up-with-ci-cd-best-practices/), and how far\ncan you go with it.\n\n\nTo keep our story technology stack-agnostic, let's assume that the app is\njust a\n\nset of HTML files. No server-side code, no fancy JS assets compilation.\n\n\nDestination platform is also simplistic – we will use [Amazon\nS3](https://aws.amazon.com/s3/).\n\n\nThe goal of the article is not to give you a bunch of copy-pasteable\nsnippets.\n\nThe goal is to show the principles and features of [GitLab\nCI](/solutions/continuous-integration/) so that you can easily apply them to\nyour technology stack.\n\n{: .alert .alert-warning}\n\n\nLet’s start from the beginning. There's no continuous integration (CI) in\nour story yet.\n\n\n## At the starting line\n\n\n**Deployment**: In your case, it means that a bunch of HTML files should\nappear on your\n\nS3 bucket (which is already configured for\n\n[static website\nhosting](http://docs.aws.amazon.com/AmazonS3/latest/dev/HowDoIWebsiteConfiguration.html?shortFooter=true)).\n\n\nThere are a million ways to do it. We’ll use the\n\n[awscli](http://docs.aws.amazon.com/cli/latest/reference/s3/cp.html#examples)\nlibrary,\n\nprovided by Amazon.\n\n\nThe full command looks like this:\n\n\n```shell\n\naws s3 cp ./ s3://yourbucket/ --recursive --exclude \"*\" --include \"*.html\"\n\n```\n\n\n![Manual\ndeployment](https://about.gitlab.com/images/blogimages/ci-deployment-and-environments/13.jpg){:\n.center}\n\nPushing code to repository and deploying are separate processes.\n\n{: .note .text-center}\n\n\nImportant detail: The command\n\n[expects\nyou](http://docs.aws.amazon.com/cli/latest/userguide/cli-chap-getting-started.html#config-settings-and-precedence)\n\nto provide `AWS_ACCESS_KEY_ID` and  `AWS_SECRET_ACCESS_KEY` environment\n\nvariables. Also you might need to specify `AWS_DEFAULT_REGION`.\n\n{: .alert .alert-info}\n\n\nLet’s try to automate it using [GitLab\nCI](/solutions/continuous-integration/).\n\n\n## The first automated deployment\n\n\nWith GitLab, there's no difference on what commands to run.\n\nYou can set up GitLab CI in a way that tailors to your specific needs, as if\nit was your local terminal on your computer. As long as you execute commands\nthere, you can tell CI to do the same for you in GitLab.\n\nPut your script to `.gitlab-ci.yml` and push your code – that’s it: CI\ntriggers\n\na _job_ and your commands are executed.\n\n\nNow, let's add some context to our story: Our website is small, there is\n20-30 daily\n\nvisitors and the code repository has only one default branch: `main`.\n\n\nLet's start by specifying a _job_ with the command from above in the\n`.gitlab-ci.yml` file:\n\n\n```yaml\n\ndeploy:\n  script: aws s3 cp ./ s3://yourbucket/ --recursive --exclude \"*\" --include \"*.html\"\n```\n\n\nNo luck:\n\n![Failed\ncommand](https://about.gitlab.com/images/blogimages/ci-deployment-and-environments/fail1.png){:\n.shadow}\n\n\nIt is our _job_ to ensure that there is an `aws` executable.\n\nTo install `awscli` we need `pip`, which is a tool for Python packages\ninstallation.\n\nLet's specify Docker image with preinstalled Python, which should contain\n`pip` as well:\n\n\n```yaml\n\ndeploy:\n  image: python:latest\n  script:\n  - pip install awscli\n  - aws s3 cp ./ s3://yourbucket/ --recursive --exclude \"*\" --include \"*.html\"\n```\n\n\n![Automated\ndeployment](https://about.gitlab.com/images/blogimages/ci-deployment-and-environments/14.jpg){:\n.center}\n\nYou push your code to GitLab, and it is automatically deployed by CI.\n  {: .note .text-center}\n\nThe installation of `awscli` extends the job execution time, but that is not\na big\n\ndeal for now. If you need to speed up the process, you can always [look for\n\na Docker image](https://hub.docker.com/explore/) with preinstalled `awscli`,\n\nor create an image by yourself.\n\n{: .alert .alert-warning}\n\n\nAlso, let’s not forget about these environment variables, which you've just\ngrabbed\n\nfrom [AWS Console](https://console.aws.amazon.com/):\n\n\n```yaml\n\nvariables:\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\nIt should work, but keeping secret keys open, even in a private repository,\n\nis not a good idea. Let's see how to deal with this situation.\n\n\n### Keeping secret things secret\n\n\nGitLab has a special place for secret variables: **Settings > CI/CD >\nVariables**\n\n\n![Picture of Variables\npage](https://res.cloudinary.com/about-gitlab-com/image/upload/v1749674076/Blog/Content%20Images/add-variable-updated.png)\n\n\nWhatever you put there will be turned into **environment variables**.\n\nChecking the \"Mask variable\" checkbox will obfuscate the variable in job\nlogs. Also, checking the \"Protect variable\" checkbox will export the\nvariable to only pipelines running on protected branches and tags. Users\nwith Owner or Maintainer permissions to a project will have access to this\nsection.\n\n\nWe could remove `variables` section from our CI configuration. However,\nlet’s use it for another purpose.\n\n\n### How to specify and use variables that are not secret\n\n\nWhen your configuration gets bigger, it is convenient to keep some of the\n\nparameters as variables at the beginning of your configuration. Especially\nif you\n\nuse them in more than one place. Although it is not the case in our\nsituation yet,\n\nlet's set the S3 bucket name as a\n[**variable**](https://docs.gitlab.com/ee/ci/variables/) for the purpose of\nthis demonstration:\n\n\n```yaml\n\nvariables:\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\n\nSo far so good:\n\n\n![Successful\nbuild](https://about.gitlab.com/images/blogimages/ci-deployment-and-environments/build.png){:\n.shadow.medium.center}\n\n\nIn our hypothetical scenario, the audience of your website has grown, so\nyou've hired a developer to help you.\n\nNow you have a team. Let's see how teamwork changes the GitLab CI workflow.\n\n\n## How to use GitLab CI with a team\n\n\nNow, that there are two users working in the same repository, it is no\nlonger convenient\n\nto use the `main` branch for development. You decide to use separate\nbranches\n\nfor both new features and new articles and merge them into `main` when they\nare ready.\n\n\nThe problem is that your current CI config doesn’t care about branches at\nall.\n\nWhenever you push anything to GitLab, it will be deployed to S3.\n\n\nPreventing this problem is straightforward. Just add `only: main` to your\n`deploy` job.\n\n\n![Automated deployment of main\nbranch](https://res.cloudinary.com/about-gitlab-com/image/upload/v1749674076/Blog/Content%20Images/15-updated.png){:\n.center}\n\nYou don't want to deploy every branch to the production website but it would\nalso be nice to preview your changes from feature-branches somehow.\n\n{: .note .text-center}\n\n\n### How to set up a separate place for testing code\n\n\nThe person you recently hired, let's call him Patrick, reminds you that\nthere is a featured called\n\n[GitLab Pages](https://docs.gitlab.com/ee/user/project/pages/). It looks like a perfect\ncandidate for\n\na place to preview your work in progress.\n\n\nTo [host websites on GitLab Pages](/blog/gitlab-pages-setup/) your CI\nconfiguration file should satisfy three simple rules:\n\n\n- The _job_ should be named `pages`\n\n- There should be an `artifacts` section with folder `public` in it\n\n- Everything you want to host should be in this `public` folder\n\n\nThe contents of the public folder will be hosted at\n`http://\u003Cusername>.gitlab.io/\u003Cprojectname>/`\n\n{: .alert .alert-info}\n\n\nAfter applying the [example config for plain-html\nwebsites](https://gitlab.com/pages/plain-html/blob/master/.gitlab-ci.yml),\n\nthe full CI configuration looks like this:\n\n\n```yaml\n\nvariables:\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\n\nWe specified two jobs. One job deploys the website for your customers to S3\n(`deploy`).\n\nThe other one (`pages`) deploys the website to GitLab Pages.\n\nWe can name them \"Production environment\" and \"Staging environment\",\nrespectively.\n\n\n![Deployment to two\nplaces](https://res.cloudinary.com/about-gitlab-com/image/upload/v1749674076/Blog/Content%20Images/16-updated.png){:\n.center}\n\nAll branches, except main, will be deployed to GitLab Pages.\n\n{: .note .text-center}\n\n\n## Introducing environments\n\n\nGitLab offers\n [support for environments](https://docs.gitlab.com/ee/ci/environments/) (including dynamic environments and static environments),\n and all you need to do it to specify the corresponding environment for each deployment *job*:\n\n```yaml\n\nvariables:\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\n\nGitLab keeps track of your deployments, so you always know what is currently\nbeing deployed on your servers:\n\n\n![List of\nenvironments](https://res.cloudinary.com/about-gitlab-com/image/upload/v1749674076/Blog/Content%20Images/envs-updated.png){:\n.shadow.center}\n\n\nGitLab provides full history of your deployments for each of your current\nenvironments:\n\n\n![List of deployments to staging\nenvironment](https://res.cloudinary.com/about-gitlab-com/image/upload/v1749674077/Blog/Content%20Images/staging-env-detail-updated.png){:\n.shadow.center}\n\n\n![Environments](https://res.cloudinary.com/about-gitlab-com/image/upload/v1749674077/Blog/Content%20Images/17-updated.png){:\n.center}\n\n\nNow, with everything automated and set up, we’re ready for the new\nchallenges that are just around the corner.\n\n\n## How to troubleshoot deployments\n\n\nIt has just happened again.\n\nYou've pushed your feature-branch to preview it on staging and a minute\nlater Patrick pushed\n\nhis branch, so the staging environment was rewritten with his work. Aargh!!\nIt was the third time today!\n\n\nIdea! \u003Ci class=\"far fa-lightbulb\" style=\"color:#FFD900; font-size:.85em\"\naria-hidden=\"true\">\u003C/i> Let's use Slack to notify us of deployments, so that\npeople will not push their stuff if another one has been just deployed!\n\n\n> Learn how to [integrate GitLab with\nSlack](https://docs.gitlab.com/ee/user/project/integrations/gitlab_slack_application.html).\n\n\n## Teamwork at scale\n\n\nAs the time passed, your website became really popular, and your team has\ngrown from two people to eight people.\n\nPeople develop in parallel, so the situation when people wait for each other\nto\n\npreview something on Staging has become pretty common. \"Deploy every branch\nto staging\" stopped working.\n\n\n![Queue of branches for review on\nStaging](https://about.gitlab.com/images/blogimages/ci-deployment-and-environments/queue.jpg){:\n.center}\n\n\nIt's time to modify the process one more time. You and your team agreed that\nif\n\nsomeone wants to see their changes on the staging\n\nserver, they should first merge the changes to the \"staging\" branch.\n\n\nThe change of `.gitlab-ci.yml` is minimal:\n\n\n```yaml\n\nexcept:\n\n- main\n\n```\n\n\nis now changed to\n\n\n```yaml\n\nonly:\n\n- staging\n\n```\n\n\n![Staging\nbranch](https://res.cloudinary.com/about-gitlab-com/image/upload/v1749674077/Blog/Content%20Images/18-updated.png){:\n.center}\n\nPeople have to merge their feature branches before preview on the staging\nserver.\n\n{: .note .text-center}\n\n\nOf course, it requires additional time and effort for merging, but everybody\nagreed that it is better than waiting.\n\n\n### How to handle emergencies\n\n\nYou can't control everything, so sometimes things go wrong. Someone merged\nbranches incorrectly and\n\npushed the result straight to production exactly when your site was on top\nof HackerNews.\n\nThousands of people saw your completely broken layout instead of your shiny\nmain page.\n\n\nLuckily, someone found the **Rollback** button, so the\n\nwebsite was fixed a minute after the problem was discovered.\n\n\n![List of\nenvironments](https://res.cloudinary.com/about-gitlab-com/image/upload/v1749674077/Blog/Content%20Images/prod-env-rollback-arrow-updated.png){:\n.shadow.center}\n\nRollback relaunches the previous job with the previous commit\n\n{: .note .text-center}\n\n\nAnyway, you felt that you needed to react to the problem and decided to turn\noff\n\nauto-deployment to Production and switch to manual deployment.\n\nTo do that, you needed to add `when: manual` to your _job_.\n\n\nAs you expected, there will be no automatic deployment to Production after\nthat.\n\nTo deploy manually go to **CI/CD > Pipelines**, and click the button:\n\n\n![Skipped job is available for manual\nlaunch](https://res.cloudinary.com/about-gitlab-com/image/upload/v1749674076/Blog/Content%20Images/manual-pipeline-arrow-updated.png){:\n.shadow.center}\n\n\nFast forward in time. Finally, your company has turned into a corporation.\nNow, you have hundreds of people working on the website,\n\nso all the previous compromises no longer work.\n\n\n### Time to start using Review Apps\n\n\nThe next logical step is to boot up a temporary instance of the application\nper feature branch for review.\n\n\nIn our case, we set up another bucket on S3 for that. The only difference is\nthat\n\nwe copy the contents of our website to a \"folder\" with the name of the\n\nthe development branch, so that the URL looks like this:\n\n\n`http://\u003CREVIEW_S3_BUCKET_NAME>.s3-website-us-east-1.amazonaws.com/\u003Cbranchname>/`\n\n\nHere's the replacement for the `pages` _job_ we used before:\n\n\n```yaml\n\nreview 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\n\nThe interesting thing is where we got this `$CI_BUILD_REF_NAME` variable\nfrom.\n\nGitLab predefines [many environment\nvariables](https://docs.gitlab.com/ee/ci/variables/predefined_variables.html)\nso that you can use them in your jobs.\n\n\nNote that we defined the `S3_BUCKET_NAME` variable inside the *job*. You can\ndo this to rewrite top-level definitions.\n\n{: .alert .alert-info}\n\n\nVisual representation of this configuration:\n\n![Review apps]![How to use GitLab CI - update - 19 -\nupdated](https://res.cloudinary.com/about-gitlab-com/image/upload/v1749674077/Blog/Content%20Images/19-updated.png){:\n.illustration}\n\n\nThe details of the Review Apps implementation varies widely, depending upon\nyour real technology\n\nstack and on your deployment process, which is outside the scope of this\nblog post.\n\n\nIt will not be that straightforward, as it is with our static HTML website.\n\nFor example, you had to make these instances temporary, and booting up these\ninstances\n\nwith all required software and services automatically on the fly is not a\ntrivial task.\n\nHowever, it is doable, especially if you use Docker containers, or at least\nChef or Ansible.\n\n\nWe'll cover deployment with Docker in a future blog post.\n\nI feel a bit guilty for simplifying the deployment process to a simple HTML\nfiles copying, and not\n\nadding some hardcore scenarios. If you need some right now, I recommend you\nread the article [\"Building an Elixir Release into a Docker image using\nGitLab\nCI.\"](/blog/building-an-elixir-release-into-docker-image-using-gitlab-ci-part-1/)\n\n\nFor now, let's talk about one final thing.\n\n\n### Deploying to different platforms\n\n\nIn real life, we are not limited to S3 and GitLab Pages. We host, and\ntherefore,\n\ndeploy our apps and packages to various services.\n\n\nMoreover, at some point, you could decide to move to a new platform and will\nneed to rewrite all your deployment scripts.\n\nYou can use a gem called `dpl` to minimize the damage.\n\n\nIn the examples above we used `awscli` as a tool to deliver code to an\nexample\n\nservice (Amazon S3).\n\nHowever, no matter what tool and what destination system you use, the\nprinciple is the same:\n\nYou run a command with some parameters and somehow pass a secret key for\nauthentication purposes.\n\n\nThe `dpl` deployment tool utilizes this principle and provides a\n\nunified interface for [this list of\nproviders](https://github.com/travis-ci/dpl#supported-providers).\n\n\nHere's how a production deployment _job_ would look if we use `dpl`:\n\n\n```yaml\n\nvariables:\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\n\nIf you deploy to different systems or change destination platform\nfrequently, consider\n\nusing `dpl` to make your deployment scripts look uniform.\n\n\n## Five key takeaways\n\n\n1. Deployment is just a command (or a set of commands) that is regularly\nexecuted. Therefore it can run inside GitLab CI.\n\n2. Most times you'll need to provide some secret key(s) to the command you\nexecute. Store these secret keys in **Settings > CI/CD > Variables**.\n\n3. With GitLab CI, you can flexibly specify which branches to deploy to.\n\n4. If you deploy to multiple environments, GitLab will conserve the history\nof deployments,\n\nwhich allows you to rollback to any previous version.\n\n5. For critical parts of your infrastructure, you can enable manual\ndeployment from GitLab interface, instead of automated deployment.\n\n\n\u003Cstyle>\n\nimg.illustration {\n  padding-left: 12%;\n  padding-right: 12%;\n\n}\n\n@media (max-width: 760px) {\n  img.illustration {\n    padding-left: 0px;\n    padding-right: 0px;\n  }\n}\n\n\u003C/style>\n",[9,696,742],"2024-07-22",{"slug":1031,"featured":6,"template":699},"ci-deployment-and-environments","content:en-us:blog:ci-deployment-and-environments.yml","Ci Deployment And Environments","en-us/blog/ci-deployment-and-environments.yml","en-us/blog/ci-deployment-and-environments",{"_path":1037,"_dir":246,"_draft":6,"_partial":6,"_locale":7,"seo":1038,"content":1044,"config":1050,"_id":1052,"_type":14,"title":1053,"_source":16,"_file":1054,"_stem":1055,"_extension":19},"/en-us/blog/cicd-tunnel-impersonation",{"title":1039,"description":1040,"ogTitle":1039,"ogDescription":1040,"noIndex":6,"ogImage":1041,"ogUrl":1042,"ogSiteName":686,"ogType":687,"canonicalUrls":1042,"schema":1043},"Fine-grained permissions with impersonation in CI/CD tunnel","Learn how to use use fine-grained permissions via generic impersonation in CI/CD Tunnel","https://res.cloudinary.com/about-gitlab-com/image/upload/v1749667435/Blog/Hero%20Images/tunnel.jpg","https://about.gitlab.com/blog/cicd-tunnel-impersonation","\n                        {\n        \"@context\": \"https://schema.org\",\n        \"@type\": \"Article\",\n        \"headline\": \"How to use fine-grained permissions via generic impersonation in CI/CD Tunnel\",\n        \"author\": [{\"@type\":\"Person\",\"name\":\"Cesar Saavedra\"}],\n        \"datePublished\": \"2022-02-01\",\n      }",{"title":1045,"description":1040,"authors":1046,"heroImage":1041,"date":1047,"body":1048,"category":717,"tags":1049},"How to use fine-grained permissions via generic impersonation in CI/CD Tunnel",[1025],"2022-02-01","\nThe [CI/CD Tunnel](https://docs.gitlab.com/ee/user/clusters/agent/ci_cd_workflow.html), which leverages the [GitLab Agent for Kubernetes](https://docs.gitlab.com/ee/user/clusters/agent/), enables users to access Kubernetes clusters from GitLab CI/CD jobs. In this blog post, we review how you can securely access your clusters from your CI/CD pipelines by using generic impersonation. In addition, we will briefly cover the activity list of the GitLab Agent for Kubernetes, a capability recently introduced by GitLab, that can help you detect and troubleshoot faulty events.\n\n## Using impersonation with your CI/CD tunnel\n\nThe CI/CD Tunnel leverages the GitLab Agent for Kubernetes, which permits the secure connectivity between GitLab and your Kubernetes cluster without the need to expose your cluster to the internet and outside your firewall. The CI/CD Tunnel allows you to connect to your Kubernetes cluster from your CI/CD jobs/pipelines.\n\nBy default, the CI/CD Tunnel inherits all the permissions from the service account used to install the Agent in the cluster. However, fine-grained permissions can be used in conjunction with the CI/CD Tunnel to restrict and manage access to your cluster resources.\n\nFine-grained permissions control with the CI/CD tunnel via impersonation:\n\n- Allows you to leverage your K8s authorization capabilities to limit the permissions of what can be done with the CI/CD tunnel on your running cluster\n\n- Lowers the risk of providing unlimited access to your K8s cluster with the CI/CD tunnel\n\n- Segments fine-grained permissions with the CI/CD tunnel at the project or group level\n\n- Controls permissions with the CI/CD tunnel at the username or service account\n\nTo restrict access to your cluster, you can use impersonation. To specify impersonations, use the access_as attribute in your Agent's configuration file and use Kubernetes RBAC rules to manage impersonated account permissions.\n\nYou can impersonate:\n- The Agent itself (default)\n= The CI job that accesses the cluster\n- A specific user or system account defined within the cluster\n\n## Steps to exercise impersonation with the CI/CD Tunnel\n\nLet's go through the steps on how you can exercise impersonation with the CI/CD Tunnel.\n\n### Creating your Kubernetes cluster\n\nIn order to exercise the capabilities described above, we need a Kubernetes cluster. Although, you can use any Kubernetes distribution, for this example, we create a GKE Standard Kubernetes cluster and name it \"csaavedra-ga4k-cluster\". We select the zone and version 1.21 of Kubernetes and ensure that our cluster will have three nodes. We leave the security and metadata screens with their defaulted values and click on the create button:\n\n![Creating a GKE cluster](https://about.gitlab.com/images/blogimages/cicd-tunnel-impersonate/0-gke-creation.png){: .shadow.medium.center.wrap-text}\nCreating a GKE cluster\n{: .note.text-center}\n\n### Sample projects to be used\n\nLet's proceed now to this [top-level group](https://gitlab.com/tech-marketing/sandbox/gl-14-5-cs-demos), which contains three projects, which we will use to show impersonation with the CI/CD tunnel. You can do this at the project or group level. In this example, we will show setting impersonation at the project level:\n\n![Project structure in GitLab](https://about.gitlab.com/images/blogimages/cicd-tunnel-impersonate/1-project-struct.png){: .shadow.medium.center.wrap-text}\nProject structure in GitLab\n{: .note.text-center}\n\nProject \"ga4k\" will configure the GitLab Agent for Kubernetes and also set impersonations with the CI/CD tunnel. Project \"sample-application\" will use the CI/CD tunnel, managed by the agent, to connect to the Kubernetes cluster and execute a pipeline using different impersonations. Project \"cluster-management\" will also use the CI/CD tunnel to connect to the cluster and install the Ingress application on it.\n\nNot only does the CI/CD tunnel streamline the deployment, management, and monitoring of Kubernetes-native applications, but it also does it securely and safely by using impersonations that leverage your Kubernetes cluster's RBAC rules.\n\nProject \"ga4k\" contains and manages the configuration for the GitLab Agent for K8s called \"csaavedra-agentk\". Looking at its \"config.yaml\" file, we see that the agent points to itself for manifest projects, but most importantly, it provides CI/CD tunnel access to two projects: \"sample-application\" and \"cluster-management\". This means that these two projects' CI/CD pipelines will have access to the K8s cluster that the agent is securely connected to:\n\n![The GitLab Agent for K8s configuration](https://about.gitlab.com/images/blogimages/cicd-tunnel-impersonate/2-agent-config.png){: .shadow.medium.center.wrap-text}\nThe GitLab Agent for K8s configuration\n{: .note.text-center}\n\nProject \"sample-application\" has a pipeline, which we will later execute under different impersonations. And project \"cluster-management\" has a pipeline that will install only the Ingress application on the Kubernetes cluster, as configured in its helmfile.yaml file:\n\n![Deployable applications in cluster-management project](https://about.gitlab.com/images/blogimages/cicd-tunnel-impersonate/3-cluster-mgmt-helmfile.png){: .shadow.medium.center.wrap-text}\nDeployable applications in cluster-management project\n{: .note.text-center}\n\n### Connecting the Agent to your Kubernetes cluster\n\nLet's head back to project \"ga4k\" and connect to the Kubernetes cluster via the agent. We select agent \"csaavedra-agentk\" to register with GitLab:\n\n![List of defined agents](https://about.gitlab.com/images/blogimages/cicd-tunnel-impersonate/4-agents-popdown.png){: .shadow.medium.center.wrap-text}\nList of defined agents\n{: .note.text-center}\n\nThis step generates a token that we can use to install the agent on the cluster. We copy the Docker command to our local desktop for later use. Notice that the command includes the generated token, which you can also copy:\n\n![Docker command to deploy agent to your K8s cluster](https://about.gitlab.com/images/blogimages/cicd-tunnel-impersonate/5-docker-cmd.png){: .shadow.medium.center.wrap-text}\nDocker command to deploy agent to your K8s cluster\n{: .note.text-center}\n\nFrom a local command window, we ensure that our connectivity parameters to GCP are correct:\n\n![Checking your GCP connectivity parameters](https://about.gitlab.com/images/blogimages/cicd-tunnel-impersonate/6-gcp-connectivity.png){: .shadow.medium.center.wrap-text}\nChecking your GCP connectivity parameters\n{: .note.text-center}\n\nWe then add the credentials to our kubeconfig file to connect to our newly created Kubernetes cluster \"csaavedra-ga4k-cluster\" and verify that our context is set to it:\n\n![Adding your cluster credentials to your kubeconfig](https://about.gitlab.com/images/blogimages/cicd-tunnel-impersonate/7-adding-creds.png){: .shadow.medium.center.wrap-text}\nAdding the credentials of your cluster to your kubeconfig\n{: .note.text-center}\n\nOnce this is done, we can list all the pods that are up and running on the cluster by entering `kubectl get pods –all-namespaces`:\n\n![Listing the pods in your running cluster](https://about.gitlab.com/images/blogimages/cicd-tunnel-impersonate/8-listing-pods.png){: .shadow.medium.center.wrap-text}\nListing the pods in your running cluster\n{: .note.text-center}\n\nFinally, we paste the docker command that will install the GitLab Agent for Kubernetes to this cluster making sure that its namespace is \"ga4k-agent\":\n\n![Deploying the agent to your K8s cluster](https://about.gitlab.com/images/blogimages/cicd-tunnel-impersonate/9-pasted-docker-cmd.png){: .shadow.medium.center.wrap-text}\nDeploying the agent to your K8s cluster\n{: .note.text-center}\n\nWe list the pods one more time to check that the agent pod is up and running on the cluster:\n\n![Agent up and running on your K8s cluster](https://about.gitlab.com/images/blogimages/cicd-tunnel-impersonate/10-agent-up.png){: .shadow.medium.center.wrap-text}\nAgent up and running on your K8s cluster\n{: .note.text-center}\n\nThe screen will refresh and show our Kubernetes cluster connected via the agent:\n\n![Agent connected to your K8s cluster](https://about.gitlab.com/images/blogimages/cicd-tunnel-impersonate/11-agent-connected.png){: .shadow.large.center.wrap-text}\nAgent connected to your K8s cluster\n{: .note.text-center}\n\n### The Agent's Activity Information page\n\nClicking on the agent name takes us to the Agent's Activity Information page, which lists agent events in real time. This information can help monitor your cluster's activity and detect and troubleshoot faulty events from your cluster. Connection and token information is currently listed with more events coming in future releases:\n\n![Agent activity information page](https://about.gitlab.com/images/blogimages/cicd-tunnel-impersonate/12-agent-activity.png){: .shadow.small.center.wrap-text}\nAgent activity information page\n{: .note.text-center}\n\n### Deploying Ingress to your Kubernetes cluster using default impersonation\n\nBy default, the CI/CD Tunnel inherits all the permissions from the service account used to install the agent in the cluster. Per the agent's configuration, the CI/CD pipelines of the \"cluster-management\" project will have access to the K8s cluster that the agent is securely connected to. Let's leverage this connectivity to deploy the Ingress application to the Kubernetes cluster from project \"cluster-management\". Let's make a small update to the project pipeline to launch it. Once the pipeline launches, we navigate to its detail view to track its completion:\n\n![Project \"cluster-management\" pipeline completed](https://about.gitlab.com/images/blogimages/cicd-tunnel-impersonate/13-cluster-mgmt-pipeline.png){: .shadow.small.center.wrap-text}\nProject \"cluster-management\" pipeline completed\n{: .note.text-center}\n\nand check the log of its **apply** job to verify that it was able to switch to the agent's context and successfully ran all the installation steps:\n\n![Ingress deployed to your cluster via CI/CD Tunnel using default impersonation](https://about.gitlab.com/images/blogimages/cicd-tunnel-impersonate/14-apply-job-log.png){: .shadow.medium.center.wrap-text}\nIngress deployed to your cluster via CI/CD Tunnel using default impersonation\n{: .note.text-center}\n\nFor further verification, we list the pods in the cluster and check that the ingress pods are up and running:\n\n![Ingress pods up and running](https://about.gitlab.com/images/blogimages/cicd-tunnel-impersonate/15-ingress-pods-up.png){: .shadow.medium.center.wrap-text}\nIngress pods up and running on your cluster\n{: .note.text-center}\n\n### Start trailing the agent's log file to watch updates\n\nBefore we start the impersonation use cases, let's start trailing the agent's log file from a command window:\n\n![Trailing agent log from the command line](https://about.gitlab.com/images/blogimages/cicd-tunnel-impersonate/16-trail-agent-log.png){: .shadow.medium.center.wrap-text}\nTrailing agent log from the command line\n{: .note.text-center}\n\nAnd also let's increase its logging to debug:\n\n![Increasing the agent log level to debug](https://about.gitlab.com/images/blogimages/cicd-tunnel-impersonate/17-agent-logging-level.png){: .shadow.medium.center.wrap-text}\nIncreasing the agent log level to debug\n{: .note.text-center}\n\n### Running impersonation using access_as:ci_job\n\nLet's now impersonate the CI job that accesses the cluster. For this, we modify the agent's configuration and add the \"access_as\" attribute with the \"ci_job\" tag under it:\n\n![Impersonating the CI job](https://about.gitlab.com/images/blogimages/cicd-tunnel-impersonate/18-ci-job-impersonation.png){: .shadow.medium.center.wrap-text}\nImpersonating the CI job\n{: .note.text-center}\n\nAs we save the updated configuration, we verify in the log output that the update has taken place in the running agent:\n\n![Agent updated with CI job impersonation](https://about.gitlab.com/images/blogimages/cicd-tunnel-impersonate/19-agent-conf-updated.png){: .shadow.large.center.wrap-text}\nAgent updated with CI job impersonation\n{: .note.text-center}\n\nNotice that the pipeline of the \"sample-application\" project has a test stage and a test job. It sets the variable KUBE_CONTEXT first, loads an image with the version of kubectl that matches the version of the K8s cluster, and executes two kubectl commands that access the remote cluster via the agent:\n\n![Project \"sample-application\" pipeline](https://about.gitlab.com/images/blogimages/cicd-tunnel-impersonate/20-sample-application-pipeline.png){: .shadow.medium.center.wrap-text}\nProject \"sample-application\" pipeline\n{: .note.text-center}\n\nWe manually execute the pipeline of the \"sample-application\" project and verify in the job log output that the context switch was successful and that the kubectl commands executed correctly:\n\n![Job log output with CI impersonation](https://about.gitlab.com/images/blogimages/cicd-tunnel-impersonate/21-ci-impersonation-job-log.png){: .shadow.medium.center.wrap-text}\nJob log output with CI impersonation\n{: .note.text-center}\n\n### Running impersonation using access_as:impersonate:username\n\nThe last use case is the impersonation of a specific user or system account defined within the cluster. I have pre-created a service account called \"jane\" on the Kubernetes cluster under the \"default\" namespace. And \"jane\" has been given the permission to do a \"get\", \"list\", and \"watch\" on the cluster pods as you can see by the output in the command window:\n\n![Jane user with permission to list pods](https://about.gitlab.com/images/blogimages/cicd-tunnel-impersonate/22-jane-and-perms.png){: .shadow.medium.center.wrap-text}\nJane user with permission to list pods\n{: .note.text-center}\n\nRemember that the service account \"gitlab-agent\" under namespace \"ga4k-agent\" was created earlier when we installed the agent by running the Docker command. In order for the agent to be able to impersonate another service account or user, it needs to have the permissions to do so. We do this by creating a clusterrole \"impersonate\" for impersonating users, groups, and service accounts, and then create a clusterrolebinding \"allowimpersonator\" to give these permissions for the \"default\" namespace to the agent \"gitlab-agent\" in the \"ga4k-agent\" namespace:\n\n![Giving impersonation permission to agent](https://about.gitlab.com/images/blogimages/cicd-tunnel-impersonate/23-clusterrole-perm-to-agent.png){: .shadow.large.center.wrap-text}\nGiving impersonation permission to agent\n{: .note.text-center}\n\nWe then edit the agent's configuration and add the \"impersonate\" attribute and provide the service account for \"jane\" as the parameter for the \"username\" tag:\n\n![Impersonating a specific user](https://about.gitlab.com/images/blogimages/cicd-tunnel-impersonate/24-user-impersonation.png){: .shadow.medium.center.wrap-text}\nImpersonating a specific user called jane\n{: .note.text-center}\n\nAs we commit the changes, we check the log output to verify that the update has taken place in the running agent:\n\n![Agent updated with user impersonation](https://about.gitlab.com/images/blogimages/cicd-tunnel-impersonate/25-agent-conf-updated.png){: .shadow.large.center.wrap-text}\nAgent updated with user impersonation\n{: .note.text-center}\n\nSince we know that \"jane\" has the permission to list the running pods in the cluster, let's head to the project \"sample-application\" pipeline and add the command \"kubectl get pods –all-namespaces\" to it:\n\n![Adding get pods command that jane is allowed to run](https://about.gitlab.com/images/blogimages/cicd-tunnel-impersonate/26-adding-get-pods-cmd.png){: .shadow.medium.center.wrap-text}\nAdding get pods command that jane is allowed to run\n{: .note.text-center}\n\nWe commit the update and head over to the running pipeline and drill into the \"test\" job log output to see that the context switch was successful and that the kubectl commands executed correctly, including the listing of the running pods in the cluster:\n\n![Job output for pipeline impersonation jane](https://about.gitlab.com/images/blogimages/cicd-tunnel-impersonate/27-user-impersonation-job-log.png){: .shadow.medium.center.wrap-text}\nJob output for pipeline impersonation jane\n{: .note.text-center}\n\n## Conclusion\n\nIn this blog post, we reviewed how you can securely access your Kubernetes clusters from your CI/CD pipelines by using generic impersonation.  In addition, we showed the activity list of the GitLab Agent for Kubernetes, which can help you detect and troubleshoot faulty events from your cluster.\n\nTo see these capabilities in action, check out the following video:\n\n\u003C!-- blank line -->\n\u003Cfigure class=\"video_container\">\n  \u003Ciframe src=\"https://www.youtube.com/embed/j8SJuHd7Zsw\" frameborder=\"0\" allowfullscreen=\"true\"> \u003C/iframe>\n\u003C/figure>\n\u003C!-- blank line -->\n\nCover image by Jakob Søby on [Unsplash](https://www.unsplash.com)\n{: .note}\n",[807,9,696,914],{"slug":1051,"featured":6,"template":699},"cicd-tunnel-impersonation","content:en-us:blog:cicd-tunnel-impersonation.yml","Cicd Tunnel Impersonation","en-us/blog/cicd-tunnel-impersonation.yml","en-us/blog/cicd-tunnel-impersonation",{"_path":1057,"_dir":246,"_draft":6,"_partial":6,"_locale":7,"seo":1058,"content":1064,"config":1069,"_id":1071,"_type":14,"title":1072,"_source":16,"_file":1073,"_stem":1074,"_extension":19},"/en-us/blog/code-counting-in-gitlab",{"title":1059,"description":1060,"ogTitle":1059,"ogDescription":1060,"noIndex":6,"ogImage":1061,"ogUrl":1062,"ogSiteName":686,"ogType":687,"canonicalUrls":1062,"schema":1063},"Lightning fast code counting for better code management intelligence","Knowledge of your code composition can come through simple counting of lines of code per language.","https://res.cloudinary.com/about-gitlab-com/image/upload/v1749682614/Blog/Hero%20Images/noaa-PkHsrwNOfBE-unsplash.jpg","https://about.gitlab.com/blog/code-counting-in-gitlab","\n                        {\n        \"@context\": \"https://schema.org\",\n        \"@type\": \"Article\",\n        \"headline\": \"Lightning fast code counting for better code management intelligence\",\n        \"author\": [{\"@type\":\"Person\",\"name\":\"Darwin Sanoy\"}],\n        \"datePublished\": \"2023-02-15\",\n      }",{"title":1059,"description":1060,"authors":1065,"heroImage":1061,"date":1066,"body":1067,"category":717,"tags":1068},[691],"2023-02-15","\n\nOne of the earliest forms of intelligence was to simply answer the question “How many?”. Counting is one of the first things that we learn as a child. As we grow older, we come to see this deceptively simple concept as somewhat childish. Yet, upon the concept of counting, the entire discipline of statistics is founded. In turn, every discipline that benefits from statistics owes a debt of gratitude to the very humble concept of counting. \n\nMany of the massive data lakes we keep are essentially vast amounts of counting. Using artificial intelligence to analyze this data, we frequently find insights we were not expecting. So it would seem that counting is somewhat of a fractal concept – it’s deceptively simple, but, when compounded, generates delightful things.\n\nSo if we have a thing we are trying to be more intelligent about, our first endeavor might be to count it. Let’s see how to apply that to our code stored in GitLab.\n\n### Why developers count code\n\nThe following list is from real-world scenarios. Many of them are also asserted in Ben Boyter’s blog post [Why count lines of code?](https://boyter.org/posts/why-count-lines-of-code/). Their enumeration here is not an endorsement of the validity or accuracy of code counting for the claimed benefit and the fundamental assumptions of such models are not stated. Because code counting is essentially a form of modeling, it is also subject to George Box’s axiom: “All models are wrong, but some are useful.”\n\n- Showing the languages in a repository using an absolute metric like source lines of code helps to quickly assess if one can contribute to the project, given their own talents. \n- Cost assessment for anything which charges by “lines of code” (some code scanning and development tools may charge this way).\n- Although [research](https://gitlab.com/gitlab-org/gitlab/-/issues/371038) shows that lines of code are not a good metric for measuring contribution, some developers have gotten used to seeing lines of code per contributor. \n- Code base shrinkage as a measure of good architecture (simplification).\n- Anything where the complexity of code affects project agility and costs. For instance, assessing and reporting status on migrating a code base to a new language. \n- Staffing a development team – understanding what language competencies are needed across the team and in what relative proportion to each other or understanding that for the entire organization’s codebase.\n- IT tooling decisions to support the needs of an organization given the most used coding languages across all repositories in the org.\n- Assessment of tech debt.\n\nWhile it is easy to create bad models with any of the above counts, the focus of this post is to get some good counts from which you can carefully build a model.\n\n### Toolsmithing GitLab CI: A working example as a shared CI library\n\nThe easiest way to differentiate between a “toolized” and “templated” solution is that you can simply and easily reuse this exact code without needing to change it. Many formal coding languages have the concept of shared libraries or dependencies that are essentially toolized. A templated solution consists of a starting point that you customize and then have to manage the code yourself. These can function as scaffolding for a starting point for an entire project or snippets of code that do a specific function. The fundamental difference is that when you use a template, you end up owning and managing the resultant code going forward.\n\nIn [GitLab CI](https://docs.gitlab.com/ee/ci/introduction/index.html), we can create our own tooling or dependencies with a few tricks stolen from [Auto DevOps](https://docs.gitlab.com/ee/topics/autodevops/). These tricks are:\n\n1. Use includes to reference the shared managed library code (this creates a “dependency” on code that is being managed outside of your own).\n2. The includable code must be written like a function where all needed inputs are either passed in, or can be collected from the environment. No hard coding is allowed because that means you’ve created a template which can’t be depended upon directly.\n3. Use GitLab CI ‘functions’. I am coining this term to indicate that in GitLab you can precede a job name with a “.” (dot) and it will not be executed when it is read. Then you can create a new job using all the code in the “dot named” job and add variables by using the `extends:` command keyword. By using dot named jobs in your includable code, the developer consuming the “managed shared CI dependency” can decide when, where, and how to call the toolized code.\n\n### The result: A code-counting GitLab CI extension\n\nHere are some of the final design attributes of this code counting solution:\n\n- Is extremely fast for the given task.\n- Leverages the Git clone opitimizations lessons contained in this article: [How much code do I have? A DevSecOps story](https://acloudguru.com/blog/engineering/how-much-code-do-i-have-a-devsecops-story).\n- Uses the [lightning fast, open source code counting tool SCC](https://github.com/boyter/scc) by Ben Boyter.\n- Is implemented as a reusable GitLab CI shared library extension.\n- Allows configuration of the file extensions that should not be checked out because they do not include source code to be counted.\n- Leverages the GitLab Run Pipeline forms capability.\n- Can enumerate and count an entire group hierarchy in GitLab, or be given a stipulated list.\n- Uses the runner token to access and read repositories by default, but can be given a specific token.\n- Uploads HTML and text artifacts that contain the code counting report.\n- Purposely emits the code counting results into CI logs for easy reference.\n\n### The output\n\nResults are shown below in the CI log but they are also captured as an HTML artifact.\n\nThe clone time is also in the log for each project so that it can be verified that the cloning optimizations are making a substantial difference.\n\nThese particular results are counting all the code in [https://gitlab.com/guided-explorations](https://gitlab.com/guided-explorations).\n\n![codecountingcilog](https://about.gitlab.com/images/blogimages/code-counting-in-gitlab/codecountingcilog.png)\n\n### The code\n\nThe code is available in this project: [https://gitlab.com/guided-explorations/code-metrics/ci-cd-extension-scc](https://gitlab.com/guided-explorations/code-metrics/ci-cd-extension-scc). You can view the scanning results in the job logs of past runs here: [https://gitlab.com/guided-explorations/code-metrics/ci-cd-extension-scc/-/pipelines](https://gitlab.com/guided-explorations/code-metrics/ci-cd-extension-scc/-/pipelines).\n\nRather than fail the entire job when a project fails to clone, the job simply logs the error from an attempted clone. This allows review of valid use cases for not being able to clone and obtains as complete of a picture as possible. The cloning error log is uploaded as a job artifact and emitted to the log.\n\n### Innovation: MR complexity metrics extension\n\nDuring a customer engagement I was asked whether there was a way to assess how much change a Merge Request contained and mark it. This was because an operations team was missing their SLAs for deployments due to the amount of change, and, therefore, risk and review could be highly variable. However, since there was no way to estimate this without human eyes on it, MRs with a high degree of change would overrun their SLA when they couldn’t be pre-triaged.\n\nI wondered if I could use the previously built code counting solution to count diffs and get a rough idea of how much change had occurred in the commits of an MR branch and then apply labels to MRs to give at rough idea of their degree of change as a sort of proxy for how much review time might be required.\n\nIt turned out to be plausible and you can review the [Shared Library in Git Diff Revision Activity Metrics CI EXTENSION](https://gitlab.com/guided-explorations/code-metrics/git-diff-revision-activity-metrics) and see the results in the MRs list of this working example project that uses that code: [MR list for Diff Revision Activity Analytics DEMO](https://gitlab.com/guided-explorations/code-metrics/diff-revision-activity-analytics-demo/-/merge_requests).\n\n### The value of remote work water cooler conversations\n\nI have to let you know why this blog was written now when this solution has been around for quite a while. You often hear about how working remotely does not allow for water cooler conversations, which in the story you’re told are where real innovation happens.\n\nWithin GitLab’s [Remote First culture](/company/culture/all-remote/guide/) it is expected that anyone in the company can schedule a “coffee chat” with anyone else. The cultural expectation is that this is normal and, unless you are getting an overwhelming number of these requests, that when asked, you will find time to socially connect.\n\nI received a coffee chat request from [Torsten Linz](https://gitlab.com/tlinz), the Senior Product Manager for the Source Code Management group, to chat about my comments and linking of a working example to an issue about code counting that he had become aware of. He also wanted to see if I could help get a copy of it working in his GitLab group.\n\nDuring that collaborative time, I discovered that my example was not working because of some major code changes in SCC and because it presumed the GitLab group to be enumerated did not need the counting job to authenticate to prove that it should have access to the projects. While we were collaborating, we fixed these problems and improved the solution to use the SCC binary, rather than depend on working Golang runtimes. After our collaborative session, as I tweaked some more, I did parameter documentation in README.md and debugged the ability to run it either with a group enumeration or a provided list of specific git repos.\n\nSo I owe big thanks to Torsten and to GitLab’s cultural support for remote first water cooler conversations for improving this working example to the point that it is worth sharing with a broader audience. If you’d like to know more, check out the GitLab handbook page: [Informal communication in an all-remote environment](/company/culture/all-remote/informal-communication/).\n\n_Cover image by [NOAA](https://unsplash.com/@noaa?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText) on [Unsplash](https://unsplash.com/s/photos/lightning-fast?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText)_\n",[741,720,9],{"slug":1070,"featured":6,"template":699},"code-counting-in-gitlab","content:en-us:blog:code-counting-in-gitlab.yml","Code Counting In Gitlab","en-us/blog/code-counting-in-gitlab.yml","en-us/blog/code-counting-in-gitlab",{"_path":1076,"_dir":246,"_draft":6,"_partial":6,"_locale":7,"seo":1077,"content":1083,"config":1091,"_id":1093,"_type":14,"title":1094,"_source":16,"_file":1095,"_stem":1096,"_extension":19},"/en-us/blog/continuously-improving-ci-lovability",{"title":1078,"description":1079,"ogTitle":1078,"ogDescription":1079,"noIndex":6,"ogImage":1080,"ogUrl":1081,"ogSiteName":686,"ogType":687,"canonicalUrls":1081,"schema":1082},"Continuously Improving CI to Lovable...again!","A transparent commentary on our Verify:Continuous Integration offering, covering how the landscape has changed and the product strategy that will carry us to Lovable.","https://res.cloudinary.com/about-gitlab-com/image/upload/v1749681907/Blog/Hero%20Images/CI-lovable.jpg","https://about.gitlab.com/blog/continuously-improving-ci-lovability","\n                        {\n        \"@context\": \"https://schema.org\",\n        \"@type\": \"Article\",\n        \"headline\": \"Continuously Improving CI to Lovable...again!\",\n        \"author\": [{\"@type\":\"Person\",\"name\":\"Jackie Porter\"}],\n        \"datePublished\": \"2021-02-22\",\n      }",{"title":1078,"description":1079,"authors":1084,"heroImage":1080,"date":1086,"body":1087,"category":1088,"tags":1089},[1085],"Jackie Porter","2021-02-22","\n\n{::options parse_block_html=\"true\" /}\n\n\n\n## What is it to be loved?\n\nAt GitLab, we use a [maturity](https://about.gitlab.com/direction/maturity/) rating to signal the readiness of a product category's capabilities for use by customers. The rating is evaluated by a scoring methodology called [Category Maturity Scorecard](https://about.gitlab.com/handbook/product/ux/category-maturity/category-maturity-scorecards/). In 2017, GitLab declared Continuous Integration \"Lovable\" after delivering significant feature functionality and becoming a [CI leader](https://about.gitlab.com/blog/gitlab-leader-continuous-integration-forrester-wave/). \n\nIn the [Verify Stage](/direction/ops/#verify), we have been able to maintain a lead against our competition by adding advanced feature functionality, relying on our [Single DevOps Platform](/solutions/devops-platform/), and executing on a strong vision. As the tides have been changing and as a practitioner of transparency, we [articulate when we change our mind](https://handbook.gitlab.com/handbook/values/#articulate-when-you-change-your-mind), especially when we learn new facts. \n\nIn a recent [opportunity canvas](https://about.gitlab.com/handbook/product/product-processes/#opportunity-canvas), we explored the adoption hurdles that prevent Source Code Management Enterprise users from adopting Verify functionality. We explored all potential features as candidates for adoption across Verify categories - CI, Testing, Pipeline Authoring, and Runner. \n\nIn this blog we will dive into they key takeways that help answer the question of what is it to be loved and how the expectations have shifted over time, and what the path to Lovable maturity looks like for Verify:Continuous Integration! \n\n## Key learnings from the research \n\nWe spent 6 weeks interviewing and collecting insights from across our customers to learn about their adoption journey with GitLab. These were the main learnings informing our approach. \n\n### GitLab is loved by developers \n\nAcross each organization, we learned that the experience for the developer was resoundingly positive and the leaders at these organizations appreciated this. The happiness of their engineering teams was and will continue to be extremely important to them, but the fact that GitLab was a significant contributing factor in developer job satisfaction was a reward on its own. \n\n### GitLab has some challenges with performance of minimal viable changes that are expected to work at a higher finish\n\nEnterprises are often larger, mature organizations shipping their own enterprise class solution. When faced with serving their needs, we learned they have a low tolerance for experimentation and a high expectation for polished features. This means a Premium or Ultimate tier offering needs to completely solve the problem and delight the user, not partially solve the problem. With our MVC and iterative methodology, we have historically shipped first and second iterations of features that may have solved the needs for individuals or small teams in small or medium-sized businesses. Although, with the expanding budgets as well as focus on developer productivity, organizations are looking for tools that will solve problems the best way possible, as comprehensively as possible. \n\n### GitLab's visibility into jobs at scale is painful, which also makes the management of diagnosing problems even more challenging \n\nWe learned a common question that organizations are unable to answer is around trends for passed and failed jobs. This is particularly relevant for projects that have over 100,000 pipelines and 300 jobs in single pipeline. Today in GitLab, there is no view where they can answer a question like how many times a particular job has failed; is it failing more often lately; does it fail at the same time each day; and other job behaviors of interest. Even filtering the job view is not possible today. So, for an organization that is looking to use GitLab at scale, it could be really challenging to easily triage and diagnose problems with their pipelines. \n\n## Path to Lovable \n\nIn the [Verify 1H Direction](/direction/ops/#verify), we identify a key piece of our strategy is to future-proof ourselves by investing in the core areas driving CI today. These top focuses include: \n\n1. [Artifacts](https://gitlab.com/groups/gitlab-org/-/epics/5125) - these are essential assets for your jobs and pipelines, getting these performant will help users take advantage of features like parent-child pipelines. \n1. [Variables](https://gitlab.com/groups/gitlab-org/-/epics/5124) - permissions and behaviors in pipelines are controlled by variables. Enabling these to work as designed will unlock the flexibility users have been looking for in their pipelines. \n\nBe sure to stay up to date on the [What's Next & Why Section](https://about.gitlab.com/direction/verify/continuous_integration/#whats-next--why) of Continuous Integration's Direction page, which will link to specific scheduled issues. \n\nBeyond this push for usability, in the 2H, we plan to tackle the challenges of visibility in diagnosing jobs via [gitlab&5022](https://gitlab.com/groups/gitlab-org/-/epics/5022) and piplelines activities in [gitlab&5071](https://gitlab.com/groups/gitlab-org/-/epics/5071), which you can see more information about in the [Maturity Plan](https://about.gitlab.com/direction/verify/continuous_integration/#maturity-plan) for Continuous Integration.\n","unfiltered",[9,1090,983],"UX",{"slug":1092,"featured":6,"template":699},"continuously-improving-ci-lovability","content:en-us:blog:continuously-improving-ci-lovability.yml","Continuously Improving Ci Lovability","en-us/blog/continuously-improving-ci-lovability.yml","en-us/blog/continuously-improving-ci-lovability",{"_path":1098,"_dir":246,"_draft":6,"_partial":6,"_locale":7,"seo":1099,"content":1105,"config":1111,"_id":1113,"_type":14,"title":1114,"_source":16,"_file":1115,"_stem":1116,"_extension":19},"/en-us/blog/craftsman-looks-at-continuous-integration",{"title":1100,"description":1101,"ogTitle":1100,"ogDescription":1101,"noIndex":6,"ogImage":1102,"ogUrl":1103,"ogSiteName":686,"ogType":687,"canonicalUrls":1103,"schema":1104},"A Craftsman looks at continuous integration","Guest author Steve Ropa shares his ideal continuous integration processes for catching errors early and shipping the best software possible.","https://res.cloudinary.com/about-gitlab-com/image/upload/v1749663786/Blog/Hero%20Images/craftsman-looks-at-continuous-integration.jpg","https://about.gitlab.com/blog/craftsman-looks-at-continuous-integration","\n                        {\n        \"@context\": \"https://schema.org\",\n        \"@type\": \"Article\",\n        \"headline\": \"A Craftsman looks at continuous integration\",\n        \"author\": [{\"@type\":\"Person\",\"name\":\"Steve Ropa\"}],\n        \"datePublished\": \"2018-01-17\",\n      }",{"title":1100,"description":1101,"authors":1106,"heroImage":1102,"date":1108,"body":1109,"category":849,"tags":1110},[1107],"Steve Ropa","2018-01-17","\nIf your version of continuous integration is just daily builds, ignoring failed tests, or not testing at all, you're doing it wrong.\n\n\u003C!-- more -->\n\n(I’d like to start with a thank you to Jimmy Buffett, as I listen to “A pirate looks at forty” while I write this post…)\n\nI visit a lot of different development teams, and at some point the subject of continuous integration comes up. Shortly after that comes the conversation around [continuous integration tools](/solutions/continuous-integration/). That usually gets kind of religious, but it is absolutely worth talking about. The conversation tends to follow a similar track. Usually it is something to the effect of “Well, we have a [CI server](https://about.gitlab.com/topics/ci-cd/continuous-integration-server/), and we use it for our daily builds, but that’s about it. Then when I ask what else they want to get from their CI tools, I usually get either a blank stare or a long story about what would be awesome if they:\n\n- Had more time.\n- Had someone whose entire job was to manage the continuous integration environment.\n- Were at a different company.\n\nSo this got me thinking, as a [Craftsman](http://manifesto.softwarecraftsmanship.org/), how do I feel about this, and how do I feel about continuous integration in the first place? I came up with a few thoughts.\n\n## Daily builds are NOT continuous integration\n\nIt is often said in Extreme Programming circles that Daily Builds are for Wimps. I don’t know if I’d go that far, but I will say that daily builds are nowhere near frequent enough. One of the primary values we gain from CI is the ability to have very short feedback loops. If I only find out once a day whether or not the work I did fits with the rest of the code base, then I am adding at least eight hours of latency to my system. In most cases, more than that, since “daily builds” are actually nightly builds, leaving us open to walking in each morning to an ugly mess. And don’t even get me started on weekends!\n\nIt’s much easier to find and fix a small changeset, say a few lines and a couple tests, than to have to go back through an entire day’s work to find the error or errors. And this problem is compounded if you have a larger team, or worse, a couple of teams using the same code base.  Now you have many levers to pull and dials to turn before you can find what really broke.\n\nOr yet worse, you will have committed a day’s worth of work and then found out the next day that someone else committed something that completely nullified your work.\n\n## A continuous integration process is more than just the build\n\nI actually am surprised that in this day and age there are teams that believe their job is done if the code compiles, without running any tests. Every continuous integration tool out there provides for automated unit testing at a minimum, usually far more than just that. A good CI process includes continuous Unit Test, and ideally continuous Acceptance Test as well.\n\nI know, you are thinking “Here we go again, another push for test-driven development and automated acceptance tests.” You’re right. Any true Craftsman uses all the tools and modern techniques available to them. That means unit tests that run every time you check in. And automated acceptance tests that run after that. A good CI process includes all of the appropriate elements.\n\nIt’s not enough to just push to your GitLab repo and have the server run a build. The days of “If it compiles it ships” are long gone. And that’s a good thing. We have a responsibility to be better than that, and modern tools and techniques make that possible. I would much prefer a CI process that includes:\n\n- Kickoff on every check-in. This is especially easy with modern repos like GitLab that provide hooks and integrations with all of the major Continuous Integration tools.\n- Build *all* of the code and libraries.\n- Run *all* of the unit and acceptance tests.\n- Report on any errors, preferably failing the build if they occur.\n\nWe can talk about going further into continuous delivery and deployment later, but those can be managed in the same way.\n\n## Listen to your continuous integration server\n\nThe proper response to a notification from your CI environment that the build or one of the tests failed is not to find the offending test and #ignore it. A failed build notification means “Everybody stop! The build is failing so our environment is technologically unsafe until this gets fixed!” Take that message seriously. Everybody stop. Find out what went wrong, and fix it. And only then do you continue with business as usual.\n\nIf we as Craftsmen see our job as to create the best software possible for our customers, we need to take these things seriously. We can either have a “CI Server” that does daily or even less frequent builds, and check the box, or we can be serious about what we do. We have a lot of tools available, and if we choose the best tools we need to use them to their fullest extent.  That is how we become truly great at our Craft.\n\n## About the guest author\n\nSteve Ropa is a Co-founder and Master Craftsman at the Rocky Mountain Programmers Guild in Denver Colorado, where he brings his long career of successful software delivery to bring developers and teams to new levels of performance and Craftsmanship.\n\nCover image by [chuttersnap](https://unsplash.com/photos/tUSN3PNeX1U?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText) on [Unsplash](https://unsplash.com/search/photos/coil?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText)\n{: .note}\n",[9],{"slug":1112,"featured":6,"template":699},"craftsman-looks-at-continuous-integration","content:en-us:blog:craftsman-looks-at-continuous-integration.yml","Craftsman Looks At Continuous Integration","en-us/blog/craftsman-looks-at-continuous-integration.yml","en-us/blog/craftsman-looks-at-continuous-integration",{"_path":1118,"_dir":246,"_draft":6,"_partial":6,"_locale":7,"seo":1119,"content":1125,"config":1134,"_id":1136,"_type":14,"title":1137,"_source":16,"_file":1138,"_stem":1139,"_extension":19},"/en-us/blog/custom-actions-rasa-gitlab-devops",{"title":1120,"description":1121,"ogTitle":1120,"ogDescription":1121,"noIndex":6,"ogImage":1122,"ogUrl":1123,"ogSiteName":686,"ogType":687,"canonicalUrls":1123,"schema":1124},"Creating custom action containers for Rasa X with GitLab","Using the GitLab DevOps Platform together with Rasa X can make it easier for stakeholders to deliver a virtual assistant by automating potentially time-consuming, error-prone steps. In this case, we’ve shown how you can build Rasa custom action servers and deploy them to Kubernetes.","https://res.cloudinary.com/about-gitlab-com/image/upload/v1749668410/Blog/Hero%20Images/vablog.jpg","https://about.gitlab.com/blog/custom-actions-rasa-gitlab-devops","\n                        {\n        \"@context\": \"https://schema.org\",\n        \"@type\": \"Article\",\n        \"headline\": \"Create and Deploy Custom Actions Containers to Rasa X using Gitlab DevOps Platform\",\n        \"author\": [{\"@type\":\"Person\",\"name\":\"William Arias\"}],\n        \"datePublished\": \"2021-04-06\",\n      }",{"title":1126,"description":1121,"authors":1127,"heroImage":1122,"date":1129,"body":1130,"category":1131,"tags":1132},"Create and Deploy Custom Actions Containers to Rasa X using Gitlab DevOps Platform",[1128],"William Arias","2021-04-06","**This blog post was a collaboration between William Arias, from Gitlab, and\nVincent D. Warmerdam, from Rasa. You can find the same blog post on [Rasa's\nblog](https://blog.rasa.com/create-and-deploy-custom-actions-containers-to-rasa-x-using-gitlab-devops-platform/)**.  \n\n\n## Create and Deploy Custom Actions Containers to Rasa X using Gitlab DevOps\nPlatform\n\nVirtual assistants do more than just carry on conversations. They can send\nemails, make updates to a calendar, or call an API endpoint. Essentially,\nthey can do actions that add significant value and convenience to the user\nexperience.\n\nIn assistants built with Rasa*, this type of functionality is executed by\ncustom code called custom actions. As with any code you run in production,\nyou’ll need to think about how you want to deploy updates to custom actions.\nIn this blog post, we’ll show you how to set up GitLab to deploy custom\naction Docker containers to your Kubernetes cluster. If we follow [good\nDevOps practices](/stages-devops-lifecycle/) we can greatly speed up the\ndevelopment and quality of our  virtual assistants.\n\n* Rasa Open Source is a machine learning framework for building text and\nvoice-based virtual assistants. It provides infrastructure for understanding\nmessages, holding conversations, and connecting to many messaging channels\nand APIs. Rasa X is a toolset that runs on top of Rasa Open Source,\nextending its capabilities. Rasa X includes key features for sharing the\nassistant with test users, reviewing and annotating conversation data, and\ndeploying the assistant. [Learn more about Rasa.](https://rasa.com/docs/)\n\n\n## Deployment high-level overview\n\nThe typical workflow for deploying a new version of custom actions is\noutlined below.  \n\n![actions-process](https://about.gitlab.com/images/blogimages/actions-process.png){:\n.shadow}\n\n\nEvery change to your custom actions code will require a new container image\nto be built and pulled by Rasa X. Gitlab CI/CD can save you from doing a lot\nof manual work and automate steps like the ones described in the workflow\nabove. Let's see how to do it.  \n\n\n## Using Rasa with Gitlab DevOps Platform\n\nLet's create a pipeline that will automate manual steps.\n\n\n---\n\n**NOTE**\n\nThis article assumes you have your [Gitlab\nProject](https://gitlab.com/warias/gl-commit-2020) with your customs Actions\nCode created along with a [Google Kubernetes\nCluster](https://cloud.google.com/kubernetes-engine/docs/quickstart).\n\n\n---\n\n\nIf you are a Gitlab user you are probably familiar with .gitlab-ci.yml file\nand its CI/CD capabilities. Every time you commit a change to your customs\nactions code you want Gitlab to run a script that will build and update your\ndocker containers. \n\n![actions-process-2](https://about.gitlab.com/images/blogimages/process2.png){:\n.shadow}\n\n\nLet's breakdown the CI/CD pipeline by describing the gitlab-ci.yml file so\nyou can use it and customize it to your needs\n\n## Variables\n\nWe make use of environment variables created in Gitlab at the moment of\nrunning the Jobs to define our actions Docker image  \n\n\n```\n\nvariables:\n    ACTIONS_CONTAINER_IMAGE: $CI_REGISTRY_IMAGE/$CI_COMMIT_REF_SLUG\n    TAG: $CI_COMMIT_SHA\n    K8S_SECRET: secret-gitlab-registry\n\n```\n\n\nThe snippet above does the following:\n\n- It defines the name of the Docker Image for custom actions using\nenvironment variables ```$CI_REGISTRY_IMAGE/$CI_COMMIT_REF_SLUG.``` This\nwill make the name of the Docker image different for every commit\n\n- It creates a secret used to pull the Rasa Action Image from the Gitlab\nPrivate Registry to the Google Kubernetes Cluster. \n\n\n## Stages\n\nWe have two main stages in our pipeline, build and deploy:\n\n```\n\nstages:\n  - build\n  - deploy \n```\n\nEvery time there is a new commit with changes to our custom actions code, or\nwhen we decide to run the CI/CD Pipeline it will:\n\n- Build: Here, we automate the building of the Docker image using the\nvariables defined above, and the Dockerfile. We also tag the image and push\nit to the GitLab container registry.\n\n- Deploy: Here we log-in to Kubernetes Engine on Google Cloud and deploy the\nnewly created Actions image to Rasa X.\n\nLet's see it in more detail:  \n\n\n**Build**:\n\n```\n\nbuild-actions-image:\n image: docker:19.03.1\n services:\n   - docker:dind\n stage: build\n script:\n   - docker login -u ```$CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY```\n   - docker build -t $ACTIONS_CONTAINER_IMAGE:$TAG -f Dockerfile .\n   - docker push $ACTIONS_CONTAINER_IMAGE:$TAG\n```\n\nThe job build-actions-image executed on the build stage takes advantage of\nthe CI/CD variables that are part of the environment where the pipelines\nrun. It automates the usage of Docker commands to build the Actions image by\nreading its corresponding Dockerfile. The output of this stage is a new\nCustom Actions image per every commit with code changes.  \n\n\n**Deploy**:\n\n```\n\ndeploy-custom-action-x:\n  stage: deploy\n  image: crileroro/gcloud-kubectl-helm\n  variables:\n    GCP_PROJECT: gke-project-302411\n    GCP_REGION: europe-west1\n    CLUSTER_NAME: gke-python-demo\n    NAMESPACE_RASA: rasa-environment \n  before_script:\n    - gcloud auth activate-service-account --key-file $SERVICE_ACCOUNT_GCP\n    - gcloud config set project $GCP_PROJECT\n    - gcloud config set compute/region $GCP_REGION\n    - gcloud container clusters get-credentials $CLUSTER_NAME\n  script:\n    - kubectl create ns $NAMESPACE_RASA --dry-run=client -o yaml | kubectl apply -f -\n    - kubectl create secret docker-registry $K8S_SECRET\n              --docker-server=$CI_REGISTRY\n              --docker-username=$CI_DEPLOY_USER\n              --docker-password=$CI_DEPLOY_PASSWORD\n              --namespace $NAMESPACE_RASA\n              -o yaml --dry-run=client | kubectl apply -f -\n    - helm repo add rasa-x https://rasahq.github.io/rasa-x-helm\n    - helm upgrade -i --reuse-values \n                      --namespace $NAMESPACE_RASA\n                      --set app.name=$ACTIONS_CONTAINER_IMAGE\n                      --set app.tag=$TAG \n                      --set images.imagePullSecrets[0].name=$K8S_SECRET rasa-x rasa-x/rasa-x\n```\n\n\nNotice the variables in ```before_script```, these ones are needed to\nauthenticate to GCP where we have our Kubernetes cluster. This step is\noptional and could be skipped in cases where you have [Gitlab\npre-integrated](https://docs.gitlab.com/ee/user/project/clusters/add_remove_clusters.html)\nwith your Kubernetes cluster running on Google Cloud.  \n\n\nThe main and most interesting part of the script is:  \n\n```\n\nscript:\n    - kubectl create ns $NAMESPACE_RASA --dry-run=client -o yaml | kubectl apply -f -\n    - kubectl create secret docker-registry $K8S_SECRET\n              --docker-server=$CI_REGISTRY\n              --docker-username=$CI_DEPLOY_USER\n              --docker-password=$CI_DEPLOY_PASSWORD\n              --namespace $NAMESPACE_RASA\n              -o yaml --dry-run=client | kubectl apply -f -\n    - helm repo add rasa-x https://rasahq.github.io/rasa-x-helm\n    - helm upgrade -i --reuse-values \n                      --namespace $NAMESPACE_RASA\n                      --set app.name=$ACTIONS_CONTAINER_IMAGE\n                      --set app.tag=$TAG \n                      --set images.imagePullSecrets[0].name=$K8S_SECRET rasa-x rasa-x/rasa-x\n\n```\n\n\nWe start by creating the *namespace* for our custom actions code, and if it\nalready exists, then we proceed to apply Kubernetes commands using kubectl\nand helm.  \n\n```\n\nhelm repo add rasa-x https://rasahq.github.io/rasa-x-helm\n    - helm upgrade -i --reuse-values \n                      --namespace $NAMESPACE_RASA\n                      --set app.name=$ACTIONS_CONTAINER_IMAGE\n                      --set app.tag=$TAG \n                      --set images.imagePullSecrets[0].name=$K8S_SECRET rasa-x rasa-x/rasa-x\n```\n\nThe snippet above adds a rasa-x Helm chart and upgrades or changes the\nvalues corresponding to the new **Custom Action Image** by assigning to it\nthe ```$ACTIONS_CONTAINER_IMAGE``` created in the build stage.\n\nNote that the pipeline described above focuses only on creating and\ndeploying the ACTIONS_CONTAINER_IMAGE. It could be extended by adding more\nstages, for example, code quality, security testing, and unit testing among\nothers.  \n\n\n## Summary\n\nUsing the GitLab DevOps Platform together with Rasa X can make it easier for\nstakeholders to deliver a virtual assistant by automating potentially\ntime-consuming, error-prone steps. In this case, we’ve shown how you can\nbuild Rasa custom action servers and deploy them to Kubernetes.\n\nPushing new custom action containers to Kubernetes only scratches the\nsurface of what you can automate with GitLab. You could also add steps for\ncode quality, security audits and unit tests. The main goal is to automate\nthe manual parts of deployment so that you can focus on what is important.\nIn the case of Rasa X, that means that more time can be spent learning from\nyour users and making a better assistant in the process.\n\n\nDo you want to learn more? Watch this video of Gitlab DevOps Platform and\nRasa [Deploy your Rasa Chatbots like a boss with\nDevOps](https://youtu.be/ko9-zPDuhQo)\n\n\nHappy hacking!\n\n\nCover image by [Eric\nKrull](https://unsplash.com/@ekrull?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText)\non [Unsplash](https://unsplash.com)\n\n{: .note}\n","devsecops",[9,1133,741],"cloud native",{"slug":1135,"featured":6,"template":699},"custom-actions-rasa-gitlab-devops","content:en-us:blog:custom-actions-rasa-gitlab-devops.yml","Custom Actions Rasa Gitlab Devops","en-us/blog/custom-actions-rasa-gitlab-devops.yml","en-us/blog/custom-actions-rasa-gitlab-devops",{"_path":1141,"_dir":246,"_draft":6,"_partial":6,"_locale":7,"seo":1142,"content":1148,"config":1154,"_id":1156,"_type":14,"title":1157,"_source":16,"_file":1158,"_stem":1159,"_extension":19},"/en-us/blog/dag-manual-fix",{"title":1143,"description":1144,"ogTitle":1143,"ogDescription":1144,"noIndex":6,"ogImage":1145,"ogUrl":1146,"ogSiteName":686,"ogType":687,"canonicalUrls":1146,"schema":1147},"How to use manual jobs with `needs:` relationships","Are you using manual jobs and needs relationship in your CI/CD pipeline? Learn more about the fix that might cause your pipeline to behave differently.","https://res.cloudinary.com/about-gitlab-com/image/upload/v1749683170/Blog/Hero%20Images/blog_cover2.png","https://about.gitlab.com/blog/dag-manual-fix","\n                        {\n        \"@context\": \"https://schema.org\",\n        \"@type\": \"Article\",\n        \"headline\": \"How to use manual jobs with `needs:` relationships\",\n        \"author\": [{\"@type\":\"Person\",\"name\":\"Dov Hershkovitch\"}],\n        \"datePublished\": \"2021-05-20\",\n      }",{"title":1143,"description":1144,"authors":1149,"heroImage":1145,"date":1151,"body":1152,"category":717,"tags":1153},[1150],"Dov Hershkovitch","2021-05-20","## A bug when job `needs` a manual job\n\n\nIn [13.12 we fixed a\nbug](https://gitlab.com/gitlab-org/gitlab/-/issues/31264) that might affect\nthe existing behavior of your pipeline. We explain why we had to fix the\nbug, the possible impact of this change on your pipeline, and the proposed\nworkaround if you would like to revert this behavior.\n\n\n## Background on a two-job pipeline\n\n\nIn GitLab CI/CD you can easily configure a job to require manual\nintervention before it runs. The job gets added to the pipeline, but doesn't\nrun until you click the **play** button on it.\n\n\nLet's look at a two-job pipeline:\n\n\n```yaml\n\nstages:\n  - stage1\n  - stage2\n\njob1:\n  stage: stage1\n  script:\n    - echo \"this is an automatic job\"\n\nmanual_job:\n  stage: stage2\n  script:\n    - echo \"This is a manual job which doesn't start automatically, and the pipeline can complete without it starting.\"\n  when: manual # This setting turns a job into a manual one\n```\n\n\nThis is how it looks when we look at the pipeline graph:\n\n\n![image2](https://about.gitlab.com/images/blogimages/11-05-2021-when-job-needs-manual/blog1.png){:\n.shadow.medium.center.wrap-text}\n\n\nNotice that the manual job gets skipped, and the pipeline completes\nsuccessfully even though the manual job did not get triggered. This happens\nbecause manual jobs are considered optional, and do not need to run.\n\n\nInternally, manual jobs have `allow_failure` set to true by default, which\nmeans that these skipped manual jobs do not cause a pipeline failure. The\nYAML code below demonstrates how to write the manual job, which results in\nthe same behavior. The job doesn't automatically start, is skipped, and the\npipeline passes.\n\n\n```yaml\n\nmanual_job:\n  stage: stage2\n  script:\n    - echo \"This is a manual job which doesn't start automatically, and the pipeline can complete without it starting.\"\n  when: manual\n  allow_failure: true # this line is redundant since manual job has this setting by default\n```\n\n\nYou can set `allow_failure` to true for any job, including both manual and\nautomatic jobs, and then the pipeline does not care if the job runs\nsuccessfully or not.\n\n\n### How to expand the configuration with `needs` (DAG)\n\n  Last year we introduced the [`needs` keyword which lets you create a Directed Acyclic Graphs (DAG) to speed up your pipeline](https://docs.gitlab.com/ee/ci/yaml/#needs). The `needs` keyword creates a dependency between two jobs regardless of their stage.\n\nLet's look at this example:\n\n\n```yaml\n\nstages:\n  - stage1\n  ....\n  - stage10\n\njob1: # this is the first job that runs in the pipeline\n  stage: stage1\n  script:\n    - echo \"exit 0\"\n.....\n\n\njob10:\n  needs:  # Defined a \"needs\" relationship with job1\n    - job1\n  stage: stage10\n  script:\n    - echo \"This job runs as soon as job1 completes, even though this job is in stage10.\"\n```\n\n\nThe `needs` keyword creates a dependency between the two jobs, so `job10`\nruns as soon as `job1` **finishes running** successfully, regardless of the\nstage ordering.\n\n\nSo what happens if a job `needs` a manual job, that doesn't start running\nautomatically?\n\n\nLet's look at the following example:\n\n\n```yaml\n\nstages:\n  - build\n  - test\n  - deploy\n\nbuild:\n  stage: build\n  script: exit 0\n\ntest:\n  stage: test\n  when: manual\n  script: exit 0\n\ndeploy:\n  stage: deploy\n  script: echo \"when should this job run?\"\n  needs:\n    - test\n```\n\n\nBefore 13.12, this type of configuration would cause the pipeline to get\nstuck. The `deploy` job can only start when the `test` job completes, but\nthe `test` job does not start automatically. The rest of the pipeline stops\nand waits for someone to run the manual `test` job.\n\n\n![image3](https://about.gitlab.com/images/blogimages/11-05-2021-when-job-needs-manual/blog2.png){:\n.shadow.medium.center.wrap-text}\n\n\nThis behavior is even worse with larger pipelines:\n\n\n![image4](https://about.gitlab.com/images/blogimages/11-05-2021-when-job-needs-manual/blog3.png){:\n.shadow.medium.center.wrap-text}\n\n\nThe example above shows there is a needs relationship between `post test`\njob and the `test` job (which is a manual job) as you can see the pipeline\nis stuck in a running state and any subsequent jobs will not run.\n\n\nThis was not the behavior most users expected, so we improved it in 13.12.\nNow, if there is a `needs` relationship pointing to a manual job, the\npipeline doesn't stop by default anymore. The manual job is considered\noptional by default in all cases now. Any jobs that have a `needs`\nrelationship to manual jobs are now also considered optional and skipped if\nthe manual job isn't triggered. If you start the manual job, the jobs that\nneed it can start after it completes.\n\n\nNote that if you start the manual job before a later job that has it in a\n`needs` configuration, the later job will still wait for the manual job to\nfinishes running.\n\n\n## What if I don't want this new behavior?\n\n\nOne of the reasons we selected this solution is that you can quickly revert\nthis change. If you made use of this inadvertent behavior and configured\nyour pipelines to use it to block on manual jobs, it's easy to return to\nthat previous behavior. All you have to do is override the default\n`allow_failure` in the manual job with `allow_failure: false`. This way the\nmanual job is no longer optional, and the pipeline status will be marked as\nblocked and wait for you to run the job manually.\n\n\n```yaml\n\nstages:\n  - build\n  - test\n  - deploy\n\nbuild:\n  stage: build\n  script: exit 0\n\ntest:\n  stage: test\n  when: manual\n  allow_failure: false  # Set to false to return to the previous behavior.\n  script: exit 0\n\ndeploy:\n  stage: deploy\n  script: exit 0\n  needs:\n    - test\n```\n\n\nShare any thoughts, comments, or questions, by opening an issue in GitLab\nand mentioning me (`@dhershkovitch`).\n",[9,808,741],{"slug":1155,"featured":6,"template":699},"dag-manual-fix","content:en-us:blog:dag-manual-fix.yml","Dag Manual Fix","en-us/blog/dag-manual-fix.yml","en-us/blog/dag-manual-fix",{"_path":1161,"_dir":246,"_draft":6,"_partial":6,"_locale":7,"seo":1162,"content":1168,"config":1175,"_id":1177,"_type":14,"title":1178,"_source":16,"_file":1179,"_stem":1180,"_extension":19},"/en-us/blog/demystifying-ci-cd-variables",{"title":1163,"description":1164,"ogTitle":1163,"ogDescription":1164,"noIndex":6,"ogImage":1165,"ogUrl":1166,"ogSiteName":686,"ogType":687,"canonicalUrls":1166,"schema":1167},"GitLab environment variables demystified","CI/CD variables are useful (and flexible) tools to control jobs and pipelines. We unpack everything you need to know about GitLab environment variables.","https://res.cloudinary.com/about-gitlab-com/image/upload/v1749664679/Blog/Hero%20Images/blog-image-template-1800x945__24_.png","https://about.gitlab.com/blog/demystifying-ci-cd-variables","\n                        {\n        \"@context\": \"https://schema.org\",\n        \"@type\": \"Article\",\n        \"headline\": \"GitLab environment variables demystified\",\n        \"author\": [{\"@type\":\"Person\",\"name\":\"Veethika Mishra\"}],\n        \"datePublished\": \"2021-04-09\",\n      }",{"title":1163,"description":1164,"authors":1169,"heroImage":1165,"date":1171,"body":1172,"category":717,"tags":1173,"updatedDate":1174},[1170],"Veethika Mishra","2021-04-09","There is a lot of flexibility when it comes to defining and using variables\nfor [CI/CD](https://about.gitlab.com/topics/ci-cd/). Variables are extremely\nuseful for controlling jobs and pipelines, and they help you avoid\nhard-coding values in your `.gitlab-ci.yml` configuration file. The\ninformation in this post should weave a larger picture by bringing together\nall (or most) of the information around defining and handling variables,\nmaking it easier to understand the scope and capabilities. Relevant\ndocumentation is linked throughout the post.\n\n\nIn [GitLab CI/CD](https://docs.gitlab.com/ee/ci/), variables can be used to\ncustomize jobs by defining and storing values. When using variables there is\nno need to hard code values. In GitLab, CI/CD variables can be defined by\ngoing to **Settings >> CI/CD >> Variables**, or by simply defining them in\nthe `.gitlab-ci.yml` file.\n\n\nVariables are useful for configuring third-party services for different\ndeployment environments, such as `testing`, `staging`, `production`, etc.\nModify the services attached to those environments by simply changing the\nvariable that points to the API endpoint the services need to use. Also use\nvariables to configure jobs and then make them available as environment\nvariables within the jobs when they run.\n\n\n![GitLab reads the .gitlab-ci.yml file to scan the referenced variable and\nsends the information to the GitLab Runner. The variables are exposed on and\noutput by the\nrunner.](https://about.gitlab.com/images/blogimages/demystifying-ci-cd-variables/variables_processing.jpeg)\n\n\n## The relationship between variables and environments\n\n\nSoftware development as a process includes stages to test a product before\nrolling it out to users.\n[Environments](https://docs.gitlab.com/ee/ci/environments/) are used to\ndefine what those stages look like and it may differ between teams and\norganizations.\n\n\nOn the other hand, variables are data values that are likely to change as a\nresult of user interaction with a product. For example, their age,\npreference, or any input you could possibly think of that might determine\ntheir next step in the product task-flow.\n\n\nWe often hear the term [environment\nvariable](https://docs.gitlab.com/ee/administration/environment_variables.html).\nThese are variables that are defined in a given environment, but outside the\napplication. GitLab CI/CD variables provide developers with the ability to\nconfigure values in their code. Using variables is helpful because it\nensures that the code is flexible. GitLab CI/CD variables allow users to\nmodify an application deployed to a certain environment without making any\nchange to code. It is simple to run tests or even integrate third-party\nservices by changing a configuration environment variable outside the\napplication.\n\n\n## The scope of variables for CI/CD\n\n\n![Order of precedence for CI/CD variables: 1) Manual pipeline run, trigger\nand schedule pipeline variables, 2) Project level, group level, instance\nlevel protected variables, 3) Inherited CI/CD variables, 4) Job level,\nglobal yml defined variables, 5) Deployment variables, 6) Pre-defined CI/CD\nvariables](https://about.gitlab.com/images/blogimages/demystifying-ci-cd-variables/variables_precedence.jpeg)\n\n\n### `.gitlab-ci.yml` defined variables\n\n\nVariables that need to be available in the job environment can be added to\nGitLab. These CI/CD variables are meant to store non-sensitive project\nconfiguration, like the database URL in the `.gitlab-ci.yml` file. Reuse\nthis variable in multiple jobs or scripts, wherever the value is needed. If\nthe value changes, you only need to update the variable once, and the change\nis reflected everywhere the variable is used.\n\n\n### Project CI/CD variables\n\n\nMoving a step above the repository-specific requirements, you can define\nCI/CD variables in [project\nsettings](https://docs.gitlab.com/ee/ci/variables/#for-a-project), which\nmakes them available to CI/CD pipelines. These are stored out of the\nrepository (not in the `.gitlab-ci.yml` file), but are still available to\nuse in the CI/CD configuration and scripts. Storing the variables outside\nthe `.gitlab-ci.yml` file keeps these values limited to a project-only\nscope, and not saved in plain text in the project.\n\n\n### Group and instance CI/CD variables\n\n\nSome variables are relevant at the group level, or even instance level, and\ncould be useful to all projects in a group or instance. Define the variables\nin the [group or instance\nsettings](https://docs.gitlab.com/ee/ci/variables/#for-a-group) so all\nprojects within those scopes can use the variables without actually needing\nto know the value  or having to create the variables for the lower scope.\nFor example, a common value that needs to be updated in multiple projects\ncan be easily managed if it stays up-to-date in a single place.\nAlternatively, multiple projects could use a specific password without\nactually needing to know the value of the password itself.\n\n\n## Jobs and pipelines as environments\n\n\nGitLab CI/CD variables, besides being used as environment variables, also\nwork in the scope of the `.gitlab-ci.yml` configuration file to configure\npipeline behavior, unrelated to any environment. The variables can be stored\nin the project/group/instance settings and be made available to jobs in\npipelines.\n\n\nFor example:\n\n\n```  \n\njob:  \n  rules:  \n    - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH  \n  script:  \n  - echo \"This job ran on the $CI_COMMIT_BRANCH branch.\"  \n```\n\n\nThe variable `($CI_COMMIT_BRANCH)` in the script section runs in the scope\nof the job in which it was defined. This scope is the \"job environment\" –\nmeaning, when the job starts, the GitLab runner starts up a Docker container\nand runs the job in that environment. The runner will make that variable\n(and all other predefined or custom variables) available to the job, and it\ncan display their value in the log output if needed.\n\n\nBut the variable is **also** used in the `if:` section to determine when the\njob should run. That in itself is not an environment, which is why we call\nthese CI/CD variables. They can be used to dynamically configure your CI/CD\njobs, **as well** as be used as environment variables when the job is\nrunning.\n\n\n## Predefined variables\n\n\nA number of variables are\n[predefined](https://docs.gitlab.com/ee/ci/variables/predefined_variables.html)\nwhen a GitLab CI/CD pipeline starts. A user can immediately access values\nfor things like commit, project, or pipeline details without needing to\ndefine the variables themselves.\n\n\n## Custom CI/CD variables\n\n\n![Runners can create two kinds of custom CI/CD variables: Type and\nFile.](https://about.gitlab.com/images/blogimages/demystifying-ci-cd-variables/variable_types.jpeg)\n\n\nWhen creating a CI/CD variable in the settings, GitLab gives the user more\nconfiguration options for the variable. Use these extra configuration\noptions for stricter control over more sensitive variables:\n\n\n**Environment scope:** If a variable only ever needs to be used in one\nspecific environment, set it to only ever be available in that environment.\nFor example, you can set a deploy token to only be available in the\n`production` environment.\n\n\n**Protected variables:** Similar to the environment scope, you can set a\nvariable to be available only when the pipeline runs on a protected branch,\nlike your default branch.\n\n\n**Variable type:** A few applications require configuration to be passed to\nit in the form of a file. If a user has an application that requires this\nconfiguration, just set the type of variable as a \"File\". Configuring the\nCI/CD variable this way means that when the runner makes the variable\navailable in the environment, it actually writes it out to a temporary file,\nand stores the path to the file as the value. Next, a user can pass the path\nto the file to any applications that need it.\n\n\nAlong with the listed ways of defining and using variables, GitLab\nintroduced a feature that generates pre-filled variables when there's a need\nto run a pipeline manually. Prefilled variables reduce the chances of\nrunning into an error and makes running the pipeline easier.\n\n\n**Masked variables:** [Masked\nvariables](https://docs.gitlab.com/ee/ci/variables/#mask-a-cicd-variable)\nare CI variables that have been **hidden in job logs** to prevent the\nvariable’s value from being displayed. \n\n\n**Masked and hidden variables:** Introduced in [GitLab\n17.4](https://about.gitlab.com/releases/2024/09/19/gitlab-17-4-released/#hide-cicd-variable-values-in-the-ui),\n[Masked and\nhidden](https://docs.gitlab.com/ee/ci/variables/#hide-a-cicd-variable)\nvariables provide the same masking feature from job logs and **keep the\nvalue hidden** **in the Settings UI**. We do not recommend using either of\nthese variables for sensitive data (e.g. secrets) as they can be\ninadvertently exposed. \n\n\n## Secrets\n\n\nA secret is a sensitive credential that should be kept confidential.\nExamples of a secret include:\n\n\n* Passwords  \n\n* SSH keys  \n\n* Access tokens  \n\n* Any other types of credentials where exposure would be harmful to an\norganization\n\n\nGitLab currently enables its users to [use external secrets in\nCI](https://docs.gitlab.com/ee/ci/secrets/), by leveraging HashiCorp Vault,\nGoogle Cloud Secret Manager, and Azure Key Vault to securely manage keys,\ntokens, and other secrets at the project level. This allows users to\nseparate these secrets from other CI/CD variables for security reasons.\n\n\n### GitLab Secrets Manager\n\n\nBesides providing support for external secrets in CI, GitLab is also working\non introducing a [native solution to secrets\nmanagement](https://gitlab.com/groups/gitlab-org/-/epics/10108) to securely\nand conveniently store secrets within GitLab. This solution will also help\ncustomers use the stored secrets in GitLab specific components and\nenvironments, and easily manage access at namespace groups and projects\nlevel. \n\n\n## Read more\n\n* [GitLab native secrets manager to give software supply chain security a\nboost](https://about.gitlab.com/blog/gitlab-native-secrets-manager-to-give-software-supply-chain-security-a-boost/)\n\n\n***Disclaimer:** This blog contains information related to upcoming\nproducts, features, and functionality. It is important to note that the\ninformation in this blog post is for informational purposes only. Please do\nnot rely on this information for purchasing or planning purposes. As with\nall projects, the items mentioned in this blog and linked pages are subject\nto change or delay. The development, release, and timing of any products,\nfeatures, or functionality remain at the sole discretion of GitLab.*\n",[696,983,915,9,109,742],"2025-01-13",{"slug":1176,"featured":6,"template":699},"demystifying-ci-cd-variables","content:en-us:blog:demystifying-ci-cd-variables.yml","Demystifying Ci Cd Variables","en-us/blog/demystifying-ci-cd-variables.yml","en-us/blog/demystifying-ci-cd-variables",{"_path":1182,"_dir":246,"_draft":6,"_partial":6,"_locale":7,"seo":1183,"content":1189,"config":1195,"_id":1197,"_type":14,"title":1198,"_source":16,"_file":1199,"_stem":1200,"_extension":19},"/en-us/blog/deploy-aws",{"title":1184,"description":1185,"ogTitle":1184,"ogDescription":1185,"noIndex":6,"ogImage":1186,"ogUrl":1187,"ogSiteName":686,"ogType":687,"canonicalUrls":1187,"schema":1188},"How to deploy to AWS with GitLab","We believe deploying to the cloud should be easy and boring. The deployment process is the same regardless of what tech stack you're using so why not automate it?","https://res.cloudinary.com/about-gitlab-com/image/upload/v1749672124/Blog/Hero%20Images/aws_rocket.jpg","https://about.gitlab.com/blog/deploy-aws","\n                        {\n        \"@context\": \"https://schema.org\",\n        \"@type\": \"Article\",\n        \"headline\": \"How to deploy to AWS with GitLab\",\n        \"author\": [{\"@type\":\"Person\",\"name\":\"Orit Golowinski\"}],\n        \"datePublished\": \"2020-12-15\",\n      }",{"title":1184,"description":1185,"authors":1190,"heroImage":1186,"date":1192,"body":1193,"category":717,"tags":1194},[1191],"Orit Golowinski","2020-12-15","\nCloud computing services are replacing traditional hardware technologies at an extremely fast pace. The majority of businesses worldwide are already moving their applications to the cloud — both public and private cloud — or plan to in the near future. Over a short period of time, this technology took over the market as businesses preferred remote access to data as well as the cloud's scalability, economy, and reach.\n\n## AWS Deployment: deploying applications to the cloud\n\nCOVID-19 and the resulting trend toward remote work forced organizations to adopt cloud technologies even if they hadn’t planned to originally. Software deployment to the cloud has also increased. Cloud is no longer just virtual machines, organizations are driving the use of [Containers as a Service (CaaS)](https://searchitoperations.techtarget.com/definition/Containers-as-a-Service-CaaS) due to their growing interest in leveraging containers to ease development and testing, speed up deployment, scale operations, and increase the efficiency of workloads running in the cloud.\n\nSince deployment to the cloud has become a standard practice, at GitLab we want to make this repeatable and [boring](https://handbook.gitlab.com/handbook/values/#boring-solutions). In this blog post, we explain how we've made it easier to deploy to Amazon Web Services (AWS) as part of your deployment process. We invite users to replicate this example to deploy to other cloud providers in a similar way.\n\nSince we want cloud deployment to be as flexible as possible (similar to a microservices architecture), we constructed atomic Docker images that function as building blocks. Users can use these images as part of their custom `gitlab-ci.yml` file or use our predefined `.gitlab-ci.yml` templates. We also added the ability to use [Auto DevOps](https://docs.gitlab.com/ee/topics/autodevops/) with the new AWS deployment targets.\n\n## AWS Deployment: how to use GitLab's official AWS Docker Images\n\n### AWS CLI Docker image\nIn [GitLab 12.6](/releases/2019/12/22/gitlab-12-6-released/), we provided an official GitLab [AWS cloud-deploy](https://gitlab.com/gitlab-org/cloud-deploy/-/blob/master/aws/cloud_deploy/Dockerfile) Docker image that downloads and installs the [AWS CLI](https://aws.amazon.com/cli/). This allows users to run `aws` commands directly from their pipelines. For more information, see [Run AWS commands from GitLab CI/CD](https://docs.gitlab.com/ee/ci/cloud_deployment/#run-aws-commands-from-gitlab-cicd).\n\n### CloudFormation stack creation Docker image\nIn [GitLab 13.5](/releases/2020/10/22/gitlab-13-5-released/), we provided a Docker image that runs a script that [creates a stack with CloudFormation](https://gitlab.com/gitlab-org/cloud-deploy/-/blob/master/aws/src/bin/gl-cloudformation). The `gl-cloudprovision create-stack` uses [aws cloudformation create-stack](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/template-anatomy.html) behind the scenes. A JSON file based on the CloudFormation template must be passed to that command. For an example of this type of JSON file, see [`cf_create_stack.json`](https://gitlab.com/ebaque/jekyll-demo/-/blob/deploy-to-ec2/aws/cf_create_stack.json). With this type of JSON file, the command creates the infrastructure on AWS, including an EC2 instance directly from the `.gitlab-ci.yml` file. The script exists once we get confirmation that the stack setup is complete or has failed (through periodic polling).\n\n### Push to S3 and Deploy to EC2 Docker image\nIn [GitLab 13.5](/releases/2020/10/22/gitlab-13-5-released/) we also provided a Docker image with [Push to S3 and Deploy to EC2 scripts](https://gitlab.com/gitlab-org/cloud-deploy/-/blob/master/aws/src/bin/gl-ec2). The `gl-ec2 push-to-s3` script pushes source code to an S3 bucket. For an example of the JSON file to pass to the `aws deploy push` command, see [`s3_push.json`](https://gitlab.com/ebaque/jekyll-demo/-/blob/deploy-to-ec2/aws/s3_push.json). This code can be whatever artifact is built from a preceding build job. The `gl-ec2 deploy-to-ec2` script uses `aws deploy create-deployment` behind the scenes to create a deployment to an EC2 instance directly from the `.gitlab-ci.yml` file. For an example of the JSON template to pass, see [`create_deployment.json`](https://gitlab.com/ebaque/jekyll-demo/-/blob/deploy-to-ec2/aws/create_deployment.json). The script ends once we get confirmation that the deployment has succeeded or failed (via polling).\n\n## AWS Deployment: using GitLab CI templates to deploy to AWS\n\n### How to deploy to Elastic Container Service (ECS) with GitLab\nIn [GitLab 12.9](/releases/2020/03/22/gitlab-12-9-released/), we created a full `.gitlab-ci.yml` template called [`Deploy-ECS.giltab-ci.yml`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/lib/gitlab/ci/templates/Jobs/Deploy/ECS.gitlab-ci.yml) that deploys to Amazon ECS and extends support for Fargate. Users can include the template in their configuration, specify a few variables, and their application will be deployed and ready to go in no time. This template can be customized for your specific needs. For example: Replacing the selected container registry, changing the path of the file location, etc.\n\n### How to deploy to Elastic Cloud Compute (EC2) with GitLab\nIn [GitLab 13.5](/releases/2020/10/22/gitlab-13-5-released/), we created a full `.gitlab-ci.yml` template called [`CF-Provision-and-Deploy-EC2.gitlab-ci.yml`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/lib/gitlab/ci/templates/Jobs/Deploy/EC2.gitlab-ci.yml) that provisions the infrastructure by leveraging [AWS CloudFormation](https://aws.amazon.com/cloudformation/). It then pushes your previously-built artifact to an [AWS S3 bucket](https://aws.amazon.com/s3/) and deploys the pushed content to [AWS EC2](https://aws.amazon.com/ec2/).\n\n## AWS Deployment: security  considerations\n\n### Predefined AWS CI/CD variables\n\nIn order to deploy to AWS, you must use AWS security keys to connect to to your AWS instance. Users can define this security keys as [CI/CD environment](/topics/ci-cd/) variables that can be used by the deployment pipeline.\n\nIn [GitLab 12.9](/releases/2020/03/22/gitlab-12-9-released/), we added support for predefined AWS variables. This support function helps users know which variables are required for deploying to AWS and also prevents typos and spelling mistakes.\n\n| Env. variable name | Value|\n| --- | --- |\n| `AWS_ACCESS_KEY_ID` | Your Access key ID |\n| `AWS_SECRET_ACCESS_KEY` | Your Secret access key |\n| `AWS_DEFAULT_REGION` | Your region code |\n\n### \"Just-in-time\" guidance for AWS deployments\n\n[GitLab 13.1](/releases/2020/06/22/gitlab-13-1-released/) provides just-in-time guidance for users who wish to deploy to AWS. Setting up AWS deployments isn't always as easy as we'd like it to be, so we've added in-product links to our AWS templates and documentation when you start adding AWS CI/CD variables to make it easier for you to use our AWS features. This will help you get up and running faster.\n\n![In-product guidance for AWS](https://about.gitlab.com/images/blogimages/aws_guide.png)\n\nAWS guide from CI/CD variables\n\n### Added security for the GitLab's official AWS Docker images\n\nIn [GitLab 13.5](/releases/2020/10/22/gitlab-13-5-released/), we changed the image identifier from the release version number to the Docker image digest. Docker supports immutable image identifiers and we adopted this best practice to update our cloud-deploy images. When a new image is tagged, we also programmatically retrieve the image digest upon its build and create a release note to effectively communicate this digest to users. This guarantees that every instance of the service runs exactly the same code. You can roll back to an earlier version of the image, even if that version wasn't tagged (or is no longer tagged). This can even prevent race conditions if a new image is pushed while a deploy is in progress.\n\n![Docker Image Digest](https://about.gitlab.com/images/blogimages/digest1.png)\n\nDocker image digest or release tag\n\n## AWS Deployment: auto DevOps support\n\nGitLab already supports Kubernetes users deploying to AWS EKS cluster. Click the link to read instructions about [how to deploy an application to a GitLab-managed Amazon EKS cluster with Auto DevOps](/blog/deploying-application-eks/#:~:text=The%20Auto%20DevOps%20function%20at,build%2C%20and%20deploy%20your%20application).\n\nWe also expanded Auto DevOps to support non-Kubernetes users. Users can specify their deployment target by adding the `AUTO_DEVOPS_PLATFORM_TARGET` variable under the CI/CD variables settings. Specifying the deployment target platform builds a full CI/CD pipeline that deploys to AWS targets.\n\nWe currently support:\n\n- `AUTO_DEVOPS_PLATFORM_TARGET: ECS` (added in GitLab 13.0)\n- `AUTO_DEVOPS_PLATFORM_TARGET: FARGATE` (added in GitLab 13.2)\n- `AUTO_DEVOPS_PLATFORM_TARGET: EC2` (added in GitLab 13.6)\n\nFor more information about Auto DevOps for AWS targets, see [requirements for Auto DevOps](https://docs.gitlab.com/ee/topics/autodevops/requirements.html) documentation.\n\nHere's a quick recording for how to use Auto Deploy to Amazon ECS:\n\n\u003Cfigure class=\"video_container\">\n  \u003Ciframe src=\"https://www.youtube-nocookie.com/embed/HzRhLLFlAos\" frameborder=\"0\" allowfullscreen=\"true\"> \u003C/iframe>\n\u003C/figure>\n\nSpeed run on how to use auto deploy to EC2 (animation):\n\n\u003Cfigure class=\"video_container\">\n  \u003Ciframe src=\"https://www.youtube-nocookie.com/embed/rVr-vZfNL6U\" frameborder=\"0\" allowfullscreen=\"true\"> \u003C/iframe>\n\u003C/figure>\n\n## AWS Deployment: Future plans to extend deployment support via GitLab\n\nCheck out some of the open issues below to see our plans are for the future of deploying to AWS using GitLab.\n\n- [Show AWS deployment success code in logs](https://gitlab.com/gitlab-org/gitlab/-/issues/215333): This will bring the success/failure codes from AWS into your GitLab pipeline logs, allowing you to see the deployment success code without needing to go into the AWS console to retrieve the logs.\n- [Show AWS deployment success code in pipeline view](https://gitlab.com/gitlab-org/gitlab/-/issues/232983): This will bring the success/failure codes from AWS into your GitLab pipeline, allowing you to see if the deployment job was successful in one view.\n- [Auto Deploy to AWS S3](https://gitlab.com/gitlab-org/gitlab/-/issues/219087): This will expand the supported deployment targets covered in this blog to include [S3 buckets](https://aws.amazon.com/s3/) as well.\n- [AWS integration per-environment role management](https://gitlab.com/gitlab-org/gitlab/-/issues/27107): This returns a set of temporary security credentials you can use to access AWS resources that you normally might not be able to access. This is accomplished by using the [AWS IAM](https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_identifiers.html) roles.\n\n## More material on deploying to EKS and Lambda\n\n- [Demo of how to deploy to EKS](https://docs.google.com/presentation/d/1iXnB6lvTx2_-_0ASElLUDZwyFPWILCRx54XjJkMFuw0/edit#slide=id.g6bb36a7017_2_42).\n- [Whitepaper on how to deploy on AWS from GitLab](/resources/whitepaper-deploy-aws-gitlab/).\n\nWe invite you to contribute to our other cloud provider solutions:\n\n- [Streamline GCP deployments](https://gitlab.com/groups/gitlab-org/-/epics/2706).\n- [Streamline Azure deployments](https://gitlab.com/groups/gitlab-org/-/epics/4846).\n\nAt GitLab, [everyone can contribute](/company/strategy/#contribute-with-gitlab). If you want to deploy to a target that isn't mentioned in this post, please let us know by adding an issue and linking it to our [Natively support hypercloud deployments](https://gitlab.com/groups/gitlab-org/-/epics/1804) epic.\n\nCover image by [SpaceX](https://unsplash.com/photos/uj3hvdfQujI) on [Unsplash](https://www.unsplash.com)\n",[1133,741,9,696],{"slug":1196,"featured":6,"template":699},"deploy-aws","content:en-us:blog:deploy-aws.yml","Deploy Aws","en-us/blog/deploy-aws.yml","en-us/blog/deploy-aws",{"_path":1202,"_dir":246,"_draft":6,"_partial":6,"_locale":7,"seo":1203,"content":1209,"config":1215,"_id":1217,"_type":14,"title":1218,"_source":16,"_file":1219,"_stem":1220,"_extension":19},"/en-us/blog/develop-c-unit-testing-with-catch2-junit-and-gitlab-ci",{"title":1204,"description":1205,"ogTitle":1204,"ogDescription":1205,"noIndex":6,"ogImage":1206,"ogUrl":1207,"ogSiteName":686,"ogType":687,"canonicalUrls":1207,"schema":1208},"Develop C++ unit testing with Catch2, JUnit, and GitLab CI","Learn how to set up, write, and automate C++ unit tests using Catch2 with GitLab CI/CD. See examples from a working air quality app project and AI-powered help from GitLab Duo.","https://res.cloudinary.com/about-gitlab-com/image/upload/v1749659684/Blog/Hero%20Images/AdobeStock_479904468__1_.jpg","https://about.gitlab.com/blog/develop-c-unit-testing-with-catch2-junit-and-gitlab-ci","\n                        {\n        \"@context\": \"https://schema.org\",\n        \"@type\": \"Article\",\n        \"headline\": \"Develop C++ unit testing with Catch2, JUnit, and GitLab CI\",\n        \"author\": [{\"@type\":\"Person\",\"name\":\"Fatima Sarah Khalid\"}],\n        \"datePublished\": \"2024-07-02\",\n      }",{"title":1204,"description":1205,"authors":1210,"heroImage":1206,"date":1212,"body":1213,"category":1131,"tags":1214},[1211],"Fatima Sarah Khalid","2024-07-02","Continuous integration (CI) and automated testing are important DevSecOps workflows for software developers to detect bugs early, improve code quality, and streamline their development processes. \n\nIn this tutorial, you'll learn how to set up unit testing on a `C++` project with [Catch2](https://github.com/catchorg/Catch2) and GitLab CI for continuous integration. You'll also see how the AI-powered features of [GitLab Duo](https://about.gitlab.com/gitlab-duo/) can help. We’ll use [an air quality monitoring application](https://gitlab.com/gitlab-da/use-cases/ai/ai-applications/air-quality-app) as our reference project.\n\n## Prerequisites\n\n- Ensure you have [CMake](https://cmake.org/ \"CMake\") installed on your machine. \n- A modern `C++` compiler such as GCC or Clang is required. \n- An API key from [OpenWeatherMap](https://openweathermap.org/api) - requires signing up for a free account (1,000/calls per day are included for free). \n\n## Set up the application for testing\n\nThe reference project we’ll be using for demonstrating testing in this blog post is an air quality monitoring application that fetches air quality data from the OpenWeatherMap API based on the U.S zip codes only provided by the user.\n\nHere are the steps to set up the application for testing:\n\n1. Fork the [the reference project](https://gitlab.com/gitlab-da/use-cases/ai/ai-applications/air-quality-app) and clone the fork to your local environment.\n\n2. Generate an API key from  [OpenWeatherMap](https://openweathermap.org/) and export it into the environment. \n\n```shell\nexport API_KEY=\"YOURAPIKEY_HERE\"\n```\n\n3. Alternatively, you can add the key into your `.env` configuration, and source it with `source ~/.env`, or use a different mechanism to populate the environment.\n\n4. Compile and build the project code with the following instructions:\n\n```cpp\ncmake -S . -B build\ncmake --build build\n```\n\n5. Run the application using the executable and passing in a U.S zip code (90210 as an example): \n\n```cpp\n./build/air_quality_app 90210\n```\n\nHere’s an example of what running the program will look like in your terminal:  \n\n```bash\n❯ ./build/air_quality_app 90210\nAir Quality Index (AQI) for Zip Code 90210: 2 (Fair)\n```\n\n## Install Catch2\n\nNow that the application is set up and working, let's start working on adding testing using Catch2. Catch2 is a modern, `C++-native` testing framework for unit tests. \n\nYou can also ask GitLab Duo Chat within your IDE for an introduction to getting started with Catch2 as a `C++` testing framework. GitLab Duo Chat will provide getting started steps as well as an example test: \n\n![GitLab Duo Chat starting steps and example test](https://res.cloudinary.com/about-gitlab-com/image/upload/v1749676997/Blog/Content%20Images/1.duo-chat-installing-catch2.png)\n\n1. First navigate to your project’s root directory and create an externals folder using the `mkdir` command.\n\n```shell\nmkdir externals\n```\n\n2. There are several ways to install Catch2 via [its CMake integration](https://github.com/catchorg/Catch2/blob/devel/docs/cmake-integration.md#top). We will use the option of installing it as a submodule and including it as part of the source code to simplify dependency management. To add Catch2 to your project in the `externals` folder: \n\n```shell\ngit submodule add https://github.com/catchorg/Catch2.git externals/Catch2\ngit submodule update --init --recursive\n```\n\n3. Update `CMakeLists.txt` to include Catch2’s directory as a subdirectory. This allows CMake to find and build Catch2 as a part of our project. \n\n```cpp\n# Assuming Catch2 in externals/Catch2\nadd_subdirectory(externals/Catch2)\n```\n\n4. Create a `tests.cpp` file in your project root to write our tests to: \n\n```shell\ntouch tests.cpp\n```\n\n5. Update `CMakeLists.txt` Link against Catch2. When defining your test executable in CMake, link it against Catch2:\n\n```cpp\n# Add tests executable and link it to Catch2\nadd_executable(tests test.cpp)\ntarget_link_libraries(tests PRIVATE Catch2::Catch2WithMain)\n```\n\n## Structure the project for testing\n\nBefore we start writing our tests, we should separate our application logic into separate files in order to maintain and test our code more efficiently. At the end of this section we should have:\n\n```\nmain.cpp containing only the main() function and application setup\nincludes/functions.cpp containing all functional code such as API calls and data processing: \nincludes/functions.h containing the declarations for the functions defined in functions.cpp.  It needs to define the preprocessor macro guards, and include all necessary headers. \n```\n\nApply the following changes to the files: \n\n1. `main.cpp`\n\n```cpp\n#include \u003Ciostream>\n#include \"functions.h\"\n\nint main(int argc, char* argv[]) {\n   if (argc \u003C 2) {\n       std::cerr \u003C\u003C \"Usage: \" \u003C\u003C argv[0] \u003C\u003C \" \u003CZip Code>\" \u003C\u003C std::endl;\n       return 1;\n   }\n\n   std::string zipCode = argv[1];\n   std::string apiKey = getApiKey();\n   if (apiKey.empty()) {\n       std::cerr \u003C\u003C \"API key not found.\" \u003C\u003C std::endl;\n       return 1;\n   }\n\n   auto [lat, lon] = geocodeZipcode(zipCode, apiKey);\n   if (lat == 0 && lon == 0) {\n       std::cerr \u003C\u003C \"Failed to geocode zipcode.\" \u003C\u003C std::endl;\n       return 1;\n   }\n\n   std::string response = fetchAirQuality(lat, lon, apiKey);\n   std::string airQualityInfo = parseAirQualityResponse(response);\n\n   std::cout \u003C\u003C \"Air Quality Index for Zip Code \" \u003C\u003C zipCode \u003C\u003C \": \" \u003C\u003C airQualityInfo \u003C\u003C std::endl;\n\n   return 0;\n}\n```\n\n2. Create a `functions.h:` in the `includes` folder: \n\n```cpp\n#ifndef FUNCTIONS_H\n#define FUNCTIONS_H\n\n#include \u003Cstring>\n#include \u003Cutility>\n#include \u003Cvector>\n\n// Declare the function prototype\nstd::string httpRequest(const std::string& url);\nbool loadEnvFile(const std::string& filename);\nstd::string getApiKey();\nstd::pair\u003Cdouble, double> geocodeZipcode(const std::string& zipCode, const std::string& apiKey);\nstd::string fetchAirQuality(double lat, double lon, const std::string& apiKey);\nstd::string parseAirQualityResponse(const std::string& response);\n\n#endif\n```\n\n3. Create a `functions.cpp` in the `includes` folder: \n\n```cpp\n#include \"functions.h\"\n#include \u003Cfstream>\n#include \u003Celnormous/HTTPRequest.hpp>\n#include \u003Cnlohmann/json.hpp>\n#include \u003Ciostream>\n#include \u003Ccstdlib> // For getenv\n\nstd::string httpRequest(const std::string& url) {\n   try {\n       http::Request request{url};\n       const auto response = request.send(\"GET\");\n       return std::string{response.body.begin(), response.body.end()};\n   } catch (const std::exception& e) {\n       std::cerr \u003C\u003C \"Request failed, error: \" \u003C\u003C e.what() \u003C\u003C std::endl;\n       return \"\";\n   }\n}\nstd::string getApiKey() {\n   const char* envApiKey = std::getenv(\"API_KEY\");\n   if (envApiKey) {\n       return std::string(envApiKey);\n   }\n   // If the environment variable is not set, fallback to the config file\n   std::ifstream configFile(\"config.txt\");\n   std::string line;\n   if (getline(configFile, line)) {\n       return line.substr(line.find('=') + 1);\n   }\n   return \"\";\n}\n\nstd::pair\u003Cdouble, double> geocodeZipcode(const std::string& zipCode, const std::string& apiKey) {\n   std::string url = \"http://api.openweathermap.org/geo/1.0/zip?zip=\" + zipCode + \",US&appid=\" + apiKey;\n   std::string response = httpRequest(url);\n   try {\n       auto json = nlohmann::json::parse(response);\n       if (json.contains(\"lat\") && json.contains(\"lon\")) {\n           double lat = json[\"lat\"];\n           double lon = json[\"lon\"];\n           return {lat, lon};\n       } else {\n           std::cerr \u003C\u003C \"Geocode response missing 'lat' or 'lon' fields: \" \u003C\u003C response \u003C\u003C std::endl;\n       }\n   } catch (const nlohmann::json::parse_error& e) {\n       std::cerr \u003C\u003C \"Failed to parse geocode response: \" \u003C\u003C e.what() \u003C\u003C \" - Response: \" \u003C\u003C response \u003C\u003C std::endl;\n   }\n   return {0, 0};\n}\n\nstd::string fetchAirQuality(double lat, double lon, const std::string& apiKey) {\n   std::string url = \"http://api.openweathermap.org/data/2.5/air_pollution?lat=\" + std::to_string(lat) + \"&lon=\" + std::to_string(lon) + \"&appid=\" + apiKey;\n   std::string response = httpRequest(url);\n   return response;\n}\n\nstd::string parseAirQualityResponse(const std::string& response) {\n   try {\n       auto json = nlohmann::json::parse(response);\n       if (json.contains(\"list\") && !json[\"list\"].empty() && json[\"list\"][0].contains(\"main\")) {\n           int aqi = json[\"list\"][0][\"main\"][\"aqi\"];\n           std::string aqiCategory;\n           switch (aqi) {\n               case 1:\n                   aqiCategory = \"Good\";\n                   break;\n               case 2:\n                   aqiCategory = \"Fair\";\n                   break;\n               case 3:\n                   aqiCategory = \"Moderate\";\n                   break;\n               case 4:\n                   aqiCategory = \"Poor\";\n                   break;\n               case 5:\n                   aqiCategory = \"Very Poor\";\n                   break;\n               default:\n                   aqiCategory = \"Unknown\";\n                   break;\n           }\n           return std::to_string(aqi) + \" (\" + aqiCategory + \")\";\n       } else {\n           return \"No AQI data available\";\n       }\n   } catch (const std::exception& e) {\n       std::cerr \u003C\u003C \"Failed to parse JSON response: \" \u003C\u003C e.what() \u003C\u003C std::endl;\n       return \"Error parsing AQI data\";\n   }\n}\n\n```\n\n4. Now that we have separated the source files, we also need to update our `CMakeLists.txt` to include `functions.cpp` in the `add_executable()` calls:\n\n```cpp\ncmake_minimum_required(VERSION 3.14)\nproject(air-quality-app)\n\n# Set the C++ standard for the project\nset(CMAKE_CXX_STANDARD 17)\nset(CMAKE_CXX_STANDARD_REQUIRED ON)\nset(CMAKE_CXX_EXTENSIONS OFF)\n\ninclude_directories(${CMAKE_SOURCE_DIR}/includes)\n\n# Define the main program executable\nadd_executable(air_quality_app main.cpp includes/functions.cpp)\n\n# Assuming Catch2 in externals/Catch2\nadd_subdirectory(externals/Catch2)\n\n# Add tests executable and link it to Catch2\nadd_executable(tests tests.cpp includes/functions.cpp)\ntarget_link_libraries(tests PRIVATE Catch2::Catch2WithMain)\n```\n\nTo verify that the changes are working, regenerate the CMake configuration and rebuild the source code with the following commands. The build will take longer now that we're compiling Catch2 files. \n\n```shell\nrm -rf build # delete existing build files\ncmake -S . -B build \ncmake --build build  \n```\n\nYou should be able to run the application without any errors.\n\n```shell\n./build/air_quality_app 90210\n```\n\n## Write tests in Catch2  \n\nCatch2 tests are made up of [macros and assertions](https://github.com/catchorg/Catch2/blob/devel/docs/assertions.md). Macros in Catch2 are used to define test cases and sections within those test cases. They help in organizing and structuring the tests. Assertions are used to verify that the code behaves as expected. If an assertion fails, the test case will fail, and Catch2 will report the failure.\n\nLet’s review a basic test scenario for an addition function to understand. Note: This test is read-only, as an example. \n\n```cpp\nint add(int a, int b) {\n   return a + b;\n}\n\nTEST_CASE(\"Addition works correctly\", \"[math]\") {\n   REQUIRE(add(1, 1) == 2);  // Test passes if 1+1 equals 2\n   REQUIRE(add(2, 2) != 5);  // Test passes if 2+2 does not equal 5\n}\n```\n\n- Each test begins with the `TEST_CASE` macro, which defines a test case container. The macro accepts two parameters: a string describing the test case and optionally a second string for tagging the test for easy filtering.\n- Tests are also composed of assertions, which are statements that check if conditions are true. Catch2 provides macros for assertion that include `REQUIRE`, which aborts the current test if the assertion fails, and `CHECK`, which logs the failure but continues with the current test.\n\n### Prepare to write tests with Catch2\n\nTo test the API retrieval functions in our air quality application, we’ll be using mock API requests. Mock API testing is a technique used to test how your application will interact with an external API without making any real API calls. Instead of sending requests to a live API server, we can simulate the responses using predefined data. Mock requests allow us to control the input data and specify exactly what the API would return for different requests, making sure that our tests aren't affected by changes in the real API responses or unexpected data. This also makes it easier for us to simulate and catch different failures.\n\nIn our `tests.cpp` file, let’s define the following function to run mock API requests.   \n\n```cpp\n#include \"includes/functions.h\"\n#include \u003Ccatch2/catch_test_macros.hpp>\n#include \u003Cstring>\n\n// Mock HTTP request function that simulates API responses\nstd::string mockHttpRequest(const std::string& url) {\n   if (url.find(\"geo\") != std::string::npos) {\n       // Mock response for geocoding\n       return R\"({\"lat\": 40.7128, \"lon\": -74.0060})\"; \n   } else if (url.find(\"air_pollution\") != std::string::npos) {\n       // Mock response for air quality\n       return R\"({\"list\": [{\"main\": {\"aqi\": 2}}]})\";\n   }\n   // Default mock response for unmatched endpoints\n   return \"{}\";\n}\n// Overriding the actual httpRequest function with the mockHttpRequest for testing\nstd::string httpRequest(const std::string& url) {\n   return mockHttpRequest(url);\n}\n```\n\n- This function simulates HTTP requests and returns predefined JSON responses based on the URL given as input. \n- It also checks the URL to determine which type of data is being requested based on the functionality of the application (geocoding, air pollution, or forecast data). If the URL doesn’t match the expected endpoint, it returns an empty JSON object. \n\nDon't compile the code just yet, as you'll see a linker error. Since we're overriding the original `httpRequest` function with our mock function for testing, we'll need a preprocessor macro to enable conditional compilation - indicating which `httpRequest` function should run when we're compiling tests. \n\n#### Define a preprocessor macro for testing  \n\nBecause we’ve overridden `httpRequest` in our `tests.cpp`, we need to exclude that code from `functions.cpp` when we’re testing. When building tests, we may need to ensure that certain parts of our code behave differently or are excluded. We can do this by defining a preprocessor macro `TESTING` which enables conditional compilation, allowing us to selectively include or exclude code when compiling the test target:  \n\nWe define the `TESTING` macro in our `CMakeLists.txt` at the end:  \n\n```cpp\n# Define TESTING macro for this target\ntarget_compile_definitions(tests PRIVATE TESTING)\n```\n\nAnd add the macro wrapper in  `functions.cpp` around the original `httpRequest` function:  \n\n```cpp\n#ifndef TESTING  // Exclude this part when TESTING is defined\nstd::string httpRequest(const std::string& url) {\n   try {\n       http::Request request{url};\n       const auto response = request.send(\"GET\");\n       return std::string{response.body.begin(), response.body.end()};\n   } catch (const std::exception& e) {\n       std::cerr \u003C\u003C \"Request failed, error: \" \u003C\u003C e.what() \u003C\u003C std::endl;\n       return \"\";\n   }\n}\n#endif\n```\n\nRegenerate the CMake configuration and rebuild the source code to verify it works.\n\n```shell\ncmake --build build  \n```\n\n### Write the first tests \n\nNow, let’s write some tests for our air quality application.\n\n#### Test 1: Verify API key retrieval \n\nThis test ensures that the `getApiKey` function retrieves the API key correctly from the environment variable or the configuration file. Add the test case to our `tests.cpp`:\n\n```cpp\n\nTEST_CASE(\"API Key Retrieval\", \"[api]\") {\n   // Set the API_KEY environment variable for testing\n   setenv(\"API_KEY\", \"test_key\", 1);\n   // Test if the key is retrieved correctly\n   REQUIRE(getApiKey() == \"test_key\");\n}\n```\n\nYou can verify that this tests passes by rebuilding the code and running the tests:\n\n```shell\ncmake --build build\n./build/tests\n```\n\n#### Test 2: Geocode the zip code\n\nThis test ensures that the `geocodeZipcode` function returns the correct latitude and longitude for a given zip code using the mock API response function we set up earlier. The  `geocodeZipcode` function is supposed to hit an API that returns geographic coordinates based on a zip code. \n\nIn `tests.cpp`, add this test case for the zip code 90210: \n\n```cpp\nTEST_CASE(\"Geocode Zip code\", \"[geocode]\") {\n   std::string apiKey = \"test_key\";\n   std::pair\u003Cdouble, double> coordinates = geocodeZipcode(\"90210\", apiKey);\n   // Check latitude\n   REQUIRE(coordinates.first == 40.7128);\n   // Check longitude \n   REQUIRE(coordinates.second == -74.0060);\n}\n```\n\nThe purpose of this test is to verify that the function `geocodeZipcode` can correctly parse the latitude and longitude from the API response. By hardcoding the expected response, we ensure that the test environment is controlled and predictable.\n\n #### Test 3: Air quality API test\n\nThis test ensures that the `fetchAirQuality` function correctly fetches air quality data using the mock API response function we set up earlier. It verifies that the function constructs the API request properly, sends it, and accurately parses the air quality index (AQI) from the mock JSON response. This validation helps ensure that the overall process of fetching and interpreting air quality data works as intended.\n\n```cpp\nTEST_CASE(\"Fetch Air Quality\", \"[airquality]\") {\n   std::string apiKey = \"test_key\";\n   double lat = 40.7128;\n   double lon = -74.0060;\n   std::string response = fetchAirQuality(lat, lon, apiKey);\n   // Check the response\n   REQUIRE(response == R\"({\"list\": [{\"main\": {\"aqi\": 2}}]})\");\n}\n```\n\n## Build and run the tests\n\nTo  build and compile our application, we'll use the same CMake commands as before:\n\n```cpp\ncmake -S . -B build\ncmake --build build\n\n```\n\nAfter building, we can run our tests by executing the test binary:  \n\n```cpp\n./build/tests\n\n```\n\nRunning this command will execute all defined tests, and you will see output indicating whether each test has passed or failed.\n\n![Output showing pass/fail of tests](https://res.cloudinary.com/about-gitlab-com/image/upload/v1749676998/Blog/Content%20Images/2.running-catch2-tests.png)\n\n## Set up GitLab CI/CD\n\nTo automate the testing process each time we push some new code to our repository, let’s set up [GitLab CI/CD](https://about.gitlab.com/topics/ci-cd/). Create a new `.gitlab-ci.yml` configuration file in the root directory. \n\n```yaml\nimage: gcc:latest\n\nvariables:\n GIT_SUBMODULE_STRATEGY: recursive\n\nstages:\n - build\n - test\n\nbefore_script:\n - apt-get update && apt-get install -y cmake\n\ncompile:\n stage: build\n script:\n   - cmake -S . -B build\n   - cmake --build build\n artifacts:\n   paths:\n     - build/\n\ntest:\n stage: test\n script:\n   - ./build/tests --reporter junit -o test-results.xml\n artifacts:\n   reports:\n     junit: test-results.xml\n```\n\nThis CI/CD configuration will compile both the main application and the test suite, then run the tests, generating a JUnit XML report which GitLab uses to display the test results.  \n\n- In `before_script`, we added an installation for `cmake`, and `git submodule sync --recursive` which initializes and updates our submodules (catch2). \n- In the `test` stage, `--reporter junit -o test-results.xml` specifies that the test results should be treated as a JUnit report which allows GitLab CI to display results in the UI. This is super helpful when you have several tests in your application.  \n\nWe also need to [add an environmental variable](https://docs.gitlab.com/ee/ci/variables/#define-a-cicd-variable-in-the-ui) with the `API_KEY` in project settings on GitLab.\n\nDon’t forget to add all new files to Git, and commit and push the changes in a new MR:\n\n```shell\ngit checkout -b tests-catch2-cicd\n\ngit add includes/functions.{h,cpp} tests.cpp .gitlab-ci.yml \ngit add CMakeLists.txt main.cpp \n\ngit commit -vm “Add Catch2 tests and CI/CD configuration”\ngit push \n```\n\n## View the test report\n\nAfter pushing our code changes, we can review the results of our tests in the GitLab UI in the Pipeline view in the `Tests` tab:\n\n![GitLab pipeline view shows test results](https://res.cloudinary.com/about-gitlab-com/image/upload/v1749676998/Blog/Content%20Images/2.0-passed-tests-UI.png)\n\n## Simulate a test failure\n\nTo demonstrate how our UI will handle test failures, we can intentionally introduce a bug into our code and observe the resulting behavior. \n\nLet's modify our `parseAirQualityResponse` function to introduce an error. We can change the AQI category for an AQI value of 2 from \"Fair\" to \"Poor.\" This change will cause the related test to fail, allowing us to see the test failure in the GitLab UI.\n\nIn `functions.cpp`, find the `parseAirQualityResponse` function and modify the switch statement for case `2` to set the `Poor` value instead of `Fair`:\n\n```cpp\n               // Intentional bug:\n               case 2:\n                   aqiCategory = \"Poor\";\n                   break;\n```\n\nIn tests.cpp, add a new test case that directly checks the output of the `parseAirQualityResponse` function. This test ensures that the `parseAirQualityResponse` function correctly parses and categorizes the air quality data from the mock API response. This function takes a JSON response, extracts the AQI value, and translates it into a human-readable category.\n\n```cpp\n\nTEST_CASE(\"Parse Air Quality Response\", \"[airquality]\") {\n   std::string mockResponse = R\"({\"list\": [{\"main\": {\"aqi\": 2}}]})\";\n   std::string result = parseAirQualityResponse(mockResponse);\n   // This should fail due to the intentional bug\n   REQUIRE(result == \"2 (Fair)\");\n}\n\n```\n\nCommit the changes, and push them into the MR. Open the MR in your browser. \n\nBy introducing an intentional bug in this function, we can see how a test failure is reported in GitLab's pipelines UI. We must add, commit, and push the changes to our repository to view the test failure in the pipeline. \n\n![Simulated test failure](https://res.cloudinary.com/about-gitlab-com/image/upload/v1749676998/Blog/Content%20Images/2.1-failed-test-simulation.png)\n\n![Details of the simulated failed test](https://res.cloudinary.com/about-gitlab-com/image/upload/v1749676998/Blog/Content%20Images/2.2-failed-test-simulation-details.png)\n\nOnce we've verified this simulated test failure, we can use `git revert` to roll back that commit. \n\n```shell\ngit revert\n```\n\n## Add and test a new feature\n\nLet’s put what you've learned together by creating a new feature in the air quality application and then writing a test for that feature using Catch2. The new feature will fetch the current weather forecast for the provided zip code.\n\nFirst, we'll define a `Weather` struct and add the function prototype in our `functions.h` file (inside the `#endif`):\n\n```cpp\n\nstruct Weather {\n   std::string main;\n   std::string description;\n   double temperature;\n};\n\nWeather getCurrentWeather(const std::string& apiKey, double lat, double lon);\n```\n\nThen, we implement the `getCurrentWeather` function in `functions.cpp`. This function calls the OpenWeatherMap API to retrieve the current weather and parses the JSON response. This code was generated using [GitLab Duo](https://about.gitlab.com/gitlab-duo/). If you start typing `Weather getCurrentWeather(const std::string& apiKey, double lat, double lon) {` to complete the function, GitLab Duo will provide the function contents for you, line by line. \n\n![GitLab Duo completing the function contents](https://res.cloudinary.com/about-gitlab-com/image/upload/v1749676998/Blog/Content%20Images/3.get-current-weather-function-completion.png)\n\nHere's what your `getCurrentWeather()` function can look like: \n\n```cpp\n\nWeather getCurrentWeather(const std::string& apiKey, double lat, double lon) {\n   std::string url = \"http://api.openweathermap.org/data/2.5/weather?lat=\" + std::to_string(lat) + \"&lon=\" + std::to_string(lon) + \"&appid=\" + apiKey;\n   std::string response = httpRequest(url);\n   auto json = nlohmann::json::parse(response);\n   Weather weather;\n   if (!json.is_null()) {\n       weather.main = json[\"weather\"][0][\"main\"];\n       weather.description = json[\"weather\"][0][\"description\"];\n       weather.temperature = json[\"main\"][\"temp\"];\n   }\n   return weather;\n}\n```\n\nAnd, finally, we update our `main.cpp` file in the main function to output the current forecast (and converting Kelvin to Celsius for the output):  \n\n```cpp\n   Weather currentWeather = getCurrentWeather(apiKey, lat, lon);\n   if (currentWeather.main.empty()) {\n       std::cerr \u003C\u003C \"Failed to fetch current weather.\" \u003C\u003C std::endl;\n       return 1;\n   }\n\n   std::cout \u003C\u003C \"Current Weather: \" \u003C\u003C currentWeather.main \u003C\u003C \", \" \u003C\u003C currentWeather.description\n       \u003C\u003C \", temperature \" \u003C\u003C currentWeather.temperature - 273.15 \u003C\u003C \" °C\" \u003C\u003C std::endl;\n```\n\nWe can confirm that our new feature is working by building and running the application:  \n\n```shell\ncmake --build build\n./build/air_quality_app \n```\n\nAnd we should see the following output or similar in case the weather is different on the day the code is run :)\n\n```\nAir Quality Index for Zip Code 90210: 2 (Poor)\nCurrent Weather: Clouds, broken clouds, temperature 23.2 °C\n```\n\nWith all new functionality, there should be testing! We can also write a test to check whether the application is fetching and parsing a weather forecast correctly. This test checks that the function returns a list containing the correct number of forecast entries and that each entry has accurate data regarding time and temperature.\n\n```cpp\nTEST_CASE(\"Current Weather functionality\", \"[api]\") {\n   auto weather = getCurrentWeather(\"dummyApiKey\", 40.7128, -74.0060);\n   // Ensure main weather description is not empty\n   REQUIRE_FALSE(weather.main.empty());\n   // Validate that temperature is a reasonable value\n   REQUIRE(weather.temperature > 0); \n}\n```\n\nWe’ll also have to update our `mockHTTPRequest` function in `tests.cpp` to account for this new test. Modify the if-condition with a new else-if branch checking for the `weather` string in the URL:  \n\n```cpp\n// Mock HTTP request function that simulates API responses\nstd::string mockHttpRequest(const std::string &url)\n{\n   if (url.find(\"geo\") != std::string::npos)\n   {\n       // Mock response for geocoding\n       return R\"({\"lat\": 40.7128, \"lon\": -74.0060})\";\n   }\n   else if (url.find(\"air_pollution\") != std::string::npos)\n   {\n       // Mock response for air quality\n       return R\"({\"list\": [{\"main\": {\"aqi\": 2}}]})\";\n   }\n   else if (url.find(\"weather\") != std::string::npos)\n   {\n       // Mock response for current weather\n       return R\"({\n          \"weather\": [{\"main\": \"Clear\", \"description\": \"clear sky\"}],\n          \"main\": {\"temp\": 298.55}\n      })\";\n   }\n   return \"{}\";\n}\n```\n\nAnd verify that our tests are working by rebuilding and running our tests:  \n\n```shell\ncmake --build build \n./build/tests\n```\n\nAll tests should pass, including the new one for Current Weather Functionality. \n\n## Optimize tests.cpp with sections\n\nTo better organize our tests as the project grows and categorize each functionality, we can use Catch2’s `SECTION` macro. The `SECTION` macro allows you to define logically separate test scenarios within a single test case, providing a clean way to test different behaviors or conditions without requiring multiple separate test cases or multiple files. This approach keeps related tests bundled together and also improves test maintainability by allowing shared setup code to be executed repeatedly for each section.\n\nSince some of our functionality is preprocessing data to retrieve information, let’s section our tests as such:\n- preprocessing steps: \n\t- API key validation\n\t- geocoding validation\n-  API data retrieval:\n\t- air pollution retrieval \n\t- forecast retrieval\n\nHere’s what our `tests.cpp` will look like if organized by sections: \n\n```cpp\n#include \"functions.h\"\n#include \u003Ccatch2/catch_test_macros.hpp>\n#include \u003Cstring>\n\n// Mock HTTP request function that simulates API responses\nstd::string mockHttpRequest(const std::string &url)\n{\n   if (url.find(\"geo\") != std::string::npos)\n   {\n       // Mock response for geocoding\n       return R\"({\"lat\": 40.7128, \"lon\": -74.0060})\";\n   }\n   else if (url.find(\"air_pollution\") != std::string::npos)\n   {\n       // Mock response for air quality\n       return R\"({\"list\": [{\"main\": {\"aqi\": 2}}]})\";\n   }\n   else if (url.find(\"weather\") != std::string::npos)\n   {\n       // Mock response for current weather\n       return R\"({\n          \"weather\": [{\"main\": \"Clear\", \"description\": \"clear sky\"}],\n          \"main\": {\"temp\": 298.55}\n      })\";\n   }\n   return \"{}\";\n}\n\n// Overriding the actual httpRequest function with the mockHttpRequest for testing\nstd::string httpRequest(const std::string &url)\n{\n   return mockHttpRequest(url);\n}\n\n// Preprocessing Steps\nTEST_CASE(\"Preprocessing Steps\", \"[preprocessing]\") {\n   SECTION(\"API Key Retrieval\") {\n       // Set the API_KEY environment variable for testing\n       setenv(\"API_KEY\", \"test_key\", 1);\n       // Test if the key is retrieved correctly\n       REQUIRE_FALSE(getApiKey().empty());\n   }\n\n   SECTION(\"Geocode Functionality\") {\n       std::string apiKey = \"test_key\";\n       std::pair\u003Cdouble, double> coordinates = geocodeZipcode(\"90210\", apiKey);\n       // Check latitude\n       REQUIRE(coordinates.first == 40.7128);\n       // Check longitude \n       REQUIRE(coordinates.second == -74.0060);\n   }\n}\n\n// API Data Retrieval\nTEST_CASE(\"API Data Retrieval\", \"[data_retrieval]\") {\n   SECTION(\"Air Quality Functionality\") {\n       std::string apiKey = \"test_key\";\n       double lat = 40.7128;\n       double lon = -74.0060;\n       std::string response = fetchAirQuality(lat, lon, apiKey);\n       // Check the response\n       REQUIRE(response == R\"({\"list\": [{\"main\": {\"aqi\": 2}}]})\");\n   }\n\n   SECTION(\"Current Weather Functionality\") {\n       auto weather = getCurrentWeather(\"dummyApiKey\", 40.7128, -74.0060);\n       // Ensure main weather description is not empty\n       REQUIRE_FALSE(weather.main.empty());\n       // Validate that temperature is a reasonable value\n       REQUIRE(weather.temperature > 0);\n   }\n}\n```\n\nRebuild the code and run the tests again to verify.\n\n```shell\ncmake --build build \n./build/tests\n```\n\n## Next steps\n\nIn this post, we covered how to integrate unit testing into a `C++` project using Catch2 testing framework and GitLab CI/CD and set up basic tests for our reference air quality application project.\n\nTo explore these concepts further, you can check out the [Catch2 documentation](https://github.com/catchorg/Catch2) and [GitLab's Unit test report examples documentation](https://docs.gitlab.com/ee/ci/testing/unit_test_report_examples.html). \n\nFor an advanced async exercise, you could build upon this project by using GitLab Duo to implement a feature that retrieves and analyzes historical air quality data and add code quality checks into the CI/CD pipeline. Happy coding! \n",[742,851,9,962,786],{"slug":1216,"featured":91,"template":699},"develop-c-unit-testing-with-catch2-junit-and-gitlab-ci","content:en-us:blog:develop-c-unit-testing-with-catch2-junit-and-gitlab-ci.yml","Develop C Unit Testing With Catch2 Junit And Gitlab Ci","en-us/blog/develop-c-unit-testing-with-catch2-junit-and-gitlab-ci.yml","en-us/blog/develop-c-unit-testing-with-catch2-junit-and-gitlab-ci",{"_path":1222,"_dir":246,"_draft":6,"_partial":6,"_locale":7,"seo":1223,"content":1229,"config":1235,"_id":1237,"_type":14,"title":1238,"_source":16,"_file":1239,"_stem":1240,"_extension":19},"/en-us/blog/devops-strategy",{"title":1224,"description":1225,"ogTitle":1224,"ogDescription":1225,"noIndex":6,"ogImage":1226,"ogUrl":1227,"ogSiteName":686,"ogType":687,"canonicalUrls":1227,"schema":1228},"Beyond CI/CD: GitLab's DevOps vision","How we're building GitLab into the complete DevOps toolchain.","https://res.cloudinary.com/about-gitlab-com/image/upload/v1749670214/Blog/Hero%20Images/devops-nova-scotia-cover.jpg","https://about.gitlab.com/blog/devops-strategy","\n                        {\n        \"@context\": \"https://schema.org\",\n        \"@type\": \"Article\",\n        \"headline\": \"Beyond CI/CD: GitLab's DevOps vision\",\n        \"author\": [{\"@type\":\"Person\",\"name\":\"Mark Pundsack\"}],\n        \"datePublished\": \"2017-10-04\",\n      }",{"title":1224,"description":1225,"authors":1230,"heroImage":1226,"date":1232,"body":1233,"category":301,"tags":1234},[1231],"Mark Pundsack","2017-10-04","\n\nWith GitLab 10.0, we shipped [Auto DevOps](https://docs.gitlab.com/ee/topics/autodevops/) for the Community and Enterprise\nEditions. Read on for an in-depth look at our strategy behind it, and beyond.\n\n\u003C!-- more -->\n\nI recently met with my colleagues\n[Joe](/company/team/#JAScheuermann) and\n[Courtland](/company/team/#mktinghipster) to give them the\nlowdown on GitLab's DevOps vision: where we've come from and where we're headed.\nYou can watch the video of our discussion or check out the lightly edited\ntranscript below. You can also jump into the rabbit hole, starting with the meta\nissue for [GitLab DevOps](https://gitlab.com/gitlab-org/gitlab-ce/issues/32639).\n\n\u003Ciframe width=\"560\" height=\"315\" src=\"https://www.youtube.com/embed/zMAB42g4MPI\" frameborder=\"0\" allowfullscreen>\u003C/iframe>\n\n\n## CI/CD: Where we've come from\n\n![CI/CD/Beyond CD](https://about.gitlab.com/images/blogimages/devops-strategy-ci-scope.svg)\n\nWhen I joined GitLab about a year ago, I created a [vision document for\nCI/CD](/direction/#ci--cd), and outlined a lot of the\nkey things that I thought were missing in [CI/CD in general](/topics/ci-cd/), and going beyond CD.\nI literally called one section \"beyond CD\" because I didn’t have a name for it\nthen.\n\nAnd in that document, I create an example pipeline to characterize all this\nstuff, to show how the pieces fit together into a development lifecycle.\n\n![Example pipeline](https://about.gitlab.com/images/blogimages/devops-strategy-example-pipeline.png){: .shadow}\n\nI love this diagram not only because it's complex and scary, but because when we\nstarted, we had maybe four boxes filled in, and now we have 10 or 12 filled in. To\nstart with, we had code management and, obviously, builds and tests. And we kind\nof did deployment, but not really.\n\nSince then, we’ve added review apps – a specific example of deployments – which\nis really awesome. We also added a more formalized mechanism for doing\ndeployments; actually recording deployments and deployment histories, keeping\ntrack of environments, and everything else. Then we added Canary Deployments in\n9.2 and code quality in 9.3. We added system monitoring with Prometheus in 9.0.\n\nWe don’t yet have what I called \"business monitoring,\" which could mean\nmonitoring revenue, or clicks, or whatever you care about; but that’s coming. We\ndon't yet have load testing, but the Prometheus team is thinking about that.\nWe don't yet have a plan for feature flags, but I think it's a really important\npart.\n\nAnd then we have this other dimension of pipelines, which is the relationship\nbetween different codebases (or projects), and in 9.3 we introduced the first\nversion of multi-project pipelines.\n\nSo we've gone from a core view of three or four boxes to where 90 percent is\ncomplete. That's pretty awesome.\n\nIt became obvious to me that we were viewing the scope with this hard line:\ndeveloper focused rather than an ops focused. For example, we’ll deploy into production,\nand we might even watch the metrics related to your code in production, but\nwe’re not going to monitor your entire production app, because that’s\noperations, and that’s clearly out of scope, right?\n\n## Where we're headed: Beyond CD\n\nWhat hit me a few months ago is, \"Why is that out of scope? That’s ridiculous.\nNo, we’re going to keep going. We're going to go past production into\noperations.\" Most of this still applies, but instead of just monitoring the\nsystem as it relates to a merge request, what about monitoring the system for\nnetwork errors, outages, or dependency problems? What if we don't stop at\nproduction, and monitor things that are typically ops related that may not\ninvolve a developer at all?\n\nThen I realized that this thing I called Beyond CD, maybe it's really [DevOps](/topics/devops/).\nMaybe the whole thing is DevOps.\n\n### The DevOps tool chain\n\nTo offer some context: DevOps is hard to define, because everybody defines it\nslightly differently. Sometimes DevOps is defined as the intersection of\ndevelopment, operations, and quality assurance.\n\n![DevOps Venn diagram](https://about.gitlab.com/images/blogimages/devops-strategy-venn-diagram.png){: .shadow}\n\n*\u003Csmall>Image by Rajiv.Pant, derived from Devops.png:, [CC BY 3.0](https://commons.wikimedia.org/w/index.php?curid=20202905)\u003C/small>*\n\nFor the most part, my personal interest in DevOps has been in that intersection.\nWe do great code management; we’ve done that for quite a while. How do we get\nthat code into production? How do we get it into QA?\n\nReview apps are a great example that fits squarely in that tiny, little triangle\nin the middle of the Venn diagram. You take your code, you deploy it, which is\nan operations thing, but you have it deployed in a temporary, ephemeral, app,\njust for QA people (or designers, product managers, or anyone who is not a\nprimary coder), so they can test your application for quality assurance, feature\nassurance, or whatever.\n\nBut now, I'm looking beyond the intersection. Here's the [DevOps tool chain\ndefinition](https://en.wikipedia.org/wiki/DevOps_toolchain) from Wikipedia:\n\n![DevOps Toolchain](https://about.gitlab.com/images/blogimages/devops-strategy-devops-toolchain.png){: .shadow}\n\n*\u003Csmall>Image by Kharnagy (Own work) [CC BY-SA 4.0](http://creativecommons.org/licenses/by-sa/4.0), via Wikimedia Commons\u003C/small>*\n\nWell, that’s everything! That’s not the intersection; that’s the union of\neverything from code, to releasing, to monitoring. And that's where things get\nconfusing. Sometimes when people talk about DevOps, they’re not talking about\nall of your code stuff. It’s the intersection parts that are the interesting\nparts of DevOps. It’s the parts where we let developers get their code into\nproduction easily. That slice, that intersection, of the Venn diagram, that’s\nthe interesting part about DevOps.\n\nHaving said that, as a product company, we are going to deliver things that are\npretty squarely on the development side, and, eventually, we’re going to deliver\nthings that are pretty squarely in the operations side. At some point, we may\nhave an operations dashboard that lets you understand your dependencies in your\nnetwork infrastructure, and your routers, and your whatever. That’s pretty far\nfetched at this point, but it could happen. Why not? Just have GitLab be\nyour one operations dashboard, and then it’s not just about the intersection of\nthe DevOps, it’s the whole DevOps tool chain.\n\nSo, that is the whirlwind, high-level summary of where we've been, and a little\nbit about where we’re going. Now let's get into specific issues.\n\n### The Ops Dashboard – [#1788](https://gitlab.com/gitlab-org/gitlab-ee/issues/1788)\n\nWe have a monitoring dashboard that's very developer centric. What about\ntaking that same content and slicing it from the operator's perspective? For a\nmoment, ignore all the stuff below, let’s just pretend there’s only the four\nboxes at the top:\n\n![Ops view of monitoring and deploy board](https://about.gitlab.com/images/blogimages/devops-strategy-monitoring-deploy-board.png){: .shadow}\n\nSo an operator might want to know, \"What’s the state of production?\" If I'm a\ndeveloper I can go into a project, into environments, see the production\nenvironment for that project, and I can see what the status is. But what if I\nwant to see all production environments? As an operations person, I care a\nlittle less about individual projects than I care about \"production.\" So this is\ngiving me the overview of \"production.\" All of these little boxes would\nrepresent production deploys of projects that you have in your GitLab\ninfrastructure.\n\nThe view is explicitly convoluted because we had just introduced sub-groups and\nI wanted to make sure this mechanism expanded. So ignore all the stuff below and\njust look at the top-level dashboards. Or maybe one level down, which is already\nstill pretty complicated, but let’s say your marketing organization had\ndifferent properties than your other developer operations; you’d be able to see\nreally quickly what the status is. If something’s red, you’d be able to click\ndown, and see details.\n\n![Ops view - service health](https://about.gitlab.com/images/blogimages/devops-strategy-service-health.png){: .shadow}\n\n![Ops view - pod health](https://about.gitlab.com/images/blogimages/devops-strategy-pod-health.png){: .shadow}\n\nYou’d be able to see graphs like this, which are similar to what we already\nprovide, but from the other angle. As a developer I’m looking at the deploy, and\nsaying, \"Oh, how did my deploy affect my performance?\" But this is saying,\n\"How’s production? Is anything wrong with my entire production suite?\"\n\nThis is really just scratching the surface of the ops views of things, but I\nthink it's going to become much more important as people embrace DevOps. You\nwant your developers to be talking the same language as your operations people.\nIn a lot of organizations, it’s already the same people – there are no separate\noperations people. Developers push code to production, and they're paged if\nsomething goes wrong. In others, developers and operators are separate, but they\nwant to work together towards DevOps.\n\nEither way, you want to be using the same tools. You want to be able to point\nto, for example, a memory bump that your operations people should also be able\nto see. But if they’re using completely different tools, like New Relic and\nDatadog, that kind of sucks. So let’s give them the same tools.\n\n### Pipeline view of environments – [#28698](https://gitlab.com/gitlab-org/gitlab-ce/issues/28698)\n\nI particularly love this proposal, and I really want to see this happen soon.\n\nThe environments page today is just a list of environments showing the last\ndeployment. The picture tells you who deployed, which is good, and you can see\nthat the commit is from the same SHA as staging, which is kind of nice. I can\nsee the deploy board, and if there's a deploy ongoing, I’m able to see the state\nas it rolls out. We don’t yet show you the current health of these pods; once\nthey're deployed, all we know is that they're deployed. This is how the\nenvironment view is today, and it's centered around deployments.\n\n![Environments list](https://about.gitlab.com/images/blogimages/devops-strategy-environments-list.png){: .shadow}\n*\u003Csmall>Current Environment view\u003C/small>*\n\nYou can click through to see the deployment history and this is actually really\nvaluable because I can see who deployed things, how long ago, and if something\nwent wrong in production I can really quickly roll back and let the developers\nhave some space to go and figure out what went wrong.\n\n![Deployment history](https://about.gitlab.com/images/blogimages/devops-strategy-deployment-history.png){: .shadow}\n*\u003Csmall>Current Deployment History view\u003C/small>*\n\nBut this proposal turns it around to have more of a DevOps view of the thing.\n\n![Pipeline view of environments](https://about.gitlab.com/images/blogimages/devops-strategy-pipeline-view-environments.png){: .shadow}\n*\u003Csmall>Proposed pipeline view of Environments\u003C/small>*\n\nThe idea is to take the same application, and instead of just looking at a list\nof environments, I’d be looking at columns with lots of review apps, and some\nnumber of staging environments, and a production environment. Instead of just\nshowing you the SHA, we would show you, for example, what merge requests have\nbeen merged into staging that are not yet in production. That’s a great\nmarriage of these two views, that you’d be able to see the diff between them.\n\nThis list, although it’s just a mockup, shows maybe the last five things that\nwere in production, or what was included in the last deploy, or whatever works\nbest for your environment. Showing what’s in the last deploy might be enough,\nbut for people who deploy 17 times a day, maybe that’s a little less useful, and\nwe just show history.\n\nBut then what about building in more of the operations kind of stuff, and\nsaying, \"Alright, what’s the state of my pods?\" Here we were flagging where the\nerror rate exceeded a threshold and there’s some alert that popped up. And here\nwe’re showing this automatic rollback kind of stuff, but basically just really\nbuilding on this ops view. Of course this is still a DevOps view, in the sense\nthat I’m looking at an individual project. So, one permutation of that would\nmarry that ops view of all of production. Or if I’m looking at a [microservices](/topics/microservices/)\nkind of thing, where there are five or 100 different projects, and I want to see\nthe status of all those really quickly. See\n[#28707](https://gitlab.com/gitlab-org/gitlab-ce/issues/28707).\n\n### Dependency security – [#28566](https://gitlab.com/gitlab-org/gitlab-ce/issues/28566)\n\nSo, here, the idea is that you've deployed something in production, and some\nmodule or something that you depend on has been updated, not by you, but by the\ncommunity, or someone else.\n\nThe easiest and most naive way to approach this is that with the next merge\nrequest, or next CI/CD run, we would go and check to see if anything’s outdated.\nAnd we might fail your CI/CD because of this.\n\nIt would make much more sense to run this stuff automatically. Even if, for\nexample, nobody pushes for seven days, and in the middle of that, there’s a\nsecurity release; just proactively run stuff and notify me. So, that's sort of a\nsecond iteration of thinking about how you would notify somebody, and tell them,\n\"Oh, you’ve got a security change. You should go in and do something about it.\"\n\nNow, the third iteration is, \"Well, what would you do with that information?\"\nYou’d go and maybe give it to your junior developer to go and make the change,\nand point to the new version. And then, of course, you need to test that it\nworks. So, you’re going to create a merge request, and then test it, to make\nsure that it still functions properly.\n\nWell, why notify somebody, and tell the junior developer to go and do this? Why\ndon’t we just do it for you? Why don’t we just go and submit the merge request\nfor you, and then tell you what the results are. And, in fact, let’s go further,\nand say, \"Hey it passed. We just deployed into production for you.\" Why would\nyou have security vulnerability in place any longer than necessary?\n\nAnd instead of having 100 alerts about 100 projects or microservices that all\nneed to get updated, you just get alerts about three of them that fail, that\nactually have some weird dependency that it didn’t work on. And then, you can\nfocus on real problems.\n\n![Dependency security](https://about.gitlab.com/images/blogimages/devops-strategy-dependency-security.png){: .shadow}\n\nSo, that’s a glimpse at how we’re thinking about this.\n\nThis would definitely be an enterprise-level feature. And again, we've fleshed\nout some ideas and it’s unscheduled, but it does really tie into the ops\nmindset.\n\n### Question: Enterprise Edition features\n\nCourtland: You mentioned that sort of automation would be an enterprise edition\nfeature. Can you talk a little bit more about why a smaller development team,\nlike under 100 developers, wouldn’t get value out of something like that?\n\nMark:\tSo, this is where things get a little tricky, because of course,\nsmaller developer teams would get value out of that too. Everybody would get\nvalue out of that. Some of it has to do with proportionality. One test I like to\nuse is: is there some other way you could achieve the same thing, using\nworkarounds, and we’re just making it easier? And that’s a good case, here. You\ncan already do this, but we’re going to automate it. And automation is something\nthat affects larger companies a lot more, because they’ve got hundreds of\nprojects, with thousands of developers. And they just can’t deal with the scale,\nor it’s worth dealing with the automation. Whereas, if you’ve got a small\ndeveloper, with a single project, you’re pretty much on top of it. And if\nsomething changes, yeah, you just go ahead and fix it; you’re aware of it. The\nbigger challenges are when you’re just not aware of how this thing might affect\none project that somebody’s almost forgotten about.\n\nThe other thing is that, just to be blunt, our concept that Enterprise Edition\nis only for more than X people, is a little flawed. It’s that it\napplies more to those companies, that those people value it more, and they’d be\nwilling to pay for it more, or however you judge your value there. Clearly,\nsmall companies would value all this automation, and everything else, but\nthey’re not going to get as much incremental value out of it, as a larger\ncompany would.\n\n~~The other way to look at it is that this is pretty advanced stuff, and frankly,\nit doesn’t deserve to be, free, open source. It’s probably really complicated\nstuff, and you’re going to have to pay there.~~ *[Editor's note: Advancedness is not a criteria in open sourcing or not open sourcing. There are advanced features that are open source, such as [Review Apps](https://docs.gitlab.com/ee/ci/review_apps/). There are basic features that are proprietary, such as [File Locking](https://docs.gitlab.com/user/project/file_lock/). The criteria we use to decide which version the features go in are documented on our [stewardship page](/company/stewardship/#what-features-are-paid-only).]* Maybe there’d be levels to it,\nright? There’d be a version that gives you an alert: we’ll run this test once a\nday. Or even just have a blog post about how to do this: you set up a recurring,\nscheduled pipeline job, once a day, to test if any of your dependencies have\nbeen updated. And you can do that today and then it would alert you. But to\nautomate it, to actually, create a merge request for you, and everything else?\nWell, that’s in the Enterprise feature. It’s not that version checking isn’t\nimportant for everybody, but the automation around it really, really matters for\nlarger companies. Does that make sense?\n\nCourtland:\tYeah, I mean, I think that the first way you described it, in that,\n\"Yeah, everyone gets some value out a feature like this, but the overwhelming\nvalue and use for this is in larger development teams,\" that resonated.\n\n### SLO and auto revert – [#1661](https://gitlab.com/gitlab-org/gitlab-ee/issues/1661)\n\nThis is a feature showing how we’re thinking about auto reverting something.\nWe’ve got canary deployments, and we have another feature we’re not currently\nworking on or scheduled, but it’s incremental rollout, so that you would not\njust rollout to a single canary, or a bucket of canaries, but it would slowly\nincrement: 1 percent, then 5 percent, then 25 percent. But let’s say, at some point, during my\nrollout, you detect an error.\n\n![Revert](https://about.gitlab.com/images/blogimages/devops-strategy-revert.png){: .shadow}\n\nThis a mockup of what it would look like. You’re like, \"Oh, error rates\nincreased by something above our threshold; let’s revert that one, go back, and\ncreate a new issue, and alert somebody to take a look at it.\" Lately, I’m\nthinking that I don’t know if I really want to automatically roll back, versus\njust stop it in its canary form, and say, \"Well, it’s canary. Let’s let canary\nbe there, so you can debug the canary, but just don’t let the canary go on\nfurther.\"\n\nError rate exceeding is a pretty tough one. But let’s say memory bumps up, and\nyou might be like, \"Yeah, we added something, and it’s using more memory, and\nwe’re okay with that. Don’t stop my deploy just because it’s using more memory.\"\nThere might need to be human intervention in there, but somewhere along this\nline we’re automating a lot of the deploy stuff.\n\n### Onboarding and adoption – [#32638](https://gitlab.com/gitlab-org/gitlab-ce/issues/32638)\n\nOnboarding and adoption is a really big issue, with lots of different ideas for\nhow to improve onboarding, how to get people actually using idea to production,\nimproving auto deploy. Not a lot of visuals, so I won’t really talk about it,\nbut it’s definitely one of our top priorities; the next most important thing\nwe’re working on.\n\n### Cloud development – [#32637](https://gitlab.com/gitlab-org/gitlab-ce/issues/32637)\n\nCloud development is the idea that setting up your local host machine is\nactually kind of a pain sometimes. Especially with microservices, where each\nservice can be in their own language, you don’t want to maintain Java, and Ruby,\nand Node, and all these other versions of dependencies, and every time something\nswitches, you’ve got to reinstall a new version of stuff. Or even these days,\nyou might develop on an iPad, and you don’t have a local host to compile things.\n\nCloud9 is the biggest, well known thing, from an IDE perspective, and Amazon\nbought them a little while ago. But even aside from the IDE portion of it, it’s\njust being able to develop in the cloud, and being able to make some changes,\nand then push them back; commit them to a repo.\n\nWe have a little bit of a demo like this, right now, with our web terminal. So,\nif you have Kubernetes, you see this terminal button, and it just pops up the\nterminal right in the staging server. And I can actually go ahead and edit a\nfile there, and... I just made a live change into my staging app.\n\nNow, generally speaking, I would not actually recommend you do that, because\nI’m messing with my staging app, that’s not what it's for. It makes an awesome\nlittle demo, but it’s not what you should do. What we want to do is come up with\na way that people could do that, but have it be not on your staging app, but in\nmaybe a dev environment that is specifically for this purpose. But that also,\nafter you make your changes, and test them, and run them live, you can then go\nand commit them back to [version control](/topics/version-control/), and close that loop. So there’s a whole\nbunch of issues related to that. And to be honest, it was what we were hoping\nthat Koding would have provided for us, and we have an integration\nwith them, but it hasn’t worked out, really, the way that we had hoped. And so,\nwe’re looking at alternatives, and we think we can probably do this ourselves.\n\nAnyway, that’s a big thing to flesh out.\n\n### GitLab PaaS – [#32820](https://gitlab.com/gitlab-org/gitlab-ce/issues/32820)\n\nHeroku is awesome, because it gives you this really great platform that’s easy\nto use, and gives you all this functionality on top of Amazon. Five or six years\nago it was super, brain-meltingly awesome to get people to do ops. For a\ndeveloper, I don’t have to be aware of how to do ops; Heroku just does ops\nfor us.\n\nGitLab PaaS is basically the idea that you’ve got a lot of these components, and\nwe’re not going to invent them all from scratch. We’re going to rely on\nKubernetes, for example. But on top of Kubernetes, we could make an awesome\nenvironment for ops. An ops environment, or a platform as a service. And so,\nthere’s an issue to discuss what it would take to do that. At some point in\ntime, this is a big item for us. If we can make it super really easy for you to\nfully manage your ops environment via GitLab, and maybe, for example, never\ntouch the Kubernetes dashboard; never touch any of the tools, just use the\nGitLab tools to do this. That’s pretty powerful.\n\nSort of related is an idea in the onboarding stuff, that on GitLab.com\nwe can actually provide you with a Kubernetes cluster; maybe a shared cluster. We\nhave to worry about security, of course. But imagine if you were a brand new\nuser on GitLab.com, and you push up an app, and you have nothing in there\nspecifically for GitLab, you just push up your code, and GitLab is like, \"Oh,\nthat’s a Ruby app. Okay, I know how to build Ruby apps. Oh, and I also know how\nto test Ruby apps. I’m just going to go and test them automatically for you.\"\nAnd, \"Oh, by the way, I know how to deploy this. I’m just going to go ahead and\ndeploy this to production.\" And we’ll make a\nproduction.project-name.ephemeral-gitlabapps.com, whatever the hell, some domain\nso that it’s not going to affect your actual production. But if you wanted to,\nyou would just point your DNS over to this production app, and you've got the\nproduction app running on GitLab infrastructure. And that’s, really, what Heroku\nprovided, right?\n\nBut that also is an onboarding thing for us to make it really easy. Because if\nwe want everybody to have CI, well, let’s turn it on for you. That’s pretty\nawesome. If we want everybody to have CD, we can’t just turn it on for you,\nbecause you have to have a place to deploy it to. So, if we just provided you a\nKubernetes cluster (\"everybody gets a cluster\"), then you just got a place. And,\nI mean, we’ll severely limit it. We’ll make it limited in some way, so that\nyou’re not going to run the production stuff for long there. Or if you do, you have\nto pay for it. But we’re not going to try and make money off of the production\nresources. We want to make money off of making it really easy. So, really, what\nwe want to do is encourage you to, then, go and spin up your own Kubernetes\ncluster, say, on Google. And we’ll make a nice little link that says, \"Go and\nspin up a cluster on GKE.\" We’ll make that really, really easy, but to make it\nsuper easy, for some number of days, we can just provide you that cluster,\nautomatically.\n\n### Feature flags – [#779](https://gitlab.com/gitlab-org/gitlab-ee/issues/779)\n\nFeature flags are really about decoupling delivery from deployment. It’s the\nidea that you make your code, you deploy it, but you haven’t turned it on, so\nit’s not delivered yet. And the idea there is that it means you can merge in the\nmain line, more often, because it’s not affecting anybody. And, also, it really\nhelps because you can do things like: when I do deliver, I can deliver it for\ncertain people; just GitLab employees or just the Beta group, and then I can\ncontrol that rollout. So then, if there's an error rate spike, well, it’s just\na few a people and I know who they are, and they’re going to complain to me.\nIt’s no big deal. But I can test things out, get it polished, fix the problems,\nbefore rolling it out. And then, you can also do things like, roll it out to 10 percent\nof the people, 50 percent of the people, whatever. It’s all about reducing risk, and\nimproving quality, and fundamentally about getting things into your mainline\nquicker. So, it’s ops-ish, in that sense, but it’s, really, still pretty fully\non dev.\n\n### Artifact management – [#2752](https://gitlab.com/gitlab-org/gitlab-ee/issues/2752)\n\nArtifact management has become a hot topic lately. We already have a container\nregistry for Docker image artifacts, and we also have file-based artifacts that\nyou can pass between jobs, and pass between pipelines, and even pass between\ncross project pipelines. And we have ways to download them, and browse them, but\nif those artifacts happen to be things like Maven or Ruby or node modules, and\nyou want to publish them, and then consume them in other pipelines, we don’t\nhave a formal way to do that.\n\nAnd you could, obviously, publish to the open source, RubyGems, for example. But\nif you want a private Gem, that is only consumed by your team... Maybe that's\nnot as big for Ruby developers, but Java developers do that all the time. A lot\nof Java developers use Artifactory or Sonatype Nexus. In order to complete the\nDevOps tool chain, we need to have some first class support for that, either by\nbundling in one of these other providers, or by adding layers, and APIs, on top\nof our existing artifacts. My personal pet favorite right now is, let’s say we\ncan just tag our existing artifact, and say, \"Oh, this is Maven type of\nartifact,\" and then we expose that via an API and so then you can declare that\nin another project, and it would just consume the APIs, and just know how to do\nthat. But it would also use our built-in authentication so you don’t have to set\nup creds and do all this declaration; you can be like, \"Oh, I’ve got access to\nthis project and this project, so I can get the artifacts, and I can consume it\nall really easily.\"\n\n### Auto DevOps – [#35712](https://gitlab.com/gitlab-org/gitlab-ce/issues/35712)\n\n*Note: We shipped the first iteration of Auto DevOps in [10.0](/releases/2017/09/22/gitlab-10-0-released/#auto-devops)*\n\nSo, let’s talk about Auto DevOps. This spans from the near-term to the very\nlong-term. It’s great that we do a lot of DevOps, and in a very simplistic way,\nit’s like, \"Oh, but shouldn’t we just make this stuff automatic?\" The way I\nphrase it is, we should provide the best practices in an easy and default way.\nYou can set up a GitLab CI YAML, but you have to actively go and do that. But,\nreally, every project should be running some kind of CI. So, why don’t we just\ndetect when you’ve pushed up a project; we’ll just build it, and we’ll go and\ntest it, because we know how to do testing. Today, with Auto Deploy, we already\nuse Auto Build, with build packs. We will automatically detect, I think, one of\nseven different languages, and automatically build your Java app, or Ruby, or\nNode... and we use Heroku’s build packs, actually, to do this build. And so we\nbuild that up, and when using Auto Deploy, we’ll go ahead and deploy that. You\nstill have to, obviously, have a Kubernetes cluster in order to do that, so it’s\nnot fully automated if you don’t have that. But if you’ve got Kubernetes, hey,\nthis is a literally one click. You pick from a menu, say, \"Oh, I’m on\nKubernetes,\" and then hit submit, and you’ve got Auto Deploy and Auto Build.\n\nBut one of the things we don’t have is Auto CI. And that’s a little annoying,\nbut it’s one of the things we want to pick up, and actually, hopefully our CTO,\nDmitriy, is going to pick that up in Q3; it's one of his OKRs. Heroku,\nthemselves, actually extended build packs to do testing, and so that means that\nthere’s at least five build packs that know how to test these languages. And so,\nhey, let’s use that. But even if that doesn’t work, there’s a lot of other\nthings we can do. Other companies have all this stuff automated, as well. So if\nwe can’t use Heroku CI, being able to say, \"Oh, this is this language; we know\nhow to test this language,\" we'll be making that automatic.\n\nAutomatic is multiple levels of things. Is it a wizard that configures this\nstuff for me? Is it one click checkbox, that says, \"Yes, turn on auto CI,\" or is\nit templates that I can easily add into my GitLab CI YAML? I think, in order to\nqualify as auto, what we have to do here is that it shouldn’t be templates. It\nshouldn’t be blog posts that tell me how to do it. That’s just CI. It should be,\nliterally, just \"I pushed and it worked;\" or at most a checkbox or two.\n\nLet’s go further, what other thing could we just automate here? And not automate\nstrictly for the purposes of automation, but about bringing best practices to\npeople. So, you have to actively work hard, to turn these things off. If you\ndon’t want CI, then shut it off, but by default you should have this.\n\nSo, this is a really, really long list of things that will take us forever to\nget to. The first ones have links, because we’re tracking real issues for this.\nAuto Metrics is a great one. If you’re running certain languages, you should\njust be able to, really easily, go and just pull the right information out of\nthere. But whatever, the list is huge.\n\nBut the idea is that we can build up this Auto DevOps, even the marketing term,\nand start talking about it in that way, and to not just say that GitLab is great\nfor your DevOps and is a complete DevOps tool chain. But, in fact, we do all\nthis stuff for you automatically.\n\nThere’s a lot to be done to make this fully automated. And what percentage of\nprojects can we really do? Auto Deploy is a great example that only works for\nweb apps. If it’s not a web app, we can’t just deploy it. What would it mean? We\ndeploy it, and it just wouldn’t function. If you made a command line app, what\nwould deploy even mean? Or if it’s a Maven, or really any kind of module that\nyou bundled up and released, that’s not the same thing as a deploy. So, maybe we\nneed an Auto Release. It’s not on this list, but maybe it should be. But within\nthe web app space, we can do some of this stuff automatically.\n\nSo that’s it. Everything you ever wanted to know about DevOps.\n",[915,741,9],{"slug":1236,"featured":6,"template":699},"devops-strategy","content:en-us:blog:devops-strategy.yml","Devops Strategy","en-us/blog/devops-strategy.yml","en-us/blog/devops-strategy",{"_path":1242,"_dir":246,"_draft":6,"_partial":6,"_locale":7,"seo":1243,"content":1249,"config":1256,"_id":1258,"_type":14,"title":1259,"_source":16,"_file":1260,"_stem":1261,"_extension":19},"/en-us/blog/devops-workflows-json-format-jq-ci-cd-lint",{"title":1244,"description":1245,"ogTitle":1244,"ogDescription":1245,"noIndex":6,"ogImage":1246,"ogUrl":1247,"ogSiteName":686,"ogType":687,"canonicalUrls":1247,"schema":1248},"JSON formatting and CI/CD linting tips for DevOps workflows","Learn how to filter in JSON data structures and interact with the REST API. Use the GitLab API to lint your CI/CD configuration and dive into Git hooks speeding up your workflows.","https://res.cloudinary.com/about-gitlab-com/image/upload/v1749681979/Blog/Hero%20Images/gert-boers-unsplash.jpg","https://about.gitlab.com/blog/devops-workflows-json-format-jq-ci-cd-lint","\n                        {\n        \"@context\": \"https://schema.org\",\n        \"@type\": \"Article\",\n        \"headline\": \"Tips for productive DevOps workflows: JSON formatting with jq and CI/CD linting automation\",\n        \"author\": [{\"@type\":\"Person\",\"name\":\"Michael Friedrich\"}],\n        \"datePublished\": \"2021-04-21\",\n      }",{"title":1250,"description":1245,"authors":1251,"heroImage":1246,"date":1253,"body":1254,"category":717,"tags":1255},"Tips for productive DevOps workflows: JSON formatting with jq and CI/CD linting automation",[1252],"Michael Friedrich","2021-04-21","## What is JSON linting?\n\n\nTo understand JSON linting, let’s quickly break down the two concepts of\nJSON and linting. \n\n\n***JSON*** is an acronym for JavaScript Object Notation, which is a\nlightweight, text-based, open standard format designed specifically for\nrepresenting structured data based on the JavaScript object syntax. It is\nmost commonly used for transmitting data in web applications. It parses data\nfaster than XML and is easy for humans to read and write.\n\n\n***Linting*** is a process that automatically checks and analyzes static\nsource code for programming and stylistic errors, bugs and suspicious\nconstructs. \n\n\nJSON has become popular because it is human-readable and doesn’t require a\ncomplete markup structure like XML. It is easy to analyze into logical\nsyntactic components, especially in JavaScript. It also has many JSON\nlibraries for most programming languages.\n\n\n### Benefits of JSON linting\n\n\nFinding an error in JSON code can be challenging and time-consuming. The\nbest way to find and correct errors while simultaneously saving time is to\nuse a linting tool. When Json code is copied and pasted into the linting\neditor, it validates and reformats Json. It is easy to use and supports a\nwide range of browsers, so applications development with Json coding don’t\nrequire a lot of effort to make them browser-compatible.\n\n\nJSON linting is an efficient way to reduce errors and it improves the\noverall quality of the JSON code. This can help accelerate development and\nreduce costs because errors are discovered earlier.\n\n\n### Some common JSON linting errors\n\n\nIn instances where a JSON transaction fails, the error information is\nconveyed to the user by the API gateway. By default, the API gateway returns\na very basic fault to the client when a message filter has failed.\n\n\nOne common JSON linting error is parsing. A “parse: unexpected character\"\nerror occurs when passing a value that is not a valid JSON string to the\nJSON. parse method, for example, a native JavaScript object. To solve the\nerror, make sure to only pass valid JSON strings to the JSON.\n\n\nAnother common error is NULL or inaccurate data errors, not using the right\ndata type per column or extension for JSON files, and not ensuring every row\nin the JSON table is in the JSON format.\n\n\n### How to fix JSON linting errors\n\n\nIf you encounter a NULL or inaccurate data error in parsing, the first step\nis to make sure you use the right data type per column. For example, in the\ncase of “age,” use 12 instead of twelve.\n\n\nAlso make sure you are using the right extension for JSON files. When using\na compressed JSON file, it must end with “json” followed by the extension of\nthe format, such as “.gz.”\n\n\nNext, make sure the JSON format is used for every row in the JSON table.\nCreate a table with a delimiter that is not in the input files. Then, run a\nquery equivalent to the return name of the file, row points and the file\npath for the null NSON rows.\n\n\nSometimes you may find files that are not your source code files, but ones\ngenerated by the system when compiling your project. In that instance, when\nthe file has a .js extension, the ESLint needs to exclude that file when\nsearching for errors. One method of doing this is by using ‘IgnorePatterns:’\nin .eslintrc.json file either after or before the “rules” tag.\n\n\n“ignorePatterns”: [“temp.js”, “**/vendor/*.js”],\n\n\n“rules”: {\n\n\nAlternatively, you can create a separate file named‘.eslintignore’ and\nincorporate the files to be excluded as shown below :\n\n**/*.js\n\nIf you opt to correct instead of ignore, look for the error code in the last\ncolumn. Correct all the errors in one fule and rerun ‘npx eslint . >errfile’\nand ensure all the errors of that type are cleared. Then look for the next\nerror code and repeat the procedure until all errors are cleared.\n\n\nOf course, there will be instances when you won’t understand an error, so in\nthat case, open\n[https://eslint.org/docs/user-guide/getting-started](https://eslint.org/docs/user-guide/getting-started)\nand type the error code in the ‘Search’ field on the top of the document.\nThere you will find very detailed instructions as to why that error is\nraised and how to fix it.\n\n\nFinally, you can forcibly fix errors automatically while generating the\nerror list using:\n\n\nNpx eslintrc . — fix \n\n\nThis is not recommended until you become more well-versed with lint errors\nand how to fix them. Also, you should keep a backup of the files you are\nlinting because while fixing errors, certain code may get overwritten, which\ncould cause your program to fail.\n\n\n## JSON linting best practices\n\n\nHere are some tips for helping your consumers use your output:\n\n\nFirst, always enclose the **Key** **:** **Value** pair within **double\nquotes**. It may be convenient (not sure how) to generate with Single\nquotes, but JSON parser don’t like to parse JSON objects with single quotes.\n\n\nFor numerical values, quotes are optional but it is a good idea to enclose\nthem in double quotes.\n\n\nNext, don’t ever use hyphens in your key fields because it breaks python and\nscala parser. Instead use underscores (_). \n\n\nIt’s a good idea to always create a root element, especially when you’re\ncreating a complicated JSON.\n\n\n\nModern web applications come with a REST API which returns JSON. The format\nneeds to be parsed, and often feeds into scripts and service daemons polling\nthe API for automation.\n\n\nStarting with a new REST API and its endpoints can often be overwhelming.\nDocumentation may suggest looking into a set of SDKs and libraries for\nvarious languages, or instruct you to use `curl` or `wget` on the CLI to\nsend a request. Both CLI tools come with a variety of parameters which help\nto download and print the response string, for example in JSON format.\n\n\nThe response string retrieved from `curl` may get long and confusing. It can\nrequire parsing the JSON format and filtering for a smaller subset of\nresults. This helps with viewing the results on the CLI, and minimizes the\ndata to process in scripts. The following example retrieves all projects\nfrom GitLab and returns a paginated result set with the first 20 projects:\n\n\n```shell\n\n$ curl \"https://gitlab.com/api/v4/projects\"\n\n```\n\n\n![Raw JSON as API\nresponse](https://about.gitlab.com/images/blogimages/devops-workflows-json-format-jq-ci-cd-lint/gitlab_api_response_raw_json.png){:\n.shadow}\n\n\nThe [GitLab REST API\ndocumentation](https://docs.gitlab.com/ee/api/#how-to-use-the-api) guides\nyou through the first steps with error handling and authentication. In this\nblog post, we will be using the [Personal Access\nToken](https://docs.gitlab.com/ee/api/#personalproject-access-tokens) as the\nauthentication method. Alternatively, you can use [project access\ntokens](https://docs.gitlab.com/ee/user/project/settings/project_access_tokens.html)\nfor [automated\nauthentication](https://docs.gitlab.com/ee/api/#authentication) that avoids\nthe use of personal credentials.\n\n\n### REST API authentication\n\n\nSince not all endpoints are accessible with anonymous access they might\nrequire authentication. Try fetching user profile data with this request:\n\n\n```shell\n\n$ curl \"https://gitlab.com/api/v4/user\"\n\n{\"message\":\"401 Unauthorized\"}\n\n```\n\n\nThe API request against the `/user` endpoint requires to pass the personal\naccess token into the request, for example, as a request header. To avoid\nexposing credentials on the terminal, you can export the token and its value\ninto the user's environment. You can automate the variable export with ZSH\nand the [.env\nplugin](https://github.com/ohmyzsh/ohmyzsh/tree/master/plugins/dotenv) in\nyour shell environment. You can also source the `.env` once in the existing\nshell environment.\n\n\n```shell\n\n$ vim ~/.env\n\n\nexport GITLAB_TOKEN=”...”\n\n\n$ source ~/.env\n\n```\n\n\nScripts and commands being run in your shell environment can reference the\n`$GITLAB_TOKEN` variable. Try querying the user API endpoint again, with\nadding the authorization header into the request:\n\n\n```shell\n\n$ curl -H \"Authorization: Bearer $GITLAB_TOKEN\"\n\"https://gitlab.com/api/v4/user\"\n\n```\n\n\nA reminder that only administrators can see the attributes of all users, and\nthe individual can only see their user profile – for example, `email` is\nhidden from the public domain.\n\n\n### How to request responses in JSON\n\n\nThe [GitLab API provides many\nresources](https://docs.gitlab.com/ee/api/api_resources.html) and URL\nendpoints. You can manage almost anything with the API that you’d otherwise\nconfigure using the graphic user interface.\n\n\nAfter sending the [API\nrequest](https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol#Request_message),\nthe [response\nmessage](https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol#Response_message)\ncontains the body as string, for example as a [JSON content\ntype](https://docs.gitlab.com/ee/api/#content-type). `curl` can provide more\ninformation about the response headers which is helpful for debugging.\nMultiple verbose levels enable the full debug output with `-vvv`:\n\n\n```shell\n\n$ curl -vvv \"https://gitlab.com/api/v4/projects\"\n\n[...]\n\n* SSL connection using TLSv1.2 / ECDHE-RSA-CHACHA20-POLY1305\n\n* ALPN, server accepted to use h2\n\n* Server certificate:\n\n*  subject: CN=gitlab.com\n\n*  start date: Jan 21 00:00:00 2021 GMT\n\n*  expire date: May 11 23:59:59 2021 GMT\n\n*  subjectAltName: host \"gitlab.com\" matched cert's \"gitlab.com\"\n\n*  issuer: C=GB; ST=Greater Manchester; L=Salford; O=Sectigo Limited;\nCN=Sectigo RSA Domain Validation Secure Server CA\n\n*  SSL certificate verify ok.\n\n[...]\n\n> GET /api/v4/projects HTTP/2\n\n> Host: gitlab.com\n\n> User-Agent: curl/7.64.1\n\n> Accept: */*\n\n[...]\n\n\u003C HTTP/2 200\n\n\u003C date: Mon, 19 Apr 2021 11:25:31 GMT\n\n\u003C content-type: application/json\n\n[...]\n\n[{\"id\":25993690,\"description\":\"project for adding\nissues\",\"name\":\"project-for-issues-1e1b6d5f938fb240\",\"name_with_namespace\":\"gitlab-qa-sandbox-group\n/ qa-test-2021-04-19-11-13-01-d7d873fd43cd34b6 /\nproject-for-issues-1e1b6d5f938fb240\",\"path\":\"project-for-issues-1e1b6d5f938fb240\",\"path_with_namespace\":\"gitlab-qa-sandbox-group/qa-test-2021-04-19-11-13-01-d7d873fd43cd34b6/project-for-issues-1e1b6d5f938fb240\"\n\n\n[... JSON content ...]\n\n\n\"avatar_url\":null,\"web_url\":\"https://gitlab.com/groups/gitlab-qa-sandbox-group/qa-test-2021-04-19-11-12-56-7f3128bd0e41b92f\"}}]\n\n* Closing connection 0\n\n```\n\n\nThe `curl` command output provides helpful insights into TLS ciphers and\nversions, the request lines starting with `>` and response lines starting\nwith `\u003C`. The response body string is encoded as JSON.\n\n\n### How to see the structure of the returned JSON\n\n\nTo get a quick look at the structure of the returned JSON file, try these\ntips:\n\n\n* Enclose square brackets to identify an array `[ …. ]`.\n\n* Enclose curly brackets identify a\n[dictionary](https://en.wikipedia.org/wiki/Associative_array) `{ … }`.\nDictionaries are also called associative arrays, maps, etc.\n\n* `”key”: value` indicates a key-value pair in a dictionary, which is\nidentified by curly brackets enclosing the key-value pairs.\n\n\nThe values in [JSON](https://en.wikipedia.org/wiki/JSON) consist of specific\ntypes - a string value is put in double-quotes. Boolean true/false, numbers,\nand floating-point numbers are also present as types. If a key exists but\nits value is not set, REST APIs often return `null`.\n\n\nVerify the data structure by running \"linters\". Python's JSON module can\nparse and lint JSON strings. The example below misses a closing square\nbracket to showcase the error:\n\n\n```shell\n\n$ echo '[{\"key\": \"broken\"}' | python -m json.tool\n\nExpecting object: line 1 column 19 (char 18)\n\n```\n\n\n[jq](https://stedolan.github.io/jq/) – a lightweight and flexible CLI\nprocessor – can be used as a standalone tool to parse and validate JSON\ndata.\n\n\n```shell\n\n$ echo '[{\"key\": \"broken\"}' | jq\n\nparse error: Unfinished JSON term at EOF at line 2, column 0\n\n```\n\n\n[`jq` is available](https://stedolan.github.io/jq/download/) in the package\nmanagers of most operating systems.\n\n\n```shell\n\n$ brew install jq\n\n$ apt install jq\n\n$ dnf install jq\n\n$ zypper in jq\n\n$ pacman -S jq\n\n$ apk add jq\n\n```\n\n\n### Dive deep into JSON data structures\n\n\nThe true power of `jq` lies in how it can be used to parse JSON data:\n\n\n> `jq` is like `sed` for JSON data. It can be used to slice, filter, map,\nand transform structured data with the same ease that `sed`, `awk`, `grep`\netc., let you manipulate text.\n\n\nThe output below shows how it looks to run the request against the project\nAPI again, but this time, the output is piped to `jq`.\n\n\n```shell\n\n$ curl \"https://gitlab.com/api/v4/projects\" | jq\n\n[\n  {\n    \"id\": 25994891,\n    \"description\": \"...\",\n    \"name\": \"...\",\n\n[...]\n\n    \"forks_count\": 0,\n    \"star_count\": 0,\n    \"last_activity_at\": \"2021-04-19T11:50:24.292Z\",\n    \"namespace\": {\n      \"id\": 11528141,\n      \"name\": \"...\",\n\n[...]\n\n    }\n  }\n]\n\n```\n\n\nThe first difference is the format of the JSON data structure, so-called\n[pretty-printed](https://en.wikipedia.org/wiki/Prettyprint). New lines and\nindents in data structure scopes help your eyes and allow you to identify\nthe inner and outer data structures involved. This format is needed to\ndetermine which `jq` filters and methods you want to apply next.\n\n\n#### About arrays and dictionaries\n\n\nThe set of results from an API often is returned as a list (or \"array\") of\nitems. An item itself can be a single value or a JSON object. The following\nexample mimics the response from the GitLab API and creates an array of\ndictionaries as a nested result set.\n\n\n```shell\n\n$ vim result.json\n\n[\n  {\n    \"id\": 1,\n    \"name\": \"project1\"\n  },\n  {\n    \"id\": 2,\n    \"name\": \"project2\"\n  },\n  {\n    \"id\": 3,\n    \"name\": \"project-internal-dev\",\n    \"namespace\": {\n      \"name\": \"🦊\"\n    }\n  }\n]\n\n```\n\n\nUse `cat` to print the file content on stdout and pipe it into `jq`. The\nouter data structure is an array – use `-c .[]` to access and print all\nitems.\n\n\n```shell\n\n$ cat result.json | jq -c '.[]'\n\n{\"id\":1,\"name\":\"project1\"}\n\n{\"id\":2,\"name\":\"project2\"}\n\n{\"id\":3,\"name\":\"project-internal-dev\",\"namespace\":{\"name\":\"🦊\"}}\n\n```\n\n\n### How to filter data structures with `jq`\n\n\nFilter items by passing `| select (...)` to `jq`. The filter takes a lambda\ncallback function as a comparator condition. When the item matches the\ncondition, it is returned to the caller.\n\n\nUse the dot indexer `.` to access dictionary keys and their values. Try to\nfilter for all items where the name is `project2`:\n\n\n```shell\n\n$ cat result.json | jq -c '.[] | select (.name == \"project2\")'\n\n{\"id\":2,\"name\":\"project2\"}\n\n```\n\n\nPractice this example by selecting the `id` with the value `2` instead of\nthe `name`.\n\n\n#### Filter with matching a string\n\n\nDuring tests, you may need to match different patterns instead of knowing\nthe full name. Think of projects that match a specific path or are located\nin a group where you only know the prefix. Simple string matches can be\nachieved with the `| contains (...)` function. It allows you to check\nwhether the given string is inside the target string – which requires the\nselected attribute to be of the string type.\n\n\nFor a filter with the select chain, the comparison condition needs to be\nchanged from the equal operator `==` to checking the attribute `.name` with\n`| contains (\"dev\")`.\n\n\n```shell\n\n$ cat result.json | jq -c '.[] | select (.name | contains (\"dev\") )'\n\n{\"id\":3,\"name\":\"project-internal-dev\",\"namespace\":{\"name\":\"🦊\"}}\n\n```\n\n\nSimple matches can be achieved with the `contains` function.\n\n\n#### Filter with matching regular expressions\n\n\nFor advanced string pattern matching, it is recommended to use regular\nexpressions. `jq` provides the [test function for this use\ncase](https://stedolan.github.io/jq/manual/#RegularexpressionsPCRE). Try to\nfilter for all projects which end with a number, represented by `\\d+`. Note\nthat the backslash `\\` needs to be escaped as `\\\\` for shell execution. `^`\ntests for beginning of the string, `$` is the ending check.\n\n\n```shell\n\n$ cat result.json | jq -c '.[] | select (.name | test (\"^project\\\\d+$\") )'\n\n{\"id\":1,\"name\":\"project1\"}\n\n{\"id\":2,\"name\":\"project2\"}\n\n```\n\n\nTip: You can [test and build the regular expression with\nregex101](https://regex101.com/) before test-driving it with `jq`.\n\n\n#### Access nested values\n\n\nKey value pairs in a dictionary may have a dictionary or array as a value.\n`jq` filters need to take this factor into account when filtering or\ntransforming the result. The example data structure provides\n`project-internal-dev` which has the key `namespace` and a value of a\ndictionary type.\n\n\n```shell\n  {\n    \"id\": 3,\n    \"name\": \"project-internal-dev\",\n    \"namespace\": {\n      \"name\": \"🦊\"\n    }\n  }\n```\n\n\n`jq` allows the user to specify the [array and dictionary\ntypes](https://stedolan.github.io/jq/manual/#TypesandValues) as `[]` and\n`{}` to be used in select chains with greater and less than comparisons. The\n`[]` brackets select filters for non-empty dictionaries for the `namespace`\nattribute, while the `{}` brackets select for all `null` (raw JSON) values.\n\n\n```shell\n\n$ cat result.json | jq -c '.[] | select (.namespace >={} )'\n\n{\"id\":3,\"name\":\"project-internal-dev\",\"namespace\":{\"name\":\"🦊\"}}\n\n\n$ cat result.json | jq -c '.[] | select (.namespace \u003C={} )'\n\n{\"id\":1,\"name\":\"project1\"}\n\n{\"id\":2,\"name\":\"project2\"}\n\n```\n\n\nThese methods can be used to access the name attribute of the namespace, but\nonly if the namespace contains values. Tip: You can chain multiple `jq`\ncalls by piping the result into another `jq` call. `.name` is a subkey of\nthe primary `.namespace` key.\n\n\n```shell\n\n$ cat result.json | jq -c '.[] | select (.namespace >={} )' | jq -c\n'.namespace.name'\n\n\"🦊\"\n\n```\n\n\nThe additional select command with non-empty namespaces ensures that only\ninitialized values for `.namespace.name` are returned. This is a safety\ncheck, and avoids receiving `null` values in the result you would need to\nfilter again.\n\n\n```shell\n\n$ cat result.json| jq -c '.[]' | jq -c '.namespace.name'\n\nnull\n\nnull\n\n\"🦊\"\n\n```\n\n\nBy using the additional check with `| select (.namespace >={} )`, you only\nget the expected results and do not have to filter empty `null` values.\n\n\n### How to expand the GitLab endpoint response\n\n\nSave the result from the API projects call and retry the examples above with\n`jq`.\n\n\n```shell\n\n$ curl \"https://gitlab.com/api/v4/projects\" -o result.json 2&>1 >/dev/null\n\n```\n\n\n### Validate CI/CD YAML with `jq` for Git hooks\n\n\nWhile writing this blog post, I learned that you can [escape and encode YAML\ninto JSON with\n`jq`](https://docs.gitlab.com/ee/api/lint.html#escape-yaml-for-json-encoding).\nThis trick comes in handy when automating YAML linting on the CLI, for\nexample as a Git pre-commit hook.\n\n\nLet’s take a look at the simplest way to test GitLab CI/CD from our\n[community meetup\nworkshops](https://gitlab.com/gitlab-de/swiss-meetup-2021-jan#resources). A\ncommon mistake with the first steps of the process can be missing the two\nspaces indent or missing whitespace between the dash and following command.\nThe following examples use `.gitlab-ci.error.yml` as a filename to showcase\nerrors and `.gitlab-ci.main.yml` for working examples.\n\n\n```shell\n\n$ vim .gitlab-ci.error.yml\n\n\nimage: alpine:latest\n\n\ntest:\n\nscript:\n  -exit 1\n```\n\n\nCommitting the change and waiting for the CI/CD pipeline to validate at\nruntime can be time-consuming. The [GitLab API provides a resource endpoint\n/ci/lint](https://docs.gitlab.com/ee/api/lint.html#validate-the-ci-yaml-configuration).\nA POST request with JSON-encoded YAML content will return a linting result\nfaster.\n\n\n#### Parse CI/CD YAML into JSON with jq\n\n\nYou can use jq to parse the raw YAML string into JSON:\n\n\n```shell\n\n$ jq --raw-input --slurp \u003C .gitlab-ci.error.yml\n\n\"image: alpine:latest\\n\\ntest:\\nscript:\\n  -exit 1\\n\"\n\n```\n\n\nThe `/ci/lint` API endpoint requires a JSON dictionary with `content` as\nkey, and the raw YAML string as a value. You can use `jq` to format the\ninput by using the arg parser:\n\n\n```shell\n\n§ jq --null-input --arg yaml \"$(\u003C.gitlab-ci.error.yml)\" '.content=$yaml'\n\n{\n  \"content\": \"image: alpine:latest\\n\\ntest:\\nscript:\\n  -exit 1\"\n}\n\n```\n\n\n#### Send POST request to /ci/lint\n\n\nThe next building block is to [send a POST request to the\n/ci/lint](https://docs.gitlab.com/ee/api/lint.html#validate-the-ci-yaml-configuration).\nThe request needs to specify the `Content-Type` header for the body. With\nusing the pipe `|` character, the JSON-encoded YAML configuration is fed\ninto the curl command call.\n\n\n```shell\n\n$ jq --null-input --arg yaml \"$(\u003C.gitlab-ci.error.yml)\" '.content=$yaml' \\\n\n| curl \"https://gitlab.com/api/v4/ci/lint?include_merged_yaml=true\" \\\n\n--header 'Content-Type: application/json' --data @-\n\n{\"status\":\"invalid\",\"errors\":[\"jobs test config should implement a script:\nor a trigger: keyword\",\"jobs script config should implement a script: or a\ntrigger: keyword\",\"jobs config should contain at least one visible\njob\"],\"warnings\":[],\"merged_yaml\":\"\n",[9,741,742],{"slug":1257,"featured":6,"template":699},"devops-workflows-json-format-jq-ci-cd-lint","content:en-us:blog:devops-workflows-json-format-jq-ci-cd-lint.yml","Devops Workflows Json Format Jq Ci Cd Lint","en-us/blog/devops-workflows-json-format-jq-ci-cd-lint.yml","en-us/blog/devops-workflows-json-format-jq-ci-cd-lint",{"_path":1263,"_dir":246,"_draft":6,"_partial":6,"_locale":7,"seo":1264,"content":1270,"config":1275,"_id":1277,"_type":14,"title":1278,"_source":16,"_file":1279,"_stem":1280,"_extension":19},"/en-us/blog/distributed-version-control",{"title":1265,"description":1266,"ogTitle":1265,"ogDescription":1266,"noIndex":6,"ogImage":1267,"ogUrl":1268,"ogSiteName":686,"ogType":687,"canonicalUrls":1268,"schema":1269},"Distributed Version Control & Collaboration","Developers can collaborate and work together in distributed environments. Adopt diverse integration patterns for branching, merging and code reviews along with granular permissions schemes ensuring code quality and safety.","https://res.cloudinary.com/about-gitlab-com/image/upload/v1749668242/Blog/Hero%20Images/assembly-3830652.jpg","https://about.gitlab.com/blog/distributed-version-control","\n                        {\n        \"@context\": \"https://schema.org\",\n        \"@type\": \"Article\",\n        \"headline\": \"Distributed Version Control & Collaboration\",\n        \"author\": [{\"@type\":\"Person\",\"name\":\"William Arias\"}],\n        \"datePublished\": \"2020-10-02\",\n      }",{"title":1265,"description":1266,"authors":1271,"heroImage":1267,"date":1272,"body":1273,"category":1088,"tags":1274},[1128],"2020-10-02","\n{::options parse_block_html=\"true\" /}\n\n\n\n[Distributed Version Control](/topics/version-control/)  allows  remote, collaborative work to flourish since a single copy of the complete project's history can be stored in any machine. But Distributed Version Control goes beyond every developer having a copy of the project in their machines, in fact it sets the foundation for a team to decide what strategy they need to adopt to deliver software.\n\n\nWith Gitlab a team can choose and adapt to different branching strategies that enable Continuous Integration being an example of that   high-frequency integration patterns where developers push very often local commits to the main branch, this is achieved through Gitlab Merge Request that favors short-lived branches augmenting the frequency of merges. \nDistributed Version Control and Collaboration  are cornerstone for software development lifecycle, Watch this video to see its capabilities in action\n\n\u003Ciframe width=\"560\" height=\"315\" src=\"https://www.youtube.com/embed/JAgIEdYhj00\" frameborder=\"0\" allow=\"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture\" allowfullscreen>\u003C/iframe>\n\n\nCover image credit:\n\nCover image by [www_slon_pics](https://pixabay.com/photos/assembly-carpenter-carpentry-3830652/) on [pixabay](https://pixabay.com)\n{: .note}\n\n",[9,9],{"slug":1276,"featured":6,"template":699},"distributed-version-control","content:en-us:blog:distributed-version-control.yml","Distributed Version Control","en-us/blog/distributed-version-control.yml","en-us/blog/distributed-version-control",{"_path":1282,"_dir":246,"_draft":6,"_partial":6,"_locale":7,"seo":1283,"content":1289,"config":1295,"_id":1297,"_type":14,"title":1298,"_source":16,"_file":1299,"_stem":1300,"_extension":19},"/en-us/blog/docker-my-precious",{"title":1284,"description":1285,"ogTitle":1284,"ogDescription":1285,"noIndex":6,"ogImage":1286,"ogUrl":1287,"ogSiteName":686,"ogType":687,"canonicalUrls":1287,"schema":1288},"Continuous integration: From Jenkins to GitLab using Docker","We're migrating all of our working tools to open source ones, and moving to GitLab has made all the difference.","https://res.cloudinary.com/about-gitlab-com/image/upload/v1749667509/Blog/Hero%20Images/continuous-integration-from-jenkins-to-gitlab-using-docker.jpg","https://about.gitlab.com/blog/docker-my-precious","\n                        {\n        \"@context\": \"https://schema.org\",\n        \"@type\": \"Article\",\n        \"headline\": \"Continuous integration: From Jenkins to GitLab using Docker\",\n        \"author\": [{\"@type\":\"Person\",\"name\":\"Abdulkader Benchi\"}],\n        \"datePublished\": \"2017-07-27\",\n      }",{"title":1284,"description":1285,"authors":1290,"heroImage":1286,"date":1292,"body":1293,"category":694,"tags":1294},[1291],"Abdulkader Benchi","2017-07-27","\n\n Here at [Linagora](https://linagora.com/), we are migrating all our working tools to open source ones. Yes, we are an open source company with open source lovers.\n\n\u003C!-- more -->\n\nAmong these different tools were the [Atlassian](https://www.atlassian.com/) development tools. We decided to switch to GitLab and it started making all the difference. Indeed, GitLab includes Git repository management, issue tracking, code review, an IDE, activity streams, wikis, and more. It's worth mentioning that GitLab has built-in [Continuous Integration (CI) and Continuous Deployment (CD)](/topics/ci-cd/) to test, build, and deploy our code. We can easily monitor the progress of our tests and build pipelines. What we love about the CI provided by GitLab is the fact that it supports Docker. Indeed, GitLab allows us to use custom [Docker](https://www.docker.com/) images, spin up services as part of testing, build new Docker images, even run on [Kubernetes](https://kubernetes.io/).\n\nIf you are a Docker lover and you want to see how to transform [Jenkins](https://jenkins.io/) CI to GitLab CI using Docker, then you are in the right place.\n\n### Jenkins job\n\nLet’s have a look at our Jenkins Job.\n\n```\nMONGOPORT=23500\nBASEDIR=`pwd`\n\n# Update tools\n(cd repo && composer update)\n\n# Run code style checker\n./repo/vendor/bin/phpcs -p --standard=repo/vendor/sabre/dav/tests/phpcs/ruleset.xml --report-checkstyle=checkstyle.xml repo/lib/\n\n# Cleanup\nrm -rf mongodb\nrm -f mongo.pid\nrm -f mongo.log\nmkdir -p mongodb\n\n\n# Start temporary mongo server\nmongod --dbpath mongodb \\\n       --port $MONGOPORT \\\n       --pidfilepath $BASEDIR/mongo.pid \\\n       --logpath mongo.log \\\n        --fork\n\nsleep 2\n\n# Configure\ncat \u003C\u003CEOF > repo/config.json\n{\n  \"webserver\": {\n    \"baseUri\": \"/\",\n    \"allowOrigin\": \"*\"\n  },\n  \"database\": {\n    \"esn\": {\n      \"connectionString\" : \"mongodb://localhost:$MONGOPORT/\",\n      \"db\": \"esn\",\n      \"connectionOptions\": {\n        \"w\": 1,\n        \"fsync\": true,\n        \"connectTimeoutMS\": 10000\n      }\n    },\n    \"sabre\": {\n      \"connectionString\" : \"mongodb://localhost:$MONGOPORT/\",\n      \"db\": \"sabredav\",\n      \"connectionOptions\": {\n        \"w\": 1,\n        \"fsync\": true,\n        \"connectTimeoutMS\": 10000\n      }\n    }\n  },\n  \"esn\": {\n    \"apiRoot\": \"http://localhost:8080/api\"\n  }\n}\nEOF\n\n# Run unit tests\n(cd repo/tests && ../vendor/bin/phpunit \\\n    --coverage-clover=$BASEDIR/clover.xml \\\n    --log-junit=$BASEDIR/junit.xml \\\n    .)\n\n# Clean up\nkill `cat mongo.pid`\n```\n\nI know, it is horrible to read this configuration, but you know we have to configure everything from A to Z in Jenkins. I can confirm that this job is one of the simplest jobs we have, because it depends on only one external service, “MongoDB.” We passed almost half of this job configuring this external service, starting it, cleaning it and killing it. Whereas, our main job is only about 10 lines. Furthermore, we suppose that on the Jenkins machine, we already have installed PHP, all PHP plugins and composer. So if we change the machine we have to reconfigure the new machine before starting using it. Docker… help please.\n\n![Docker help us](https://about.gitlab.com/images/blogimages/sos-docker.jpg){: .shadow}\u003Cbr>\n\n### GitLab job\n\nBefore starting, it's worth mentioning that good documentation about this part is presented [here](https://docs.gitlab.com/ee/ci/docker/using_docker_images.html). If you got it right, all GitLab’s CI configuration is to be done in a file called .gitlab-ci.yml. I will start presenting the final result before discussing the details:\n\n```\nimage: linagora/php-deps-composer:5.6.30\n\nservices:\n  - mongo:3.2\n\nstages:\n  - build\n  - deploy_dev\n\nbuild:\n  stage: build\n  script:\n    - composer up\n    - cp config.tests.json config.json\n    - ./vendor/bin/phpcs -p --standard=vendor/sabre/dav/tests/phpcs/ruleset.xml --report-checkstyle=checkstyle.xml lib/\n    - cd tests\n    - ../vendor/bin/phpunit --coverage-clover=${CI_PROJECT_DIR}/clover.xml --log-junit=${CI_PROJECT_DIR}/junit.xml .\n\ndeploy_dev:\n  stage: deploy_dev\n  only:\n    - master\n  script:\n    - cd /srv/sabre.dev\n    - git fetch --all\n    - git checkout ${CI_COMMIT_SHA}\n    - composer up\n```\n\n### Migration procedure\n\nWe start defining the image of which of GitLab’s Docker executors will run to perform the CI tasks. This is done by using the image keyword (line 1). This is a custom image we build to provide all the dependencies we need for our CI tasks. Here is the corresponding Dockerfile:\n\n```\nFROM php:5.6.30\n\nMAINTAINER Linagora Folks \u003Clgs-openpaas-dev@linagora.com>\n\nRUN apt-get update \\\n    apt-get -y install unzip git php5-curl php5-dev php-amqplib \\\n    docker-php-ext-install bcmath \\\n    pecl install mongo \\\n    docker-php-ext-enable mongo \\\n    curl https://getcomposer.org/installer | php \\\n    mv composer.phar /usr/local/bin/composer.phar \\\n    ln -s /usr/local/bin/composer.phar /usr/local/bin/composer\n```\n\nAs I mentioned before, our CI requires an external MongoDB service. But this time, Docker is here to do the magic. It helps us with configuring, starting and killing the service correctly. All what we have to do is to declare mongo as a service (line 4), et voilà!\n\nNow we have set up our environment, we can leverage script tag to test our code (lines 13–17) and deploy it (lines 24–27). It is worth noting that config.test.json contains all the configuration we have had in Jenkins (Lines 28–56 from Jenkins configuration).\n\n#### Running the GitLab job locally\n\nWe can easily test our GitLab builds locally using [GitLab Runner](https://gitlab.com/gitlab-org/gitlab-runner/blob/master/docs/commands/README.md). Here is the procedure:\n\n* Install it locally, either using a package repository or directly from here. If you do not want to install the GitLab Runner locally, you can always leverage Docker to do so. Have a look [here](https://gitlab.com/gitlab-org/gitlab-runner/issues/312).\n* Run the build: gitlab-runner exec docker {my-job}. Whereas, my-job is the name of the job defined in .gitlab-ci.yml. In our case, it is called build.\n\n#### Wrap up\n\nAs you can see, our CI job becomes easier to read thanks to GitLab and Docker. Along the same lines, we do not need to configure our machine to run tests anymore. Docker has got our back. In my opinion, the most important advantage of using Docker to run tests is to guarantee that our tests are always being run in the same conditions each time. These tests are totally isolated (and also independent) from the machine on which they run.\n\nThis post originally appeared on _[Medium](https://medium.com/linagora-engineering/docker-my-precious-6efbce900dcb)_.\n\n### About the Guest Author\n\nAbdulkader Benchi is the Javascript team leader at [Linagora](https://linagora.com/careers).\n",[916,9],{"slug":1296,"featured":6,"template":699},"docker-my-precious","content:en-us:blog:docker-my-precious.yml","Docker My Precious","en-us/blog/docker-my-precious.yml","en-us/blog/docker-my-precious",{"_path":1302,"_dir":246,"_draft":6,"_partial":6,"_locale":7,"seo":1303,"content":1308,"config":1315,"_id":1317,"_type":14,"title":1318,"_source":16,"_file":1319,"_stem":1320,"_extension":19},"/en-us/blog/efficient-devsecops-workflows-with-rules-for-conditional-pipelines",{"title":1304,"description":1305,"ogTitle":1304,"ogDescription":1305,"noIndex":6,"ogImage":754,"ogUrl":1306,"ogSiteName":686,"ogType":687,"canonicalUrls":1306,"schema":1307},"DevSecOps workflows with conditional CI/CD pipeline rules","CI/CD pipelines can be simple or complex, what makes them efficient are CI rules that define when and how they run.","https://about.gitlab.com/blog/efficient-devsecops-workflows-with-rules-for-conditional-pipelines","\n                        {\n        \"@context\": \"https://schema.org\",\n        \"@type\": \"Article\",\n        \"headline\": \"How to create efficient DevSecOps workflows with rules for conditional CI/CD pipelines\",\n        \"author\": [{\"@type\":\"Person\",\"name\":\"Abubakar Siddiq Ango\"}],\n        \"datePublished\": \"2023-06-27\",\n      }",{"title":1309,"description":1305,"authors":1310,"heroImage":754,"date":1312,"body":1313,"category":717,"tags":1314},"How to create efficient DevSecOps workflows with rules for conditional CI/CD pipelines",[1311],"Abubakar Siddiq Ango","2023-06-27","CI/CD pipelines can be simple or complex – what makes them efficient are\nrules that define when and how they run. By using rules, you create smarter\nCI/CD pipelines, which increase teams' productivity and allow organizations\nto iterate faster. In this tutorial, you will learn about the different\ntypes of CI/CD pipelines and rules and their use cases.\n\n\n## What is a pipeline?\n\nA pipeline is a top-level component of [continuous\nintegration](https://docs.gitlab.com/ee/ci/introduction/index.html#continuous-integration)\nand [continuous\ndelivery](https://docs.gitlab.com/ee/ci/introduction/index.html#continuous-delivery)/[continuous\ndeployment](https://docs.gitlab.com/ee/ci/introduction/index.html#continuous-deployment),\nand it comprises [jobs](https://docs.gitlab.com/ee/ci/jobs/index.html),\nwhich are lists of tasks to be executed. Jobs are organized in\n[stages](https://docs.gitlab.com/ee/ci/yaml/index.html#stages), which define\nwhen the jobs run.\n\n\nA pipeline can be a [basic\none](https://docs.gitlab.com/ee/ci/pipelines/pipeline_architectures.html#basic-pipelines)\nin which jobs run concurrently in each stage. Pipelines can also be complex,\nlike [parent-child\npipelines](https://docs.gitlab.com/ee/ci/pipelines/downstream_pipelines.html#parent-child-pipelines),\n[merge trains](https://docs.gitlab.com/ee/ci/pipelines/merge_trains.html),\n[multi-project\npipelines](https://docs.gitlab.com/ee/ci/pipelines/downstream_pipelines.html#multi-project-pipelines),\nor the more advanced [Directed Acyclic Graph\npipelines](https://docs.gitlab.com/ee/ci/directed_acyclic_graph/index.html)\n(DAG).\n\n\n![Complex pipeline showing\ndependencies](https://about.gitlab.com/images/blogimages/2023-06-15-efficient-devsecops-workflows-with-rules-for-conditional-pipelines/complex-pipelines.png)\n\n\nA [gitlab-runner\npipeline](https://gitlab.com/gitlab-org/gitlab-runner/-/pipelines/798871212/)\nshowing job dependencies.\n\n{: .note.text-center}\n\n\n![Directed Acyclic\nGraph](https://about.gitlab.com/images/blogimages/2023-06-15-efficient-devsecops-workflows-with-rules-for-conditional-pipelines/dag-pipelines.png)\n\n\nDirected Acyclic Graph pipeline\n\n{: .note.text-center}\n\n\nUse cases determine how complicated a pipeline can get. A use case might\nrequire testing an application and packaging it into a container; the\npipeline can even further deploy the container to an orchestrator like\nKubernetes or a container registry. Another use case might involve building\napplications that target different platforms with varying dependencies,\nwhich is where DAG pipelines shine.\n\n\n## What are CI/CD rules?\n\nCI/CD rules are the key to managing the flow of jobs in a pipeline. One of\nthe powerful features of GitLab CI/CD is the ability to control when a CI/CD\njob runs, which can depend on context, changes made,\n[workflow](https://docs.gitlab.com/ee/ci/yaml/workflow.html) rules, values\nof CI/CD variables, or custom conditions. Aside from using `rules`, you can\nalso control the flow of CI/CD pipelines using:\n\n\n* [`needs`](https://docs.gitlab.com/ee/ci/yaml/index.html#needs):\nestablishes relationships between jobs and used in DAG pipelines\n\n* [`only`](https://docs.gitlab.com/ee/ci/yaml/index.html#only--except):\ndefines when a job should run\n\n* [`except`](https://docs.gitlab.com/ee/ci/yaml/index.html#only--except):\ndefines when a job should not run\n\n* [`workflow`](https://docs.gitlab.com/ee/ci/yaml/workflow.html): controls\nwhen pipelines are created\n\n\n`only` and `except` should not be used with `rules` as this can lead to\nunexpected behavior. It is recommended to use `rules`, learn more in the\nfollowing sections.\n\n\n## What is the `rules` feature?\n\n`rules` determine when and if a job runs in a pipeline. If you have multiple\nrules defined, they are all evaluated in order until a matching rule is\nfound and the job is executed according to the specified configuration.\n\n\n[Rules](https://docs.gitlab.com/ee/ci/yaml/#rules) can be defined using the\nkeywords: `if`, `changes`, `exists`, `allow_failure`, `variables`, `when`\nand `needs`.\n\n\n### `rules:if`\n\nThe `if` keyword evaluates if a job should be added to a pipeline. The\nevaluation is done based on the values of [CI/CD\nvariables](https://docs.gitlab.com/ee/ci/variables/index.html) defined in\nthe scope of the job or pipeline and [predefined CI/CD\nvariables](https://docs.gitlab.com/ee/ci/variables/predefined_variables.html).\n\n\n```yaml\n\njob:\n  script:\n    - echo $(date)\n  rules:\n    - if: $CI_MERGE_REQUEST_SOURCE_BRANCH_NAME == $CI_DEFAULT_BRANCH\n```\n\n\nIn the CI/CD script above, the job prints the current date and time with the\n`echo` command. The job is only executed if the source branch of a merge\nrequest (`CI_MERGE_REQUEST_SOURCE_BRANCH_NAME`) is the same as the project's\ndefault branch (`CI_DEFAULT_BRANCH`) in a [merge request\npipeline](https://docs.gitlab.com/ee/ci/pipelines/merge_request_pipelines.html).\nYou can use the `==` and `!=` operators for comparison, while `=~` and `!~`\nallow you to compare a variable to a regular expression. You can combine\nmultiple expressions using the `&&` (AND), `||` (OR) operators, and\nparentheses for grouping expressions.\n\n\n### `rules:changes`\n\nWith the `changes` keyword, you can watch for changes to certain files or\nfolders for a job to execute. GitLab uses the output of [Git\ndiffstat](https://git-scm.com/docs/git-diff#Documentation/git-diff.txt\n\n\n```yaml\n\njob:\n  script:\n    - terraform plan\n  rules:\n    - if: $CI_PIPELINE_SOURCE == \"merge_request_event\"\n      changes:\n        - terraform/**/*.tf\n```\n\n\nIn this example, the `terraform plan` is only executed when files with the\n`.tf` extension are changed in the `terraform` folder and its\nsubdirectories. An additional rule ensures the job is executed for [merge\nrequest\npipelines](https://docs.gitlab.com/ee/ci/pipelines/merge_request_pipelines.html).\n\n\nThe `changes` rule can look for changes in specific files with `paths`:\n\n\n```yaml\n\njob:\n  script:\n    - terraform plan\n  rules:\n    - if: $CI_PIPELINE_SOURCE == \"merge_request_event\"\n      changes:\n        paths:\n          - terraform/main.tf\n```\n\n\nChanges to files in a source reference (branch, tag, commit) can also be\ncompared against other references in the Git repository. The CI/CD job will\nonly execute when the source reference differs from the [specified reference\nvalue defined in\n`rules:changes:compare_to`](https://docs.gitlab.com/ee/ci/yaml/#ruleschangescompare_to).\nThis value can be a Git commit SHA, tag, or branch name. The following\nexample compares the source reference to the current `production` branch\n(`refs/head/production`).\n\n\n```yaml\n\njob:\n  script:\n    - terraform plan\n  rules:\n    - if: $CI_PIPELINE_SOURCE == \"merge_request_event\"\n      changes:\n        paths:\n          - terraform/main.tf\n        compare_to: 'refs/head/production'\n```\n\n\n### `rules:exists`\n\nLike `changes`, you can execute CI/CD jobs only when specific files exist\n[using `rules:exists`\nrules](https://docs.gitlab.com/ee/ci/yaml/#rulesexists). For example, you\ncan run a job that checks whether a `Gemfile.lock` file exists. The\nfollowing example audits a Ruby project for vulnerable versions of gems or\ninsecure gem sources using the [bundler-audit\nproject](https://github.com/rubysec/bundler-audit).\n\n\n```yaml\n\njob:\n  script:\n    - bundle-audit check --format json --output bundle-audit.json\n  rules:\n    - if: $CI_PIPELINE_SOURCE == \"merge_request_event\"\n      changes:\n        exits:\n          - Gemfile.lock\n```\n\n\n### `rules:allow_failure`\n\nThere are scenarios where the failure of a job should not affect the\nfollowing jobs and stages of the pipeline. This can be useful in use cases\nwhere non-blocking tasks are required as part of a project but don't impact\nthe project in any way. The [`rules:allow_failure`\nrule](https://docs.gitlab.com/ee/ci/yaml/#rulesallow_failure) can be set to\n`true` or `false`. It defaults to `false` implicitly when the rule is not\nspecified.\n\n\n```yaml\n\njob:\n  script:\n    - bundle-audit check --format json --output bundle-audit.json\n  rules:\n    - if: $CI_PIPELINE_SOURCE == \"merge_request_event\" && $CI_MERGE_REQUEST_TARGET_BRANCH_PROTECTED == \"false\"\n      changes:\n        exits:\n          - Gemfile.lock\n      allow_failure: true\n```\n\n\nIn this example, the job can fail only if a merge request event triggers the\npipeline and the target branch is not protected.\n\n\n### `rules:needs`\n\nDisabled by fault,\n[`rules:needs`](https://docs.gitlab.com/ee/ci/yaml/#rulesneeds) was\nintroduced in [GitLab\n16](https://about.gitlab.com/releases/2023/05/22/gitlab-16-0-released/) and\ncan be enabled with the `introduce_rules_with_needs` [feature\nflag](https://docs.gitlab.com/ee/user/feature_flags.html).\n[`needs`](https://docs.gitlab.com/ee/ci/yaml/index.html#needs) is used to\nexecute jobs out of order without waiting for other jobs in a stage to\ncomplete. When used with `rules`, it replaces the job's `needs`\nspecification when the set conditions are met.\n\n\n```yaml\n\nstages:\n  - build\n  - qa\n  - deploy\n\nbuild-dev:\n  stage: build\n  rules:\n    - if: $CI_COMMIT_BRANCH != $CI_DEFAULT_BRANCH\n  script: echo \"Building dev version...\"\n\nbuild-prod:\n  stage: build\n  rules:\n    - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH\n  script: echo \"Building production version...\"\n\nqa-checks:\n  stage: qa\n  script:\n    - echo \"Running QA checks before publishing to Production....\"\n\ndeploy:\n  stage: deploy\n  needs: ['build-dev']\n  rules:\n    - if: $CI_COMMIT_REF_NAME == $CI_DEFAULT_BRANCH\n      needs: ['build-prod', 'qa-checks']\n    - when: on_success # Run the job in other cases\n  script: echo \"Deploying application.\"\n\n```\n\n\nIn the example above, the deploy job has the `build-dev` job as a dependency\nbefore it runs; however, when the commit branch is the project's default\nbranch, its dependency changes to `build-prod` and `qa-checks`. This can\nallow for extra checks to be implemented based on context.\n\n\n### `rules:variables`\n\nIn some situations, you only need certain variables in specific conditions,\nor their values change based on content; you can use the\n[`rules:variables`](https://docs.gitlab.com/ee/ci/yaml/#rulesvariables) rule\nto define variables when specific conditions are met. This also allows to\ncreate more dynamic CI/CD execution workflows.\n\n\n```\n\njob:\n  variables:\n    DEPLOY_VERSION: \"dev\"\n  rules:\n    - if: $CI_COMMIT_REF_NAME == $CI_DEFAULT_BRANCH\n      variables:\n        DEPLOY_VERSION: \"stable\"\n  script:\n    - echo \"Deploying $DEPLOY_VERSION version\"\n```\n\n\n### `workflow:rules`\n\nSo far, we have looked at controlling when jobs run in a pipeline using the\n`rules` keyword. Sometimes, you want to control how the entire pipeline\nbehaves: That's where [`workflow:rules` provide a powerful\noption](https://docs.gitlab.com/ee/ci/yaml/#workflowrules). `workflow:rules`\nare evaluated before jobs and take precedence over the job rules. For\nexample, if a job has rules that allow it to run against a specific branch,\nbut the workflow rules set jobs running against the branch to `when: never`,\nthe jobs will not run.\n\n\nAll the features of `rules` mentioned in the previous sections work for\n`workflow:rules`.\n\n\n```yaml\n\nworkflow:\n  rules:\n    - if: $CI_PIPELINE_SOURCE == \"schedule\"\n      when: never\n    - if: $CI_PIPELINE_SOURCE == \"push\"\n      when: never\n    - when: always\n```\n\n\nIn the example above, the CI/CD pipeline runs except when a schedule or push\nevent is triggered.\n\n\n## Use cases for CI/CD rules\n\nIn the previous section, we looked at different ways of using the `rules`\nfeature of GitLab CI/CD. In this section, we will explore practical use\ncases.\n\n\n### Developer experience\n\nOne of the benefits of a DevSecOps platform is to allow developers to focus\non what they do best: writing their code and doing as little operations as\npossible. A company's DevOps or Platform team can create CI/CD templates for\ndifferent stages of their development lifecycle and use rules to add CI/CD\njobs to handle specific tasks based on their technology stack. A developer\nonly needs to include a default CI/CD script and pipelines are automatically\ncreated based on files detected, refs used, or defined variables, leading to\nincreased productivity.\n\n\n### Security and quality assurance\n\nA major function of CI/CD pipelines is to catch bugs or vulnerabilities\nbefore they are deployed into production infrastructure. Using CI/CD rules,\nsecurity and quality assurance teams can dynamically run extra checks on\nchanges introduced when certain factors are introduced. For example, malware\nscans can be added when new file extensions not in an approved list are\ndetected, or more advanced performance tests are automatically added when a\ncertain level of change has been introduced to the codebase. With GitLab's\nbuilt-in security, including security in your pipelines can be done with\njust a few lines of code.\n\n\n```yaml\n\ninclude:\n  # Static\n  - template: Jobs/Container-Scanning.gitlab-ci.yml\n  - template: Jobs/Dependency-Scanning.gitlab-ci.yml\n  - template: Jobs/SAST.gitlab-ci.yml\n  - template: Jobs/Secret-Detection.gitlab-ci.yml\n  - template: Jobs/SAST-IaC.gitlab-ci.yml\n  - template: Jobs/Code-Quality.gitlab-ci.yml\n  - template: Security/Coverage-Fuzzing.gitlab-ci.yml\n  # Dynamic\n  - template: Security/DAST.latest.gitlab-ci.yml\n  - template: Security/BAS.latest.gitlab-ci.yml\n  - template: Security/DAST-API.latest.gitlab-ci.yml\n  - template: API-Fuzzing.latest.gitlab-ci.yml\n```\n\n\n### Automation\n\nThe power of CI/CD rules shines through in the (nearly) limitless\npossibilities of automating your CI/CD pipelines. GitLab\n[AutoDevOps](https://docs.gitlab.com/ee/topics/autodevops/) is an example.\nIt uses an opinionated best-practice collection of [GitLab CI/CD\ntemplates](https://gitlab.com/gitlab-org/gitlab/-/tree/master/lib/gitlab/ci/templates)\nand rules to detect the technology stack used. AutoDevOps creates relevant\njobs that take your application all the way to production from a push. You\ncan review the [AutoDevOps\ntemplate](https://gitlab.com/gitlab-org/gitlab/-/blob/master/lib/gitlab/ci/templates/Auto-DevOps.gitlab-ci.yml)\nto learn how it leverages CI/CD rules for greater efficiency.\n\n\n### Using CI/CD components\n\nGrowth comes with several iterations of work and creating best practices.\nWhile building CI/CD pipelines, your DevOps team would have made several\nCI/CD scripts that they repurpose across pipelines using the\n[`include`](https://docs.gitlab.com/ee/ci/yaml/#include) keyword. In [GitLab\n16](https://about.gitlab.com/releases/2023/05/22/gitlab-16-0-released/),\nGitLab [introduced CI/CD\nComponents](https://about.gitlab.com/releases/2023/05/22/gitlab-16-0-released/#cicd-components),\nan experimental feature that allows your team to create reusable CI/CD\ncomponents and publish them as a catalog that can be used to build smarter\nCI/CD pipelines rapidly. You can learn more [about using CI/CD\ncomponents](https://docs.gitlab.com/ee/ci/components/) and the [component\ncatalog\ndirection](https://about.gitlab.com/direction/verify/component_catalog/).\n\n\nGitLab CI/CD enables you to run smarter pipelines, and it does so together\nwith [GitLab Duo, AI-powered workflows](/gitlab-duo/) to help you build more\nsecure software, faster.\n",[742,9,696,786,496],{"slug":1316,"featured":6,"template":699},"efficient-devsecops-workflows-with-rules-for-conditional-pipelines","content:en-us:blog:efficient-devsecops-workflows-with-rules-for-conditional-pipelines.yml","Efficient Devsecops Workflows With Rules For Conditional Pipelines","en-us/blog/efficient-devsecops-workflows-with-rules-for-conditional-pipelines.yml","en-us/blog/efficient-devsecops-workflows-with-rules-for-conditional-pipelines",{"_path":1322,"_dir":246,"_draft":6,"_partial":6,"_locale":7,"seo":1323,"content":1329,"config":1335,"_id":1337,"_type":14,"title":1338,"_source":16,"_file":1339,"_stem":1340,"_extension":19},"/en-us/blog/efficient-pipelines",{"title":1324,"description":1325,"ogTitle":1324,"ogDescription":1325,"noIndex":6,"ogImage":1326,"ogUrl":1327,"ogSiteName":686,"ogType":687,"canonicalUrls":1327,"schema":1328},"Extract greater efficiency from your CI pipelines","Learn some techniques to find the balance between pipeline performance and resource utilization.","https://res.cloudinary.com/about-gitlab-com/image/upload/v1749667534/Blog/Hero%20Images/ci-pipeline.jpg","https://about.gitlab.com/blog/efficient-pipelines","\n                        {\n        \"@context\": \"https://schema.org\",\n        \"@type\": \"Article\",\n        \"headline\": \"Extract greater efficiency from your CI pipelines\",\n        \"author\": [{\"@type\":\"Person\",\"name\":\"Vlad Budica\"}],\n        \"datePublished\": \"2022-03-09\",\n      }",{"title":1324,"description":1325,"authors":1330,"heroImage":1326,"date":1332,"body":1333,"category":717,"tags":1334},[1331],"Vlad Budica","2022-03-09","\nWhen discussing efficiency, typically we need to balance two things: time and money. It's quite easy to optimize for just one of these parameters. However, that can be an oversimplification. Within some constraints, more resources (i.e., hardware and Runners) equal better performance. Yet, the exact opposite is true for other constraints. In this article, I will walk you through the process of finding the sweet spot in optimizing your GitLab CI pipeline. The principles that I'll cover work well for existing pipelines and also for new ones. Please note that this is subjective and the sweet spot might be very different for different users in different scenarios.\n\nAs we dig into the technical aspects, note that we are looking for an overall optimization of a pipeline, as opposed to just looking at a particular job. The reasoning behind it is that local optimizations might make the overall pipeline less efficient (we might generate bottlenecks).\n\nThe optimization recommendations below fall into two categories:\n- Execute fewer jobs and pipelines\n- Shorten the execution time of jobs and pipelines\n\nThe first step before modifying an aspect of a system is to understand it. Observe it in full. You need to know the overall pipeline architecture and also the current metrics for it. You need to know the total execution time, jobs that take a large amount of time to finish (any bottlenecks), and the total job workload (potential queue time) and Runner capacity – these last two go hand in hand. Finally, we can use [Directed Acyclic Graphs](https://docs.gitlab.com/ee/ci/directed_acyclic_graph/), or DAGs, to visualize the pipeline and see the critical path (the minimum and maximum pipeline duration). We want to do this because we want to minimize as much as possible the detrimental impact doing changes can have on pipeline performance.\n\n## Execute fewer jobs and pipelines\n\nLet's look at ways of reducing the number of jobs and pipelines that get executed.\n\n### Apply rules\n\nThe first thing would be to decide what needs to be executed and when. For example, with a website, if the only change that was performed is to the text on the page, then the resulting pipeline doesn't need to contain all the tests and checks that are performed when changing the web app.\n\nThis requires the use of the [rules keyword](https://docs.gitlab.com/ee/ci/yaml/#rules). Rules are evaluated when a pipeline is created (at each trigger), and evaluated in order until the first match. When a match is found, the job is either included or excluded from the pipeline, depending on the configuration.\n\nThrough the rules keyword you can decide very precisely when a job should run or not. More information about use cases and configuration parameters can be found in the [doc page for rules](https://docs.gitlab.com/ee/ci/yaml/#rules).\n\n### Make jobs interruptible\n\nNow that jobs are only running when needed, you can focus on what happens when a new pipeline is triggered while a job is still running. This can lead to inefficiencies because we already know the job isn't running on the latest change performed on the target branch and that the results will get scrapped.\n\nThis is where the [interruptible keyword](https://docs.gitlab.com/ee/ci/yaml/#interruptible) comes in. It enables us to specify that a job can be interrupted when a newer one is triggered on the same branch. This should be coupled with the [automatic cancellation of redundant pipelines feature](https://docs.gitlab.com/ee/ci/pipelines/settings.html#auto-cancel-redundant-pipelines) so, in the end, jobs will be automatically canceled when newer pipelines are triggered.\n\nOne word of caution, use this mechanism only with jobs that are safe to stop such as a build or a test job. Don't use this with your deployment jobs as you're eventually going to end up with partial deployments.\n\nOne last point around executing fewer jobs and pipelines is to try to reschedule non-essential pipelines to as least frequent as possible. It's a balance that needs to be found between running the pipelines too often and not running them enough. Just go with the minimum acceptable by your company policy.\n\n## Shorten the execution time of jobs and pipelines\n\nThe next thing would be to find ways of making our jobs and pipelines execute in less time.\n\n### Execute jobs in parallel\n\nYou can [create DAGs in your pipelines](https://docs.gitlab.com/ee/ci/directed_acyclic_graph/) to create relationships between jobs and ensure that jobs are executed as soon as all the requirements are met if there are any and that they aren't waiting unnecessarily for other jobs to finish. By using the [needs keyword](https://docs.gitlab.com/ee/ci/yaml/#needs) together with the [parallel keyword](https://docs.gitlab.com/ee/ci/yaml/#parallel), you can implement DAGs.\n\nAnother useful mechanism to drive parallelism is [parent-child pipelines](https://docs.gitlab.com/ee/ci/pipelines/downstream_pipelines.html), which enable you to trigger concurrently running pipelines.\n\nThese offer great flexibility and by using them you can execute your workloads in parallel as efficiently as possible. This can be a double-edged sword though as DAGs and [parent-child pipelines](https://docs.gitlab.com/ee/ci/pipelines/downstream_pipelines.html) will increase the complexity of your pipelines, making them harder to analyze and understand. Within this very complex environment, you can run into unwanted side effects such as increased cost or even reduced efficiency.\n\nThe more jobs and pipelines you run in parallel, the more load will be put on your Runner infrastructure. If you do have an autoscaling mechanism and a large enough pool of resources, this will ensure no big queues are created and that things are running smoothly, but also lead to increased infrastructure costs. On the other hand, if you don't have autoscaling or if you have lower limits for the amount of resources available, the costs will be kept in check but your overall execution time will suffer because jobs will wait longer in queues.\n\n### Fail fast\n\nIt's desirable to detect errors and critical failures as soon as possible in your jobs and pipelines, and stop the execution. If you wait until toward the end of the pipeline to fail, the whole pipeline will waste hardware resources and increase your execution and waiting times. This is easier to implement when first designing a pipeline but can be achieved as well through refactoring of your existing ones.\n\nTesting usually takes a lot of time so this means that we're waiting for the execution to finish before canceling the whole pipeline if the tests fail. What we want to do is move the jobs that run quicker earlier in the pipeline thus getting feedback sooner. To configure this behavior, use the [allow_failure keyword](https://docs.gitlab.com/ee/ci/yaml/#allow_failure) and only for jobs that when fail should fail the whole pipeline.\n\n### Caching\n\nYou can also optimize the caching of your dependencies, which will improve the execution time. This can be very useful for jobs that fail often but for which the dependencies don't change that often.\n\nTo configure this in your jobs, you should use the [cache:when keyword](https://docs.gitlab.com/ee/ci/yaml/#cachewhen).\n\n### Optimize your container images\n\nUsing big images in your pipelines can slow things down significantly, as they take longer to be pulled. So the solution would be to use smaller images. Simple, right?\n\nWell, it's not always that easy to do, so you should start by analyzing your base image and your network speed as these two will give an indication of how long it will take for the image to be pulled. The network connection we're interested in is the one between your Runner and your container registry.\n\nOnce we have this kind of information, we can decide to host the image in another container registry. If you have GitLab hosted in a public cloud you should use the container image registry provided by that provider. An alternative that works no matter where GitLab is hosted is to use the internal GitLab container registry that's included with your service.\n\nYou will get better results if instead of using a master container image that holds everything that you need to run the whole pipeline, you use multiple smaller ones that are tailored for each job. It's faster if you use custom container images and have all the tools you need pre-installed. This would also be a safer option because you can validate more thoroughly the contents of the image.\n\nMore information about this topic can be found in [Docker's \"Best practices for writing Dockerfiles\"](https://docs.docker.com/develop/develop-images/dockerfile_best-practices/).\n\n## Pipeline optimization is part science, part art\n\nYou should approach your pipeline optimization efforts through a continuous improvement lens. This process is part science, part art as there aren't any quick solutions that you can apply and get your ideal result.\n\nI encourage you to test, document, and analyze the results when it comes to pipeline optimization efforts. You try one thing, look for feedback from the metrics of your pipelines, document the results, the changes, and the new architecture (this can happen in GitLab issues and merge requests) so you can extract some learnings, and the cycle starts again.\n\nSmall gains will add up and provide significant improvements at a higher scale. As I mentioned before, look for overall improvements instead of local ones. Now applying these principles to each project (pipeline templates makes it easier to adopt at scale), we can look at how these improvements across projects add up.\n\nRead more: Learn how to [troubleshoot a GitLab pipeline failure](https://docs.gitlab.com/ci/debugging/).\n",[741,9,696],{"slug":1336,"featured":6,"template":699},"efficient-pipelines","content:en-us:blog:efficient-pipelines.yml","Efficient Pipelines","en-us/blog/efficient-pipelines.yml","en-us/blog/efficient-pipelines",{"_path":1342,"_dir":246,"_draft":6,"_partial":6,"_locale":7,"seo":1343,"content":1349,"config":1357,"_id":1359,"_type":14,"title":1360,"_source":16,"_file":1361,"_stem":1362,"_extension":19},"/en-us/blog/enable-slos-as-code",{"title":1344,"description":1345,"ogTitle":1344,"ogDescription":1345,"noIndex":6,"ogImage":1346,"ogUrl":1347,"ogSiteName":686,"ogType":687,"canonicalUrls":1347,"schema":1348},"Enable SLO-as-Code with Nobl9 and GitLab","Learn how to take advantage of a streamlined SLO process and maintain a single source of truth for SLO definitions within your DevOps platform.","https://res.cloudinary.com/about-gitlab-com/image/upload/v1749669455/Blog/Hero%20Images/nobl9_1.jpg","https://about.gitlab.com/blog/enable-slos-as-code","\n                        {\n        \"@context\": \"https://schema.org\",\n        \"@type\": \"Article\",\n        \"headline\": \"Enable SLO-as-Code with Nobl9 and GitLab\",\n        \"author\": [{\"@type\":\"Person\",\"name\":\"Quan To\"},{\"@type\":\"Person\",\"name\":\"Jeremy Cooper\"},{\"@type\":\"Person\",\"name\":\"Ian Bartholomew\"}],\n        \"datePublished\": \"2022-05-09\",\n      }",{"title":1344,"description":1345,"authors":1350,"heroImage":1346,"date":1354,"body":1355,"category":805,"tags":1356},[1351,1352,1353],"Quan To","Jeremy Cooper","Ian Bartholomew","2022-05-09","\n\nNobl9 recently integrated with GitLab's CI to enable a consistent mechanism to publish Service Level Objectives (SLO) definitions from GitLab to Nobl9. With this SLO-as-Code integration, DevOps teams can take action when their error budgets are burning too fast or are about to be exhausted.\n\nIn today’s systems, 100% uptime isn’t realistic given the complex architectures and dependencies involved. SLOs enable you to define targets and have an error budget for tracking what's “good enough.” For example, you can target uptime of 99.9%, 99%, or even 95% because what truly matters is how much downtime or errors are acceptable before there is real customer impact.\n\nTypically when organizations think about SLO-as-Code, they must use separate products to ensure their SLO definitions are always in sync with whatever tool they are using. This usually includes running command-line tools manually or building custom integrations within their code repositories.    \n\nWith this CI configuration, every time you build your repo, GitLab will call [sloctl](https://docs.nobl9.com/sloctl-user-guide), our command-line tool, and push the SLO definition to Nobl9. Customers can continue using GitLab to version their SLO definitions and keep their SLOs consistent. This ensures your SLO definition will always be up to date with what’s in Nobl9 and removes any discrepancies over what the latest SLO definition actually is. SREs, engineers, and anyone using the SLOs can still debate what the targets need to be, but there will always be a definitive source of truth in your code repository on what the current definition is.\n\n## Getting started\n\nTo set this up in GitLab, follow these steps:\n\n**1.** Select Settings -> CI/CD, and click the Expand button next to Variables. \n\n![CICD_settings](https://about.gitlab.com/images/blogimages/nobl9_2.png)\n\n\n**2.** Add the following variables:\n\n- CLIENT_ID\n\n- CLIENT_SECRET\n\n- ACCESS_TOKEN\n\n- PROJECT \n\n- SLOCTL_YML\n\n\n**Note:** If you haven’t done so already, you’ll need to install sloctl. You can install the executable on your local machine by following the instructions in the [user guide](https://docs.nobl9.com/sloctl-user-guide#setting-up-sloctl). Once sloctl is installed, you can run the following command to retrieve your CLIENT_ID, CLIENT_SECRET, and ACCESS_TOKEN:\n\n\n    cat ~/.config/nobl9/config.toml\n\n\n    The PROJECT value is the name of the project inside Nobl9 that your SLO belongs \n    to.\n\n\n    The SLOCTL_YML value is the Nobl9 YAML file you want to push to Nobl9 on each \n    change.\n\n\n\n![install_sloctl](https://about.gitlab.com/images/blogimages/nobl9_3.png)\n\n\n\n**3.** Create the CI/CD job to apply the YAML, by going to CI/CD -> Jobs and clicking “Create CI/CD configuration file”. \n\n\n\n![create_config](https://about.gitlab.com/images/blogimages/nobl9_4.png)\n\n\n\nEnter the following code in the _.gitlab.ci.yml_ file:\n\n\n        variables:\n\n\n          CLIENT_ID: $NOBL9_CLIENT_ID\n\n\n          CLIENT_SECRET: $NOBL9_CLIENT_SECRET\n\n\n          ACCESS_TOKEN: $NOBL9_ACCESS_TOKEN\n\n\n          PROJECT: $NOBL9_PROJECT\n\n\n          SLOCTL_YML: $SLOCTL_YML\n\n\n        include:\n\n\n          - project: 'nobl9/nobl9-ci-template'\n\n\n            ref: main\n\n\n            file: '/nobl9.gitlab-ci.yml'\n\n\n\n\n**4.** Kick off a build. Any changes to the SLOCTL_YML file that you reference will now automatically be pushed to Nobl9 once the updates are committed.\n\nBy partnering with GitLab and providing a convenient CI script and a command-line tool for managing SLOs, Nobl9 has truly enabled SLO-as-Code. We encourage existing Nobl9 customers who use GitLab to give it a try. \n\nIf you haven’t experienced Nobl9 yet, you can sign up for a free 30-day trial at [nobl9.com/signup](http://nobl9.com/signup) to see all that it has to offer.\n\n_Quan To is Senior Director of Product Management, Jeremy Cooper is Senior Solutions Engineer, and Ian Bartholomew is SRE Manager at Nobl9._\n\nCover image by [Vardan Papikayan](https://unsplash.com/@varpap) on [Unsplash](https://unsplash.com/photos/JzE1dHEaAew)\n",[233,9,741],{"slug":1358,"featured":6,"template":699},"enable-slos-as-code","content:en-us:blog:enable-slos-as-code.yml","Enable Slos As Code","en-us/blog/enable-slos-as-code.yml","en-us/blog/enable-slos-as-code",{"_path":1364,"_dir":246,"_draft":6,"_partial":6,"_locale":7,"seo":1365,"content":1371,"config":1379,"_id":1381,"_type":14,"title":1382,"_source":16,"_file":1383,"_stem":1384,"_extension":19},"/en-us/blog/ensuring-compliance",{"title":1366,"description":1367,"ogTitle":1366,"ogDescription":1367,"noIndex":6,"ogImage":1368,"ogUrl":1369,"ogSiteName":686,"ogType":687,"canonicalUrls":1369,"schema":1370},"How to ensure separation of duties and enforce compliance with GitLab","Use your DevSecOps platform to help maintain compliance without compromising on development speed.","https://res.cloudinary.com/about-gitlab-com/image/upload/v1750098232/Blog/Hero%20Images/Blog/Hero%20Images/AdobeStock_479904468%20%281%29_4lmOEVlaXP0YC3hSFmOw6i_1750098232241.jpg","https://about.gitlab.com/blog/ensuring-compliance","\n                        {\n        \"@context\": \"https://schema.org\",\n        \"@type\": \"Article\",\n        \"headline\": \"How to ensure separation of duties and enforce compliance with GitLab\",\n        \"author\": [{\"@type\":\"Person\",\"name\":\"Beatriz Barbosa\"},{\"@type\":\"Person\",\"name\":\"Fernando Diaz\"}],\n        \"datePublished\": \"2022-04-04\",\n      }",{"title":1366,"description":1367,"authors":1372,"heroImage":1368,"date":1375,"body":1376,"category":784,"tags":1377,"updatedDate":1378},[1373,1374],"Beatriz Barbosa","Fernando Diaz","2022-04-04","In this article, you'll learn the different ways to ensure **separation of\nduties** and\n\n**continuous compliance** with the GitLab DevSecOps platform. But first,\nlet's level-set on two key concepts:\n\n\n**Compliance** means being in accordance with guidelines and specifications\nthat have been\n\ndefined either by your corporation or a regulatory agency. Compliance helps\nmaintain\n\ncorporate ethics, appropriate user policies, security standards, and much\nmore for\n\nthe safety of consumers.\n\n\nNon-compliance may result in a bundle of legal fees and fines, so it is very\nimportant to maintain compliance. While maintaining compliance, DevSecOps\nteams must also ensure sustained development velocity, providing necessary\nsimplicity, visibility, and control.\n\n\n**Separation of duties** requires multiple actors to complete a task to\nincrease protection from error as well as prevent malicious activity.\nSeparation of duties ensures roles best-suited for the job are the only ones\nthat can perform it. As an example, some of the following\n\nactors are observed, each with a specific purpose:\n\n\n- a developer will be responsible for developing new features\n\n- a compliance officer will be responsible for creating and enforcing the\nusage of a pipeline\n\n- an application security engineer will be responsible for approving merge\nrequests with vulnerabilities\n\n\nConsidering the above roles, we can ensure that a developer cannot change a\nrunning pipeline.\n\nThis is a task that can only be performed by a compliance officer, ensuring\nonly compliant code can be pushed without approval.\n\n\nAn application security engineer is responsible for reviewing and approving\ncode with vulnerabilities, ensuring proper mitigation can be performed, and\nthat nothing comes as a surprise in the future. In this scenario, developers\ncan't merge code until compliance\n\nand security requirements are met.\n\n\n## Security policies\n\nGitLab provides **Security Policies**, which enable security teams to\nrequire security scans to run according to a configuration. This gives\nsecurity teams confidence that the configured scans have not been changed or\ndisabled.\n\n\nSecurity policies can be scoped to meet certain **Compliance Frameworks**.\nThis means that your project has certain compliance requirements and needs\nadditional oversight. This label can be created in **Secure > Compliance\nCenter > Frameworks** under your top-level group.\n\n\n![Compliance Framework\nLabel](https://about.gitlab.com/images/blogimages/compliance-04-2022/cf-step-2.png)\n\n\n**Note:** Compliance labels can only be assigned to projects within the\ntop-level group in which we create the label.\n\n\nThere are three types of policies, [Scan Execution\nPolicies](https://docs.gitlab.com/ee/user/application_security/policies/scan_execution_policies.html),\n[Merge Request Approval\nPolicies](https://docs.gitlab.com/ee/user/application_security/policies/merge_request_approval_policies.html),\nand [Pipeline Execution\nPolicies](https://docs.gitlab.com/ee/user/application_security/policies/pipeline_execution_policies.html).\n\n\n* **Scan Execution Policies:** Require that security scans run on a\nspecified schedule or with the project pipeline.\n\n* **Merge Request Approval Policies:** Take action based on scan results,\nsuch as requiring approval from the security team before a merge can occur.\n\n* **Pipeline Execution Policies:** Enforce CI/CD jobs for applicable\nprojects.\n\n\nThese policies can be configured via the Policy Editor in a few simple\nsteps.\n\n\n### Scan execution\n\n\n1. Go to **Security & Compliance > Policies**.\n\n\n2. Create a new policy by pressing the **New Policy** button.\n\n\n3. Select **Scan Execution**.\n\n\n4. Create the rule. I'm creating a rule that requires\n[SAST](https://docs.gitlab.com/ee/user/application_security/sast/) to be\nconfigured in order for a pipeline to run.\n\n\n```yaml\n\nname: force_sast\n\ndescription: 'require sast to run'\n\nenabled: true\n\nrules:\n\n- type: pipeline\n  branches:\n  - main\nactions:\n\n- scan: sast\n\n```\n\n\n5. Submit the policy by creating a merge request and then merge.\n\n\nAll scan execution policy changes are applied through a background job that\nruns once every 10 minutes.\n\nAllow up to 10 minutes for any policy changes committed to this project to\ntake effect.\n\n\n6. Try and run a pipeline. It will not be run unless SAST is defined in the\nYAML.\n\n\n**Note**: You can also force SAST to run on a timer. For more information,\nsee the scan execution\n\npolicies\n[documentation](https://docs.gitlab.com/ee/user/application_security/policies/scan-execution-policies.html).\n\n\n### Merge Request Approval\n\n\n1. Go to **Secure > Policies**.\n\n\n2. Create a new policy by pressing the **New Policy** button.\n\n\n3. Select **Merge Request Approval Policy**.\n\n\n4. Define policy scope.\n\n\n5. Create the rule.\n\n\n![separation of duties update - image\n1](https://res.cloudinary.com/about-gitlab-com/image/upload/v1750098241/Blog/Content%20Images/Blog/Content%20Images/image1_aHR0cHM6_1750098241214.png)\n\n\n6. Add action to take.\n\n\n![separation of duties update - image\n2](https://res.cloudinary.com/about-gitlab-com/image/upload/v1750098241/Blog/Content%20Images/Blog/Content%20Images/image3_aHR0cHM6_1750098241215.png)\n\n\n**Note:** The policy is evaluated according to the rules you set. This means\nthat, if the rules are invalid, or can’t be evaluated, approval is required.\nTo prevent this, the default Fallback behavior field can be changed to\n`open`.\n\n\n![separation of duties update - image\n3](https://res.cloudinary.com/about-gitlab-com/image/upload/v1750098241/Blog/Content%20Images/Blog/Content%20Images/image5_aHR0cHM6_1750098241217.png)\n\n\n1. Submit the policy by creating a merge request and then merging\n\n\n2. Create a separate merge request with vulnerabilities\n\n\nYou can see how to add vulnerabilities by checking out the Developer\nWorkflow section of the GitLab DevSecOps Workshop.\n\n\n3. Verify Merge Request Approval Policy is being used by viewing merge\nrequest.\n\n\n### Pipeline Execution Policy\n\n\nTo set up a pipeline execution policy, you need to first create a project\ncontaining the CI files you would like to run. Make sure that only the\nsecurity team and/or administrator has access to ensure separation of\nduties. I created the \"Compliance and Deploy\" project, which contains the\nYAML I wish to enforce.\n\n\n1. Go to **Secure > Policies**.\n\n\n2. Create a new policy by pressing the **New Policy** button.\n\n\n3. Select **Pipeline Execution Policy**.\n\n\n4. Define policy scope.\n\n\n5. Add action to take.\n\n\n![separation of duties update - image\n4](https://res.cloudinary.com/about-gitlab-com/image/upload/v1750098241/Blog/Content%20Images/Blog/Content%20Images/image8_aHR0cHM6_1750098241219.png)\n\n\n6. Add conditions.\n\n\n7. Submit the policy by creating a merge request and then merging.\n\n\n8. Try and run a pipeline. You will see the policy specific jobs and stages\nin your pipeline.\n\n\n## Audit Management and Compliance Dashboard\n\n\nAnother important part of compliance is knowing it is actually happening in\nyour groups/projects. GitLab has Audit Events and Compliance Reports to\nassist with audits.\n\n\n**Audit Events** allows GitLab owners and administrators to track important\nevents such as who performed certain actions and the time they occurred.\n\n\n![Audit\nevents](https://about.gitlab.com/images/blogimages/compliance-04-2022/project-audit-events.png)\n\n\nAudit Events records different events per group and per project, which can\nbe seen\n\nin the [audit\nevents](https://docs.gitlab.com/ee/administration/audit_events.html)\ndocumentation.\n\nAudit Events can be accessed by going to **Security & Compliance > Audit\nEvents**.\n\nSome examples include:\n\n\n- user was added to project and their permissions\n\n- permission changes of a user assigned to a project\n\n- project CI/CD variable added, removed, or protected status changed\n\n- user was added to group and their permissions\n\n- group name or path changed\n\n\nAudit Events can also be sent to an HTTP endpoint using Audit Event\nStreaming. Learn how\n\nto implement Audit Event Streaming in this\n[video](https://youtu.be/zHwVF9-i7e4?t=52).\n\n\n**Standards Adherence** gives you the ability to see a group's merge request\nactivity. It provides a high-level view for all projects in the group.\n\n\n![separation of duties update - image\n5](https://res.cloudinary.com/about-gitlab-com/image/upload/v1750098241/Blog/Content%20Images/Blog/Content%20Images/image2_aHR0cHM6_1750098241222.png)\n\n\nYou can use the report to:\n\n- get an overview of the latest merge request for each project\n\n- see if merge requests were approved and by whom\n\n- see merge request authors\n\n- see the latest CI/CD pipeline result for each merge request\n\n\nThe Standards Adherence report can be accessed in the top-level group by\ngoing to **Secure > Compliance Center**, and choosing the **Standards\nAdherence** tab.\n\n\n---\n\n\nThanks for reading! For more information on separation of duties within\nGitLab, check out [Continous Software Compliance with\nGitLab](/solutions/compliance/)\n",[784,9,696,983],"2024-12-16",{"slug":1380,"featured":6,"template":699},"ensuring-compliance","content:en-us:blog:ensuring-compliance.yml","Ensuring Compliance","en-us/blog/ensuring-compliance.yml","en-us/blog/ensuring-compliance",{"_path":1386,"_dir":246,"_draft":6,"_partial":6,"_locale":7,"seo":1387,"content":1393,"config":1397,"_id":1399,"_type":14,"title":1400,"_source":16,"_file":1401,"_stem":1402,"_extension":19},"/en-us/blog/first-code-to-ci-cd-deployments-in-5-minutes",{"title":1388,"description":1389,"ogTitle":1388,"ogDescription":1389,"noIndex":6,"ogImage":1390,"ogUrl":1391,"ogSiteName":686,"ogType":687,"canonicalUrls":1391,"schema":1392},"A journey from the first code to CI/CD deployments in 5 minutes?","From writing, building, and testing code to reviewing, releasing, and deploying in 5 minutes. Is this possible? Learn which hurdles you might encounter and how to solve them. Spoiler: Without Kubernetes.","https://res.cloudinary.com/about-gitlab-com/image/upload/v1749665823/Blog/Hero%20Images/snow-speed-unsplash.jpg","https://about.gitlab.com/blog/first-code-to-ci-cd-deployments-in-5-minutes","\n                        {\n        \"@context\": \"https://schema.org\",\n        \"@type\": \"Article\",\n        \"headline\": \"A journey from the first code to CI/CD deployments in 5 minutes?\",\n        \"author\": [{\"@type\":\"Person\",\"name\":\"Michael Friedrich\"}],\n        \"datePublished\": \"2020-12-15\",\n      }",{"title":1388,"description":1389,"authors":1394,"heroImage":1390,"date":1192,"body":1395,"category":1088,"tags":1396},[1252],"\n{::options parse_block_html=\"true\" /}\n\nSoftware architecture and [DevOps](/topics/devops/) strategies are hard. Trust me, I know from experience. In my previous role, I was involved in \"all the things\" relating to our DevOps lifecycle, and we faced issues with everything from [continuous deployment (CD)](/topics/ci-cd/) to database management to implementing microservices.\n\nDo any of these scenarios sound familiar?\n\n- We want to adopt microservices but our application is not ready.\n- We know Kubernetes and containers are awesome but we cannot figure out how to get started.\n- We want to do CD but we are still doing manual deployments.\n\nIf you are facing one of these situations, you are not alone. I have lived through them in past roles and now spend my days talking to and helping folks across the industry who are facing these problems (and worse). These common problems lead to a larger conversation at GitLab: Why does it take 20 minutes or more to create a production app in 2020?\n\nThis question is why we challenged ourselves (okay, it's why Sid challenged us) to create a 5 minute production app. The goal is to get from having a free AWS account to a Rails/Node production app with a persistent [serverless](/topics/serverless/) database, Auto DevOps, Single Sign-On (SSO), Redis, object storage, and email in 5 minutes using only the GitLab UI.\n\nOur vision for the 5 minute production app is to provide everyone with a pathway to efficient deployments by minimizing infrastructure dependencies. This builds on learned lessons from [Auto DevOps](https://docs.gitlab.com/ee/topics/autodevops/) and [Infrastructure as Code with Terraform](https://docs.gitlab.com/ee/user/infrastructure/) (for example, by removing the requirement for Kubernetes).\n\n### Common problems\n\n#### Kubernetes and microservices\n\nKubernetes and containers can be overwhelming. The value they add comes with increased levels of complexity. Similarly, microservices can improve efficiency and high availability but they may not fit for all application architectures. Large rewrites might be necessary to take advantage of the benefits they provide.\n\nHeroku and Cloud Native Build Packs are a great way to automate Docker image creation with all dependencies but not all use cases are covered. When these deployments break, debugging can be hard without in-depth knowledge of the components. Defining and maintaining the dependencies in the build process by yourself can help, for example in your own Docker container group using the [GitLab Container Registry](https://docs.gitlab.com/ee/user/packages/container_registry/).\n\n#### Backend requirements\n\nA web application can have a stateful backend where it stores persistent data. This can be a file on disk, a database server or an object storage in the cloud. The stored data can be user settings, inputs into web forms and generated content for example.\n\nDepending on the programming language, the interaction with the backend can get complex. A database client library is required to communicate with a PostgreSQL server. The database schema needs to be initialised, and future changes require incremential schema updates. The schema update migrations can be automated by the application. This requires client libraries providing this functionality. Ruby on Rails uses rake db tasks while it can get more complicated with PHP.\n\nThe database server needs to be running in order for the web application to work. This can happen on the same host, a central database cluster, or a cloud service such as Amazon Aurora. Someone must be responsible for keeping the server running, monitoring it, and managing software updates.\n\nAll backend solutions require maintenance. As a developer, you want to have these steps automated and abstracted. Your code communicates with the backend interfaces as a blackbox, expecting them to be healthy and operational when the application starts.\n\n### Path to resolution\n\n#### Deploy and run the application\n\nThe production environment for a basic web application requires the following steps:\n\n- Start/Detect the database server or service\n- Initialize/Migrate the database schema\n- Start the web application\n- Schedule periodic health checks and add performance monitoring\n\nIn addition to the boot steps, these web applications can depend on additional libraries and packages. Common best practice is to define them in the programming language's package dependency manager, for example `requirements.txt` with Python, or `Gemfile` with Ruby. The software deployment process evolved over the years with packaging the application into container images, containing the application and all dependencies. The CI/CD jobs do not need to add any extra steps for software installation. As a developer, you don’t care about the OS or distribution where the application is deployed.\n\n#### Choose your stack\n\nThe decision to choose the \"right\" tools for the job can be hard. It helps to define the required steps and map them onto existing functionality provided by GitLab:\n\n- Provision a new virtual machine\n- Define the state with Infrastructure as Code\n- Build and deploy the application\n- Run the application\n\nWe have decided start with AWS as a deployment scenario:\n\n- Ask for AWS credentials for EC2\n- Run Terraform and provision the VM\n- Create AWS Aurora RDS as PostgreSQL backend\n- Install application package dependencies into a container image\n- Pull the image on the host\n- Run and monitor the application\n\nThis process involves lots of steps, requiring different tools and frameworks. After all those years, isn’t there a ready-to-use workflow to abstract this and have everything automatically deployed?\n\n### How we settled on the stack for the 5 min production app\n\n1. AWS: Biggest cloud\n2. Terraform: Most popular infrastructure provisioning\n3. Auto DevOps: Same direction\n\nWe have refined the decisions during the implementation of the deployment process. The first iteration attempted to work without container images. This resulted in having many different ways to distribute and install software. We decided to take one step back and use container images to build the web application as package. The GitLab container registry works as package repository. The container image is pulled and run on the deployed host.\n\nAWS provides Aurora RDS as serverless PostgreSQL database service. We decided to use an existing service in the first iteration, and evaluate database instance management in the future. Terraform as deployment provisioner allows us to build on the foundation from our [Infrastructure as Code integration](https://docs.gitlab.com/ee/user/infrastructure/). The first apps are written in Ruby on Rails and [Python](https://gitlab.com/gitlab-de/5-min-prod-app-python-web), we are planning with more to come soon.\n\n![GitLab CI/CD pipeline deployment](https://about.gitlab.com/images/blogimages/5-min-prod-app/gitlab_cicd_pipeline_deployed.png){: .shadow.medium.center}\n\n![AWS EC2 view](https://about.gitlab.com/images/blogimages/5-min-prod-app/aws_ec2_view.png){: .shadow.medium.center}\n\nOur vision for the 5 minute production app flow:\n\n1. Go to GitLab.com.\n2. Sign in with your AWS account.\n3. New Project.\n4. Rails/Node/etc. template.\n5. Write some code and create a merge request.\n6. Get a review app and test results in the MR.\n7. Merge the MR.\n8. Automatically deployed to production.\n9. Share URL of production app with a friend.\n10. Production app has a persistent state and can reset passwords via email (DB, s3, redis, mail) and provides the full Auto DevOps features (Monitoring, etc.).\n11. No manual steps for setting up DB, s3, redis, mail. Terraform takes care of automated setup.\n12. All within AWS in the free tier.\n13. No command line or terminal required, everything accessible in the GitLab UI.\n\n### What comes next\n\nThe next iterations include more scenarios and questions:\n\n- Domain and SSL support\n- Review environments and rollbacks\n- Python web application with database migrations\n- NodeJS app with a PostgreSQL backend\n- Support for more cloud providers and local deployments\n- Decoupled database server management\n\nThe deployment template will soon be [merged into GitLab Core](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/49487). This is great news for everyone joining us for feedback and tests. Let us know what you think, and follow our progress with these resources:\n\n- [Issue Board](https://gitlab.com/gitlab-org/5-minute-production-app/deploy-template/-/boards)\n- [Recordings on YouTube](https://www.youtube.com/playlist?list=PL05JrBw4t0Krf0LZbfg80yo08DW1c3C36)\n- [Deploy Template project](https://gitlab.com/gitlab-org/5-minute-production-app/deploy-template)\n\nCover image by [Nicolas J Leclercq](https://unsplash.com/@nicolasjleclercq?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText) on [Unsplash](https://unsplash.com/s/photos/speed-snow)\n",[9,696,269],{"slug":1398,"featured":6,"template":699},"first-code-to-ci-cd-deployments-in-5-minutes","content:en-us:blog:first-code-to-ci-cd-deployments-in-5-minutes.yml","First Code To Ci Cd Deployments In 5 Minutes","en-us/blog/first-code-to-ci-cd-deployments-in-5-minutes.yml","en-us/blog/first-code-to-ci-cd-deployments-in-5-minutes",{"_path":1404,"_dir":246,"_draft":6,"_partial":6,"_locale":7,"seo":1405,"content":1411,"config":1417,"_id":1419,"_type":14,"title":1420,"_source":16,"_file":1421,"_stem":1422,"_extension":19},"/en-us/blog/five-great-phabricator-features-inspired-gitlab",{"title":1406,"description":1407,"ogTitle":1406,"ogDescription":1407,"noIndex":6,"ogImage":1408,"ogUrl":1409,"ogSiteName":686,"ogType":687,"canonicalUrls":1409,"schema":1410},"5 Great Phabricator features that inspired GitLab","Take a deep dive into the Phabricator features that prompted GitLab to build new tooling around automation, integrated CI, and better code reviews.","https://res.cloudinary.com/about-gitlab-com/image/upload/v1749667482/Blog/Hero%20Images/cover-image-unsplash.jpg","https://about.gitlab.com/blog/five-great-phabricator-features-inspired-gitlab","\n                        {\n        \"@context\": \"https://schema.org\",\n        \"@type\": \"Article\",\n        \"headline\": \"5 Great Phabricator features that inspired GitLab\",\n        \"author\": [{\"@type\":\"Person\",\"name\":\"Michael Friedrich\"}],\n        \"datePublished\": \"2021-08-13\",\n      }",{"title":1406,"description":1407,"authors":1412,"heroImage":1408,"date":1413,"body":1414,"category":301,"tags":1415},[1252],"2021-08-13","Innovation often happens because competition sparks new ideas. We unpack how\nPhabricator inspired GitLab to add new features.\n\n\n## Phabricator explained\n\n\nTurning back time a bit, what exactly is Phabricator? Built on the concept\nof web-based applications, Phabricator enables developers to collaborate\nwith code reviews, repository browser, change monitoring, bug tracking and\nwiki. On May 29, 2021, Phacility, the maintainer and sponsor of Phabricator\n[announced\nend-of-life](https://admin.phacility.com/phame/post/view/11/phacility_is_winding_down_operations/)\nand stopped maintaining Phabricator.\n\n\n[GitLab co-founder and CEO, Sid Sijbrandij](/company/team/#sytses) gives\ncredit to Phabricator on\n[HackerNews](https://news.ycombinator.com/item?id=27334636):\n\n\n> Phabricator was an inspiration to me when starting GitLab. It is shutting\ndown now. [Many of its features were years ahead of its\ntime](https://news.ycombinator.com/item?id=27334511) and there was a lot of\nhumor in the product. As a tribute to it shall we add Clowcoptarize as a way\nto merge? This would be an [opt in option introduced in GitLab\n14.0](https://gitlab.com/gitlab-org/gitlab/-/issues/332215).\n\n\nIt got me curious: What are these inspirations Sid is referring to? Let's\ndive into GitLab's history together and see what we can learn.\n\n\n_Tip: Features in the [GitLab documentation](https://docs.gitlab.com/) often\nhave a `Version History` box. You can use the issue URLs to dive deeper into\nfeature proposals, discussions, etc._\n\n\n### Review workflows\n\n\nA typical engineering workflow is as follows: The engineering manager\nassigns a new issue as a task to a developer. The developer works in their\npreferred IDE – local in VS Code or in the\n[Gitpod](/blog/teams-gitpod-integration-gitlab-speed-up-development/)\ncloud environment. Changes happen in a new feature branch in Git, which gets\npushed to the remote Git server for collaboration.\n\n\nThe Git branch is not ready yet and stays hidden in a potentially long list\nof branches. To keep better track of their feature branches, developers\ncould copy-paste the branch name or URL into the related issue - which I did\n10 years ago. The concept of a \"diff linked to a task for review\" in\nPhabricator, likewise a \"Git branch with commits linked to Merge Requests\"\nin GitLab was not invented yet. \n\n\nPhabricator inspired GitLab to create a [default\nworkflow](https://secure.phabricator.com/phame/post/view/766/write_review_merge_publish_phabricator_review_workflow/)\nfor reviews. The Phabricator workflow makes the review more dominant and\nsquashes all changes into a single commit after the review is approved.\nThere are upsides and downsides to automatically squashing commits.\nSquashing the commits could mean losing information from review history and\ncreate more discussion. Depending on the application architecture, the\nfrequency of changes, and debugging requirements, this can be a good thing\nor a bad thing. GitLab allows you to choose to [squash\ncommits](https://docs.gitlab.com/ee/user/project/merge_requests/squash_and_merge.html)\nbefore merging a MR and/or specifying the default project settings around\nsquashing commits.\n\n\nPhabricator treated a MR (or what they call \"diff tasks\") as the single\nsource of truth for tracking changes and the review history. We felt this\nwas a great idea, and replicated the process of a \"diff task\" in Phabricator\nin GitLab MRs. One of the major upsides to GitLab's version is that\ncollaboration and discussion that happened in issues and epics is still\navailable even after the change is merged.\n\n\n#### Draft MR (or \"diff tasks\")\n\n\nMany times when a MR is created in GitLab, the branch requires additional\nwork before it is ready to be merged. Phabricator introduced a [formal\n\"Draft\" / \"Not Yet Ready for Review\"\nstate](https://secure.phabricator.com/T2543) in 2013 for \"diff tasks\", which\nhelped keep track of work in this state. GitLab added [WIP MRs in\n2016](/blog/feature-highlight-wip/), which we then renamed to draft merge\nrequests in 2020. While `WIP` may make sense to some people, acronyms can\nexclude newcomers. We found `Draft` is more recognizable. To avoid\nconfusion, GitLab [deprecated WIP and moved forward with draft merge\nrequests](https://gitlab.com/gitlab-org/gitlab/-/issues/32692).\n\n\n#### Keep history in MRs for future debugging\n\n\nThe commit history in GitLab is enriched with links to the MR and the\ncorresponding Git review history. In case of a production emergency, having\neverything documented allows for faster research and debugging.\n\n\nGitLab stores the MR short URL with `\u003Cnamespace>/\u003Cproject>!1234` in the\nmerge commit message. Check the history of a [demo project for the\nKubernetes\nagent](https://gitlab.com/everyonecancontribute/kubernetes/k8s-agent/-/commits/main/)\nto see how the merge commit is rendered.\n\n\n![GitLab history with MR commit\nlinks](https://about.gitlab.com/images/blogimages/phabricator-features-inspired-gitlab/gitlab_history_mr_metadata_link.png)\n\nGitLab commit history includes link to the MR.\n\n{: .note.text-center}\n\n\nThis raw information is stored in the Git repository, whereas the MR itself\nstays in GitLab's database backend. You can verify this by cloning a\nrepository and inspecting the history with this command:\n\n\n```sh\n\n$ git log\n\n```\n\n\n![git log MR\nmetadata](https://about.gitlab.com/images/blogimages/phabricator-features-inspired-gitlab/git_log_mr_merge_commit_metadata_link.png)\n\nMR metadata included in output from `git log` command.\n\n{: .note.text-center}\n\n\n### Code coverage in MRs\n\n\nCode coverage reports provide insight into how many lines of the source code\nare covered with unit tests. Reaching 100% test coverage is a developer myth\n- visualizing a decrease or increase can help monitor a trend in code\nquality. Phabricator implemented support for various languages with unit\ntest engines and parsing the output, for example in\n[Golang](https://secure.phabricator.com/D12621).\n\n\nWith many different languages and report output formats, integrating code\ncoverage reports into GitLab MRs was challenging. [GitLab launched the first\niteration of code coverage reports in\n2016](/blog/publish-code-coverage-report-with-gitlab-pages/), which\ngenerated the reports with CI/CD jobs and used GitLab pages to publish the\nHTML reports.\n\n\nIn this first iteration, the test coverage is parsed with a regular\nexpression from the CI/CD job output, specified in the project settings or\nwith the [coverage](https://docs.gitlab.com/ee/ci/yaml/#coverage) keyword\ninside the CI/CD job configuration. We can see this in the job view inside\nthe [MR\nwidget](https://docs.gitlab.com/ee/ci/pipelines/settings.html#add-test-coverage-results-to-a-merge-request)\nand as a coverage badge for the project. See the test coverage history by\nnavigating into `Analytics > Repository`.\n\n\n![Test coverage as project badge in\nGitLab](https://about.gitlab.com/images/blogimages/phabricator-features-inspired-gitlab/gitlab_project_badge_test_coverage.png)\n\nThe test coverage badge in a GitLab project.\n\n{: .note.text-center}\n\n\nJUnit XML test reports were introduced as common format specification and\nadded as an [MR widget in\n2018](https://docs.gitlab.com/ee/ci/unit_test_reports.html). The test\nreports runs in the background, using CI/CD artifacts to upload the XML\nreports from the runner to the server, where the MR/pipeline view visualizes\nthe coverage reports in a tab.\n\n\nThe generic JUnit integration also helped with customization requests to\nunit tests, updated CLI commands, or changed coverage report outputs to\nparse. GitLab provides [CI/CD template\nexamples](https://docs.gitlab.com/ee/ci/examples/)\n\n\nThe missing piece for GitLab was having inline code coverage remarks inside\nMR diffs. It took about five years for [Sid's initial proposal for inline\ncode coverage remarks](https://gitlab.com/gitlab-org/gitlab/-/issues/3708)\nto be implemented. In 2020, inline code coverage remarks were released in\n[GitLab\n13.5](https://docs.gitlab.com/ee/user/project/merge_requests/test_coverage_visualization.html).\n\n\n![Test Coverage with Rust in\nGitLab](https://about.gitlab.com/images/blogimages/phabricator-features-inspired-gitlab/gitlab_mr_diff_inline_test_coverage.png)\n\nHow inline code coverage works in GitLab.\n\n{: .note.text-center}\n\n\nCheck out [this MR to practice verifying the test\ncoverage](https://gitlab.com/everyonecancontribute/dev/rust-code-coverage-llvm/-/merge_requests/1/diffs?view=inline\nvalidating some Rust code). Make sure to select the inline diff view.\n\n\n### Automated workflows and integrated CI\n\n\nPhabricator provides\n[Herald](https://secure.phabricator.com/book/phabricator/article/herald/) as\nan automated task runner and rule engine to listen for changes. Herald can\nalso be used to ensure [protected\nbranches](https://docs.gitlab.com/ee/user/project/protected_branches.html)\nand [approval\nrules](https://docs.gitlab.com/ee/user/project/merge_requests/approvals/rules.html#add-multiple-approval-rules)\nto enforce a strong permission model in development workflows. There are\nmore examples in this [HackerNews post from\n2016](https://news.ycombinator.com/item?id=12501025) and somehow, I feel\nlike an explorer seeing many great GitLab features in similar ways. 🦊\n\n\n[GitLab CI/CD pipeline\nschedules](https://docs.gitlab.com/ee/ci/pipelines/schedules.html) remind me\nof the task runner, similarly to\n[webhooks](https://docs.gitlab.com/ee/user/project/integrations/webhooks.html)\nand the [REST API](https://docs.gitlab.com/ee/api/) being instrumented from\nCI/CD jobs. The pipeline schedules are also a great way to periodically\nregenerate caches and rebuild container images for cloud native deployments.\n\n\n[Harbormaster](https://secure.phabricator.com/book/phabricator/article/harbormaster/)\nis Phabricator's integration for CI. It's not built from multiple tools in\nthe [DevOps](/topics/devops/) stack, but is instead fully integrated in the\nproduct.\n\n\nThe first version of GitLab CI was created in [November\n2012](/company/history/). In 2015, a GitLab team member came up with the\nidea of combining SCM with CI and [the all-in-one DevOps platform was\nborn](/blog/gitlab-hero-devops-platform/). Built-in CI/CD\ninspired for more features and fostered a better way to innovate together.\nThe [new pipeline editor](/blog/pipeline-editor-overview/) is\njust one example of a streamlined way to configure CI/CD pipelines in\nGitLab.\n\n\nLet's throwback to 2017 and watch as we demonstrate how to take an idea to\nproduction in GitLab, using GKE:\n\n\n\u003Ciframe width=\"560\" height=\"315\"\nsrc=\"https://www.youtube.com/embed/39chczWRKws\" title=\"YouTube video player\"\nframeborder=\"0\" allow=\"accelerometer; autoplay; clipboard-write;\nencrypted-media; gyroscope; picture-in-picture\" allowfullscreen>\u003C/iframe>\n\n\n\u003Cbr>\n\n\n### Work boards for issue management\n\n\nWork needs to be organized. Phabricator led the way with a board which\nallowed users to filter tasks and provide a more detailed view into planning\nand project management.\n\n\n![Phabricator work\nboards](https://about.gitlab.com/images/blogimages/phabricator-features-inspired-gitlab/phabricator_work_boards.png)\n\nInside Phabricator work boards.\n\n{: .note.text-center}\n\n\nGitLab users will recognize the similar look between Phabricator's work\nboards and GitLab [issue\nboards](https://docs.gitlab.com/ee/user/project/issue_board.html). In GitLab\n14.1, we built on existing epic tracking and labeling to create [Epic\nboards](https://docs.gitlab.com/ee/user/group/epics/epic_boards.html) to\nkeep teams organized and measure progress.\n\n\nIn Phabricator, users can drag and drop between columns, which automatically\nchanges the work status for a particular task. This feature inspired the\nboards in GitLab to automatically change the labels in a [defined\nworkflow](/blog/4-ways-to-use-gitlab-issue-boards/) by dragging and dropping\nbetween columns. Users can go a level deeper with scoped labels to switch\nbetween workflow states:\n\n\n* `workflow::design`\n\n* `workflow::planning breakdown`\n\n* `workflow::ready for development`\n\n* `workflow::in dev`\n\n* `workflow::verification`\n\n\nThe [GitLab engineering handbook](/handbook/engineering/workflow/#basics)\ndocuments the different workflows.\n\n\n![Epic boards in\nGitLab](https://about.gitlab.com/images/blogimages/phabricator-features-inspired-gitlab/gitlab_epic_boards.png)\n\nTake a look at the Epic boards in GitLab.\n\n{: .note.text-center}\n\n\n### Put it all together\n\n\nIn Phabricator, a diff task (in GitLab they're MRs) in the \"review\" state is\nlinked to another task specifying the requirements. The UX needs to be clear\nso the relationship between the diffs can be accessed and understood. Unless\nnecessary, the user shouldn't have to navigate manually. The context of the\nchange review defines possible links to labels, states, dependent issues,\ndiff tasks (MRs), and more.\n\n\nGitLab links [related\nissues](https://docs.gitlab.com/ee/user/project/issues/related_issues.html).\nIf an issue is mentioned in a MR, or vice versa, [GitLab automatically links\nthem](https://docs.gitlab.com/ee/user/project/issues/crosslinking_issues.html#from-merge-requests).\nThe user also has the option to have the issue close automatically once a\nchange is merged. Read a blog post from 2016 to learn more about [how issues\nand MRs can relate to each other in\nGitLab](/blog/gitlab-tutorial-its-all-connected/).\n\n\n![Linked issues and MRs in\nGitLab](https://about.gitlab.com/images/blogimages/phabricator-features-inspired-gitlab/gitlab_linked_issues_mrs.png)\n\nLinked issues and related MRs in GitLab.\n\n{: .note.text-center}\n\n\nUX work is challenging, and we continue to iterate to improve workflows in\nGitLab. For example, in GitLab 13.8, we reduced the number of clicks it\ntakes to [download a CI/CD job artifact from the\nMR](https://gitlab.com/gitlab-org/gitlab/-/issues/37346).\n\n\n\n### Did we miss a feature Phabricator inspired?\n\n\nWhile writing this blog post, my research revealed more gems. For example, I\nfound a proposal to add [visual graphs for issue\ndependencies](https://gitlab.com/gitlab-org/gitlab/-/issues/273597) in the\n[HN thread](https://news.ycombinator.com/item?id=27336818).\n\n\nWhich features from Phabricator are missing in GitLab? Let us know in the\ncomments, create a new [feature\nproposal](https://gitlab.com/gitlab-org/gitlab/-/issues/new?issuable_template=Feature%20Proposal%20-%20lean)\nor start your [contribution journey](/community/contribute/) in a new MR\nright away! \n\n\nCover image by [Johannes Plenio](https://unsplash.com/photos/DKix6Un55mw) on\n[Unsplash](https://unsplash.com)\n\n{: .note}\n",[9,1416,1004],"code review",{"slug":1418,"featured":6,"template":699},"five-great-phabricator-features-inspired-gitlab","content:en-us:blog:five-great-phabricator-features-inspired-gitlab.yml","Five Great Phabricator Features Inspired Gitlab","en-us/blog/five-great-phabricator-features-inspired-gitlab.yml","en-us/blog/five-great-phabricator-features-inspired-gitlab",{"_path":1424,"_dir":246,"_draft":6,"_partial":6,"_locale":7,"seo":1425,"content":1431,"config":1437,"_id":1439,"_type":14,"title":1440,"_source":16,"_file":1441,"_stem":1442,"_extension":19},"/en-us/blog/five-ways-to-streamline-cloud-adoption",{"title":1426,"description":1427,"ogTitle":1426,"ogDescription":1427,"noIndex":6,"ogImage":1428,"ogUrl":1429,"ogSiteName":686,"ogType":687,"canonicalUrls":1429,"schema":1430},"5 ways to streamline your cloud adoption","As companies migrate to the cloud, consider these helpful tips for making the move smoother and more efficient.","https://res.cloudinary.com/about-gitlab-com/image/upload/v1749663930/Blog/Hero%20Images/daytime-clouds_1800x945.png","https://about.gitlab.com/blog/five-ways-to-streamline-cloud-adoption","\n                        {\n        \"@context\": \"https://schema.org\",\n        \"@type\": \"Article\",\n        \"headline\": \"5 ways to streamline your cloud adoption\",\n        \"author\": [{\"@type\":\"Person\",\"name\":\"Sharon Gaudin\"}],\n        \"datePublished\": \"2023-09-05\",\n      }",{"title":1426,"description":1427,"authors":1432,"heroImage":1428,"date":1434,"body":1435,"category":1131,"tags":1436},[1433],"Sharon Gaudin","2023-09-05","\nMoving to the cloud makes sense to a lot of companies — it’s getting there that can be difficult.\n\n[GitLab’s 2023 Global DevSecOps Survey](https://about.gitlab.com/developer-survey/) showed that migrating to the cloud can help organizations release software faster: Respondents who were running at least 25% of their applications in the cloud were twice as likely to release software faster than they were a year ago.\n\nHowever, the migration, whether to a single-cloud service or a multi-cloud environment, can be a big lift. IT teams are tasked with securing major data stores and workloads, navigating the complexities of moving legacy applications, and ensuring that cloud environments comply with applicable data regulations and laws. It can be complicated, with a lot of moving pieces that are often difficult to track.\n\nAnd the longer a migration drags on, the more things can go wrong and the more expensive it can get. It only makes sense to look for a way to make something so critical to the business easier, faster, and less expensive.\n\nAbubakar Siddiq Ango, developer evangelism program manager at GitLab, and Fatima Sarah Khalid, developer evangelist at GitLab, share five ways organizations can alleviate some of the time-consuming, repetitive, and arduous tasks it takes to successfully make that move.\n\n## 1. Take care of your data\nOne of the most difficult parts of a cloud migration is moving the data itself – especially if it’s complex and stored across multiple systems – but there are a few ways you can organize and streamline the tasks involved to make them more straightforward. For example, to save time and increase efficiency, Khalid notes that team members can create [issues](https://docs.gitlab.com/ee/user/project/issues/), break tasks down into [milestones](/blog/tackle-nists-plan-of-action-and-milestones-with-gitlabs-risk-management-features/), and use the [Roadmap](https://docs.gitlab.com/ee/user/group/roadmap/) feature, which gives teams a more granular view of their workflow.\n\n## 2. Avoid security pitfalls\n[Security](/blog/its-time-to-put-the-sec-in-devsecops/) should be a key consideration in any cloud migration. Moving to a cloud environment can inadvertently cause misconfigured servers, unsecure APIs, compliance infringements, and data loss. Any of these problems can trip up cloud migration efforts and expose the company to risk.\n\nTo ensure the move to the cloud proceeds smoothly while minimizing security risks, Ango says teams can use [container](https://docs.gitlab.com/ee/user/application_security/container_scanning/) and [dependency scanning](https://docs.gitlab.com/ee/user/application_security/dependency_scanning/) and [static application security testing](https://docs.gitlab.com/ee/user/application_security/sast/) (SAST) to identify and remediate known vulnerabilities in container images, dependencies, and source code. Teams also can use features such as [code quality](https://docs.gitlab.com/ee/ci/testing/code_quality.html) analysis to supplement existing code review processes and ensure that the project’s code is simple, high-quality, and straightforward to maintain — and, therefore, less likely to cause issues during the migration.\n\n## 3. Automate compliance\n[Compliance](/blog/top-5-compliance-features-to-leverage-in-gitlab/) is another critical issue. IT teams need to ensure the new cloud environment continues to meet all of the organization's regulatory requirements — a potentially large number of standards. That means making sure processes and safeguards focused on data protection are in place and cover the information and applications being moved to the cloud. Manually, that can involve spreadsheets, seemingly endless checklists, and cross-functional teams of people culling through data. Automation makes this more streamlined, requires far fewer people to navigate the process, and is simpler to manage. Automated DevOps practices, like security scanning, [policy automation](/solutions/compliance/), and making compliance standards part of the CI/CD pipeline, all act as guardrails to [keep an organization’s compliance needs on track](/blog/the-importance-of-compliance-in-devops/). With these tools at hand, team members can trust that when they create compliance frameworks and policies, the associated rules will be automatically deployed and enforced throughout the software development lifecycle.\n\n## 4. Relieve configuration challenges\nSetting up and configuring a cloud platform can be a time-consuming and complicated job, but [CI/CD capabilities](/blog/introducing-ci-components/) help automate the configuration process, says Ango. With CI templates, teams can build and deploy applications to different cloud providers or installation targets without having to write their own CI script every time. For instance, [Auto DevOps](https://docs.gitlab.com/ee/topics/autodevops/), a collection of pre-configured features and integrations, uses CI/CD templates to handle deployments on each different cloud environment.\n\nThe [GitLab agent for Kubernetes](https://docs.gitlab.com/ee/user/clusters/agent/install/) also can offer integration capabilities for different cloud providers and services. The agent, which helps set up GitOps, automatically deploys workloads to Kubernetes clusters. Any time new changes are made, it pulls them in and deploys them into a cluster.\nAlso, teams can use [GitLab and Terraform for infrastructure as code](https://docs.gitlab.com/ee/user/infrastructure/iac/), removing the complexities of making configuration changes repeatable, traceable, and more scalable, which is essential for cloud environments.\n\n## 5. Go multi-cloud\nWhile some companies are making initial moves to the cloud, others are expanding from a single cloud to a multi-cloud environment. This strategy enables organizations to run different workloads on different cloud platforms. Being cloud agnostic means they can use the same development tools and internal processes, and then choose where they want to have their workloads run based on their business needs. Problems can arise, though, when IT teams turn to vendor-locked, cloud native developer tools, which are tailored to their own services and might, or might not, support other cloud environments. Using different tools for each cloud platform isn’t efficient, so it’s key to find tools that are cloud or provider agnostic.\n\n## Uncomplicate cloud migration with a DevSecOps platform\nYes, there are different ways to ease a cloud migration – but do teams have to go out and round up a dozen different tools to ensure their migration is fast, secure, and compliant? No, they don't.\n\n“A lot of teams are realizing that having a single, unified place to simplify, automate, and manage the process of setting up or migrating to the cloud is a game changer,” says Khalid. “With an end-to-end [DevSecOps platform](/blog/utilize-the-gitlab-devops-platform-to-avoid-cloud-migration-hazards/), users are able to deploy to any of the common public clouds; support collaboration through features like merge requests, code reviews, and issue tracking; support integrations with a variety of third-party tools; and have built-in security features that allow teams to meet their needs.”\n\nTaking advantage of the GitLab DevSecOps Platform can uncomplicate a lot of those adoption challenges. And GitLab works with any cloud provider.\n\n“I know when people think about the GitLab platform, they focus on security, source code management, and [collaboration](/blog/5-ways-collaboration-boosts-productivity-and-your-career/). But we also really should be thinking about how it’s a tool that helps organizations get their [workload to the cloud](/blog/shifting-from-on-prem-to-cloud/),” says Ango. “You have to be able to work fast, move fast and deploy fast on whatever cloud environment you need, and do it all securely. That is what GitLab offers. That is a big deal.”\n\n_To find the features — all in one place — that your organization needs to ease and speed a cloud migration, check out this [free trial of GitLab Ultimate](https://about.gitlab.com/free-trial/devsecops/)._\n",[786,1133,9,696],{"slug":1438,"featured":6,"template":699},"five-ways-to-streamline-cloud-adoption","content:en-us:blog:five-ways-to-streamline-cloud-adoption.yml","Five Ways To Streamline Cloud Adoption","en-us/blog/five-ways-to-streamline-cloud-adoption.yml","en-us/blog/five-ways-to-streamline-cloud-adoption",{"_path":1444,"_dir":246,"_draft":6,"_partial":6,"_locale":7,"seo":1445,"content":1451,"config":1458,"_id":1460,"_type":14,"title":1461,"_source":16,"_file":1462,"_stem":1463,"_extension":19},"/en-us/blog/getting-started-with-gitlab-application-security",{"title":1446,"description":1447,"ogTitle":1446,"ogDescription":1447,"noIndex":6,"ogImage":1448,"ogUrl":1449,"ogSiteName":686,"ogType":687,"canonicalUrls":1449,"schema":1450},"Getting started with GitLab application security","This tutorial shows how to incorporate GitLab security scan templates into a .gitlab-ci.yml file and view scan results.","https://res.cloudinary.com/about-gitlab-com/image/upload/v1749663993/Blog/Hero%20Images/2018-developer-report-cover.jpg","https://about.gitlab.com/blog/getting-started-with-gitlab-application-security","\n                        {\n        \"@context\": \"https://schema.org\",\n        \"@type\": \"Article\",\n        \"headline\": \"Getting started with GitLab application security\",\n        \"author\": [{\"@type\":\"Person\",\"name\":\"Victor Hernandez\"},{\"@type\":\"Person\",\"name\":\"Julie Byrne\"}],\n        \"datePublished\": \"2023-03-15\",\n      }",{"title":1446,"description":1447,"authors":1452,"heroImage":1448,"date":1455,"body":1456,"category":717,"tags":1457},[1453,1454],"Victor Hernandez","Julie Byrne","2023-03-15","As software security becomes increasingly important, many companies want to\nintroduce standard code scanning processes into development workflows to\nfind and remediate security vulnerabilities before they get to production.\nGitLab's DevSecOps Platform allows users to perform security scans in CI/CD\npipelines, which can easily be enabled to check applications for security\nvulnerabilities such as unauthorized access, data leaks, and denial of\nservice (DoS) attacks. While most of what is covered in this blog will\npertain to Ultimate features, there are some features available for free and\nPremium tier users as well. By the end of this blog, you will have a solid\nstarting point for adopting GitLab security scans, with any tier license,\nand understand the steps to take next to mature your DevSecOps practices.\n\n\n## Prerequisites\n\nTo enable security scanning for a project, you must have the following:\n\n- a GitLab project that meets the requirements of the security scan you\nchoose to enable, with CI enabled\n\n- a `.gitlab-ci.yml` file for the project that has at least a build job\ndefined\n\n- a Linux-based GitLab Runner with the Docker or Kubernetes executor\n\n\n## Get started: Add a scan template to your pipeline\n\n\nHere are the first steps to introduce security scanning.\n\n\n### Available security scans\n\n\nGitLab provides a variety of security scanners, each with its own set of\ncriteria for adoption:\n\n\n| Scan type | Minimum tier | Prerequisites | Application requirements |\n\n| --- | --- | --- | --- |\n\n| [Static application security testing\n(SAST)](https://docs.gitlab.com/ee/user/application_security/sast/) | Free |\nNone | See [SAST\nrequirements](https://docs.gitlab.com/ee/user/application_security/sast/index.html#requirements)\n|\n\n| [Secret\ndetection](https://docs.gitlab.com/ee/user/application_security/secret_detection/)\n| Free | None | None |\n\n| [Container\nscanning](https://docs.gitlab.com/ee/user/application_security/container_scanning/)\n| Free | Container image built and pushed to registry | [Docker 18.09.03 or\nhigher installed on the same computer as the\nrunner](https://docs.gitlab.com/ee/user/application_security/container_scanning/index.html#requirements);\nimage uses a [supported\ndistribution](https://docs.gitlab.com/ee/user/application_security/container_scanning/index.html#supported-distributions)\n|\n\n| [Infrastructure as code (IaC)\nscanning](https://docs.gitlab.com/ee/user/application_security/iac_scanning/)\n|  Free | None | See [supported languages and\nframeworks](https://docs.gitlab.com/ee/user/application_security/iac_scanning/#supported-languages-and-frameworks)\n|\n\n| [Dependency\nscanning](https://docs.gitlab.com/ee/user/application_security/dependency_scanning/)\n- includes license compliance | Ultimate | None | Application must use one\nof the [supported languages and package\nmanagers](https://docs.gitlab.com/ee/user/application_security/dependency_scanning/index.html#supported-languages-and-package-managers)\n|\n\n| [Dynamic application security testing\n(DAST)](https://docs.gitlab.com/ee/user/application_security/dast/) |\nUltimate | [Deployed target\napplication](https://docs.gitlab.com/ee/user/application_security/dast/index.html#prerequisites)\n| See [GitLab DAST scanning\noptions](https://docs.gitlab.com/ee/user/application_security/dast/index.html#gitlab-dast)\n|\n\n| [Coverage-guided fuzz\ntesting](https://docs.gitlab.com/ee/user/application_security/coverage_fuzzing/)\n| Ultimate | Instrumented version of application | See [supported fuzzing\nengines and\nlanguages](https://docs.gitlab.com/ee/user/application_security/coverage_fuzzing/index.html#supported-fuzzing-engines-and-languages)\n|\n\n| [Web API fuzz\ntesting](https://docs.gitlab.com/ee/user/application_security/api_fuzzing/)\n|  Ultimate | Deployed target application | See [supported API\ntypes](https://docs.gitlab.com/ee/user/application_security/api_fuzzing/#enable-web-api-fuzzing)\n|\n\n\nMany customers will start with secret detection, dependency scanning, or\nSAST scanning, as they have the fewest requirements for usage.\n\n\n### Add the scanner template\n\n\nGitLab provides a [CI template for each security\nscan](https://docs.gitlab.com/ee/user/application_security/#security-scanning-without-auto-devops)\nthat can be added to your existing `.gitlab-ci.yml` file. This can be done\nby manually editing the CI file and adding the appropriate template path in\nthe templates section of the file. Several scanners can also be [enabled via\nthe\nUI](https://docs.gitlab.com/ee/user/application_security/sast/#configure-sast-in-the-ui),\nwhere a merge request will be created to add the appropriate scanner to the\n`.gitlab-ci.yml` file. \n\n\nI will use a simple spring boot application as an example and enable\ndependency scanning, a scanner that is popular amongst our customers, as my\nfirst security scan. Dependency scanning will find vulnerabilities in the\nlibraries I am using to build my application. My project is a Java\napplication built via Maven and includes a `pom.xml` file, so it meets the\nrequirements for dependency scanning. Since dependency scanning can be\nenabled via the UI, I'm going to take advantage of that feature here. \n\n\nFor this project, I have created a `.gitlab-ci.yml` file that contains a\nbuild and test stage and a build job. I'm using the Auto DevOps auto-build\njob, but you can define your own build job if desired. This is the starting\npipeline code in my `.gitlab-ci.yml` file:\n\n\n```\n\nimage: alpine:latest\n\n\ninclude:\n  - template: Jobs/Build.gitlab-ci.yml  # https://gitlab.com/gitlab-org/gitlab-foss/blob/master/lib/gitlab/ci/templates/Jobs/Build.gitlab-ci.yml\n\nstages:\n\n- build\n\n- test\n\n\n```\n\n\nTo enable dependency scanning, I'll first navigate to the **Security &\nCompliance** menu, **Configuration** sub-menu.\n\n\n![web\nidentity](https://about.gitlab.com/images/blogimages/2023-02-26-getting-started-with-gitlab-application-security/security_config.png){:\n.shadow}\n\n\nThe option to enable dependency scanning is available about halfway down the\npage. When I click `Configure with a merge request`, a branch is created and\nI am prompted to create a corresponding draft merge request. I'll click\n`Create Merge Request` to save the merge request.\n\n\nOnce the merge request has been created, I see that a new branch\n`set-dependency-scanning-config-1` has been created and the `.gitlab-ci.yml`\nfile has been updated with this code:\n\n\n```\n\n# You can override the included template(s) by including variable overrides\n\n# SAST customization:\nhttps://docs.gitlab.com/ee/user/application_security/sast/#customizing-the-sast-settings\n\n# Secret Detection customization:\nhttps://docs.gitlab.com/ee/user/application_security/secret_detection/#customizing-settings\n\n# Dependency Scanning customization:\nhttps://docs.gitlab.com/ee/user/application_security/dependency_scanning/#customizing-the-dependency-scanning-settings\n\n# Container Scanning customization:\nhttps://docs.gitlab.com/ee/user/application_security/container_scanning/#customizing-the-container-scanning-settings\n\n# Note that environment variables can be set in several places\n\n# See https://docs.gitlab.com/ee/ci/variables/#cicd-variable-precedence\n\nimage: alpine:latest\n\ninclude:\n\n- template: Jobs/Build.gitlab-ci.yml\n\n- template: Security/Dependency-Scanning.gitlab-ci.yml\n\nstages:\n\n- build\n\n- test\n\n\n```\n\n\nThe change kicks off a pipeline, which will now include the dependency scan.\n\n\n![web\nidentity](https://about.gitlab.com/images/blogimages/2023-02-26-getting-started-with-gitlab-application-security/dependency_job.png){:\n.shadow}\n\n\n## View results of the security scan\n\n\nFor all license tiers, you can view the results of any security scan jobs in\nthe appropriate JSON report that can be downloaded from the merge request.\n\n\n![web\nidentity](https://about.gitlab.com/images/blogimages/2023-02-26-getting-started-with-gitlab-application-security/mr_artifacts.png){:\n.shadow}\n\n\nWith GitLab Ultimate, you will also see the vulnerabilities found by the\nscan in the merge request widget.\n\n\n![web\nidentity](https://about.gitlab.com/images/blogimages/2023-02-26-getting-started-with-gitlab-application-security/mr_widget.png){:\n.shadow}\n\n\nAt this point, the `.gitlab-ci.yml` changes that enable security scanning\nare only available in the `set-dependency-scanning-config-1` branch. I will\nmerge them to `main` so that the changes will be included in all future\nfeature branches.\n\n\nWith GitLab Ultimate, merging to `main` will also provide the baseline\n**Vulnerability Report** for our application.  \n\n\n![web\nidentity](https://about.gitlab.com/images/blogimages/2023-02-26-getting-started-with-gitlab-application-security/vuln_report.png){:\n.shadow}\n\n\nNow, scan results presented in the merge request widget for any new merge\nrequests will only show vulnerabilities introduced by those new code changes\nin the corresponding feature branch, and not the baseline of vulnerabilities\nthat already exist on `main`.\n\n\n## Scan enforcement\n\n\nOnce you have enabled your first scans in your CI/CD pipelines, you might be\ncurious to know how you can enforce security scans, or enforce a review and\napproval when critical vulnerabilities are found in new code changes. I\nrecommend reviewing these resources that cover these topics. \n - For Ultimate customers: [How to ensure separation of duties and enforce compliance with GitLab](/blog/ensuring-compliance/)\n - For Premium customers: [How to action security vulnerabilities in GitLab Premium](https://about.gitlab.com/blog/actioning-security-vulnerabilities-in-gitlab-premium/)\n\nNow that you've gained comfort with security scanners as part of the GitLab\nCI/CD pipeline, check out our [Getting Started with GitLab Application\nSecurity](https://docs.gitlab.com/ee/user/application_security/get-started-security.html)\ndocumentation for recommended next steps.\n\n\n## More resources\n - [How GitLab's application security dashboard helps AppSec engineers](/blog/secure-stage-for-appsec/)\n - [Running security scans in limited connectivity and offline environments](/blog/offline-environments/)\n - [GitLab's newest continuous compliance features bolster software supply chain security](/blog/gitlabs-newest-continuous-compliance-features-bolster-software/)\n",[786,9,696,784],{"slug":1459,"featured":6,"template":699},"getting-started-with-gitlab-application-security","content:en-us:blog:getting-started-with-gitlab-application-security.yml","Getting Started With Gitlab Application Security","en-us/blog/getting-started-with-gitlab-application-security.yml","en-us/blog/getting-started-with-gitlab-application-security",{"_path":1465,"_dir":246,"_draft":6,"_partial":6,"_locale":7,"seo":1466,"content":1472,"config":1479,"_id":1481,"_type":14,"title":1482,"_source":16,"_file":1483,"_stem":1484,"_extension":19},"/en-us/blog/getting-started-with-gitlab-understanding-ci-cd",{"title":1467,"description":1468,"ogTitle":1467,"ogDescription":1468,"noIndex":6,"ogImage":1469,"ogUrl":1470,"ogSiteName":686,"ogType":687,"canonicalUrls":1470,"schema":1471},"Getting started with GitLab: Understanding CI/CD","Learn the basics of continuous integration/continuous delivery in this beginner's guide, including what CI/CD components are and how to create them.","https://res.cloudinary.com/about-gitlab-com/image/upload/v1749659525/Blog/Hero%20Images/blog-getting-started-with-gitlab-banner-0497-option4-fy25.png","https://about.gitlab.com/blog/getting-started-with-gitlab-understanding-ci-cd","\n                        {\n        \"@context\": \"https://schema.org\",\n        \"@type\": \"Article\",\n        \"headline\": \"Getting started with GitLab: Understanding CI/CD\",\n        \"author\": [{\"@type\":\"Person\",\"name\":\"GitLab\"}],\n        \"datePublished\": \"2025-04-25\",\n      }",{"title":1467,"description":1468,"authors":1473,"heroImage":1469,"date":1475,"body":1476,"category":1477,"tags":1478},[1474],"GitLab","2025-04-25","*Welcome to our \"Getting started with GitLab\" series, where we help\nnewcomers get familiar with the GitLab DevSecOps platform.*\n\n\nImagine a workflow where every code change is automatically built, tested,\nand deployed to your users. That's the power of [Continuous\nIntegration/Continuous Delivery\n(CI/CD)](https://about.gitlab.com/topics/ci-cd/)! CI/CD helps you catch bugs\nearly, ensures code quality, and delivers software faster and more\nfrequently.\n\n\n### What is CI/CD?\n\n\n* **Continuous Integration** is a development practice where developers\nintegrate code changes into a shared repository frequently, preferably\nseveral times a day. Each integration is then verified by an automated build\nand test process, allowing teams to detect problems early.  \n\n* **Continuous Delivery** extends CI by automating the release pipeline,\nensuring that your code is *always* in a deployable state. You can deploy\nyour application to various environments (e.g., staging, production) with a\nsingle click or automatically.  \n\n* **Continuous Deployment** takes it a step further by automatically\ndeploying *every successful build* to production. This requires a high\ndegree of confidence in your automated tests and deployment process.\n\n\n### Why GitLab CI/CD?\n\n\nGitLab CI/CD is a powerful, integrated system that comes built-in with\nGitLab. It offers a seamless experience for automating your entire software\ndevelopment lifecycle. With GitLab CI/CD, you can:\n\n\n* **Automate everything:** Build, test, and deploy your applications with\nease.  \n\n* **Catch bugs early:** Detect and fix errors before they reach\nproduction.  \n\n* **Get faster feedback:** Receive immediate feedback on your code\nchanges.  \n\n* **Improve collaboration:** Work together more effectively with automated\nworkflows.  \n\n* **Accelerate delivery:** Release software faster and more frequently.  \n\n* **Reduce risk:** Minimize deployment errors and rollbacks.\n\n\n### The elements of GitLab CI/CD\n\n\n* `.gitlab-ci.yml`**:** This [YAML\nfile](https://docs.gitlab.com/ee/ci/yaml/), located in your project's root\ndirectory, defines your CI/CD pipeline, including stages, jobs, and\nrunners.  \n\n* [**GitLab Runner**](https://docs.gitlab.com/runner/)**:** This agent\nexecutes your CI/CD jobs on your infrastructure (e.g. physical machines,\nvirtual machines, Docker containers, or Kubernetes clusters).  \n\n* [**Stages**](https://docs.gitlab.com/ee/ci/yaml/#stages)**:** Stages\ndefine the order of execution for your jobs (e.g. build, test, and\ndeploy).  \n\n* [**Jobs**](https://docs.gitlab.com/ee/ci/yaml/#job-keywords)**:** Jobs are\nindividual units of work within a stage (e.g. compile code, run tests, and\ndeploy to staging).\n\n\n### Setting up GitLab CI\n\n\nGetting started with GitLab CI is simple. Here's a basic example of a\n`.gitlab-ci.yml` file:\n\n\n```yaml\n\n\nstages:\n  - build\n  - test\n  - deploy\n\nbuild_job:\n  stage: build\n  script:\n    - echo \"Building the application...\"\n\ntest_job:\n  stage: test\n  script:\n    - echo \"Running tests...\"\n\ndeploy_job:\n  stage: deploy\n  script:\n    - echo \"Deploying to production...\"\n  environment:\n    name: production\n\n```\n\n\nThis configuration defines three stages: \"build,\" \"test,\" and \"deploy.\" Each\nstage contains a job that executes a simple script.\n\n\n### CI/CD configuration examples\n\n\nLet's explore some more realistic examples.\n\n\n**Building and deploying a Node.js application**\n\n\nThe pipeline definition below outlines using npm to build and test a Node.js\napplication and [dpl](https://docs.gitlab.com/ci/examples/deployment/) to\ndeploy the application to Heroku. The deploy stage of the pipeline makes use\nof [GitLab CI/CD variables](https://docs.gitlab.com/ci/variables/), which\nallow developers to store sensitive information (e.g. credentials) and\nsecurely use them in CI/CD processes. In this example, an API key to deploy\nto Heroku is stored under the variable key name `$HEROKU_API_KEY` used by\nthe dpl tool.\n\n\n```yaml\n\n\nstages:\n  - build\n  - test\n  - deploy\n\nbuild:\n  stage: build\n  image: node:latest\n  script:\n    - npm install\n    - npm run build\n\ntest:\n  stage: test\n  image: node:latest\n  script:\n    - npm run test\n\ndeploy:\n  stage: deploy\n  image: ruby:latest\n  script:\n    - gem install dpl\n    - dpl --provider=heroku --app=$HEROKU_APP_NAME --api-key=$HEROKU_API_KEY\n\n```\n\n\n**Deploying to different environments (staging and production)**\n\n\nGitLab also offers the idea of\n[Environments](https://docs.gitlab.com/ci/environments/) with CI/CD. This\nfeature allows users to track deployments from CI/CD to infrastructure\ntargets. In the example below, the pipeline adds stages with an environment\nproperty for a staging and production environment. While the deploy_staging\nstage will always run its script, the deploy_production stage requires\nmanual approval to prevent accidental deployment to production.  \n\n\n```yaml\n\n\nstages:\n  - build\n  - test\n  - deploy_staging\n  - deploy_production\n\nbuild:\n  # ...\n\ntest:\n  # ...\n\ndeploy_staging:\n  stage: deploy_staging\n  script:\n    - echo \"Deploying to staging...\"\n  environment:\n    name: staging\n\ndeploy_production:\n  stage: deploy_production\n  script:\n    - echo \"Deploying to production...\"\n  environment:\n    name: production\n  when: manual  # Requires manual approval\n\n```\n\n\n### GitLab Auto DevOps\n\n\n[GitLab Auto DevOps](https://docs.gitlab.com/ee/topics/autodevops/)\nsimplifies CI/CD by providing a pre-defined configuration that automatically\nbuilds, tests, and deploys your applications. It leverages best practices\nand industry standards to streamline your workflow.\n\n\nTo enable Auto DevOps:\n\n\n1. Go to your project's **Settings > CI/CD > General pipelines**.  \n\n2. Enable the **Auto DevOps** option.\n\n\nAuto DevOps automatically detects your project's language and framework and\nconfigures the necessary build, test, and deployment stages. You don’t even\nneed to create a `.gitlab-ci.yml` file.\n\n\n### CI/CD Catalog\n\n\nThe [CI/CD\nCatalog](https://about.gitlab.com/blog/faq-gitlab-ci-cd-catalog/)\nis a list of projects with published [CI/CD\ncomponents](https://docs.gitlab.com/ee/ci/components/) you can use to extend\nyour CI/CD workflow. Anyone can create a component project and add it to the\nCI/CD Catalog or contribute to an existing project to improve the available\ncomponents. You can find published components in the [CI/CD\nCatalog](https://gitlab.com/explore/catalog) on GitLab.com.\n\n\n> [Tutorial: How to set up your first GitLab CI/CD\ncomponent](https://about.gitlab.com/blog/tutorial-how-to-set-up-your-first-gitlab-ci-cd-component/)\n\n\n### CI templates\n\n\nYou can also create your own [CI\ntemplates](https://docs.gitlab.com/ee/ci/examples/) to standardize and reuse\nCI/CD configurations across multiple projects. This promotes consistency and\nreduces duplication.\n\n\nTo create a CI template:\n\n\n1. Create a `.gitlab-ci.yml` file in a dedicated project or repository.  \n\n2. Define your CI/CD configuration in the template.  \n\n3. In your project's `.gitlab-ci.yml` file, use the `include` keyword to\ninclude the template.\n\n\n## Take your development to the next level\n\n\nGitLab CI/CD is a powerful tool that can transform your development\nworkflow. By understanding the concepts of CI/CD, configuring your\npipelines, and leveraging features like Auto DevOps, the CI/CD Catalog, and\nCI templates, you can automate your entire software development lifecycle\nand deliver high-quality software faster and more efficiently.\n\n\n> Want to take your learning to the next level? Sign up for [GitLab\nUniversity courses](https://university.gitlab.com/). Or you can get going\nright away with a [free trial of GitLab\nUltimate](https://about.gitlab.com/free-trial/).\n\n\n## \"Getting Started with GitLab\" series\n\n\nCheck out more articles in our \"Getting Started with GitLab\" series:\n\n\n- [How to manage\nusers](https://about.gitlab.com/blog/getting-started-with-gitlab-how-to-manage-users/)\n\n- [How to import your projects to\nGitLab](https://about.gitlab.com/blog/getting-started-with-gitlab-how-to-import-your-projects-to-gitlab/)  \n\n- [Mastering project\nmanagement](https://about.gitlab.com/blog/getting-started-with-gitlab-mastering-project-management/)\n\n- [Automating Agile workflows with the gitlab-triage\ngem](https://about.gitlab.com/blog/automating-agile-workflows-with-the-gitlab-triage-gem/)\n\n- [Working with CI/CD\nvariables](https://about.gitlab.com/blog/getting-started-with-gitlab-working-with-ci-cd-variables/)\n","product",[109,9,696,496,1477,742],{"slug":1480,"featured":91,"template":699},"getting-started-with-gitlab-understanding-ci-cd","content:en-us:blog:getting-started-with-gitlab-understanding-ci-cd.yml","Getting Started With Gitlab Understanding Ci Cd","en-us/blog/getting-started-with-gitlab-understanding-ci-cd.yml","en-us/blog/getting-started-with-gitlab-understanding-ci-cd",{"_path":1486,"_dir":246,"_draft":6,"_partial":6,"_locale":7,"seo":1487,"content":1492,"config":1498,"_id":1500,"_type":14,"title":1501,"_source":16,"_file":1502,"_stem":1503,"_extension":19},"/en-us/blog/getting-started-with-gitlab-working-with-ci-cd-variables",{"title":1488,"description":1489,"ogTitle":1488,"ogDescription":1489,"noIndex":6,"ogImage":1469,"ogUrl":1490,"ogSiteName":686,"ogType":687,"canonicalUrls":1490,"schema":1491},"Getting started with GitLab: Working with CI/CD variables","Learn what CI/CD variables are, why they are important in DevSecOps, and best practices for utilizing them.","https://about.gitlab.com/blog/getting-started-with-gitlab-working-with-ci-cd-variables","\n                        {\n        \"@context\": \"https://schema.org\",\n        \"@type\": \"Article\",\n        \"headline\": \"Getting started with GitLab: Working with CI/CD variables\",\n        \"author\": [{\"@type\":\"Person\",\"name\":\"GitLab Team\"}],\n        \"datePublished\": \"2025-05-27\",\n      }",{"title":1488,"description":1489,"authors":1493,"heroImage":1469,"date":1495,"body":1496,"category":1477,"tags":1497},[1494],"GitLab Team","2025-05-27","*Welcome to our \"Getting started with GitLab\" series, where we help\nnewcomers get familiar with the GitLab DevSecOps platform.*\n\n\nIn an earlier article, we explored [GitLab\nCI/CD](https://about.gitlab.com/blog/getting-started-with-gitlab-understanding-ci-cd/).\nNow, let's dive deeper into the world of **CI/CD variables** and unlock\ntheir full potential.\n\n\n### What are CI/CD variables?\n\n\nCI/CD variables are dynamic key-value pairs that you can define at different\nlevels within your GitLab environment (e.g., project, group, or instance).\nThese variables act as placeholders for values that you can use in your\n`.gitlab-ci.yml` file to customize your pipelines, securely store sensitive\ninformation, and make your CI/CD configuration more maintainable.\n\n\n### Why are CI/CD variables important?\n\n\nCI/CD variables offer numerous benefits:\n\n\n* **Flexibility** - Easily adapt your pipelines to different environments,\nconfigurations, or deployment targets without modifying your core CI/CD\nscript.  \n\n* **Security** - Securely store sensitive information like API keys,\npasswords, and tokens, preventing them from being exposed directly in your\ncode.  \n\n* **Maintainability** - Keep your CI/CD configuration clean and organized by\ncentralizing values in variables, making updates and modifications easier.  \n\n* **Reusability** - Define variables once and reuse them across multiple\nprojects, promoting consistency and reducing duplication.\n\n\n### Scopes of CI/CD variables: Project, group, and instance\n\n\nGitLab allows you to define CI/CD variables with different scopes,\ncontrolling their visibility and accessibility:\n\n\n* **Project-level variables** - These variables are specific to a single\nproject and are ideal for storing project-specific settings, such as:\n  * Deployment URLs: Define different URLs for staging and production environments.  \n  * Database credentials: Store database connection details for testing or deployment.  \n  * Feature flags: Enable or disable features during different stages of your pipeline.  \n  * Example: You have a project called \"MyWebApp\" and want to store the production deployment URL. You create a project-level variable named `DPROD_DEPLOY_URL` with the value `https://mywebapp.com`.  \n* **Group-level variables** - These variables are shared across all projects\nwithin a GitLab group. They are useful for settings that are common to\nmultiple projects, such as:\n\n  * API keys for shared services: Store API keys for services like AWS, Google Cloud, or Docker Hub that are used by multiple projects within the group.  \n  * Global configuration settings: Define common configuration parameters that apply to all projects in the group.  \n  * Example: You have a group called \"Web Apps\" and want to store an API key for Docker Hub. You create a group-level variable named `DOCKER_HUB_API_KEY` with the corresponding API key value.  \n* **Instance-level variables** - These variables are available to all\nprojects on a GitLab instance. They are typically used for global settings\nthat apply across an entire organization such as:\n\n  * Default runner registration token: Provide a default token for registering new [runners](https://docs.gitlab.com/runner/).  \n  * License information: Store license keys for GitLab features or third-party tools.  \n  * Global environment settings: Define environment variables that should be available to all projects.  \n  * Example: You want to set a default Docker image for all projects on your GitLab instance. You create an instance-level variable named `DEFAULT_DOCKER_IMAGE` with the value `ubuntu:latest`.\n\n### Defining CI/CD variables\n\n\nTo define a CI/CD variable:\n\n\n1. Click on the **Settings > CI/CD** buttons for  your project, group, or\ninstance.  \n\n2. Go to the **Variables** section.  \n\n3. Click **Add variable**.  \n\n4. Enter the **key** (e.g., `API_KEY`) and **value**.  \n\n5. Optionally, check the **Protect variable** box for sensitive information.\nThis ensures that the variable is only available to pipelines running on\nprotected branches or tags.  \n\n6. Optionally, check the **Mask variable** box to hide the variable's value\nfrom job logs, preventing accidental exposure.  \n\n7. Click **Save variable**.\n\n\n### Using CI/CD variables\n\n\nTo use a CI/CD variable in your `.gitlab-ci.yml` file, simply prefix the\nvariable name with `$`:\n\n\n```yaml\n\ndeploy_job:\n  script:\n    - echo \"Deploying to production...\"\n    - curl -H \"Authorization: Bearer $API_KEY\" https://api.example.com/deploy\n```\n\n\n### Predefined CI/CD variables\n\n\nGitLab provides a set of [predefined CI/CD\nvariables](https://docs.gitlab.com/ci/variables/predefined_variables/) that\nyou can use in your pipelines. These variables provide information about the\ncurrent pipeline, job, project, and more.\n\n\nSome commonly used predefined variables include:\n\n\n* `$CI_COMMIT_SHA`: The commit SHA of the current pipeline.  \n\n* `$CI_PROJECT_DIR`: The directory where the project is cloned.  \n\n* `$CI_PIPELINE_ID`: The ID of the current pipeline.  \n\n* `$CI_ENVIRONMENT_NAME`: The name of the environment being deployed to (if\napplicable).\n\n\n### Best practices\n\n\n* Securely manage sensitive variables: Use protected and masked variables\nfor API keys, passwords, and other sensitive information.  \n\n* Avoid hardcoding values: Use variables to store configuration values,\nmaking your pipelines more flexible and maintainable.  \n\n* Organize your variables: Use descriptive names and group related variables\ntogether for better organization.  \n\n* Use the appropriate scope: Choose the correct scope (project, group, or\ninstance) for your variables based on their intended use and visibility.\n\n\n### Unlock the power of variables\n\n\nCI/CD variables are a powerful tool for customizing and securing your GitLab\npipelines. By mastering variables and understanding their different scopes,\nyou can create more flexible, maintainable, and efficient workflows.\n\n\nWe hope you found it helpful and are now well-equipped to leverage the power\nof GitLab for your development projects.\n\n\n> Get started with CI/CD variables today with a [free trial of\nGitLab Ultimate with Duo Enterprise](https://about.gitlab.com/free-trial/).\n\n\n## \"Getting Started with GitLab\" series\n\nRead more articles in our \"Getting Started with GitLab\" series:\n\n\n- [How to manage\nusers](https://about.gitlab.com/blog/getting-started-with-gitlab-how-to-manage-users/)\n\n-  [How to import your projects to\nGitLab](https://about.gitlab.com/blog/getting-started-with-gitlab-how-to-import-your-projects-to-gitlab/)  \n\n- [Mastering project\nmanagement](https://about.gitlab.com/blog/getting-started-with-gitlab-mastering-project-management/)\n\n- [Automating Agile workflows with the gitlab-triage\ngem](https://about.gitlab.com/blog/automating-agile-workflows-with-the-gitlab-triage-gem/)\n\n- [Understanding\nCI/CD](https://about.gitlab.com/blog/getting-started-with-gitlab-understanding-ci-cd/)\n",[1477,742,9,696,109,983],{"slug":1499,"featured":91,"template":699},"getting-started-with-gitlab-working-with-ci-cd-variables","content:en-us:blog:getting-started-with-gitlab-working-with-ci-cd-variables.yml","Getting Started With Gitlab Working With Ci Cd Variables","en-us/blog/getting-started-with-gitlab-working-with-ci-cd-variables.yml","en-us/blog/getting-started-with-gitlab-working-with-ci-cd-variables",{"_path":1505,"_dir":246,"_draft":6,"_partial":6,"_locale":7,"seo":1506,"content":1512,"config":1518,"_id":1520,"_type":14,"title":1521,"_source":16,"_file":1522,"_stem":1523,"_extension":19},"/en-us/blog/github-launch-continuous-integration",{"title":1507,"description":1508,"ogTitle":1507,"ogDescription":1508,"noIndex":6,"ogImage":1509,"ogUrl":1510,"ogSiteName":686,"ogType":687,"canonicalUrls":1510,"schema":1511},"GitHub Actions affirms all-in-one is eating the marketplace model","GitHub announces GitHub Actions, a continuous integration tool, affirming the need for single application for the entire DevOps lifecycle.","https://res.cloudinary.com/about-gitlab-com/image/upload/v1749678806/Blog/Hero%20Images/single-application.png","https://about.gitlab.com/blog/github-launch-continuous-integration","\n                        {\n        \"@context\": \"https://schema.org\",\n        \"@type\": \"Article\",\n        \"headline\": \"GitHub Actions affirms all-in-one is eating the marketplace model\",\n        \"author\": [{\"@type\":\"Person\",\"name\":\"Sid Sijbrandij\"}],\n        \"datePublished\": \"2018-10-16\",\n      }",{"title":1507,"description":1508,"authors":1513,"heroImage":1509,"date":1515,"body":1516,"category":301,"tags":1517},[1514],"Sid Sijbrandij","2018-10-16","\nGitHub announced the launch of their continuous integration tool, [GitHub Actions](https://blog.github.com/2018-10-16-future-of-software/), entering into competition with 14 of its [continuous integration marketplace vendors](https://github.com/marketplace/category/continuous-integration), including Travis CI, CircleCI, and CodeShip. This isn’t the first time we’ve seen GitHub compete against a popular area of its marketplace; they also competed against marketplace vendors in the [project management](https://github.com/marketplace/category/project-management) (Waffle.io vs. issue boards) and [dependency scanning](https://github.com/marketplace/category/dependency-management) categories (Snyk).\n\nWhy compete with vendors within their own marketplace? Similar to [Amazon’s private brands](https://www.businessinsider.com/amazon-owns-these-brands-list-2018-7), which compete in categories with well-established leaders on its own platform, all-in-one is eating the marketplace model, and GitHub is ready to eat its own marketplace to stay competitive.\n\nToday’s increasingly complex technology landscape demands a simplified and seamless all-in-one solution – and built-in [continuous integration](/solutions/continuous-integration/) is a logical first step. We know this because when we decided to build a [single application for the entire DevOps lifecycle](/why/), integrated pipelines were the critical first step to helping development teams build, test, deploy, and monitor their code. Companies like [Ticketmaster](/blog/continuous-integration-ticketmaster/) and [Paessler AG](/customers/paessler/) have shown us that when teams are working within a seamlessly integrated application experience, cycle times are reduced by as much as 200%, and the speed of pipelines can be reduced from over two hours to within eight minutes.\n\nWhile there will undoubtedly be space for some successful point solutions, we’re seeing a turning point from disparately integrated toolchains to all-in-one solutions in the tech tools landscape.\n\n## Need for speed and simplicity\n\nSoftware development and delivery is getting more complicated, requiring more tools per team and project. The advent of Kubernetes has brought a desire for DevOps and with it an avalanche of highly focused, sharp tools. The proliferation of teams and tools makes toolchain maintenance unmanageable and cumbersome, slowing down cycle times and inhibiting collaboration at a time when speed to market is critical to business success. Chaining together tools comes at too great of a cost. The explosion of microservices has exacerbated the issue. As more development teams embrace cloud native, building and running application in containers, the number of projects multiples and changes need to be made frequently. Disparate toolchains were not built to handle this level of integration complexity.\n\nA single application removes this complexity, providing a single setup, datastore, flow, and interface where teams can work collaboratively and concurrently. It enables [Concurrent DevOps](/topics/concurrent-devops/), removing the need for sequential handoffs, allowing cross-functional collaboration at speed. Developers, engineers, product managers, and security experts can all work on their piece without slowing each other down, allowing better visibility into work in flight, and the opportunity to shift left contributions from various teams.\n\nEliminating context switching, automated links between environments, code, issues, and epics, real-time updates, and everything in context are just a few reasons the all-in-one model beats out the toolchain. For a complete list, see our [advantages of a single application](/handbook/product/single-application/) page.\n\n## GitLab is a complete DevOps platform, delivered as a single application\n\nWe shipped [GitLab CI/CD](/solutions/continuous-integration/) in 2016, and completed our Master Plan to ship the entire software development lifecycle by the end of 2016. For the past two years, we’ve been continuously improving our single application, and we’re now working on packaging, monitoring, Kubernetes, and even [serverless](/topics/serverless/).\n\nWe’ve made a couple of [acquisitions](/handbook/acquisitions/) to integrate great point-solutions into our single application. It’s our prediction that we will see more acquisitions, big and small, across the technology landscape as the demand for an all-in-one solution grows.\n",[805,741,9],{"slug":1519,"featured":6,"template":699},"github-launch-continuous-integration","content:en-us:blog:github-launch-continuous-integration.yml","Github Launch Continuous Integration","en-us/blog/github-launch-continuous-integration.yml","en-us/blog/github-launch-continuous-integration",{"_path":1525,"_dir":246,"_draft":6,"_partial":6,"_locale":7,"seo":1526,"content":1532,"config":1539,"_id":1541,"_type":14,"title":1542,"_source":16,"_file":1543,"_stem":1544,"_extension":19},"/en-us/blog/gitlab-achieves-aws-devops-competency-certification",{"title":1527,"description":1528,"ogTitle":1527,"ogDescription":1528,"noIndex":6,"ogImage":1529,"ogUrl":1530,"ogSiteName":686,"ogType":687,"canonicalUrls":1530,"schema":1531},"GitLab achieves AWS DevOps Competency certification","GitLab has been certified with AWS DevOps Competency, affirming our further commitment as a technology partner with Amazon Web Services.","https://res.cloudinary.com/about-gitlab-com/image/upload/v1749666959/Blog/Hero%20Images/gitlab-aws-cover.png","https://about.gitlab.com/blog/gitlab-achieves-aws-devops-competency-certification","\n                        {\n        \"@context\": \"https://schema.org\",\n        \"@type\": \"Article\",\n        \"headline\": \"GitLab achieves AWS DevOps Competency certification\",\n        \"author\": [{\"@type\":\"Person\",\"name\":\"Tina Sturgis\"},{\"@type\":\"Person\",\"name\":\"Eliran Mesika\"}],\n        \"datePublished\": \"2018-11-28\",\n      }",{"title":1527,"description":1528,"authors":1533,"heroImage":1529,"date":1536,"body":1537,"category":301,"tags":1538},[1534,1535],"Tina Sturgis","Eliran Mesika","2018-11-28","\n\nToday, we are proud to announce GitLab has been certified with [AWS DevOps Competency](https://aws.amazon.com/devops/partner-solutions/), affirming our further commitment as a technology partner with Amazon Web Services (AWS).\n\nBuilding on the foundation of our AWS partnership over the last three years, with this DevOps certification we’ve now received the highest level of accreditation available from AWS. We bring proven customer success with measurable return on investment for customers running GitLab on AWS and [using GitLab to deploy their software to AWS](/partners/technology-partners/aws/).\n\n![AWS DevOps Competency badge](https://about.gitlab.com/images/blogimages/DevOps_competency_badge.png){: .small.right.wrap-text}\n\n### Why the AWS DevOps Competency matters\n\nAchieving this certification sets GitLab apart as an AWS Partner Network (APN) member that provides demonstrated DevOps technical proficiency and proven customer success, with specific focus in the [Continuous Integration](/solutions/continuous-integration/) and [Continuous Delivery](/solutions/continuous-integration/) category.\n\nThis is important for our own customers who are either looking to move to AWS or are already using it, as well as for current AWS customers. Potential users of GitLab with AWS can be assured that the GitLab solution has been reviewed and approved by an AWS Architect Review Board and that it meets [AWS Security Best Practices](https://d0.awsstatic.com/whitepapers/Security/AWS_Security_Best_Practices.pdf).\n\nThrough this process we were able to demonstrate our product is production ready on AWS for DevOps, specifically for improving application delivery, application build/test, or infrastructure/configuration management.\n\n### GitLab and AWS customer success\n\nTo learn more about the GitLab customer case studies considered for this competency, please review both the Axway and [Trek10](/customers/trek10/) case studies. You can also access information about other customers on the [GitLab customers page](/customers/).\n\n### More about the AWS Competency Program\n\nAWS established the program to help customers identify, through the AWS Partner Network, partners with deep industry experience and expertise in specialized solution areas. Attaining an AWS Competency allows partners to differentiate themselves to customers by showcasing expertise in a specific solution area.\n\nWe are honored to obtain this AWS DevOps Competency status, and believe this helps advance [our mission to allow everyone to contribute](/company/mission/#mission). Our definition of everyone now extends further, to those who are small and large users of AWS and AWS Services on their DevOps journey.   \n\nFor more information on GitLab’s partnership with AWS, check out [about.gitlab.com/solutions/aws](/partners/technology-partners/aws/).\n\nTo learn more about GitLab’s Technology Partners, visit [about.gitlab.com/partners](/partners/technology-partners/).\n",[9,741,805],{"slug":1540,"featured":6,"template":699},"gitlab-achieves-aws-devops-competency-certification","content:en-us:blog:gitlab-achieves-aws-devops-competency-certification.yml","Gitlab Achieves Aws Devops Competency Certification","en-us/blog/gitlab-achieves-aws-devops-competency-certification.yml","en-us/blog/gitlab-achieves-aws-devops-competency-certification",{"_path":1546,"_dir":246,"_draft":6,"_partial":6,"_locale":7,"seo":1547,"content":1553,"config":1559,"_id":1561,"_type":14,"title":1562,"_source":16,"_file":1563,"_stem":1564,"_extension":19},"/en-us/blog/gitlab-and-google-cloud",{"title":1548,"description":1549,"ogTitle":1548,"ogDescription":1549,"noIndex":6,"ogImage":1550,"ogUrl":1551,"ogSiteName":686,"ogType":687,"canonicalUrls":1551,"schema":1552},"How GitLab and Google Cloud drive innovation and efficiency for retailers","Learn how pairing DevSecOps with multicloud environments eases the development burden on retailers.","https://res.cloudinary.com/about-gitlab-com/image/upload/v1749667457/Blog/Hero%20Images/open_source_program_blog_image.jpg","https://about.gitlab.com/blog/gitlab-and-google-cloud","\n                        {\n        \"@context\": \"https://schema.org\",\n        \"@type\": \"Article\",\n        \"headline\": \"How GitLab and Google Cloud drive innovation and efficiency for retailers\",\n        \"author\": [{\"@type\":\"Person\",\"name\":\"Regnard Raquedan\"}],\n        \"datePublished\": \"2023-03-08\",\n      }",{"title":1548,"description":1549,"authors":1554,"heroImage":1550,"date":1556,"body":1557,"category":1131,"tags":1558},[1555],"Regnard Raquedan","2023-03-08","\nInnovation and growth can sometimes be at odds in the world of retail, especially when trying to develop, deploy, and manage modern applications across multicloud environments. GitLab and Google Cloud together help retailers create and secure software that scales along with their business.\n\nGitLab’s comprehensive [DevSecOps Platform](/the-source/platform/devops-teams-want-to-shake-off-diy-toolchains-a-platform-is-the-answer/) connects with Google Cloud’s [Distributed Cloud Edge](https://cloud.google.com/distributed-cloud/edge/latest/docs/overview) edge networking environment and [Anthos](https://cloud.google.com/anthos/docs/concepts/overview) cloud-centric container platform to provide retailers with enterprise-class features such as collaboration and planning, continuous integration ([CI](https://docs.gitlab.com/ee/ci/)), configuration management, and built-in security and compliance.\n\nGitLab enables development teams to streamline management of their distributed, hybrid environments right out of the gate. Retailers can utilize the following capabilities:\n\n* Agile planning and collaboration to ensure Anthos cloud container cluster configurations and policies are up to date and compliant with company standards.\n* Continuous integration to help develop quality code and configurations while simultaneously reducing code errors.\n* Configuration management to roll back to previously working Anthos states or configurations.\n* Native integration with Google Cloud to deploy software faster and more securely.\n\n## Why GitLab for retail?\n\nMulticloud environments are beneficial to retailers because they enable them to easily deploy and manage applications across a vast network of stores, warehouses, and the like. In addition, developing and hosting applications in the cloud provides more choice, faster delivery (i.e. time to market), real-time data access, and the ability to automatically scale resources (up or down). As more retailers look to cloud platforms to achieve these goals, GitLab is uniquely positioned to help them manage these environments in a way that keeps them agile, secure, and able to meet customer demands. \n\nGitLab’s DevSecOps Platform is geared toward helping retailers gain operational efficiencies throughout the software development lifecycle and across [multicloud environments](https://about.gitlab.com/topics/multicloud/) like Google Cloud. \n\nDevelopers at retailers can leverage the full range of features in GitLab’s DevSecOps Platform to build, test, deploy, and secure high-performance, low-latency business-critical applications, such as point of sale. \n\n[Google Distributed Cloud Edge](https://cloud.google.com/distributed-cloud/edge/latest/docs/overview) retail customers can use GitLab to manage their hybrid cloud policies, manage configurations, and administer [Anthos](https://cloud.google.com/anthos/docs) clusters. GitLab’s industry-leading DevSecOps Platform helps developers streamline in-store technology management processes and makes it easier for DevSecOps teams to collaborate. GitLab’s DevSecOps Platform also has built-in security and compliance to meet the unique auditing and reporting needs of retailers. \n\n## Use case: Automated deployment at scale\n\nRetail companies with multiple locations need technology that enables them to manage sprawling resources and maintain smooth operations, even when major changes are introduced. With GitLab’s DevSecOps Platform, retailers can automatically sync configurations and data across their Google Cloud, as well as other cloud and on-premises environments. This is critical for large retailers looking to scale hybrid Anthos clusters vertically across their network with Google Distributed Cloud Edge machines.\n\nGitLab also lets developers easily collaborate and make changes using Agile tools, Merge Requests, and requirements-based workflows. This creates a streamlined, audit-ready process that helps team members make decisions quickly.\n\nConfigurations are stored as YAML files in GitLab repositories, where teams can use a different repository per configuration state. Anthos Configuration Management then retrieves the appropriate configurations when network access is available, allowing for specific regional changes to be made.\n\nOnce changes are reconciled across regions, the new configurations are automatically applied and propagated to the correct Google Distributed Cloud Edge nodes. This secure, scalable process can be used at thousands of locations, decreasing the company's time to value and increasing ROI.\n\nHaving the right technology is a key driver of growth and innovation for retailers. Investing in technology and utilizing platforms like GitLab and Google Cloud can be a game changer for retailers looking to thrive in today's competitive market.\n",[1133,9,784,233],{"slug":1560,"featured":6,"template":699},"gitlab-and-google-cloud","content:en-us:blog:gitlab-and-google-cloud.yml","Gitlab And Google Cloud","en-us/blog/gitlab-and-google-cloud.yml","en-us/blog/gitlab-and-google-cloud",{"_path":1566,"_dir":246,"_draft":6,"_partial":6,"_locale":7,"seo":1567,"content":1573,"config":1578,"_id":1580,"_type":14,"title":1581,"_source":16,"_file":1582,"_stem":1583,"_extension":19},"/en-us/blog/gitlab-apis-ci",{"title":1568,"description":1569,"ogTitle":1568,"ogDescription":1569,"noIndex":6,"ogImage":1570,"ogUrl":1571,"ogSiteName":686,"ogType":687,"canonicalUrls":1571,"schema":1572},"Using Gitlab APIs: Real Use Case Scenario","Learn about how GitLab CI and APIs can help you automate bulk tasks","https://res.cloudinary.com/about-gitlab-com/image/upload/v1749681037/Blog/Hero%20Images/gitlabapi-cover.jpg","https://about.gitlab.com/blog/gitlab-apis-ci","\n                        {\n        \"@context\": \"https://schema.org\",\n        \"@type\": \"Article\",\n        \"headline\": \"Using Gitlab APIs: Real Use Case Scenario\",\n        \"author\": [{\"@type\":\"Person\",\"name\":\"William Arias\"}],\n        \"datePublished\": \"2020-01-22\",\n      }",{"title":1568,"description":1569,"authors":1574,"heroImage":1570,"date":1575,"body":1576,"category":1088,"tags":1577},[1128],"2020-01-22","\n\n{::options parse_block_html=\"true\" /}\n\n\n\nGitlab APIs along with  Continuous Integration can be very helpful when executing certain bulk tasks.\n\nConsider this requirement derived from a real-world scenario\n\n* Company XYZ possess several repositories that have been organized under a Gitlab group\n\n![group](https://about.gitlab.com/images/blogimages/gitlab-apis-ci/gitlab-group.png){: .shadow.medium.center.wrap-text}\n\n* The company needs to test the building of projects in bulk using new  hardware (Runner with different CPU Architecture) that will bring down  execution costs, whenever the build in each of the projects fails an issue must be  automatically created.\n\n![runner](https://about.gitlab.com/images/blogimages/gitlab-apis-ci/runner.png){: .shadow.medium.center.wrap-text}\n\n* Lastly, all the issues that were automatically created whenever a project built failed,  should be collected in bulk and reported back to a Wiki\n\n![pipelineview](https://about.gitlab.com/images/blogimages/gitlab-apis-ci/3-pipelineview-collect-issues.png){: .shadow.medium.center.wrap-text}\n\nHow do we test the building of those several projects and create issues and reports about its execution automatically? Let's use Gitlab CI and  APIs.\n\n\n## 1. Company groups and projects Structure\n\nIn this case, the set of projects were grouped under a single group, following this structure:\n\n![groupview](https://about.gitlab.com/images/blogimages/gitlab-apis-ci/4-group-view-api-blog.png){: .shadow.medium.center.wrap-text}\n\n## 2. Automatically creating Issues leveraging Gitlab CI and API\n\nIn order to create issues using Gitlab API we will use the Issues API an example of that  can use the following cURL command:\n\n![curl](https://about.gitlab.com/images/blogimages/gitlab-apis-ci/5-create-issue-api-gitlabapi.png){: .shadow.medium.center.wrap-text}\n\nThe API Call: \n\n `curl --request POST --header \"PRIVATE-TOKEN:$ISSUE_API_KEY\" \"https://gitlab.com/api/v4/projects/$CI_PROJECT_ID/issues?title=Build%20Failed&labels=ARMbuild&description=Project%20Tests%20Failed%20on%20ARM\"`\n\n The previous Gitlab API call can be configured to be executed whenever a job fails. Let's dissect this API Call to understand its parameters so you can potentially customize it  for your project environment\n\n* Base URL:  https://gitlab.com/api/v4/projects\n* Project where we want to add the issue:  $CI_PROJECT_ID Notice this ID is unique and corresponds to the project where the CI/CD pipeline runs \n* Issues: Endpoint we use to tell Gitlab we want to add an issue to the project\n* Parameters:\n  * Title: How we want the issue to be titled\n  * Labels: Helpful to group issues by label or type, They help you organize and tag your work so you can track and find the work items you’re interested in.\n  * Description: Field to explain the nature of the issue if needed\n\n The request is of type POST, because we are sending data to our receiver service.  For this call to be successful it requires  authentication for which we will use *PRIVATE-TOKEN* header\n\n The private token can be generated by following these steps [How-to-generate-token](https://docs.gitlab.com/ee/user/profile/personal_access_tokens.html)\n\nWhen we execute the above API call, we create an issue in the corresponding Gitlab project\n![issueproject](https://about.gitlab.com/images/blogimages/gitlab-apis-ci/5-issues-created.png){: .shadow.medium.center.wrap-text}\n\nGreat, so once the multi-project pipeline has run,  each of the projects that failed in its building stage will create an issue warning us to double check why it failed while documenting the failure and labeling it for future follow-up.\n![multiproject](https://about.gitlab.com/images/blogimages/gitlab-apis-ci/7.1-multiproject-pipeline-gitlabapi.png){: .shadow.medium.center.wrap-text}\n\n## 3. Automatically collecting all the issues from Gitlab Group\n\nThanks to Gitlab CI and APIs we can collect all the issues created and report them back, by adding this script  in  your pipeline stage\n\n![collectissues](https://about.gitlab.com/images/blogimages/gitlab-apis-ci/7-collecting-issues-apiblog.png){: .shadow.medium.center.wrap-text}\n\nLet's dissect again the main API call:\n\n`curl --header \"PRIVATE-TOKEN:$GROUP_ISSUE_LIST\" \"https://gitlab.com/api/v4/groups/9123625/issues`\n\n* Base url: https://gitlab.com/api/v4/\n* Group resource: /groups/9123625\n* Issues resources: /issues \n\nThe previous API call will return a json object, the one we will save as an artifact when executing our pipeline job. Notice this artifact is created and saved automatically by Gitlab CI\nGreat! So far we created issues per failed project, and collected them all in one single step\n\n\n## 4. Reporting back to Wiki Project \n\n![wikijob](https://about.gitlab.com/images/blogimages/gitlab-apis-ci/8-reportwiki-gitlab-api.png){: .shadow.medium.center.wrap-text}\n\nFor convenience, the json report was transformed to markdown, then using the following script we publish the markdown report to the Wiki of an specific project\n\n`curl --data \"format=markdown&title=$CI_JOB_ID&content=$results\" --header \"PRIVATE-TOKEN:$API_WIKI\" \"https://gitlab.com/api/v4/projects/20852684/wikis\"`\n\nLet's breakdown again the API call:\n\n* Base url: https://gitlab.com/api/v4/\n* Project resource ID : /projects/20852684\n* Wiki resource: /wiki\n* Parameters: \n  * Data format: markdown. We want to publish a markdown table\n  * Title: Title of the Wiki entry, we use the environment variable corresponding to the CI_JOB that was executed\n  * Content: The markdown table generated with the issues collection\n\n Finally, when the last API call has been executed, this is an example of the output we can get: \n\n ![report](https://about.gitlab.com/images/blogimages/gitlab-apis-ci/10-test-report-gitlabapi.png){: .shadow.medium.center.wrap-text}\n\nLet's recapitulate, by using Gitlab CI in a multi project pipeline along with APIs we were able to test and report automatically x-number of projects and its compatibility with a new hardware CPU architecture. More information about the APIs utilized for this project here:\n\n[Issues-api](https://docs.gitlab.com/ee/api/issues.html#new-issue)\n[Collect-group-issues](https://docs.gitlab.com/ee/api/issues.html#list-group-issues)\n[WikisAPI](https://docs.gitlab.com/ee/api/wikis.html)\n\n[Multi-project-pipeline](https://about.gitlab.com/blog/cross-project-pipeline/)\n\n\nIf you’d like to see GitLab’s API in action, watch this [video](https://youtu.be/zdBwMHARkU0?t=469).\n\nFor more information, visit [LEARN@GITLAB](https://about.gitlab.com/learn/).\n\nCover image credit:\n\nCover image by [Mohanan](https://unsplash.com/photos/yQpAaMsQzYE) on [Unsplash](https://unsplash.com)\n{: .note}\n\n",[9,808,741,913],{"slug":1579,"featured":6,"template":699},"gitlab-apis-ci","content:en-us:blog:gitlab-apis-ci.yml","Gitlab Apis Ci","en-us/blog/gitlab-apis-ci.yml","en-us/blog/gitlab-apis-ci",{"_path":1585,"_dir":246,"_draft":6,"_partial":6,"_locale":7,"seo":1586,"content":1592,"config":1598,"_id":1600,"_type":14,"title":1601,"_source":16,"_file":1602,"_stem":1603,"_extension":19},"/en-us/blog/gitlab-ci-cd-features-improvements",{"title":1587,"description":1588,"ogTitle":1587,"ogDescription":1588,"noIndex":6,"ogImage":1589,"ogUrl":1590,"ogSiteName":686,"ogType":687,"canonicalUrls":1590,"schema":1591},"GitLab CI/CD's 2018 highlights","We move quickly, always with an eye to the future, but let's take a moment to look back on how GitLab CI/CD has evolved in the past six months.","https://res.cloudinary.com/about-gitlab-com/image/upload/v1749663779/Blog/Hero%20Images/cicd-2018_blogimage.jpg","https://about.gitlab.com/blog/gitlab-ci-cd-features-improvements","\n                        {\n        \"@context\": \"https://schema.org\",\n        \"@type\": \"Article\",\n        \"headline\": \"GitLab CI/CD's 2018 highlights\",\n        \"author\": [{\"@type\":\"Person\",\"name\":\"Jason Yavorska\"}],\n        \"datePublished\": \"2019-01-21\",\n      }",{"title":1587,"description":1588,"authors":1593,"heroImage":1589,"date":1595,"body":1596,"category":301,"tags":1597},[1594],"Jason Yavorska","2019-01-21","\nHello everyone, and happy New Year! For those who don't know me, my name is [Jason Yavorska](/company/team/#jyavorska) and I've been the product manager of GitLab CI/CD since around the middle of last year. 2018 was a big year for CI/CD improvements in GitLab, and I'm so proud of our team and what we've been able to deliver in partnership with you, our users. Even just looking back on the last six months of improvements, we've delivered a ton of changes that move our vision for CI/CD forward, address important asks from our users, and build the foundation for an amazing 2019.\n\nBelow are a few of the highlights from my time here so far; be sure to let me know in the comments if I missed something that meant a lot to you.\n\n## Access control for GitLab Pages\n\nOne of the most amazing things about working for an open core company like GitLab is that our community of users can play an outsized role in how our product grows and develops, thanks to their always impressive contributions. Last year we introduced [Access control for Pages (11.5)](https://gitlab.com/gitlab-org/gitlab-ce/issues/33422), a feature with 304 👍 that was actually part of our 2019 vision, and was built thanks to a significant community contribution from MVP [Tuomo Ala-Vannesluoma](https://gitlab.com/tuomoa).\n\nThis was not just a great feature, but also highlights how GitLab and community contributors can work together to do amazing things. It came out shortly after I joined as a new product manager here, and it really opened my eyes to the possibilities inherent in working together transparently and openly with our user community. Now I don't think I could ever go back to any other way of working.\n\n## Feature flags\n\nI'm always looking for ways to expand our horizons and bring more great capabilities into the CI/CD space, and the team achieved that last year with [Allow users to create and manage feature flags for their applications (11.4)](https://gitlab.com/gitlab-org/gitlab-ee/issues/6220). A major piece of our 2018 vision, feature flags are so important to continuous delivery workflows since they allow you to safely isolate delivering your code to production, from the moment users engage with it, giving you more control and better options when it comes to how and when you deliver software.\n\n![CI/CD feature flags](https://about.gitlab.com/images/blogimages/cicd-feature_flags.png){: .shadow.medium.center}\n\n## Pipelines for merge requests\n\nSometimes, what you do in one year may be valuable on its own, but it also helps establish a foundation for more in the future. A common request from the community last year had been to make pipelines more aware of merge requests, so that at runtime, information such as the target branch, merge request name and ID, and other information was available to the pipeline. In 2018 we introduced [`only/except: merge_requests` for merge request pipelines (11.6)](https://gitlab.com/gitlab-org/gitlab-ce/issues/15310), which created this linkage. One great way to take advantage of this feature already is to use it to only create [Review Apps](https://docs.gitlab.com/ee/ci/review_apps/) on merge requests, helping to save money on environments versus creating them for every pipeline.\n\nPerhaps even more exciting than this feature on its own, is that it will continue to evolve and grow into the ability to [Run a pipeline on what the merged result will be](https://gitlab.com/gitlab-org/gitlab-ee/issues/7380). I can already say with confidence that this will be a game changer for teams that want to prioritize keeping their `master` branch green. As far as predicting the future outside of GitLab, I'm still accepting merge requests for that 😉\n\n![pipelines for merge requests](https://about.gitlab.com/images/blogimages/cicd-mr_pipelines.png){: .shadow.medium.center}\n\n## Usability improvements for the merge request widget\n\nSpeaking of merge requests, in general the team has made a lot of improvements to how the merge request widget interacts with CI/CD. We added [JUnit XML Test Summary (11.2)](https://gitlab.com/gitlab-org/gitlab-ce/issues/45318), part of our 2018 vision to make testing a more interactive part of the CI pipeline. We also now [Show enhanced information on running deploys (11.5)](https://gitlab.com/gitlab-org/gitlab-ce/issues/25140), and [Link directly to changed pages in Review App (11.5)](https://gitlab.com/gitlab-org/gitlab-ce/issues/33418), which uses [Route Maps](https://docs.gitlab.com/ee/ci/environments/index.html#go-directly-from-source-files-to-public-pages-on-the-environment) to send you directly to the updated content. Both of these changes were welcome improvements that made it much easier to see what was going on, all in one place.\n\n![CI/CD review app link](https://about.gitlab.com/images/blogimages/cicd-reviewapp_link.png){: .shadow.medium.center}\n\n## #movingtogitlab\n\n[#movingtogitlab](https://twitter.com/hashtag/movingtogitlab?src=hash) was an exciting movement in 2018, and I wanted to ensure a great experience for everyone checking us out, even if they were just trying out GitLab CI or other features, and still using GitHub for repositories. One of the challenges that people ran into early on was the way status checks were named by GitLab CI, which didn't play nicely with the way GitHub expected them to work. The team was able to introduce [Name status checks consistently to support GitHub-integrated CI workflow (11.5)](https://gitlab.com/gitlab-org/gitlab-ce/issues/53902) as a change to unblock this, ensuring a valuable experience for everyone, even if you weren't ready to go \"all in\" on GitLab yet.\n\n## Stewardship\n\nHere at GitLab, we take [stewardship of open source](/company/stewardship/) seriously. I was very happy to move the `include:` keyword from paid to free, because I know how important it is for CI/CD users to support proper reuse instead of copy-pasted code. [Move \"include external files in .gitlab-ci.yml\" from Starter to Core (11.4)](https://gitlab.com/gitlab-org/gitlab-ce/issues/42861) (with a grand total of 267 👍 on the issue) achieved this, and opened up new doors, not just for avoiding duplication, but also for more secure ways of implementing common workflows by moving compliance, security, and governance job implementation to a centrally controlled location.\n\n## Honorable mentions\n\nThere wasn't enough time to cover everything in this post without making it a mile long, but there are a few other honorable mentions I want to call out:\n\n- [11.2: Manually stopping environments](https://gitlab.com/gitlab-org/gitlab-ce/issues/25388) (with 245 👍 from our users) added the ability to manually stop your environments, such as review apps, instead of only through pipeline automation.\n- [11.3: Improve handling of includes in `.gitlab-ci.yml` to better enable script reuse/templates](https://gitlab.com/gitlab-org/gitlab-ce/issues/51521) introduced a new way to `extend` your job definitions using templates, including from across different files.\n- [11.4: Run jobs only/except when there are changes for a given path or file](https://gitlab.com/gitlab-org/gitlab-ce/issues/19232) (with a whopping 424 👍) gave you the ability to control whether a job runs or not, based on which files were changed.\n- [11.4: Add support for interactive web terminal to Docker executor](https://gitlab.com/gitlab-org/gitlab-runner/issues/3467) let you connect an interactive to a build/deploy environment and troubleshoot on the live runner host.\n- [11.4: Add timed deployments to AutoDevOps incremental rollouts](https://gitlab.com/gitlab-org/gitlab-ee/issues/7545) enabled new deployment strategies where the rollout was done over time in phases.\n- [11.5: `parallel` job keyword to speed up pipelines](https://gitlab.com/gitlab-org/gitlab-ce/issues/21480) added an easy way to run parallel instances of a job without creating duplicate jobs in your `gitlab-ci.yml`.\n- [11.6: Allow pipelines to be deleted by project owners](https://gitlab.com/gitlab-org/gitlab-ce/issues/41875) (265 👍) gave control over removing old and invalid pipelines, as well as those which may have accidentally included sensitive information in the outputs.\n\n## What's next?\n\nOf course, the mission to improve GitLab CI/CD doesn't stop here. We're bringing [Brendan O'Leary](/company/team/#olearycrew) on board as the full-time product manager for CI (what we call the [Verify stage](/stages-devops-lifecycle/verify/)), freeing me up to focus entirely on CD (what we call [Release](/stages-devops-lifecycle/release/)). We're also significantly growing headcount for the engineering teams supporting us. Having full-time product managers and larger teams dedicated to each of these stages is going to allow us to deliver even more amazing things, even faster.\n\nI've touched on a couple points above, but tried to avoid making this a preview of what's coming for CI/CD in 2019. If you're interested in where Brendan and I are headed, you can visit our direction pages for [Verify (CI)](/direction/verify/) and [Release (CD)](/direction/release/), and feel free to reach out to us directly if you'd like to have a conversation – we'd love to chat about your ideas. Being a transparent, open core company, we also welcome participation in all of our public issues (which you'll find linked to from the above direction pages). For me, the best part of this job is interacting with you, the users of GitLab, so thank you for that opportunity. Here's to another great year of working together to make the job of delivering software fun and rewarding!\n\n## Just one more thing...\n\nI'd be remiss if I didn't mention how great GitLab is as a place to work. If you're interested in joining our all-remote team, we're constantly growing and looking for great PMs and others to join us. Check out [our jobs page](/jobs/) to learn more. I'd encourage you to apply even if you don't see an exact match – GitLab is great at finding the right fit for the right personality, even if that's not exactly listed on our hiring website. If you're really unsure, feel free to reach out to me directly ([@j4yav](https://twitter.com/j4yav)) and I'll help you get in touch with the right person.\n",[9,269,983,915,916],{"slug":1599,"featured":6,"template":699},"gitlab-ci-cd-features-improvements","content:en-us:blog:gitlab-ci-cd-features-improvements.yml","Gitlab Ci Cd Features Improvements","en-us/blog/gitlab-ci-cd-features-improvements.yml","en-us/blog/gitlab-ci-cd-features-improvements",{"_path":1605,"_dir":246,"_draft":6,"_partial":6,"_locale":7,"seo":1606,"content":1612,"config":1618,"_id":1620,"_type":14,"title":1621,"_source":16,"_file":1622,"_stem":1623,"_extension":19},"/en-us/blog/gitlab-com-artifacts-cdn-change",{"title":1607,"description":1608,"ogTitle":1607,"ogDescription":1608,"noIndex":6,"ogImage":1609,"ogUrl":1610,"ogSiteName":686,"ogType":687,"canonicalUrls":1610,"schema":1611},"GitLab.com CI artifacts to use Google Cloud CDN","GitLab CI users might benefit from faster downloads from edge caches closest to the user's location.","https://res.cloudinary.com/about-gitlab-com/image/upload/v1749663009/Blog/Hero%20Images/ESA_case_study_image.jpg","https://about.gitlab.com/blog/gitlab-com-artifacts-cdn-change","\n                        {\n        \"@context\": \"https://schema.org\",\n        \"@type\": \"Article\",\n        \"headline\": \"GitLab.com CI artifacts to use Google Cloud CDN\",\n        \"author\": [{\"@type\":\"Person\",\"name\":\"Stan Hu\"}],\n        \"datePublished\": \"2022-10-25\",\n      }",{"title":1607,"description":1608,"authors":1613,"heroImage":1609,"date":1615,"body":1616,"category":805,"tags":1617},[1614],"Stan Hu","2022-10-25","Over the next month and going forward, requests for GitLab CI artifacts\ndownloads may be redirected\n\nto [Google Cloud CDN](https://cloud.google.com/cdn) instead of\n\n[Google Cloud Storage](https://cloud.google.com/storage). We anticipate that\nGitLab CI users may benefit from faster\n\ndownloads from edge caches closest to your location.\n\n\n**Disclaimer:** This blog contains information related to upcoming products,\nfeatures, and functionality. It is important to note that the information in\nthis blog post is for informational purposes only. Please do not rely on\nthis information for purchasing or planning purposes. As with all projects,\nthe items mentioned in this blog and linked pages are subject to change or\ndelay. The development, release, and timing of any products, features, or\nfunctionality remain at the sole discretion of GitLab.\n\n\n## How will this work?\n\n\nCurrently when a CI runner or other client [downloads a CI\nartifact](https://docs.gitlab.com/ee/api/job_artifacts.html),\n\nGitLab.com responds with a 302 redirect to a time-limited, pre-signed URL\nwith a domain of `storage.googleapis.com`.\n\n\nAfter this change, the domain will change to\n`cdn.artifacts.gitlab-static.net`.\n\n\nThe exception is for requests originating from within the Google Cloud\n\nPlatform. These will continue to be redirected to Cloud Storage.\n\n\n## When will this change occur?\n\n\nWe expect to start the transition around the end of October 2022. This will\nbe a\n\ngradual transition using a percentage-based rollout, so we anticipate that\nyou will see\n\nan increasing number of your requests redirected to Google Cloud\n\nCDN instead of Google Cloud Storage until all of the requests are served by\nthe\n\nformer.\n\n\nYou can follow along with the progress of this initiative and raise any\n\nquestions in [this\nissue](https://gitlab.com/gitlab-com/gl-infra/production/-/issues/7894). We\n\nwill post more detailed timelines in that issue as we refine the rollout\n\nplan.\n\n\n## How does this change impact you?\n\n\nSince GitLab CI runners and certain clients automatically handle URL\n\nredirections already, we expect that downloads for CI artifacts should\n\ncontinue to work without any action.\n\n\nWe encourage upgrading to the latest version of the GitLab Runner in\n\norder to take advantage of the CDN. This feature was [introduced in\n\nGitLab Runner\nv13.1.0](https://gitlab.com/gitlab-org/gitlab-runner/-/merge_requests/2115).\n\nIf a runner cannot download from the CDN host, it will retry without the\n\nCDN and download the artifact directly through GitLab.com.\n\n\nHowever, if you have a firewall that only allows\n\n`storage.googleapis.com`, you will need to add\n\n`cdn.artifacts.gitlab-static.net` (34.110.204.38) to the allow list.\n\n\n### What do these warning messages mean?\n\n\nWith this change, users may see warning messages in the CI job logs:\n\n\n#### read: connection reset by peer\n\n\n```plaintext\n\nERROR: Downloading artifacts from coordinator... error couldn't execute GET\nagainst https://gitlab.com/api/v4/jobs/\u003Cjob\nid>/artifacts?direct_download=true: Get\n\"https://cdn.artifacts.gitlab-static.net/...\n\nread tcp 172.17.0.2:59332->34.110.204.38:443: read: connection reset by\npeer  id=1234 token=\u003Csome token>\n\nWARNING: Retrying...                                error=invalid argument\n\nDownloading artifacts from coordinator... ok        id=1234\nresponseStatus=200 OK token=\u003Csome token>\n\n```\n\n\nThis error suggests the runner was not able to access the CDN. Check\n\nyour network firewalls and allow access to the IP 34.110.204.38.\n\n\nNote that there are two `Downloading artifacts from coordinator`\n\nmessages. The second attempt succeeded because the runner retried\n\nwithout the CDN.\n\n\n#### x509: certificate signed by unknown authority\n\n\n```plaintext\n\nERROR: Downloading artifacts from coordinator... error couldn't execute GET\nagainst https://gitlab.com/api/v4/jobs/\u003Cjob\nid>/artifacts?direct_download=true: Get\n\"https://storage.googleapis.com/gitlab-gprd-artifacts/...: x509: certificate\nsigned by unknown authority  id=1234 token=\u003Csome token>\n\n```\n\n\nIf you see this error with a Windows runner, upgrade to v15.5.0 since it\n\nis compiled with [Go 1.18](https://tip.golang.org/doc/go1.18), which\n\nsupports [using the system certificate\npool](https://github.com/golang/go/issues/16736).\n\n\nOtherwise, this error suggests the runner is configured with [custom SSL\ncertificates](https://docs.gitlab.com/runner/configuration/tls-self-signed.html).\n\nYou may need to update your certificates or include the certificates\ndirectly in the bundle.\n\n\n#### Authentication required\n\n\nSome clients may report a 401 error with `Authentication required` after\n\nrequesting to download a job artifact:\n\n\n```xml\n\n\u003C?xml version='1.0'\nencoding='UTF-8'?>\u003CError>\u003CCode>AuthenticationRequired\u003C/Code>\u003CMessage>Authentication\nrequired.\u003C/Message>\u003C/Error>\n\n```\n\n\nThis error message suggests the HTTP client is following the 302\n\nredirect and sending the `Authorization` header with the redirected\n\nURL. This is a known issue with Java HTTP clients.\n\n\nUpdate your client to drop the `Authorization` header the\n\nredirect. Google Cloud Storage ignores this header if it were set, but\n\nCloud CDN rejects requests that have the `Authorization` header set.\n",[741,915,1007,233,9],{"slug":1619,"featured":6,"template":699},"gitlab-com-artifacts-cdn-change","content:en-us:blog:gitlab-com-artifacts-cdn-change.yml","Gitlab Com Artifacts Cdn Change","en-us/blog/gitlab-com-artifacts-cdn-change.yml","en-us/blog/gitlab-com-artifacts-cdn-change",{"_path":1625,"_dir":246,"_draft":6,"_partial":6,"_locale":7,"seo":1626,"content":1632,"config":1638,"_id":1640,"_type":14,"title":1641,"_source":16,"_file":1642,"_stem":1643,"_extension":19},"/en-us/blog/gitlab-for-cicd-agile-gitops-cloudnative",{"title":1627,"description":1628,"ogTitle":1627,"ogDescription":1628,"noIndex":6,"ogImage":1629,"ogUrl":1630,"ogSiteName":686,"ogType":687,"canonicalUrls":1630,"schema":1631},"How to use GitLab for Agile, CI/CD, GitOps, and more","Read our example engineering stories from the past two years that show how to use GitLab for you DevOps cycle, including GitOps, CI/CD and more.","https://res.cloudinary.com/about-gitlab-com/image/upload/v1749681825/Blog/Hero%20Images/triangle_geo.jpg","https://about.gitlab.com/blog/gitlab-for-cicd-agile-gitops-cloudnative","\n                        {\n        \"@context\": \"https://schema.org\",\n        \"@type\": \"Article\",\n        \"headline\": \"How to use GitLab for Agile, CI/CD, GitOps, and more\",\n        \"author\": [{\"@type\":\"Person\",\"name\":\"Sara Kassabian\"}],\n        \"datePublished\": \"2020-12-17\",\n      }",{"title":1627,"description":1628,"authors":1633,"heroImage":1629,"date":1635,"body":1636,"category":717,"tags":1637},[1634],"Sara Kassabian","2020-12-17","\n\nOn this blog, our community frequently shares tips, tricks, stories, and tutorials that demonstrate how to do different things with GitLab. This collection features some of our most popular and enduring how-to blog posts from the past two years, covering [CICD](/topics/ci-cd/), GitOps, Machine learning and more! See how various team members, companies, and users leverage GitLab to deliver software faster and more efficiently by reading and watching some of the tutorials we've featured.\n\n## Code review with GitLab\n\nWe know that code review is essential to effective collaboration, but the logistics of it all can be challenging. [Master code review by watching the demo](/blog/demo-mastering-code-review-with-gitlab/) included with this blog post.\n\n## Cool ways to use GitLab CI/CD\n\n### The basics of CI/CD\n\nBrand new to CI/CD? Read our [beginner's guide to the vocabulary and concepts](/blog/beginner-guide-ci-cd/).\n\nHere’s the [code you’ll need to build a CI/CD pipeline](/blog/how-to-create-a-ci-cd-pipeline-with-auto-deploy-to-kubernetes-using-gitlab/) with AutoDeploy to Kubernetes, using GitLab and Helm.\n\nNext, find the [code you'll need to build a CI pipeline with GitLab](/blog/basics-of-gitlab-ci-updated/), allowing you to run jobs sequentially, in parallel, or out of order.\n\n### Pipelines with CI/CD\n\nLearn how to [build a CI/CD pipeline in 20 minutes (or less) using GitLab’s AutoDevOps](/blog/building-a-cicd-pipeline-in-20-mins/) capabilities by following the instructions in this blog post, which is based on a popular GitLab Commit Brooklyn presentation that you can watch below.\n\nDiscover [how to trigger pipelines across multiple projects](/blog/cross-project-pipeline/) using GitLab CI/CD.\n\n\u003C!-- blank line -->\n\u003Cfigure class=\"video_container\">\n  \u003Ciframe src=\"https://www.youtube-nocookie.com/embed/-shvwiBwFVI\" frameborder=\"0\" allowfullscreen=\"true\"> \u003C/iframe>\n\u003C/figure>\n\u003C!-- blank line -->\n\n### CI/CD with Android\n\nAndroid project users are in luck because in [this post we explain how to set up GitLab continuous integration (CI) functions](/blog/setting-up-gitlab-ci-for-android-projects/) in Android projects.\n\nGitLab and fastlane pair up to [help users publish applications to the iOS store](/blog/ios-publishing-with-gitlab-and-fastlane/) using a GitLab CI/CD runner.\n\n### CI/CD and GKE\n\n![GitLab CI/CD and GKE integration](https://about.gitlab.com/images/blogimages/gitlab-gke-integration-cover.png){: .shadow.medium.center}\n\nWe explain [how to get started with GitLab CI/CD and Google Kubernetes Engine (GKE)](/blog/getting-started-gitlab-ci-gcp/) in this initial demo.\n\nGitLab self-managed user? ✅\nUsing Google Kubernetes engine? ✅\nGreat! The [next tutorial is all about how to use GitLab CI to install GitLab runners on GKE](/blog/gitlab-ci-on-google-kubernetes-engine/) using our integration. It shouldn’t take you more than 15 minutes.\n\n## GitLab for machine learning\n\nBut what about GitLab for machine learning? We’ve got you covered. Watch the demo from GitLab Virtual Commit to see how you can use GitLab to leverage tasks for machine learning pipelines.\n\n\u003C!-- blank line -->\n\u003Cfigure class=\"video_container\">\n  \u003Ciframe src=\"https://www.youtube-nocookie.com/embed/DJbQJDXmjew\" frameborder=\"0\" allowfullscreen=\"true\"> \u003C/iframe>\n\u003C/figure>\n\u003C!-- blank line -->\n\n## GitLab for Agile\n\nGitLab features work for many software development methodologies, including [Agile](/solutions/agile-delivery/).\n\nStart by [mapping Agile artifacts to GitLab features](/blog/gitlab-for-agile-software-development/) and explore how iteration works using GitLab.\n\n![GitLab issue board](https://about.gitlab.com/images/blogimages/issue-board.png){: .shadow.medium.left}\n\nThe GitLab issue board allows for flexible workflows and can be organized to represent [Agile software development](/topics/agile-delivery/) states.\n{: .note.text-center}\n\nThen go more in-depth to learn [how to use GitLab for Agile portfolio planning and project management](/blog/gitlab-for-agile-portfolio-planning-project-management/).\n\n## Giddy for GitOps?\n\n[GitOps](/topics/gitops/) takes DevOps best practices that are used for application development such as [version control](/topics/version-control/), collaboration, compliance, and CI/CD, and applies them to infrastructure automation.\n\nGitLab is the [DevOps platform](/topics/devops/) that does it all, and it’s built using Git, making it the ideal solution for GitOps processes.\n\nFirst, we explained [how GitLab and Ansible can be used together for GitOps](/blog/using-ansible-and-gitlab-as-infrastructure-for-code/) processes and [infrastructure as code](/topics/gitops/infrastructure-as-code/). In a follow-up post, we explain how [GitLab can also be paired with Terraform for GitOps](/topics/gitops/gitlab-enables-infrastructure-as-code/) and IaC.\n\nThe video on how to use Ansible and GitLab together has been viewed more than 13,000 times since it was first created in 2019, and is embedded for you below.\n\n\u003C!-- blank line -->\n\u003Cfigure class=\"video_container\">\n  \u003Ciframe src=\"https://www.youtube-nocookie.com/embed/M-SgRTKSeOg\" frameborder=\"0\" allowfullscreen=\"true\"> \u003C/iframe>\n\u003C/figure>\n\u003C!-- blank line -->\n\n## Visibility\n\nOne of our principles at GitLab is to [dogfood everything](/handbook/engineering/development/principles#dogfooding), so you can rest assured that we aren’t about to introduce an engineering feature without first trying it out for ourselves. When it comes to our Insights tool though, the process happened in reverse. Our Engineering Productivity team at GitLab needed a particular tool, and as we built it, we realized it would benefit our GitLab Ultimate customers. Read on to [learn how our Insights tool came to be](/blog/insights/).\n\nDig into this [valuable explanation of how we discovered that Prometheus query language can be used to detect anomalies](/blog/anomaly-detection-using-prometheus/) in the time-series data that GitLab.com reports.\n\n## In the clouds\n\nWatch the demo to learn how GitLab runner and RedHat OpenShift can work together to jump start your application development and deployment to the cloud.\n\n\u003C!-- blank line -->\n\u003Cfigure class=\"video_container\">\n  \u003Ciframe src=\"https://www.youtube-nocookie.com/embed/yGWiQwrWimk\" frameborder=\"0\" allowfullscreen=\"true\"> \u003C/iframe>\n\u003C/figure>\n\u003C!-- blank line -->\n\nAnd finally, although Docker Hub may be enforcing new rate limits, there's no need to panic. We [explain how to build a monitoring plug-in](/blog/docker-hub-rate-limit-monitoring/) to help you monitor the number of pull requests.\n\nCan you think of some other stand-out blog posts or demos that we should include here? Drop the link in a comment below.\n\nCover image by [Chris Robert](https://unsplash.com/@chris_robert) on [Unsplash](https://unsplash.com/photos/kY-uPDLXxHg)\n{: .note}\n",[9,696,915,913],{"slug":1639,"featured":6,"template":699},"gitlab-for-cicd-agile-gitops-cloudnative","content:en-us:blog:gitlab-for-cicd-agile-gitops-cloudnative.yml","Gitlab For Cicd Agile Gitops Cloudnative","en-us/blog/gitlab-for-cicd-agile-gitops-cloudnative.yml","en-us/blog/gitlab-for-cicd-agile-gitops-cloudnative",{"_path":1645,"_dir":246,"_draft":6,"_partial":6,"_locale":7,"seo":1646,"content":1652,"config":1657,"_id":1659,"_type":14,"title":1660,"_source":16,"_file":1661,"_stem":1662,"_extension":19},"/en-us/blog/gitlab-is-the-single-source-of-truth-for-ecommerce-provider",{"title":1647,"description":1648,"ogTitle":1647,"ogDescription":1648,"noIndex":6,"ogImage":1649,"ogUrl":1650,"ogSiteName":686,"ogType":687,"canonicalUrls":1650,"schema":1651},"GitLab is the single source of truth for eCommerce provider","Swell uses GitLab company-wide and says the biggest advantage so far is the review operations capability.","https://res.cloudinary.com/about-gitlab-com/image/upload/v1749668755/Blog/Hero%20Images/swelllogo3.png","https://about.gitlab.com/blog/gitlab-is-the-single-source-of-truth-for-ecommerce-provider","\n                        {\n        \"@context\": \"https://schema.org\",\n        \"@type\": \"Article\",\n        \"headline\": \"GitLab is the single source of truth for eCommerce provider\",\n        \"author\": [{\"@type\":\"Person\",\"name\":\"GitLab\"}],\n        \"datePublished\": \"2022-06-23\",\n      }",{"title":1647,"description":1648,"authors":1653,"heroImage":1649,"date":1654,"body":1655,"category":1131,"tags":1656},[1474],"2022-06-23","eCommerce platform provider [Swell](https://www.swell.is) was built to give entrepreneurs the opportunity to build the online business that they envision. A GitLab customer since 2021, GitLab has been adopted as Swell's one DevOps, project management, and support ticketing tool for the whole organization. It's the foundational platform that the business works on.\n\nSwell is using GitLab Premium in many different areas, including for product development and to build the platform infrastructure, says Nico Bistolfi, vice president of technology.\n\n\"GitLab is our source of truth for everything,\" Bistolfi says. Now, Swell is looking into expanding its usage of the platform to leverage features such as code quality, automation, and other types of dynamic application security and static application security.\n\n## GitLab for CI/CD\nSwell upgraded to the Premium version and the biggest advantage so far πpath-to-decomposing-gitlab-database-part2has been the review operations capability, Bistolfi says. The company has created environments for every merge request users make, and that replicates in production for testers to see what was changed, whether a fix was made, or how the new feature is working.\n\n\"We could not go to our software development lifecycle today without the review ops. That's something that is critical for us,\" Bistolfi says.\n\nGitLab is used for both continuous integration (CI) and continuous deployment (CD). While building the [CI/CD](/topics/ci-cd/) pipeline process is ongoing, Bistolfi says, “We are slowly changing it and relying more and more on GitLab” in areas, including application security.\n\nBefore moving to GitLab, Swell was using bare-metal servers. The company now uses GitLab’s container management solutions and all API updates are happening through the platform.\n\n## From inputting issues to resolution\nEveryone at Swell is using GitLab — not just developers — and for a variety of tasks. The company has created a way to process support tickets through the platform. Another use case is knowledge management.\n\n\"We find ourselves making some decisions from comments in GitLab,\" he says. The whole process from the time a ticket is created to being resolved is done within the platform.\n\nThe company culture is about full information transparency, Bistolfi says, particularly since Swell is fully remote and employees work from 11 different countries. So one goal is to maintain asynchronous communication.\n\nWhen an issue is created in the platform, a little bit of coding is required, but he said non-developer users have adapted well. The feedback so far has been that using GitLab has been frictionless.\n\n## Speed to delivery\nInitially, for some services, it took about 30 minutes to build and deploy an image. Now, the process has been decreased to between one and five minutes in most cases.\n\nSwell manually sets release dates for system improvements and, right now, there are about two a week. The company is working on automating the process for continuous delivery with the goal of soon having releases every couple of hours.\n\n## Team play\nSwell manages team backlogs, sprints, milestones, and future work using its own flavor of Kanban with what Bistolfi calls \"quick labels.\"\n\nEngineering teams are being scaled and, in addition to Kanban, some projects are done using Scrum. Changing their GitLab configuration has let teams measure velocity better.  \n\nA future goal is to gain visibility into team results, as well as use GitLab for project planning and management, he says.\n\n## GitLab as a product and company\nBistolfi is unequivocal in his enthusiasm for GitLab. \"We know that GitLab is there for us to continue growing,\" he says. \"We know we can rely on that. And something that I always tell a team when we are evaluating what we're going to do or how we're going to solve certain problems is that there are areas GitLab is just starting to innovate on or is just starting to launch new features.\"\n\nIf those areas are at 80% of what Swell needs, the company will continue to use GitLab. \"We need to have very, very strong reasons to look for another tool to integrate with GitLab.\" He added that \"we trust that GitLab is going in the right direction for us. In addition, we've gained efficiency in our ability to provide consistent test environments using Gitlab Review Apps to reduce regressions and improve new feature development.\"\n\nThe Swell team also likes that GitLab provides thorough and complete information in its handbook, which has been very beneficial in helping the company manage things internally. \"That has been inspiring for many of us on the executive team,\" he notes.\n\nFor example, during the pandemic, Bistolfi put together a document called \"The Ultimate Guide for Swell Engineers,\" which contains three pages of information about culture, what to expect from teammates, and how to communicate and prioritize tasks.\n\nA lot of guidance came from the GitLab handbook, he adds.\n\nMoving forward with GitLab, Bistolfi says: \"We are incorporating most of the Security and Compliance tools in order to keep track and audit for our compliance. We plan to expand the usage to other projects, but we are already using container and dependency scanning, SAST, secrets detection, and license scanning for some of our core and more sensitive services.\"\n\nWhat Swell likes most about GitLab is the thoroughness of the tool. \"From an engineering perspective, 10 years ago, you would never have imagined all the features and capabilities that GitLab offers being incorporated into one platform,\" Bistolfi says.",[741,9,696,784,1007],{"slug":1658,"featured":6,"template":699},"gitlab-is-the-single-source-of-truth-for-ecommerce-provider","content:en-us:blog:gitlab-is-the-single-source-of-truth-for-ecommerce-provider.yml","Gitlab Is The Single Source Of Truth For Ecommerce Provider","en-us/blog/gitlab-is-the-single-source-of-truth-for-ecommerce-provider.yml","en-us/blog/gitlab-is-the-single-source-of-truth-for-ecommerce-provider",{"_path":1664,"_dir":246,"_draft":6,"_partial":6,"_locale":7,"seo":1665,"content":1671,"config":1676,"_id":1678,"_type":14,"title":1679,"_source":16,"_file":1680,"_stem":1681,"_extension":19},"/en-us/blog/gitlab-leader-continuous-integration-forrester-wave",{"title":1666,"description":1667,"ogTitle":1666,"ogDescription":1667,"noIndex":6,"ogImage":1668,"ogUrl":1669,"ogSiteName":686,"ogType":687,"canonicalUrls":1669,"schema":1670},"GitLab Continuous Integration named a Leader in the Forrester Wave™","GitLab cited as a Leader in The Forrester Wave™&#58; Continuous Integration Tools, Q3 2017 report released today.","https://res.cloudinary.com/about-gitlab-com/image/upload/v1749683243/Blog/Hero%20Images/gitlab-ci-wave-cover.png","https://about.gitlab.com/blog/gitlab-leader-continuous-integration-forrester-wave","\n                        {\n        \"@context\": \"https://schema.org\",\n        \"@type\": \"Article\",\n        \"headline\": \"GitLab Continuous Integration named a Leader in the Forrester Wave™\",\n        \"author\": [{\"@type\":\"Person\",\"name\":\"GitLab\"}],\n        \"datePublished\": \"2017-09-27\",\n      }",{"title":1666,"description":1667,"authors":1672,"heroImage":1668,"date":1673,"body":1674,"category":301,"tags":1675},[1474],"2017-09-27","Today Forrester has evaluated GitLab as a Leader in [Continuous Integration (CI)](/solutions/continuous-integration/) in The Forrester Wave™: Continuous Integration Tools, Q3 2017 report. As the report states, “GitLab delivers ease of use, scalability, integration, and innovation.” We see CI/CD as a critical component of our offering, making it quicker and easier to deliver value through automation. The progress we’ve made in developing a CI/CD solution that’s seamlessly integrated is thanks largely to the participation and feedback of our community and customers.\n\n\u003C!-- more -->\n\nWe’re excited about [what the future holds for GitLab CI/CD](/direction/#ci--cd), so it’s great to know that others see its value. Furthermore Forrester states, “GitLab’s vision is to serve enterprise-scale, integrated software development teams that want to spend more time writing code and less time maintaining their tool chain.”\n{: .text-justify}\n\nWe’ve said it before (we say it a lot, actually): CI/CD is the cornerstone of modern software development. Delivering what customers want, faster, requires a modernized software development lifecycle that saves time, effort, and cost. Automation is key to this more efficient release cycle, which is why we’ve spent a lot of time developing our built-in test and release automation, so developers can spend less time stringing together their tool chain, and more on innovation and integrating customer feedback. We’re gratified to have been named a leader in CI by Forrester in their report on Continuous Integration Tools for Q3, 2017, which evaluated 10 vendors across 25 different criteria.\n{: .text-justify}\n\nWe had some key areas of success:\n\n## Top score in the Current Offering category\n\nGitLab received the highest score in Forrester’s Current Offering evaluation, which we believe confirms that our product is hard to beat when it comes to ease of installation, configuring builds, platform support, analytics, an intuitive UI, container support and more. This all adds up to a more seamless development process which facilitates DevOps practices and collaboration.\n\n## Highest score possible in the Strategy category\n\nForrester assessed our product strategy, market approach, training, consulting and support, giving us a 5.0, the highest possible score, for all three criteria. This is huge validation for our CI/CD vision, shaped with the help of our customers, community, and their contributions.\n\n## Just the beginning\n\nA significant part of that vision, Auto DevOps, just shipped with our [10.0 release](/releases/2017/09/22/gitlab-10-0-released/). Auto DevOps is a hands-free DevOps experience that automatically configures your build, test, code quality assurance, review apps, deployment, and monitoring in a single environment. Our out-of-the-box templates allow you to set up an end-to-end DevOps lifecycle at the push of a button.\n\nAuto DevOps is just the first step on a journey to building a complete and highly automated DevOps tool chain. Building on strong momentum, we’re working on a number of new features to better leverage our CI/CD tools in a more automated way for both operations as well as development teams. With the full automation we are planning, you will not only be able to deliver a new, functioning application from a developer’s machine to a production environment in minutes, but also monitor all production environments in one dashboard.\n\n### Faster time to value\n\nReducing the time teams spend on manual tasks means you can focus on what matters: building great products that your users want. GitLab CI/CD facilitates a more iterative approach to software development, by automatically testing new code, helping you to catch potential problems earlier in the release cycle, taking the anxiety out of deployments and reducing the cost of fixing bugs. This puts you in a position to release more often, shortening the feedback loop and helping you to release features and improvements that your customers are asking for.\n\n### Collaboration without dependency\n\nThe fact that GitLab CI/CD is fully integrated makes all the difference. Less context-switching means key information doesn’t slip through the cracks like it can when teams work in different tools, with no single source of truth. Working in an integrated tool fosters collaboration by improving visibility, keeping everyone on the same page and making it easy for different teams to participate and comment. Features like review apps – which automatically spin up a dynamic environment for merge requests, so you can preview changes right away – make it even easier to share improvements with stakeholders and gather feedback.\n\nBeing named a Leader in this Wave evaluation, and receiving the Top Score in Current Offering, as well as the highest score possible for Strategy, is validation for us of the strength of today’s GitLab CI offering as well as our vision for the future.",[805,9],{"slug":1677,"featured":6,"template":699},"gitlab-leader-continuous-integration-forrester-wave","content:en-us:blog:gitlab-leader-continuous-integration-forrester-wave.yml","Gitlab Leader Continuous Integration Forrester Wave","en-us/blog/gitlab-leader-continuous-integration-forrester-wave.yml","en-us/blog/gitlab-leader-continuous-integration-forrester-wave",{"_path":1683,"_dir":246,"_draft":6,"_partial":6,"_locale":7,"seo":1684,"content":1690,"config":1695,"_id":1697,"_type":14,"title":1698,"_source":16,"_file":1699,"_stem":1700,"_extension":19},"/en-us/blog/gitlab-oracle-cloud-arm-based",{"title":1685,"description":1686,"ogTitle":1685,"ogDescription":1686,"noIndex":6,"ogImage":1687,"ogUrl":1688,"ogSiteName":686,"ogType":687,"canonicalUrls":1688,"schema":1689},"How to use GitLab with OCI ARM-based compute instances","We explain two ways to set up GitLab on Oracle ARM-based instances.","https://res.cloudinary.com/about-gitlab-com/image/upload/v1749679507/Blog/Hero%20Images/ci-cd.png","https://about.gitlab.com/blog/gitlab-oracle-cloud-arm-based","\n                        {\n        \"@context\": \"https://schema.org\",\n        \"@type\": \"Article\",\n        \"headline\": \"How to use GitLab with OCI ARM-based compute instances\",\n        \"author\": [{\"@type\":\"Person\",\"name\":\"Abubakar Siddiq Ango\"}],\n        \"datePublished\": \"2021-05-25\",\n      }",{"title":1685,"description":1686,"authors":1691,"heroImage":1687,"date":1692,"body":1693,"category":805,"tags":1694},[1311],"2021-05-25","\n\n[ARM-based processors](https://en.wikipedia.org/wiki/ARM_architecture) have gained popularity due to their energy-saving capabilities and performance as shown in the recent adoptions by Apple. Previously a mainstay for mobile, edge, or small devices, ARM-based chips are now used for almost all types of systems, including servers. \n\nThis surge in the use of ARM-based systems means development toolchains have to support building for the ARM architecture reliably and efficiently. It is here where the GitLab Runner shines, allowing users to run CI/CD jobs on ARM servers. Coupling the GitLab Runner with the Oracle Cloud Infrastructure (OCI) offerings of ARM-based compute instances lets development teams have best in class CI/CD infrastructure to target both ARM and x86 architecture.\n \nThe recommended method of installing GitLab is using the automated deployment options for OCI by clicking the \"[Deploy to Oracle Cloud](https://console.us-phoenix-1.oraclecloud.com/resourcemanager/stacks/create?region=home&zipUrl=https://gitlab.com/gitlab-com/alliances/oracle/sandbox-projects/gitlab-terraform-oci/-/jobs/artifacts/main/raw/oci-gitlab-orm.zip?job=package_repo)\" button, which takes advantage of full-tested scripts for single click deployment through the OCI console.s.\n\nIf you will be deploying manually on virtual machines on OCI, there are certain caveats users need to be aware of when setting up GitLab Runner and GitLab on an OCI ARM-based instance.\n\n## How to set up GitLab CI/CD on ARM instances\n\nThe core feature of [GitLab CI/CD](/topics/ci-cd/) is the runner – it executes all the instructions to accomplish the jobs in the CI/CD pipelines. One of its strengths is the support for the diverse architecture and operating systems, including Oracle Linux server distribution running on ARM-based systems. This functionality allows users to maintain diverse runners targeting different architectures for the various workloads of development teams. \n\nInstalling the GitLab Runner on ARM-based instances is straightforward: After adding the official GitLab package repository, install the runner. However, if you are running Oracle Linux Server release 8.x (ol/8), you will need to manually set up the package repository, because  ol/8 by PackageCloud, which GitLab uses to host packages, is not supported. \n\nTo set up the repository manually use the following commands:\n\n```\ncurl https://packages.gitlab.com/install/repositories/runner/gitlab-runner/script.rpm.sh > script.sh\nchmod +x script.sh\nos=el dist=8 ./script.sh\n```\n\n## How to set up GitLab EE/Core on ARM instances\n\nSimilar to the Runner, you will need to manually set up GitLab's package repository if you are running ol/8. The commands are similar, aside from the package URL as shown below: \n\n```\ncurl https://packages.gitlab.com/install/repositories/gitlab/gitlab-ee/script.rpm.sh > script.sh\nchmod +x script.sh\nos=el dist=8 ./script.sh\nEXTERNAL_URL=\"[GITLAB-INSTANCE-URL]\" dnf install -y gitlab-ee\n```\n\nOne caveat to deploying to recent versions of GitLab using Omnibus is the [ARM64 cow bug affecting Redis](https://github.com/redis/redis/pull/8405), which is bundled with GitLab Omnibus installations. This bug only affects GitLab versions from 13.9 – which was the version in which the bundled Redis was upgraded to 6.0.10. You can install pre-13.9 versions of GitLab manually. For example, to install version 13.8use the command: `yum install gitlab-ee-13.8.6-ee.0.el8.aarch64`.\n\nThe fix for the bug is pulled into the [6.0 branch of the Redis upstream project](https://github.com/redis/redis/commit/dcf409f8e72dcd6bbf2f31d2ecc8f6f797c303c2) and will make its way to future GitLab releases. The bug only affects Redis on ARM64 architecture (aarch64) and is not specific to GitLab or the Oracle Linux server. You can [disable the bundled Redis instance and configure](https://docs.gitlab.com/omnibus/settings/redis.html) a separate local Redis instance or an external service.\n## Watch and learn\nWatch the video to see how to set up a Runner on an OCI ARM64 instance running the Oracle Linux server.\n\n\u003C!-- blank line -->\n\u003Cfigure class=\"video_container\">\n  \u003Ciframe src=\"https://www.youtube-nocookie.com/embed/Q2o0JYdQAWE\" frameborder=\"0\" allowfullscreen=\"true\"> \u003C/iframe>\n\u003C/figure>\n\u003C!-- blank line -->\n",[805,9,696],{"slug":1696,"featured":6,"template":699},"gitlab-oracle-cloud-arm-based","content:en-us:blog:gitlab-oracle-cloud-arm-based.yml","Gitlab Oracle Cloud Arm Based","en-us/blog/gitlab-oracle-cloud-arm-based.yml","en-us/blog/gitlab-oracle-cloud-arm-based",{"_path":1702,"_dir":246,"_draft":6,"_partial":6,"_locale":7,"seo":1703,"content":1709,"config":1714,"_id":1716,"_type":14,"title":1717,"_source":16,"_file":1718,"_stem":1719,"_extension":19},"/en-us/blog/gitlab-runner-update-required-to-use-auto-devops-and-sast",{"title":1704,"description":1705,"ogTitle":1704,"ogDescription":1705,"noIndex":6,"ogImage":1706,"ogUrl":1707,"ogSiteName":686,"ogType":687,"canonicalUrls":1707,"schema":1708},"GitLab Runner update required to use SAST in Auto DevOps","Make sure you upgrade GitLab Runner to 11.5+ to coninue using SAST in Auto DevOps.","https://res.cloudinary.com/about-gitlab-com/image/upload/v1749666262/Blog/Hero%20Images/default-blog-image.png","https://about.gitlab.com/blog/gitlab-runner-update-required-to-use-auto-devops-and-sast","\n                        {\n        \"@context\": \"https://schema.org\",\n        \"@type\": \"Article\",\n        \"headline\": \"GitLab Runner update required to use SAST in Auto DevOps\",\n        \"author\": [{\"@type\":\"Person\",\"name\":\"Fabio Busatto\"}],\n        \"datePublished\": \"2018-12-06\",\n      }",{"title":1704,"description":1705,"authors":1710,"heroImage":1706,"date":910,"body":1712,"category":717,"tags":1713},[1711],"Fabio Busatto","\n\nWe are introducing a major change for the [SAST] job definition for [Auto DevOps] with **GitLab 11.6**, shipping Dec. 22.\nAs a result, SAST jobs will fail after the upgrade to GitLab 11.6 if they are picked up by a version of [GitLab Runner]\nprior to 11.5. The jobs will fail, but they will not block pipelines. However, you won't see results\nfor SAST in the merge request or at the pipeline level anymore.\n\nThe same change will happen for [Dependency Scanning], [Container Scanning], [DAST], and [License Management] in future releases.\n\n## Why did this happen?\n\nThe [new job definition] uses the [`reports` syntax], which is necessary to show SAST results in the [Group Security Dashboard].\nUnfortunately, this syntax is not supported by GitLab Runner prior to 11.5.\n\n## Who is affected?\n\nYou are affected by this change if you meet **all** the requirements in the following list:\n1. You are using Auto DevOps **AND**\n1. you have at least one GitLab Runner 11.4 or older set up for your projects **AND**\n1. you are interested in security reports.\n\n## Who is not affected?\n\nYou are **not** affected by this change if you meet **at least one** of the requirements in the following list:\n1. You are not using Auto DevOps **OR**\n1. you are using only GitLab Runner 11.5 or newer **OR**\n1. you are using only shared runners on GitLab.com (we already upgraded them) **OR**\n1. you are not interested in security reports.\n\n## How to solve the problem\n\nIf you are not affected by the change, you don't need to take any action.\n\nIf you are affected, you should upgrade your GitLab Runners to version 11.5 or newer as soon as possible.\nIf you don't, you will not have new SAST reports until you do upgrade. If you upgrade your runners later, SAST will\nstart to work again correctly.\n\n## Which is the expected timeline?\n\nGitLab 11.6 will be released on **Dec. 22**.  This change may also be shipped in an early release\ncandidate (RC) version.\n\nIf you are using a **self-managed** GitLab instance, and you don't install RC versions, you will be affected when\nyou'll upgrade to GitLab 11.6.\n\nIf you are using **GitLab.com**, you will be affected as soon as the RC version with the change will be deployed.\n\nFeel free to reach out to us with any further questions!\n\n[SAST]: https://docs.gitlab.com/ee/user/application_security/sast/\n[Auto DevOps]: https://docs.gitlab.com/ee/topics/autodevops/\n[new job definition]: https://docs.gitlab.com/ee/user/application_security/sast/\n[`reports` syntax]: https://docs.gitlab.com/ee/ci/yaml/#artifactsreportssast-ultimate\n[Group Security Dashboard]: https://docs.gitlab.com/ee/user/application_security/security_dashboard/\n[GitLab Runner]: https://docs.gitlab.com/runner/\n[Dependency Scanning]: https://docs.gitlab.com/ee/user/application_security/dependency_scanning/\n[Container Scanning]: https://docs.gitlab.com/ee/user/application_security/container_scanning/\n[DAST]: https://docs.gitlab.com/ee/user/application_security/dast/\n[License Management]: https://docs.gitlab.com/ee/user/compliance/license_compliance/index.html\n",[9,741,983,807,784],{"slug":1715,"featured":6,"template":699},"gitlab-runner-update-required-to-use-auto-devops-and-sast","content:en-us:blog:gitlab-runner-update-required-to-use-auto-devops-and-sast.yml","Gitlab Runner Update Required To Use Auto Devops And Sast","en-us/blog/gitlab-runner-update-required-to-use-auto-devops-and-sast.yml","en-us/blog/gitlab-runner-update-required-to-use-auto-devops-and-sast",{"_path":1721,"_dir":246,"_draft":6,"_partial":6,"_locale":7,"seo":1722,"content":1728,"config":1735,"_id":1737,"_type":14,"title":1738,"_source":16,"_file":1739,"_stem":1740,"_extension":19},"/en-us/blog/gitlab-workflow-with-jira-jenkins",{"title":1723,"description":1724,"ogTitle":1723,"ogDescription":1724,"noIndex":6,"ogImage":1725,"ogUrl":1726,"ogSiteName":686,"ogType":687,"canonicalUrls":1726,"schema":1727},"Demo: GitLab + Jira + Jenkins","See how you can use our Jira and Jenkins integrations to reduce context switching and streamline your workflow.","https://res.cloudinary.com/about-gitlab-com/image/upload/v1749680048/Blog/Hero%20Images/gitlab-jira-jenkins-cover.png","https://about.gitlab.com/blog/gitlab-workflow-with-jira-jenkins","\n                        {\n        \"@context\": \"https://schema.org\",\n        \"@type\": \"Article\",\n        \"headline\": \"Demo: GitLab + Jira + Jenkins\",\n        \"author\": [{\"@type\":\"Person\",\"name\":\"Joel Krooswyk\"}],\n        \"datePublished\": \"2018-07-30\",\n      }",{"title":1723,"description":1724,"authors":1729,"heroImage":1725,"date":1731,"body":1732,"category":301,"tags":1733},[1730],"Joel Krooswyk","2018-07-30","\n\nOne of the things we love about GitLab is that while it can replace all your other software development lifecycle tools [(no, really)](/); it doesn't have to. Whether you want to rip and replace everything or use it for one or two stages of your workflow, [alongside your existing toolset](/partners/technology-partners/integrate/) (for now, or forever), we've got you covered.\n\nOne of the things we're most often asked about is how GitLab works together with [Jira](/solutions/jira/) for issue tracking, and [Jenkins](/solutions/jenkins/) for CI. This could be for one of two reasons:\n\n1. Your organization is happy with your issue tracking and CI solutions, and just want to use GitLab for other features, or\n2. You plan to move to GitLab for your end-to-end software development lifecycle, but that's a significant undertaking and it may be less disruptive to migrate on a project-by-project basis.\n\nNo matter the reason, what's important is maintaining the context of work without having to switch between applications frequently. With these integrations you can transition Jira issue states via GitLab, as well as see GitLab commits, branches, and merge requests in the Jira development panel. You can also view the status of Jenkins pipelines in GitLab to optimize your use of GitLab Merge Requests.\n\nI recorded this demo to show what a workflow using all three would look like.\n\n\n\u003C!-- blank line -->\n\u003Cfigure class=\"video_container\">\n  \u003Ciframe src=\"https://www.youtube.com/embed/Jn-_fyra7xQ\" frameborder=\"0\" allowfullscreen=\"true\"> \u003C/iframe>\n\u003C/figure>\n\u003C!-- blank line -->\n",[9,913,233,1734],"workflow",{"slug":1736,"featured":6,"template":699},"gitlab-workflow-with-jira-jenkins","content:en-us:blog:gitlab-workflow-with-jira-jenkins.yml","Gitlab Workflow With Jira Jenkins","en-us/blog/gitlab-workflow-with-jira-jenkins.yml","en-us/blog/gitlab-workflow-with-jira-jenkins",{"_path":1742,"_dir":246,"_draft":6,"_partial":6,"_locale":7,"seo":1743,"content":1749,"config":1755,"_id":1757,"_type":14,"title":1758,"_source":16,"_file":1759,"_stem":1760,"_extension":19},"/en-us/blog/gitlabs-contributions-to-git-2-44-0",{"title":1744,"description":1745,"ogTitle":1744,"ogDescription":1745,"noIndex":6,"ogImage":1746,"ogUrl":1747,"ogSiteName":686,"ogType":687,"canonicalUrls":1747,"schema":1748},"GitLab's contributions to Git 2.44.0","Find out the topics that GitLab’s Git team – as well as the wider community – contributed to the latest Git release, including fast scripted rebases via git-replay.","https://res.cloudinary.com/about-gitlab-com/image/upload/v1749666069/Blog/Hero%20Images/AdobeStock_639935439.jpg","https://about.gitlab.com/blog/gitlabs-contributions-to-git-2-44-0","\n                        {\n        \"@context\": \"https://schema.org\",\n        \"@type\": \"Article\",\n        \"headline\": \"GitLab's contributions to Git 2.44.0\",\n        \"author\": [{\"@type\":\"Person\",\"name\":\"Patrick Steinhardt\"}],\n        \"datePublished\": \"2024-02-26\",\n      }",{"title":1744,"description":1745,"authors":1750,"heroImage":1746,"date":1752,"body":1753,"category":694,"tags":1754},[1751],"Patrick Steinhardt","2024-02-26","The Git project recently released [Git 2.44.0](https://git-scm.com/downloads). In this blog post, we will highlight the contributions made by GitLab's Git team, as well as those from the wider Git community.\n\n## Fast scripted rebases via `git-replay`\n\nThe `git-rebase` command can be used to reapply a set of commits onto a different base commit. This can be quite useful when you have a feature branch where the main branch it was originally created from has advanced since creating the feature branch.\n\nIn this case, `git-rebase` can be used to reapply all commits of the feature branch onto the new commits of the main branch.\n\nSuppose you have the following commit history with the main development branch `main` and your feature branch `feature`:\n\n![main and feature branch](https://res.cloudinary.com/about-gitlab-com/image/upload/v1749678099/Blog/Content%20Images/Screenshot_2024-02-20_at_2.15.37_PM.png)\n\nYou have originally created your feature branch from `m-2`, but since then the `main` branch has gained two additional commits. Now `git-rebase` can be used to reapply your commits `f-1` and `f-2` on top of the newest commit `m-4`:\n\n![applying git-rebase](https://res.cloudinary.com/about-gitlab-com/image/upload/v1749678099/Blog/Content%20Images/Screenshot_2024-02-20_at_2.16.28_PM.png)\n\nYou can see this functionality in GitLab when you create a merge request. When you want to reapply the commits of your merge request onto new commits in the target branch, all you have to do is [to create a comment that contains the `/rebase` command](https://docs.gitlab.com/ee/topics/git/git_rebase.html#rebase-from-the-ui). The magic then happens behind the scenes.\n\nThere is one problem though: `git-rebase` only works on repositories that have a worktree (a directory where a branch, tag or commit has been checked out). The repositories we host at GitLab are “bare” repositories, which don’t have a worktree. This means that the files and directories tracked by your commits are only tracked as Git objects in the `.git` directory of the repository. This is mostly done to save precious disk space and speed up operations.\n\nIn the past, we used [libgit2](https://libgit2.org/) to implement rebases. But for various reasons, we decided to remove this dependency in favor of only using Git commands to access Git repositories. But this created a problem for\nus because we could neither use libgit2 nor `git-rebase` to perform rebases. While we could create an ad-hoc worktree to use `git-rebase`, this would have been prohibitively expensive in large monorepos.\n\nLuckily, [Elijah Newren](https://www.linkedin.com/in/elijah-newren-0a41665/) has upstreamed a new merge algorithm called `merge-ort` in Git 2.33. Despite being significantly faster than the old `recursive` merge strategy in almost all cases, it also has the added benefit that it can perform merges in-memory. In practice, this also allows us to perform such rebases in-memory.\n\nEnter `git-replay`, which is a new command that does essentially the same thing as `git-rebase` but in-memory, thus not requiring a worktree anymore. This is an\nimportant building block to allow us to develop faster rebasing of merge requests in the future.\n\nYou may ask: Why a new command instead of updating `git-rebase`? The problem here was that `git-rebase` is essentially a user-focused command (also called a\n\"porcelain\" command in Git). Thus it performs several actions that are not required by a script at all, like, for example, executing hooks or checking out files into the worktree. The new `git-replay` command is a script-focused\ncommand (also called a \"plumbing\" command in Git) and has a different set of advantages and drawbacks. Furthermore, besides doing rebases, we plan to use it to do cherry-picks and reverts in the future, too.\n\nThis topic was a joint effort by [Elijah Newren](https://www.linkedin.com/in/elijah-newren-0a41665/) and\n[Christian Couder](https://www.gitlab.com/chriscool).\n\n## Commit-graph object existence checks\n\nYou may know that each commit can have an arbitrary number of parents:\n\n- The first commit in your repository has no parents. This is the \"root\" commit.\n- Normal commits have a single parent.\n- Merge commits have at least two, but sometimes even more than two parents.\n\nThis parent relationship is part of what forms the basis of Git's object model and establishes the object graph. If you want to traverse this object graph, Git must look up an entry point commit and from there walk the parent chain of commits.\n\nTo fully traverse history from the newest to the oldest commit, you must look up and parse all commit objects in between. Because repositories can consist of hundreds of thousands or even millions of such commits, this can be\nquite an expensive operation. But users of such repositories still want to be able to, for example, search for a specific commit that changes a specific file\nwithout waiting several minutes for the search to complete.\n\nThe Git project introduced a commit-graph data structure a long time ago that essentially caches a lot of the parsed information in a more accessible data structure. This commit-graph encodes the parent-child relation and some additional information, like, for example, a [bloom filter](https://en.wikipedia.org/wiki/Bloom_filter) of changed\nfiles.\n\nThis commit-graph is usually updated automatically during repository housekeeping. Because housekeeping only runs every so often, the commit-graph can be missing entries for recently added commits. This is perfectly fine and expected to happen, and Git knows to instead look up and parse the commit object in such a case.\n\nNow, the reverse case also theoretically exists: The commit-graph contains cached information of an object that does not exist anymore because it has been deleted without regenerating the commit-graph. The consequence would\nbe that lookups of this commit succeed even though they really shouldn't. To avoid this, in Git 2.43.0, we upstreamed a change into Git that detects commits\nthat exist in the commit-graph but no longer in the object database.\n\nThis change requires us to do an existence check for every commit that we parse via the commit-graph. Naturally, this change leads to a performance regression, which was measured to be about 30% in the worst case. This was\ndeemed acceptable though, because it is better to return the correct result slowly than to return the wrong result quickly. Furthermore, the commit-graph still results in a significant performance improvement compared to not using the commit-graph at all. To give users an escape hatch in case they do not want this performance regression, we also introduced a `GIT_COMMIT_GRAPH_PARANOIA` environment variable that can be used to disable this check.\n\nAfter this change was merged and released though, we heard of cases where the impact was even worse than 30%: counting the number of commits via `git rev-list --count` in the Linux repository regressed by about 100%. After some\ndiscussion upstream, we changed the default so that we no longer verify commit existence for the commit-graph to speed up such queries again. Because repository housekeeping should ensure that commit-graphs are consistent, this change should stop us from needlessly pessimizing this uncommon case.\n\nThis change was implemented by\n[Patrick Steinhardt](https://gitlab.com/pks-gitlab).\n\n## Making Git ready for a new ref backend\n\nA common theme among our customers is that large monorepos with many refs create significant performance problems with many workloads. The range of problems here are manyfold, but the more refs a repository has, the more pronounced the problems become.\n\nMany of the issues are inherent limitations of the way Git stores refs. The so-called `files` ref backend uses a combination of two mechanisms:\n- \"Loose refs\" are simple files that contain the object ID they point to.\n- \"Packed refs\" are a single file that contains a collection of refs.\n\nWhenever you update or create a ref, Git creates them as a loose ref. Every once in a while, repository housekeeping then compresses all loose refs into the `packed-refs` file and deletes the corresponding loose refs. A typical repo looks as follows:\n\n```shell\n $ git init --ref-format=files repo\nInitialized empty Git repository in /tmp/repo/.git/\n $ cd repo/\n $ git commit --allow-empty --message \"initial commit\"\n $ tree .git/\n.git/\n├── config\n├── HEAD\n├── index\n└── refs\n\t├── heads\n\t│   └── main\n\t└── tags\n $ cat .git/HEAD\nref: refs/heads/main\n $ cat .git/refs/heads/main\nbf1814060ed3a88bd457ac4dca055d000ffe4482\n\n $ git pack-refs --all\n $ cat .git/packed-refs\n# pack-refs with: peeled fully-peeled sorted\nbf1814060ed3a88bd457ac4dca055d000ffe4482 refs/heads/main\n```\n\nWhile this model has served the Git project quite well, relying on a filesystem like this has several limitations:\n- Deleting a single ref requires you to rewrite the `packed-refs` file, which can be gigabytes in size.\n- It is impossible to do atomic reads because you cannot atomically scan multiple files at once when a concurrent writer may modify some refs.\n- It is impossible to do atomic writes because creating or updating several refs requires you to write to several files.\n- Housekeeping via `git-pack-refs` does not scale well because of its all-into-one repacking nature.\n- The storage format of both loose and packed refs is inefficient and wastes disk space.\n- Filesystem-specific behavior can be weird and may restrict which refs can be created. For example, Case-insensitivity on filesystems like FAT32 can cause issues, when trying to create two refs with the same name that only differ in their case.\n\nSeveral years ago, [Shawn Pearce](https://sfconservancy.org/blog/2018/jan/30/shawn-pearce/) had proposed the \"reftable\" format as an alternative new format to store refs in a repository. This new format was supposed to help with most or all of the above issues and is essentially a\nbinary format specifically catered towards storing references in Git.\n\nThis new \"reftable\" format has already been implemented by\n[JGit](https://www.eclipse.org/jgit/) and is used extensively by the [Gerrit project](https://www.gerritcodereview.com/). And, in 2021, [Han-Wen Nienhuys](https://www.linkedin.com/pub/dir/han-wen/nienhuys) upstreamed a library to read and write reftables into the Git project. What is still missing though is the backend that ties together the reftable library and\nGit, and unfortunately progress has stalled here. As we experience much of the pain that the reftable format is supposed to address, we decided to take over the work from Han-Wen and continue the upstreaming process.\n\nBefore we can upstream the reftable backend itself though, we first had to prepare several parts of Git for such a new backend. While the Git project already has a concept of different ref backends, the boundaries were very blurry because until now there only exists a single \"files\" backend.\n\nThe biggest contribution by GitLab in this release was thus a joint effort to prepare all the parts of Git for the new backend that were crossing boundaries:\n- Some commands used to read or write refs directly via the filesystem without going through the ref backend.\n- The ref databases of worktrees created via `git-worktree` were initialized ad-hoc instead of going through the ref backend.\n- Cloning a repository created the ref database with the wrong object format when using SHA256. This did not matter with the \"files\" backend because the format was not stored anywhere by the ref backend itself. But because the reftable backend encodes the format into its binary format, this was a problem.\n- Many tests read or write refs via the filesystem directly.\n- We invested quite some time already into bug fixing and performance optimizations for the reftable library itself.\n- We introduced a new `refStorage` extension that tells Git in which format the repository stores its refs. This can be changed when creating a new repository by specifying `--ref-format` flag in `git-init` or `git-clone`. For now, only the “files” format is supported.\n\nThe overarching goal was to get the work-in-progress reftable backend into a state where it passes the complete test suite. And even though the reftable backend is not yet part of Git 2.44.0, I am happy to report that we have\nsucceeded in this goal: Overall, we have contributed more than 150 patches to realize it. Given the current state, we expect that the new reftable backend will become available with Git v2.45.0.\n\nWe will not cover the new reftable format in this post because it is out of scope, but stay tuned for more details soon!\n\nThis project was a joint effort by\n[John Cai](https://gitlab.com/jcaigitlab),\n[Justin Tobler](https://gitlab.com/justintobler),\n[Karthik Nayak](https://gitlab.com/knayakgl),\n[Stan Hu](https://gitlab.com/stanhu),\n[Toon Claes](https://gitlab.com/toon),\nand [Patrick Steinhardt](https://gitlab.com/pks-gitlab), who has led the effort. Credit also goes to\n[Shawn Pearce](https://sfconservancy.org/blog/2018/jan/30/shawn-pearce/) as original inventor of the format and [Han-Wen Nienhuys](https://www.linkedin.com/pub/dir/han-wen/nienhuys) as the\nauthor of the reftable library.\n\n## Support for GitLab CI\n\nAs all the preparations for the new `reftable` backend demonstrate, we have significantly increased our investments into the long-term vision and health of\nthe Git project. And because a very important part of our product depends on the Git project to remain healthy, we want to continue investing into the Git project like this.\n\nFor us, this means that it was high time to improve our own workflows in the context of the Git project. Naturally, we were already using GitLab CI as part of the process instead of the GitHub Workflows support that existed in\nthe Git project. But we were using a [`.gitlab-ci.yml` definition](https://docs.gitlab.com/ee/ci/yaml/) that was not part of the upstream repository and instead maintained outside the Git project.\n\nWhile this worked reasonably well, there were two significant downsides:\n- Test coverage was significantly lower than that of the GitHub Workflows definition. Notably, we did not test on macOS, had no static analysis, and didn't test with non-default settings. This often led to failures in the GitHub Workflows pipeline that we could have detected earlier if we had better CI integration.\n- Other potential contributors to Git who may already be using GitLab on a daily basis didn't have easy access to a GitLab CI pipeline.\n\nTherefore, we decided to upstream a new GitLab CI definition that integrates with the preexisting CI infrastructure that the Git project already had. Because we reuse a lot of pre-existing infrastructure, this ensures that both GitLab CI and GitHub Workflows run tests mostly in the same way.\n\nAnother benefit of GitLab CI support is that, for the first time, we now also exercise an architecture other than `x86_64` or `i686`: the [macOS runners we provide at GitLab.com](https://docs.gitlab.com/ee/ci/runners/saas/macos_saas_runner.html) use an Apple M1, which is based on the `arm64` architecture.\n\nThis change was contributed by [Patrick Steinhardt](https://gitlab.com/pks-gitlab).\n\n## More to come\n\nThis blog post gives just a glimpse into what has happened in the Git project, which lies at the heart of [source code management](https://about.gitlab.com/solutions/source-code-management/) at GitLab. Stay tuned for more insights into future contributions and the reftable backend in particular!",[961,916,9],{"slug":1756,"featured":6,"template":699},"gitlabs-contributions-to-git-2-44-0","content:en-us:blog:gitlabs-contributions-to-git-2-44-0.yml","Gitlabs Contributions To Git 2 44 0","en-us/blog/gitlabs-contributions-to-git-2-44-0.yml","en-us/blog/gitlabs-contributions-to-git-2-44-0",{"_path":1762,"_dir":246,"_draft":6,"_partial":6,"_locale":7,"seo":1763,"content":1769,"config":1775,"_id":1777,"_type":14,"title":1778,"_source":16,"_file":1779,"_stem":1780,"_extension":19},"/en-us/blog/hosting-vuejs-apps-using-gitlab-pages",{"title":1764,"description":1765,"ogTitle":1764,"ogDescription":1765,"noIndex":6,"ogImage":1766,"ogUrl":1767,"ogSiteName":686,"ogType":687,"canonicalUrls":1767,"schema":1768},"How to host VueJS apps using GitLab Pages","Follow this tutorial, including detailed configuration guidance, to quickly get your application up and running for free.","https://res.cloudinary.com/about-gitlab-com/image/upload/v1749683489/Blog/Hero%20Images/hosting.png","https://about.gitlab.com/blog/hosting-vuejs-apps-using-gitlab-pages","\n                        {\n        \"@context\": \"https://schema.org\",\n        \"@type\": \"Article\",\n        \"headline\": \"How to host VueJS apps using GitLab Pages\",\n        \"author\": [{\"@type\":\"Person\",\"name\":\"Sophia Manicor\"},{\"@type\":\"Person\",\"name\":\"Noah Ing\"}],\n        \"datePublished\": \"2023-09-13\",\n      }",{"title":1764,"description":1765,"authors":1770,"heroImage":1766,"date":1772,"body":1773,"category":717,"tags":1774},[1771,781],"Sophia Manicor","2023-09-13","\nIf you use VueJS to build websites, then you can host your website for free with GitLab Pages. This short tutorial walks you through a simple way to host and deploy your VueJS applications using GitLab CI/CD and GitLab Pages.\n\n## Prequisites\n- A VueJS application\n- Working knowledge of GitLab CI\n- 5 minutes\n\n## Setting up your VueJS application\n\n1) Install vue-cli.\n\n```bash\nnpm install -g @vue/cli\n# OR\nyarn global add @vue/cli\n```\nYou can check you have the right version of Vue with:\n\n```bash\nvue --version\n```\n\n2) Create your application using:\n\n```bash\nvue create name-of-app\n```\n\nWhen successfully completed, you will have a scaffolding of your VueJS application.\n\n## Setting up .gitlab-ci.yml for GitLab Pages\nBelow is the [GitLab CI configuration](https://gitlab.com/demos/applications/vuejs-gitlab-pages/-/blob/main/.gitlab-ci.yml) necessary to deploy to GitLab Pages. Put this file into your root project. GitLab Pages always deploys your website from a specific folder called `public`.\n\n```yaml\nimage: \"node:16-alpine\"\n\nstages:\n  - build\n  - test\n  - deploy\n\nbuild:\n  stage: build\n  script:\n    - yarn install --frozen-lockfile --check-files --non-interactive\n    - yarn build\n  artifacts:\n    paths:\n      - public\n\npages:\n  stage: deploy\n  script:\n    - echo 'Pages deployment job'\n  artifacts:\n    paths:\n      - public\n  only:\n    - main\n\n```\n\n## Vue config (vue.config.js)\nIn Vue, the artifacts are built in a folder called dist, in order for GitLab to deploy to Pages, we need to change the path of the artifacts. One way to do this is by changing the [Vue config file](https://gitlab.com/demos/applications/vuejs-gitlab-pages/-/blob/main/vue.config.js), `vue.config.js`.\n\n```\nconst { defineConfig } = require('@vue/cli-service')\n\nfunction publicPath () {\n  if (process.env.CI_PAGES_URL) {\n    return new URL(process.env.CI_PAGES_URL).pathname\n  } else {\n    return '/'\n  }\n}\n\nmodule.exports = defineConfig({\n  transpileDependencies: true,\n  publicPath: publicPath(),\n  outputDir: 'public'\n})\n```\n\nHere we have set `outputDir` to `public` so that GitLab will pick up the build artifacts and deploy to Pages. Another important piece when creating this configuration file is to change the `publicPath`, which is the base URL your application will be deployed at. In this case, we have create a function `publicPath()` that checks if the CI_PAGES_URL environment variable is set and returns the correct base URL.\n\n## Run GitLab CI\n\n![vuejs-gitlab-pages-pipeline](https://about.gitlab.com/images/blogimages/2023-05-11-hosting-vuejs-apps-using-gitlab-pages/vuejs-gitlab-pages-pipeline.png){: .shadow}\n\n\n## Check Pages to get your URL\n\n![gitlab-pages-domain](https://about.gitlab.com/images/blogimages/2023-05-11-hosting-vuejs-apps-using-gitlab-pages/gitlab-page-domain.png){: .shadow}\n\nVoila! You have set up a VueJS project with a fully functioning CI/CD pipeline. Enjoy your VueJS application hosted by GitLab Pages!\n\n## References\n- [https://cli.vuejs.org/guide/installation.html](https://cli.vuejs.org/guide/installation.html)\n- [https://cli.vuejs.org/guide/creating-a-project.html](https://cli.vuejs.org/guide/creating-a-project.html)\n- [https://gitlab.com/demos/applications/vuejs-gitlab-pages](https://gitlab.com/demos/applications/vuejs-gitlab-pages)\n\n",[109,742,9,696],{"slug":1776,"featured":6,"template":699},"hosting-vuejs-apps-using-gitlab-pages","content:en-us:blog:hosting-vuejs-apps-using-gitlab-pages.yml","Hosting Vuejs Apps Using Gitlab Pages","en-us/blog/hosting-vuejs-apps-using-gitlab-pages.yml","en-us/blog/hosting-vuejs-apps-using-gitlab-pages",{"_path":1782,"_dir":246,"_draft":6,"_partial":6,"_locale":7,"seo":1783,"content":1789,"config":1794,"_id":1796,"_type":14,"title":1797,"_source":16,"_file":1798,"_stem":1799,"_extension":19},"/en-us/blog/how-automation-is-making-devops-pros-jobs-easier",{"title":1784,"description":1785,"ogTitle":1784,"ogDescription":1785,"noIndex":6,"ogImage":1786,"ogUrl":1787,"ogSiteName":686,"ogType":687,"canonicalUrls":1787,"schema":1788},"How automation is making DevOps pros’ jobs easier","Six ways automation in a DevSecOps platform aids security, monitoring, compliance, and CI/CD.","https://res.cloudinary.com/about-gitlab-com/image/upload/v1749662504/Blog/Hero%20Images/devsecops-automated-security.jpg","https://about.gitlab.com/blog/how-automation-is-making-devops-pros-jobs-easier","\n                        {\n        \"@context\": \"https://schema.org\",\n        \"@type\": \"Article\",\n        \"headline\": \"How automation is making DevOps pros’ jobs easier\",\n        \"author\": [{\"@type\":\"Person\",\"name\":\"Sharon Gaudin\"}],\n        \"datePublished\": \"2022-12-12\",\n      }",{"title":1784,"description":1785,"authors":1790,"heroImage":1786,"date":1791,"body":1792,"category":1131,"tags":1793},[1433],"2022-12-12","\nAs DevOps professionals look for ways to save time, money, and tech muscle as they work to push better and more secure software out the door, they’re increasingly seeing the advantages of automation — and that those advantages seamlessly come with adopting an end-to-end [DevSecOps](/topics/devsecops/) platform. \n\nIn a 2022 GitLab quiz, more than 82% of respondents said automation plays a “vital” role in developing and deploying safer and faster releases. \n\nIt’s clear that DevOps professionals are realizing that automation minimizes the need for a lot of extra hands-on and time-consuming work, like backup, installation, and maintenance. It also can reduce the potential for human error and provide consistency. A DevSecOps platform, unlike a cobbled-together [DIY toolchain](https://page.gitlab.com/resources-ebook-trading-diy-devops-for-a-single-platform.html), offers many advantages, like visibility and collaboration. Another major benefit is that it offers automation for everything from alerts to [testing](/blog/want-faster-releases-your-answer-lies-in-automated-software-testing/) and monitoring.\n\n## Benefits of DevSecOps automation\n\nHere is how automation throughout the software lifecycle could help DevOps teams cut time and money spent on repetitive tasks, eliminate human errors, and streamline the whole DevOps process:\n\n1. Security – A critical benefit of migrating to a full DevSecOps platform is that software won’t simply get a security test at the end of the pipeline – an inefficient, and often costly, feedback system. When [security is shifted left](/blog/efficient-devsecops-nine-tips-shift-left/), if a vulnerability or compliance issue is introduced into the code, it’s identified almost immediately thanks to automated and consistent testing. Automation built into a DevOps platform leads to better software and reduces the time between designing new, higher-quality features and rolling them out into production. And that maximizes the overall return on software development.\n\n2. Compliance – With a single DevSecOps application, [compliance confirmation](/stages-devops-lifecycle/govern/) lives within the platform and is automated. That means professionals can verify the compliance of their code without leaving their workflow, removing the need for compliance managers to require developers to context switch among different point solutions in a DIY toolchain, which can lead to the loss of productivity and efficiency. \n\n3. Configuration – It’s a complicated job to set up, manage, and maintain application environments. [Automated configuration management](/stages-devops-lifecycle/configure/) is designed to handle these complex environments across servers, networks, and storage systems.\n\n4. Continuous integration (CI) – This is the step that enables the DevOps practice of iteration by committing changes to a shared source code repository early and often – often several times a day. [CI](/blog/basics-of-gitlab-ci-updated/) is all about efficiency. By automating manual work and testing code more frequently, teams can iterate faster and deploy new features with fewer bugs more often.\n\n5. Continuous delivery (CD) – This is a software development process that works in conjunction with continuous integration to automate the application release process. When [deployments are handled automatically](/blog/cd-automated-integrated/), software release [processes are low-risk, consistent, and repeatable](/blog/boring-solutions-faster-iteration/). \n\n6. Monitoring – This is a proactive, automated part of the process, focused on tracking software, infrastructure, and networks to trace status and raise alerts to problems. [Monitoring](/stages-devops-lifecycle/monitor/) increases security, reliability, and agility. \n\n## Automation by the numbers\n\nIn fact, the [GitLab 2022 Global DevSecOps Survey](https://learn.gitlab.com/dev-survey-22/2022-devsecops-report), which polled more than 5,000 DevSecOps professionals, showed that automation is becoming increasingly critical to all DevOps teams.\n\nThe survey found that 47% of teams report their testing is fully automated today, up from 25% last year. Another 21% plan to roll out test automation at some point in 2022, and 15% hope to do so in the next two or more years. And three-quarters of respondents told us their teams use a DevSecOps platform or plan to use one this year. \n\nWhy are they using a platform? Well, security professionals called out easier automation and more streamlined deployments.\n\n## Fewer repetitive and unnecessary tasks\n\nSo what is all of this automation enabling DevOps professionals to do? They’re able to let go of a lot of work. \n \nAccording to the DevSecOps Survey, respondents said they’ve been able to reduce a lot of repetitive tasks. For instance, they say they no longer have to do as much infrastructure “handholding” — they’re not manually testing their code, writing messy code, and ignoring code quality. \n \nWith automation, each task is performed identically and with consistency, reliability, and accuracy. This promotes speed and increases deliveries, and, ultimately, deployments. While it doesn’t remove humans from the picture, automation minimizes dependency on humans for managing recurring tasks. \n\nAnd with GitLab’s single, end-to-end DevSecOps platform, automation is a system feature and not something that has to be added in. Automation with the GitLab platform is ready to go. Check out the [“Ditching DIY DevOps for GitLab’s Single Platform”](https://page.gitlab.com/resources-ebook-trading-diy-devops-for-a-single-platform.html) to learn more ways a platform can help DevOps teams.\n",[741,1004,9,696],{"slug":1795,"featured":6,"template":699},"how-automation-is-making-devops-pros-jobs-easier","content:en-us:blog:how-automation-is-making-devops-pros-jobs-easier.yml","How Automation Is Making Devops Pros Jobs Easier","en-us/blog/how-automation-is-making-devops-pros-jobs-easier.yml","en-us/blog/how-automation-is-making-devops-pros-jobs-easier",{"_path":1801,"_dir":246,"_draft":6,"_partial":6,"_locale":7,"seo":1802,"content":1808,"config":1815,"_id":1817,"_type":14,"title":1818,"_source":16,"_file":1819,"_stem":1820,"_extension":19},"/en-us/blog/how-devops-and-gitlab-cicd-enhance-a-frontend-workflow",{"title":1803,"description":1804,"ogTitle":1803,"ogDescription":1804,"noIndex":6,"ogImage":1805,"ogUrl":1806,"ogSiteName":686,"ogType":687,"canonicalUrls":1806,"schema":1807},"How DevOps and GitLab CI/CD enhance a frontend workflow","The GitLab frontend team uses DevOps and CI/CD to ensure code consistency, fast delivery, and simple automation.","https://res.cloudinary.com/about-gitlab-com/image/upload/v1749679026/Blog/Hero%20Images/frontendworkflow.jpg","https://about.gitlab.com/blog/how-devops-and-gitlab-cicd-enhance-a-frontend-workflow","\n                        {\n        \"@context\": \"https://schema.org\",\n        \"@type\": \"Article\",\n        \"headline\": \"How DevOps and GitLab CI/CD enhance a frontend workflow\",\n        \"author\": [{\"@type\":\"Person\",\"name\":\"José Iván Vargas\"}],\n        \"datePublished\": \"2018-08-09\",\n      }",{"title":1803,"description":1804,"authors":1809,"heroImage":1805,"date":1811,"body":1812,"category":717,"tags":1813},[1810],"José Iván Vargas","2018-08-09","\nIt might seem like a lot of what we do on frontend is to make our lives easier,\nbut what I’ve learned in the past two years as a GitLab team-member and a community contributor\nis that if we make our lives easier, we can make a lot of customers happier, too.\nOver the years, I’ve experienced many changes at GitLab, from a change in processes\nto an increase in team members. From an early stage, the frontend team has been\ncommitted to continuous improvements, but working in a rapidly growing team\nrequired an investment in the way we work.\n\nWhen I joined GitLab we still used some of the default conventions that the [Rails\nframework](/blog/upgrade-to-rails5/) recommended for the frontend, and it helped us for quite a while, but\nthe more code we touched, the more code we needed to test and build for\nperformance, making it more challenging for us to maintain. The frontend team\nrealized that we needed a way to facilitate code consistency, fast delivery, and\nsimple automation, so we decided to incorporate [DevOps](/topics/devops/) and\n[CI/CD](/solutions/continuous-integration/) into our workflow.\n\n## Frontend DevOps and CI/CD workflow\n\nWe used CI in a few scenarios, including using linters to help write a consistent\nstyle of code throughout GitLab, but in the case of our JavaScript code, we\nrealized that building for performance and maintainability was becoming\nincreasingly difficult. So, we moved away from the\n[asset pipeline and utilized webpack](/blog/vue-big-plan/),\nwhich has given us a series of benefits. For example,  when we develop locally,\ndebugging code is now a breeze, and the jobs that are frontend related run on\nproduction-bundled code, ensuring a testing environment that closely resembles\nthat of a user.\n\nAfter CI, we publish code using DevOps by hosting it with\n[GitLab Pages](https://docs.gitlab.com/ee/user/project/pages/)). We’ve seen several projects benefit from\nadopting a DevOps model, including\n[GitLab SVG libraries](https://gitlab.com/gitlab-org/gitlab-svgs) and\n[Trello Power-Up](https://docs.gitlab.com/ee/integration/trello_power_up.html).\n\nWhen we created GitLab SVG libraries, we wanted to use them for ourselves and\nmake them available to the general public, so whenever we publish a new version,\nwe use GitLab Pages so that it’s fully automated every time.\n\nWith the Trello Power-Up plugin, we use DevOps to address compatibility\nissues when a new version of Trello is released. GitLab Pages makes it easy to\ndeploy a new version, in a fast and diligent manner, so that it’s accessible in\nthe Trello Marketplace as quickly as possible.\n\n## Frontend DevOps and Data-driven efforts\n\nIncorporating frontend DevOps and CI/CD into the workflow has had a significant\nimpact on efficiency and results. We have greater insight into our operations\nand have metrics to help us detect major areas of improvement. We set up\n[Sitespeed](https://www.sitespeed.io) using Kubernetes to analyze sets of pages\nand provide reports on anything that could hamper our users’ perceived\nperformance, from CSS and JavaScript bundle sizes to accessibility issues and\nthe render time differences between various points in time. The information we gathered using\nSitespeed has helped us improve the merge requests page and identify pages that\nrender slowly. Having more data has changed the way we approach problems at\nGitLab, because we are able to focus our efforts on specific areas.\n\n## The unexpected discovery of problems\n\nOne of the unexpected benefits of our workflow is the discovery of problems that\nwe may not have identified.\n\n### A lack of automation\n\nWe realized, for example, that we lack some automation in our tools. For\ninstance, every time we didn’t format code in a specific way, our linter\nnotified us, but analyzing and fixing the code slowed down developer velocity,\nso we decided to add [Prettier](https://prettier.io/) to format our code in our\nmerge requests for us. We also realized that, sometimes, we need a little bit of\nautomation when we publish code. As an all-remote company, many of us work on\npublic WiFi, and we found that unreliable connections could have detrimental\neffects while deploying code. The combination of CI and DevOps made deployments\neasier. If we triggered a pipeline and a coffee shop WiFi goes vamoose, it\ndoesn't matter. We already automated a significant part of our development\nprocess, but we’re always striving for more.\n\n### A lack of speed\n\nIn the case of CI, we noticed that our own tools can be a source of problems. We\nfound that we didn’t make the necessary considerations to keep our test suite fast.\nAs developers, we want to go back to developing as fast as possible. A few of my\nteammates discovered that our test runs were becoming slower and slower with each\nrelease. Even though these are not customer-facing changes, it has made both\nproduct managers and team managers consider investing in those issues, because\nthe easier the development cycle is for the developers involved, the better it\nis for our customers, since we can deliver even more features. Furthermore, we\ncan prevent regressions from happening by having solid foundations, such as\ntesting, code style, and code formatting.\n\nEvery time we discover problems that affect us or our work, we realize that we\ncan also jeopardize the features and experiences we want to deliver to our\ncustomers. It has changed the culture inside the team, because we view\nperformance issues as developers rather than as GitLab team-members.\n\n## Advice to frontend teams\n\nUsing DevOps and CI/CD in a frontend workflow is compatible with teams of any\nsize, including small teams that may want to ensure that their code styling is\nthe same.\n\n### Put a linter in place\n\nWith CI, the smallest and perhaps one of the most significant steps is\nto put a linter in place, and if the pipeline doesn't pass, you can’t merge the\ncode. That's such a simple, effective way to improve your code and to keep it\ntidy and clean in the long run. Just setting up some simple steps using CI will\nimprove your team’s code and your developers’ quality of life so that they don't\nhave to worry about combing through past code. Even though small teams might not\nfind the value in the short term, when they scale, they certainly will.\n\n### Create consistent scenarios\n\nThe bigger the project, the more you realize that some of your tooling ends up\nrunning locally, and it's beneficial to run it on CI. If something doesn't work\non a generic type of machine that has enough dependencies installed to run your\nCI setup, that means there’s something wrong and that you should probably fix it\nbefore merging your code. As long as you can create a consistent scenario in which\nyou can do things like testing and linting, you should be in a good position to\ndeliver a great product.\n\n### Select CI-compatible tools\n\nFor teams of all sizes, it’s important that the tools you select as part of your\nworkflow are compatible with CI in some way, so that even if you had a big part\nof your workflow running locally, you can easily move to CI by creating a pipeline\nthat resembles that of your daily workflow. Regardless of the tool that you choose,\ncreating a job for it will return a lot of value in the long run. If it makes\nsense, I encourage you to add it, because there’s very little incentive not to.\nCI-compatible tools include tests runners, linters, Prettier, or any custom-made\ntools that help you in some way. One decision you might want to avoid is creating\non servers that live on CI runners. Since they only run for a limited amount of\ntime, these servers will stop existing. You could also add deployments to your\nCI workflow, helping you with DevOps and preventing you from worrying about\ncomplicated local setups for new developers. The possibilities are huge.\n\n### Add performance testing\n\nTo add to the pool of possibilities, why not add performance testing to your\nmerge requests with a tool such as\n[Lighthouse](https://developers.google.com/web/tools/lighthouse/), which can\nhelp you understand potential performance bottlenecks in your website. Or, maybe\nyour team can add the ability to generate code documentation and publish it via\nGitLab Pages. CI/CD can be a really good tool, because it will return something\nimmediately. It's just a matter of how you want to use it, depending on your needs.\n\nThe more the frontend team uses CI and DevOps, the more we discover ways to use\nit, so it’s worth it to us to invest in this tool.\n\nSometimes, we just want to\nget stuff out there without too much consideration for tooling and CI and CD,\nbut because of the benefits we’ve experienced, we now include CI/CD in all of\nour projects. With GitLab, everything is integrated, so why skip it? Instead of\nfighting against automation, I encourage teams to embrace the idea that CI is\nthere to help you.\n\n[Cover image](https://unsplash.com/photos/UbGqwmzQqZM) by\n[Zhipeng Ya](https://unsplash.com/photos/UbGqwmzQqZM?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText), licensed\nunder [CC X](https://unsplash.com/license).\n{: .note}\n",[1814,1734,9,741],"frontend",{"slug":1816,"featured":6,"template":699},"how-devops-and-gitlab-cicd-enhance-a-frontend-workflow","content:en-us:blog:how-devops-and-gitlab-cicd-enhance-a-frontend-workflow.yml","How Devops And Gitlab Cicd Enhance A Frontend Workflow","en-us/blog/how-devops-and-gitlab-cicd-enhance-a-frontend-workflow.yml","en-us/blog/how-devops-and-gitlab-cicd-enhance-a-frontend-workflow",{"_path":1822,"_dir":246,"_draft":6,"_partial":6,"_locale":7,"seo":1823,"content":1829,"config":1835,"_id":1837,"_type":14,"title":1838,"_source":16,"_file":1839,"_stem":1840,"_extension":19},"/en-us/blog/how-gitlab-can-help-mitigate-deletion-open-source-images-docker-hub",{"title":1824,"description":1825,"ogTitle":1824,"ogDescription":1825,"noIndex":6,"ogImage":1826,"ogUrl":1827,"ogSiteName":686,"ogType":687,"canonicalUrls":1827,"schema":1828},"GitLab helps mitigate Docker Hub's open source image removal","CI/CD and Kubernetes deployments can be affected by Docker Hub tier changes. This tutorial walks through analysis, mitigations, and long-term solutions.","https://res.cloudinary.com/about-gitlab-com/image/upload/v1749659883/Blog/Hero%20Images/post-cover-image.jpg","https://about.gitlab.com/blog/how-gitlab-can-help-mitigate-deletion-open-source-images-docker-hub","\n                        {\n        \"@context\": \"https://schema.org\",\n        \"@type\": \"Article\",\n        \"headline\": \"How GitLab can help mitigate deletion of open source container images on Docker Hub\",\n        \"author\": [{\"@type\":\"Person\",\"name\":\"Michael Friedrich\"}],\n        \"datePublished\": \"2023-03-16\",\n      }",{"title":1830,"description":1825,"authors":1831,"heroImage":1826,"date":1832,"body":1833,"category":717,"tags":1834},"How GitLab can help mitigate deletion of open source container images on Docker Hub",[1252],"2023-03-16","Docker, Inc. shared an email update to Docker Hub users that it will [sunset\nFree Team\norganizations](https://www.infoworld.com/article/3690890/docker-sunsets-free-team-subscriptions-roiling-open-source-projects.html).\nIf accounts do not upgrade to a paid plan before April 14, 2023, their\norganization's images may be deleted after 30 days. This change can affect\nopen source organizations that publish their images on Docker Hub, as well\nas consumers of these container images, used in CI/CD pipelines, Kubernetes\ncluster deployments, or docker-compose demo environments. This blog post\ndiscusses tools and features on the GitLab DevSecOps platform to help users\nanalyze and mitigate the potential impact on production environments.\n\n\n_Update (March 20, 2023): Docker, Inc. [published an apology blog\npost](https://www.docker.com/blog/we-apologize-we-did-a-terrible-job-announcing-the-end-of-docker-free-teams/),\nincluding a FAQ, and clarifies that the company will not delete container\nimages by themselves. Maintainers can migrate to a personal account, join\nthe Docker-sponsored open source program, or opt into a paid plan. If open\nsource container image maintainers do nothing, this leads into another\nissue: Stale container images can become a security problem. The following\nblog post can help with security analysis and migration too._\n\n\n_Update (March 27, 2023): On March 24, 2023, Docker, Inc. [published another\nblog\npost](https://www.docker.com/blog/no-longer-sunsetting-the-free-team-plan/)\nannouncing the reversal of the decision to sunset the Free team plan and\nupdated its [FAQ for Free Team\norganization](https://www.docker.com/developers/free-team-faq/). While this\nis a welcome development for the entire community, it is still crucial to\nensure the reliability of your software development lifecycle by ensuring\nredundancies are in place for your container registries, as detailed in this\nblog post._\n\n\n### Inventory of used container images\n\n\nCI/CD pipelines in GitLab can execute jobs in containers. This is specified\nby the [`image` keyword](https://docs.gitlab.com/ee/ci/yaml/#image) in jobs,\njob templates, or as a global\n[`default`](https://docs.gitlab.com/ee/ci/yaml/#default) attribute. For the\nfirst iteration, you can clone a GitLab project locally, and search for the\n`image` string in all CI/CD configuration files. The following example shows\nhow to execute the `find` command on the command line interface (CLI),\nsearching for files matching the name pattern `*ci.yml`, and looking for the\n`image` string in the file content. The command line prints a list of search\npattern matches, and the corresponding file name to the standard output. The\nexample inspects the [project](https://gitlab.com/gitlab-com/www-gitlab-com)\nfor the [GitLab handbook](https://handbook.gitlab.com/handbook/) and\n[website](https://about.gitlab.com/) to analyze whether its CI/CD deployment\npipelines could be affected by the Docker Hub changes.\n\n\n```bash\n\n$ git clone https://gitlab.com/gitlab-com/www-gitlab-com && cd\nwww-gitlab-com\n\n\n$ find . -type f -iname '*ci.yml' -exec sh -c \"grep 'image:' '{}' && echo\n{}\" \\;\n\n  image: registry.gitlab.com/gitlab-org/gitlab-build-images:www-gitlab-com-debian-${DEBIAN_VERSION}-ruby-3.0-node-16\n  image: alpine:edge\n  image: alpine:edge\n  image: debian:stable-slim\n  image: debian:stable-slim\n  image: registry.gitlab.com/gitlab-org/gitlab-build-images:danger\n./.gitlab-ci.yml\n\n```\n\n\nA [discussion on Hacker News](https://news.ycombinator.com/item?id=35168802)\nmentions that \"official Docker images\" are not affected, but this is not\nofficially confirmed by Docker yet. [Official Docker\nimages](https://hub.docker.com/u/library) do not use a namespace prefix,\ni.e. `namespace/imagename` but instead `debian:\u003Ctagname>` for example.\n`registry.gitlab.com/gitlab-org/gitlab-build-images:danger` uses a full URL\nimage string, which includes the image registry server domain,\n`registry.gitlab.com` in the shown example.\n\n\nIf there is no full URL prefix in the image string, this is an indicator\nthat this image could be pulled from Docker Hub by default. There might be\nother infrastructure safety nets put in place, for example a cloud provider\nregistry which caches the Docker Hub images (Google Cloud, AWS, Azure,\netc.).\n\n\n#### Advanced search for images\n\n\nYou can use the [project lint API\nendpoint](https://docs.gitlab.com/ee/api/lint.html#validate-a-projects-ci-configuration)\nto fetch the CI configuration. The following script uses the [python-gitlab\nAPI\nlibrary](https://python-gitlab.readthedocs.io/en/stable/gl_objects/ci_lint.html)\nto implement the API endpoint:\n\n\n1. Collect all projects from either a single project ID, a group ID with\nprojects, or from the instance.\n\n2. Run the `project.ci_lint.get()` method to get a merged yaml configuration\nfor CI/CD from the current GitLab project.\n\n3. Parse the yaml content and print only the job names, and the image keys.\n\n\nThe [full script is located\nhere](https://gitlab.com/gitlab-da/use-cases/gitlab-api/gitlab-api-python/-/blob/main/get_all_cicd_job_images.py),\nand is open source, licensed under MIT.\n\n\n```python\n\n#!/usr/bin/env python\n\n\nimport gitlab\n\nimport os\n\nimport sys\n\nimport yaml\n\n\nGITLAB_SERVER = os.environ.get('GL_SERVER', 'https://gitlab.com')\n\nGITLAB_TOKEN = os.environ.get('GL_TOKEN') # token requires developer\npermissions\n\nPROJECT_ID = os.environ.get('GL_PROJECT_ID') #optional\n\n# https://gitlab.com/gitlab-da/use-cases/docker\n\nGROUP_ID = os.environ.get('GL_GROUP_ID', 65096153) #optional\n\n\n#################\n\n# Main\n\n\nif __name__ == \"__main__\":\n    if not GITLAB_TOKEN:\n        print(\"🤔 Please set the GL_TOKEN env variable.\")\n        sys.exit(1)\n\n    gl = gitlab.Gitlab(GITLAB_SERVER, private_token=GITLAB_TOKEN)\n\n    # Collect all projects, or prefer projects from a group id, or a project id\n    projects = []\n\n    # Direct project ID\n    if PROJECT_ID:\n        projects.append(gl.projects.get(PROJECT_ID))\n\n    # Groups and projects inside\n    elif GROUP_ID:\n        group = gl.groups.get(GROUP_ID)\n\n        for project in group.projects.list(include_subgroups=True, all=True):\n            # https://python-gitlab.readthedocs.io/en/stable/gl_objects/groups.html#examples\n            manageable_project = gl.projects.get(project.id)\n            projects.append(manageable_project)\n\n    # All projects on the instance (may take a while to process)\n    else:\n        projects = gl.projects.list(get_all=True)\n\n    print(\"# Summary of projects and their CI/CD image usage\")\n\n    # Loop over projects, fetch .gitlab-ci.yml, run the linter to get the full translated config, and extract the `image:` setting\n    for project in projects:\n\n        print(\"# Project: {name}, ID: {id}\\n\\n\".format(name=project.name_with_namespace, id=project.id))\n\n        # https://python-gitlab.readthedocs.io/en/stable/gl_objects/ci_lint.html\n        lint_result = project.ci_lint.get()\n\n        data = yaml.safe_load(lint_result.merged_yaml)\n\n        for d in data:\n            print(\"Job name: {n}\".format(n=d))\n            for attr in data[d]:\n                if 'image' in attr:\n                    print(\"Image: {i}\".format(i=data[d][attr]))\n\n        print(\"\\n\\n\")\n\nsys.exit(0)\n\n```\n\n\nThe\n[script](https://gitlab.com/gitlab-de/use-cases/gitlab-api/gitlab-api-python/-/blob/main/get_all_cicd_job_images.py)\nrequires Python (tested with 3.11) and the python-gitlab and pyyaml modules.\nExample on macOS with Homebrew:\n\n\n```shell\n\n$ brew install python\n\n$ pip3 install python-gitlab pyyaml\n\n```\n\n\nYou can execute the script and set the different environment variables to\ncontrol its behavior:\n\n\n```shell\n\n$ export GL_TOKEN=$GITLAB_TOKEN\n\n\n$ export GL_GROUP_ID=12345\n\n$ export GL_PROJECT_ID=98765\n\n\n$ python3 get_all_cicd_job_images.py\n\n\n# Summary of projects and their CI/CD image usage\n\n# Project: Developer Evangelism at GitLab  / use-cases / Docker Use cases  /\nCustom Container Image Python, ID: 44352983\n\n\nJob name: docker-build\n\nImage: docker:latest\n\n\n# Project: Developer Evangelism at GitLab  / use-cases / Docker Use cases  /\nGitlab Dependency Proxy, ID: 44351128\n\n\nJob name: .test-python-version\n\nJob name: image-docker-hub\n\nImage: python:3.11\n\nJob name: image-docker-hub-dep-proxy\n\nImage: ${CI_DEPENDENCY_PROXY_GROUP_IMAGE_PREFIX}/python:3.11\n\n```\n\n\nPlease verify the script and fork it for your own analysis and mitigation.\nThe missing parts are checking the image URLs, and doing a more\nsophisticated search. The code has been prepared to either check against a\nsingle project, a group with projects, or an instance (this may take very\nlong, use with care).\n\n\nYou can perform a more history-focused analysis by fetching the CI/CD job\nlogs from GitLab and search for the pulled container image to get an\noverview of past Docker executor runs – for example: `Using Docker executor\nwith image python:3.11 ...`. The screenshot shows the CI/CD job logs UI\nsearch – you can automate the search using the GitLab API, and the\n[python-gitlab\nlibrary](https://python-gitlab.readthedocs.io/en/stable/gl_objects/pipelines_and_jobs.html#jobs),\nfor example.\n\n\n![GitLab CI/CD job logs, searching for the `image`\nkeyword](https://about.gitlab.com/images/blogimages/docker-hub-oss-image-deletion-mitigation/cicd_gitlab_job_logs_search_image.png)\n\n\nThis snippet can be used in combination with the code shared for the CI lint\nAPI endpoint. It fetches the job trace logs, and searches for the `image`\nkeyword in the log. The missing parts are splitting the log line by line,\nand extracting the image key information. This is left as an exercise for\nthe reader.\n\n\n```python\n        for job in project.jobs.list():\n            log_trace = str(job.trace())\n\n            print(log_trace)\n\n            if 'image' in log_trace:\n                print(\"Job ID: {i}, URL {u}\".format(i=job.id, u=job.web_url))\n                print(log_trace)\n```\n\n\n### More inventory considerations\n\n\nSimilar to the API script for CI/CD navigating through all projects, you\nwill need to analyze all Kubernetes manifest configuration files – using\neither a pull- or push-based approach. This can be achieved by using the\n[python-gitlab methods to load files from the\nrepository](https://python-gitlab.readthedocs.io/en/stable/gl_objects/projects.html#project-files)\nand searching the content in similar ways. Helm charts use container images,\ntoo, and will require additional analysis.\n\n\nAn additional search possibility: Custom-built container images that use\nDocker Hub images as a source. A project will consist of:\n\n\n1. `Dockerfile` file that uses `FROM \u003Cimagename>`\n\n2. `.gitlab-ci.yml` configuration file that builds container images (using\nDocker-in-Docker, Kaniko, etc.)\n\n\nAn alternative search method for customers is available by using the\n[Advanced\nSearch](https://docs.gitlab.com/ee/user/search/advanced_search.html) through\nthe GitLab UI and API. The following example uses the [scope:\nblobs](https://docs.gitlab.com/ee/api/search.html#scope-blobs-premium-2) to\nsearch for the `FROM` string:\n\n\n```shell\n\n$ export GITLAB_TOKEN=xxxxxxxxx\n\n\n# Search in https://gitlab.com/gitlab-da\n\n/use-cases/docker/custom-container-image-python\n\n\n$ curl --header \"PRIVATE-TOKEN: $GITLAB_TOKEN\"\n\"https://gitlab.com/api/v4/projects/44352983/search?scope=blobs&search=FROM%20filename:Dockerfile*\"\n\n```\n\n\n![Command line output from Advanced Search API, scope blobs, search `FROM`\nin `Dockerfile*` file\nnames.](https://about.gitlab.com/images/blogimages/docker-hub-oss-image-deletion-mitigation/cli_gitlab_advanced_search_api_dockerfile_from.png)\n\n\n## Mitigations and solutions\n\n\nThe following sections discuss potential mitigation strategies, and\nlong-term solutions.\n\n\n### Mitigation: GitLab dependency proxy\n\n\nThe dependency proxy provides a caching mechanism for Docker Hub images. It\nhelps reduce the bandwidth and time required to download and pull the\nimages. It also helped to [mitigate the Docker Hub pull rate limits\nintroduced in\n2020](/blog/minor-breaking-change-dependency-proxy/). The\ndependency proxy can be configured for public and private projects.\n\n\nThe [dependency\nproxy](https://docs.gitlab.com/ee/user/packages/dependency_proxy/) needs to\nbe enabled for a group. It also needs to be enabled by an instance\nadministrator for self-managed environments, if turned off.\n\n\nThe following example creates two jobs: `image-docker-hub` and\n`image-docker-hub-dep-proxy`. The dependency proxy job uses the\n`CI_DEPENDENCY_PROXY_GROUP_IMAGE_PREFIX` CI/CD variable to instruct GitLab\nto store the image in the cache, and only pull it once when not available.\n\n\n```yaml\n\n.test-python-version:\n  script:\n    - echo \"Testing Python version:\"\n    - python --version\n\nimage-docker-hub:\n  extends: .test-python-version\n  image: python:3.11\n\nimage-docker-hub-dep-proxy:\n  extends: .test-python-version\n  image: ${CI_DEPENDENCY_PROXY_GROUP_IMAGE_PREFIX}/python:3.11\n```\n\n\nThe configuration is available in [this\nproject](https://gitlab.com/gitlab-de/use-cases/docker/gitlab-dependency-proxy).\n\n\nThe stored container image is visible at the group level in the `Package and\ncontainer registries > Dependency Proxy` menu.\n\n\n### Mitigation: Container registry mirror\n\n\n[This blog\npost](/blog/mitigating-the-impact-of-docker-hub-pull-requests-limits/)\ndescribes how to run a local container registry mirror. Skopeo from Red Hat\nis another alternative for syncing container image registries, a practical\nexample is described [in this\narticle](https://marcbrandner.com/blog/transporting-container-images-with-skopeo/).\n\n\nThe GitLab Cloud Native installation ([Helm\ncharts](https://docs.gitlab.com/charts/) and\n[Operator](https://docs.gitlab.com/operator/)) use a [mirror of tagged\nimages](https://gitlab.com/gitlab-org/cloud-native/mirror/images) consumed\nby the related projects. Other product stages follow a similar approach, the\n[security scanners are shipped in container\nimages](https://docs.gitlab.com/ee/user/application_security/offline_deployments/#container-registries-and-package-repositories)\nmaintained by GitLab. This also enables self-managed airgapped\ninstallations.\n\n\n### Mitigation: Custom images in GitLab container registry\n\n\nReproducible builds and compliance requirements may have required you to\ncreate custom container images for CI/CD and Kubernetes already. This is\nalso key to verify that no untested and untrusted images are being used in\nproduction. GitLab provides a fully integrated [container\nregistry](https://docs.gitlab.com/ee/user/packages/container_registry/),\nwhich can be used natively within CI/CD pipelines and [GitOps workflows with\nthe agent for\nKubernetes](https://docs.gitlab.com/ee/user/clusters/agent/gitops.html).\n\n\nThe following `Dockerfile` example extends an existing image layer, and\ninstalls additional tools using the Debian Apt package manager.\n\n\n```\n\nFROM python:3.11-bullseye\n\n\nENV DEBIAN_FRONTEND noninteractive\n\n\nRUN apt update && apt -y install git curl jq && rm -rf /var/lib/apt/lists/*\n\n```\n\n\nYou can [use Docker to build container\nimages](https://docs.gitlab.com/ee/ci/docker/using_docker_build.html), and\nalternative options are Kaniko or Podman. On GitLab.com SaaS, you can use\nthe Docker CI/CD template to build and push images. The following example\nmodifies the `docker-build` job to only build the latest tag from the\ndefault branch:\n\n\n```yaml\n\ninclude:\n  - template: Docker.gitlab-ci.yml\n\ndocker-build:\n  stage: build\n  rules:\n    - if: '$CI_COMMIT_REF_NAME == $CI_DEFAULT_BRANCH || $CI_COMMIT_TAG'\n      #when: manual\n      #allow_failure: true\n```\n\n\nFor this example, we specifically want to provide a Git tag that gets used\nfor the container image tag as well.\n\n\n```\n\n$ git tag 3-11-bullseye\n\n$ git push --tags\n\n```\n\n\nThe image will be available at the GitLab container registry URL and the\nproject namespace path.This path needs to be replaced in all projects that\nuse a Python-based image. You can [create scripts for the GitLab\nAPI](/blog/efficient-devsecops-workflows-hands-on-python-gitlab-api-automation/)\nto update files and create MRs automatically,\n\n\n```\n\nimage:\nregistry.gitlab.com/gitlab-da/use-cases/docker/custom-container-image-python:3-11-bullseye\n\n```\n\n\n_Note: This is a demo project and not actively maintained. Please fork/copy\nit for your own needs._\n\n\n## Observability and security\n\n\nThe [number of failed CI/CD\npipelines](https://docs.gitlab.com/ee/user/analytics/ci_cd_analytics.html)\ncan be a good service level indicator (SLI) to verify whether the\nenvironment is affected by the Docker Hub changes. The same SLI applies for\nCI/CD jobs that build container images, using a `Dockerfile` file, which is\nbased on Docker Hub images (FROM \u003Cimagename>).\n\n\nA similar SLI applies to Kubernetes cluster deployments – if they continue\nto generate failures in GitOps pull or CI/CD push scenarios, additional\nanalysis and actions are required. The pod status `ErrImagePull` and\n[`ImagePullBackOff`](https://kubernetes.io/docs/concepts/containers/images/#imagepullbackoff)\nwill immediately show the problems. The [image pull\npolicy](https://kubernetes.io/docs/concepts/containers/images/#image-pull-policy)\nshould also be revised – `Always` will immediately cause a problem, while\n`IfNotPresent` will use the local image cache.\n\n\n[This alert rule\nexample](https://awesome-prometheus-alerts.grep.to/rules.html#rule-kubernetes-1-18)\nfor Prometheus observing a Kubernetes cluster can help detect the pod state\nas not healthy.\n\n\n```yaml\n  - alert: KubernetesPodNotHealthy\n    expr: sum by (namespace, pod) (kube_pod_status_phase{phase=~\"Pending|Unknown|Failed\"}) > 0\n    for: 15m\n    labels:\n      severity: critical\n    annotations:\n      summary: Kubernetes Pod not healthy (instance {{ $labels.instance }})\n      description: \"Pod has been in a non-ready state for longer than 15 minutes.\\n  VALUE = {{ $value }}\\n  LABELS = {{ $labels }}\"\n```\n\n\nCI/CD pipeline linters and Git hooks can also be helpful to enforce using a\nGitLab registry URL prefix in all `image` tags, when new updates to CI/CD\nconfigurations are being pushed into merge requests.\n\n\nKubernetes deployment images can be controlled through additional\nintegrations with the [Open Policy Agent\nGatekeeper](https://www.openpolicyagent.org/docs/latest/kubernetes-introduction/)\nor\n[Kyverno](https://kyverno.io/policies/best-practices/restrict_image_registries/restrict_image_registries/).\nKyverno also allows you to [mutate the image registry\nlocation](https://kyverno.io/policies/other/replace_image_registry/replace_image_registry/),\nand redirect the pod image to trusted sources.\n\n\n[Operational container\nscanning](https://docs.gitlab.com/ee/user/clusters/agent/vulnerabilities.html)\nin Kubernetes clusters and [container scanning in CI/CD\npipelines](https://docs.gitlab.com/ee/user/application_security/container_scanning/)\nare recommended. This ensures that all images do not expose security\nvulnerabilities.\n\n\n## Long-term solutions\n\n\nAs a long-term solution, analyze the affected Docker Hub organizations\nimages and match them against your image usage inventory. Some organizations\nhave raised their concerns in [this Docker Hub feedback\nissue](https://github.com/docker/hub-feedback/issues/2314). Be sure to\nidentify critical production CI/CD workflows and replace all external\ndependencies with local maintained images.\n\n\nFork/copy project Dockerfile files from the upstream Git repositories, and\nuse them as the single source of truth for custom container builds. This\nwill also require training and documentation for DevSecOps teams, for\nexample optimizing container images for [efficient CI/CD\npipelines](https://docs.gitlab.com/ee/ci/pipelines/pipeline_efficiency.html).\nMore DevSecOps efficiency tips can be found in my Chemnitz Linux Days talk\nabout \"Efficient DevSecOps Pipelines in a Cloud Native World\"\n([slides](https://go.gitlab.com/RPog2h)).\n\n\n\u003Ciframe\nsrc=\"https://docs.google.com/presentation/d/e/2PACX-1vT3jcfpddKL2jq7leX01QX6S4Y8vfLLBZMz4L1ZHMLY3xzB4IGOOIExODLEzH8YQM1atCNPm07Bw9m_/embed?start=false&loop=true&delayms=3000\"\nframeborder=\"0\" width=\"960\" height=\"569\" allowfullscreen=\"true\"\nmozallowfullscreen=\"true\" webkitallowfullscreen=\"true\">\u003C/iframe>\n\n\nPlease share your ideas and thoughts about Docker Hub change mitigations and\ntools on the [GitLab community forum](https://forum.gitlab.com/). Thank you!\n\n\nCover image by [Roger Hoyles](https://unsplash.com/photos/sTOQyRD8m74) on\n[Unsplash](https://www.unsplash.com)\n\n{: .note}\n",[9,914,916],{"slug":1836,"featured":6,"template":699},"how-gitlab-can-help-mitigate-deletion-open-source-images-docker-hub","content:en-us:blog:how-gitlab-can-help-mitigate-deletion-open-source-images-docker-hub.yml","How Gitlab Can Help Mitigate Deletion Open Source Images Docker Hub","en-us/blog/how-gitlab-can-help-mitigate-deletion-open-source-images-docker-hub.yml","en-us/blog/how-gitlab-can-help-mitigate-deletion-open-source-images-docker-hub",{"_path":1842,"_dir":246,"_draft":6,"_partial":6,"_locale":7,"seo":1843,"content":1849,"config":1855,"_id":1857,"_type":14,"title":1858,"_source":16,"_file":1859,"_stem":1860,"_extension":19},"/en-us/blog/how-orange-uses-gitlab-ci-cd-for-modern-devops",{"title":1844,"description":1845,"ogTitle":1844,"ogDescription":1845,"noIndex":6,"ogImage":1846,"ogUrl":1847,"ogSiteName":686,"ogType":687,"canonicalUrls":1847,"schema":1848},"How Orange made a first step toward CI/CD standardization with GitLab","Find out how Orange made a first step toward CI/CD standardization with GitLab","https://res.cloudinary.com/about-gitlab-com/image/upload/v1749682084/Blog/Hero%20Images/oranges.jpg","https://about.gitlab.com/blog/how-orange-uses-gitlab-ci-cd-for-modern-devops","\n                        {\n        \"@context\": \"https://schema.org\",\n        \"@type\": \"Article\",\n        \"headline\": \"How Orange made a first step toward CI/CD standardization with GitLab\",\n        \"author\": [{\"@type\":\"Person\",\"name\":\"Pierre Smeyers\"}],\n        \"datePublished\": \"2021-07-29\",\n      }",{"title":1844,"description":1845,"authors":1850,"heroImage":1846,"date":1852,"body":1853,"category":694,"tags":1854},[1851],"Pierre Smeyers","2021-07-29","CI/CD is a foundational piece to modern software development. It's a major\nbrick in the [DevOps](/topics/devops/) \"Automation\" pillar and every company\ninvolved in IT has to implement CI/CD or they're already quite far behind\nthe curve.\n\n\nBut [implementing CI/CD](/topics/ci-cd/) can be challenging especially for\ngrowing or large companies. Some of those challenges include:\n\n\n* DevOps expertise and technical skills\n\n* [DevSecOps](/topics/devsecops/)\n\n* Standardization\n\n\n## Three key hurdles that come with implementing CI/CD\n\n\nThis blog post unpackes these challenges and explains how\n[Orange](https://orange.com/) overcame them using GitLab.\n\n\n### DevOps and technical skills\n\n\nNo matter which CI/CD tool you're using, it requires some amount of\nexpertise to implement it right.\n\n\n**DevOps expertise** is important because your team needs some experience\nwith Git workflows, deployment, environments, secrets management, etc. You\ncan't ask a complete rookie to implement a state-of-the art DevOps pipeline\nwithout expertise or experience.\n\n\n**Technical skills** are also important for implementing CI/CD. Any\nprofessional can tell you that getting started tutorials are insufficient.\nWe inevitably need advanced functions, and that requires knowing the tool\npretty well. This is particularly true with GitLab CI/CD, which is a\nfantastic functionally rich tool. GitLab CI/CD is constantly evolving, which\ncreates an ongoing burden for projects that want to integrate new tooling as\nthey go.\n\n\n### DevSecOps\n\n\nDevOps is all about finding the right balance between shortening the cycle\nand maximizing your confidence.\n\n\n[DevSecOps tools](/solutions/security-compliance/) are a keystone in maximizing our\nconfidence because they detect issues with things like security, code\nquality, and compliance, etc., almost instantly. But DevSecOps tools are\nevolving quickly and today's Docker container scanner tools can be replaced\nby newcomers in just a few months.\n\n\nAlso, having each development team in the company choose and integrate\nvarious DevSecOps tools doesn't make sense and will be a waste of time and\nresources. Going this route means most developers won't use any DevSecOps\ntool because the opportunity cost isn't worth the time and effort.\n\n\n### Standardization\n\n\nThe last challenge in implementing CI/CD at a large company is the lack of\nstandardization.\n\n\nGitLab CI/CD - as with most other CI/CD tools - is mainly a sophisticated\nscheduler, allowing a team to define technical tasks and their sequence.\nGitLab CI/CD cares little about the nature of these tasks, and does not give\nany clues as to the \"right\" way to build a DevOps pipeline. The consequence\nof this is that every company, project team, and developer will implement a\nDevOps pipeline their own way, in a manner that is probably significantly\ndifferent from their colleagues'.\n\n\nAs a lifelong Javaist, I like to compare the current situation in CI/CD with\nwhat was the Java build in the pre-Maven era. Back then, we used\nnon-structuring tools such as\n[Make](https://en.wikipedia.org/wiki/Make_(software)) or [Apache\nAnt](https://en.wikipedia.org/wiki/Apache_Ant). Each project created its own\nbuild system, adopted its own conventions, code, and resource files\nstructure. In short, it was a happy mess with everyone reinventing the\nwheel. When joining another project, a user had to ask: \"How does the build\nwork here?\".\n\n\nIn 2004, Maven was released (and Gradle three years later). For a while,\nthere were heated debates between the proponents of standardization and the\ndefenders of expertise and customization. Today it would not occur to anyone\nto build a Java project with anything other than Maven or Gradle. Now, if I\njoin a project developed in Java, I will immediately know how files are\norganized and how the project is built. Java build is now standardized.\n\n\nI believe that CI/CD ought to go a similar route: tools should offer a more\nopinionated framework so that CI/CD too becomes a non-topic.\n\n\n## How a single GitLab feature changed the game for Orange\n\n\nAt Orange - probably like many other companies involved in IT - we struggled\nwith the three challenges summarized above.\n\n\nThen in January 2019, the\n[`include`](https://docs.gitlab.com/ee/ci/yaml/#include) feature was\nreleased in the [Community Edition (version 11.7) of\nGitLab](/releases/2019/01/22/gitlab-11-7-released/):\n\n\n```yaml\n\ninclude:\n  - project: a-path/to-some-project'\n    file: '/very-smart-template.yml'\n```\n\n\nThis feature finally gave us the ability to develop and share\nstate-of-the-art GitLab CI/CD pipeline templates!\n\n\nSo that's what we did.\n\n\nFor two years, a handful of DevOps/security/languages/cloud experts\ndeveloped ready-to-use GitLab CI/CD pipeline templates. This personal\ninitiative quickly became recognized as an internal project, attracting more\nusers and contributors, bringing the community to 1000+ members as of June\n2021, and leveraging about 30 available templates. The visible effect of\nthis increasing adoption is the beginning of a **CI/CD standardization at\nOrange**.\n\n\nWe were so happy with our results and convinced that it's a general need\nthat we open sourced our templates under the name [\"to be\ncontinuous\"](https://to-be-continuous.gitlab.io/doc/).\n\n\n![To be continuous\nlogo](https://about.gitlab.com/images/blogimages/orange_tbc.jpg){: .shadow}\n\nThe \"to be continuous\" logo.\n\n{: .note.text-center}\n\n\n### What is in *to be continuous*?\n\n\nFor now, *to be continuous* has 26 templates of six kinds:\n\n\n* **Build & Test**: Angular, Bash, Go, Gradle, Maven, MkDocs, Node.js, PHP,\nPython\n\n* **Code Analysis**: Gitleaks, SonarQube\n\n* **Packaging**: Docker\n\n* **Infrastructure** (IaC): Terraform\n\n* **Deploy & Run**: Ansible, Cloud Foundry, Google Cloud, Helm, Kubernetes,\nOpenShift, S3 (Simple Storage Service)\n\n* **Acceptance**: Cypress, Postman, Puppeteer, Robot Framework, SSL test, k6\n\n* **Others**: semantic-release\n\n\n*To be continuous* is thoroughly documented:\n\n\n* [Basic notions and\nphilosophy](https://to-be-continuous.gitlab.io/doc/understand/)\n\n* [General usage principles](https://to-be-continuous.gitlab.io/doc/usage/)\n\n* How to use *to be continuous* in a [self-managed instance of\nGitLab](https://to-be-continuous.gitlab.io/doc/self-managed/basic/)\n\n* Every template also has [its own\ndocumentation](https://to-be-continuous.gitlab.io/doc/ref/angular/)\n\n\nTo get started quickly, *to be continuous* provides an [interactive\nconfigurer](https://to-be-continuous.gitlab.io/kicker/) (aka *\"kicker\"*)\nthat allows generating the `.gitlab-ci.yml` file simply by selecting the\ntechnical characteristics of your project.\n\n\nFinally, *to be continuous* exposes several [example\nprojects](https://gitlab.com/to-be-continuous/samples), illustrating how to\nuse the templates in production-like projects, combining multiple templates.\n\n\n### A quick glance at *to be continuous*\n\n\nThere are tons of resources to get started with *to be continuous*. But here\nis a quick example to get the taste of it.\n\n\nHere is the `.gitlab-ci.yml` file for a project:\n\n\n* Developed in Java 11 (built with Maven)\n\n* Code analysis with SonarQube\n\n* Packaged as a Docker image\n\n* Deployed to Kubernetes cluster\n\n* GUI tests with Cypress\n\n* API tests with Postman (Newman)\n\n\n```yaml\n\ninclude:\n  # Maven template\n  - project: \"to-be-continuous/maven\"\n    ref: \"1.4.2\"\n    file: \"templates/gitlab-ci-maven.yml\"\n  # Docker template\n  - project: \"to-be-continuous/docker\"\n    ref: \"1.2.0\"\n    file: \"templates/gitlab-ci-docker.yml\"\n  # Kubernetes template\n  - project: \"to-be-continuous/kubernetes\"\n    ref: \"1.2.0\"\n    file: \"templates/gitlab-ci-k8s.yml\"\n  # Cypress template\n  - project: \"to-be-continuous/cypress\"\n    ref: \"1.2.0\"\n    file: \"templates/gitlab-ci-cypress.yml\"\n  # Postman template\n  - project: \"to-be-continuous/postman\"\n    ref: \"1.2.0\"\n    file: \"templates/gitlab-ci-postman.yml\"\n\n# Global variables\n\nvariables:\n  # Explicitly define the Maven + JDK version\n  MAVEN_IMAGE: \"maven:3.8-openjdk-11\"\n\n  # Enables SonarQube analysis (on sonarcloud.io)\n  SONAR_URL: \"https://sonarcloud.io\"\n  # organization & projectKey defined in pom.xml\n  # SONAR_AUTH_TOKEN defined as a secret CI/CD variable\n\n  # Kubernetes\n  K8S_KUBECTL_IMAGE: \"bitnami/kubectl:1.17\" # client version matching my cluster\n  K8S_URL: \"https://k8s-api.my.domain\" # Kubernetes Cluster API url\n  # K8S_CA_CERT & K8S_TOKEN defined as secret CI/CD variables\n  # enable review, staging & prod\n  K8S_REVIEW_SPACE: \"non-prod\"\n  K8S_STAGING_SPACE: \"non-prod\"\n  K8S_PROD_SPACE: \"prod\"\n\n  # Cypress & Postman: enable test on review aps\n  REVIEW_ENABLED: \"true\"\n\n# Pipeline steps\n\nstages:\n  - build\n  - test\n  - package-build\n  - package-test\n  - review\n  - staging\n  - deploy\n  - acceptance\n  - publish\n  - production\n  ```\n\nThis fully declarative file produces the following **development pipeline**\n(any feature branch):\n\n\n![Screenshot of development\npipeline](https://about.gitlab.com/images/blogimages/orange_development_pipeline.jpg){:\n.shadow}\n\n\n... and the following **production pipeline** (`master` or `main` depending\non your preferences):\n\n\n![Screenshot of production\npipeline](https://about.gitlab.com/images/blogimages/orange_production_pipeline.jpg){:\n.shadow}\n\n\nAlthough they look pretty much the same, they aren't:\n\n\n* While the production pipeline privileges sureness and completeness,\ndevelopment pipelines privilege short cycles and developer experience. While\ncode analysis jobs and acceptance tests are blocked in production, they only\ngenerate a non-blocking warning in development in case of failure.\n\n* The production pipeline deploys to the staging environment before\ndeploying to production (provided acceptance tests are green). Development\npipelines may deploy to a dynamically generated review environment\n(optional).\n\n* Developers may prefer to use a single integration environment (associated\nwith the develop branch) instead of one review app per feature branch. The\ndefault behavior of the integration pipeline is much closer to the\nproduction one.\n\n\nWhat you can't see:\n\n\n* Java unit tests are automatically executed, their report is [integrated to\nGitLab](https://docs.gitlab.com/ee/ci/unit_test_reports.html), with [code\ncoverage](https://docs.gitlab.com/ee/ci/yaml/#coverage) too.\n\n* SonarQube integration automatically uses [branch\nanalysis](https://docs.sonarqube.org/latest/branches/overview/) or [MR\nanalysis](https://docs.sonarqube.org/latest/analysis/pull-request/) (with MR\ndecoration) depending on the context.\n\n* Kubernetes environments are obviously [integrated to\nGitLab](https://docs.gitlab.com/ee/ci/environments/) too.\n\n* [Review apps](https://docs.gitlab.com/ee/ci/review_apps/index.html) can be\ncleaned-up manually or automatically on branch deletion.\n\n* Cypress and Postman tests reports are also [integrated to\nGitLab](https://docs.gitlab.com/ee/ci/unit_test_reports.html).\n\n* Docker uses the Kaniko build by default but it might be configured to use\nDocker-in-Docker instead. It uses the GitLab registry by default but can be\nconfigured to use any other registry.\n\n* Each template integrates the most appropriate DevSecOps tools:\n[kube-score](https://kube-score.com/) for Kubernetes,\n[hadolint](https://github.com/hadolint/hadolint) for Docker, [OWASP\nDependency-Check](https://jeremylong.github.io/DependencyCheck/) for Maven,\namong others.\n\n* All those templates combine themselves gracefully. For example, Kubernetes\nmay simply deploy the Docker image built upstream; Cypress and Postman tests\nautomatically test the application deployed in the upstream jobs; Kubernetes\ncould be replaced with OpenShift, GCP or any other supported hosting\ntechnology, it would behave the same.\n\n\n## Contribute to *to be continuous*\n\n\n[to be continuous](https://to-be-continuous.gitlab.io/doc) is out and\neagerly waiting for users and contributors.\n\n\nHave a look and share your feedback. Whether you like our choices or not, we\nwant to hear from you. Your inputs are even more valuable to help us improve\n*to be continuous* and cover as many use cases as possible.\n\n\nBut anyway, never forget this:\n[`include`](https://docs.gitlab.com/ee/ci/yaml/#include) is undoubtedly the\nfeature that makes CI/CD standardization possible in your company (and\nbeyond).\n\n\nCover image by [Graphic Node](https://unsplash.com/@graphicnode) on\n[Unsplash](https://unsplash.com/photos/yi1YB_FubH8)\n\n{: .note}\n",[741,916,9],{"slug":1856,"featured":6,"template":699},"how-orange-uses-gitlab-ci-cd-for-modern-devops","content:en-us:blog:how-orange-uses-gitlab-ci-cd-for-modern-devops.yml","How Orange Uses Gitlab Ci Cd For Modern Devops","en-us/blog/how-orange-uses-gitlab-ci-cd-for-modern-devops.yml","en-us/blog/how-orange-uses-gitlab-ci-cd-for-modern-devops",{"_path":1862,"_dir":246,"_draft":6,"_partial":6,"_locale":7,"seo":1863,"content":1869,"config":1874,"_id":1876,"_type":14,"title":1877,"_source":16,"_file":1878,"_stem":1879,"_extension":19},"/en-us/blog/how-to-become-more-productive-with-gitlab-ci",{"title":1864,"description":1865,"ogTitle":1864,"ogDescription":1865,"noIndex":6,"ogImage":1866,"ogUrl":1867,"ogSiteName":686,"ogType":687,"canonicalUrls":1867,"schema":1868},"How to become more productive with Gitlab CI","Explore some CI/CD strategies that can make your team more efficient and productive.","https://res.cloudinary.com/about-gitlab-com/image/upload/v1749667358/Blog/Hero%20Images/gitlab-productivity.jpg","https://about.gitlab.com/blog/how-to-become-more-productive-with-gitlab-ci","\n                        {\n        \"@context\": \"https://schema.org\",\n        \"@type\": \"Article\",\n        \"headline\": \"How to become more productive with Gitlab CI\",\n        \"author\": [{\"@type\":\"Person\",\"name\":\"Veethika Mishra\"}],\n        \"datePublished\": \"2021-06-21\",\n      }",{"title":1864,"description":1865,"authors":1870,"heroImage":1866,"date":1871,"body":1872,"category":717,"tags":1873},[1170],"2021-06-21","CI/CD pipelines are the preeminent solution to mitigate potential risks\nwhile integrating code changes into the repository. CI/CD pipelines help\nisolate the impact of potential errors, making it easier to fix them. Top\nthat with a tool that provides effective visibility into the running tasks\nand there you have a recipe for success.\n\n\nSince the primary purpose of CI/CD pipelines is to speed up the development\nprocess and provide value to the end user faster, there's always room to\nmake the process more efficient. This blog post unpacks some strategies that\ncan help you get the most out of your pipeline definition in [GitLab\nCI](/solutions/continuous-integration/).\n\n\n## How Directed Acyclic Graphs (DAG) enable concurrent pipelines\n\n\n![By using Needs keyword you can define dependencies for jobs that need to\nbe used from previous\nstages.](https://about.gitlab.com/images/blogimages/dag-explained.jpeg)\n\nBy using the \"Needs\" keyword you can define dependencies for jobs that need\nto be used from previous stages.\n\n{: .note.text-center}\n\n\nIn a\n[basic-pipeline](https://docs.gitlab.com/ee/ci/pipelines/pipeline_architectures.html#basic-pipelines)\nstructure, all the jobs in a particular stage run concurrently and the jobs\nin the subsequent stage have to wait on those to finish to get started. This\ncontinues for all the stages.\n\n\nIn the image above, the first job in the second stage only depends on the\nfirst two job in the first stage to get started. But with the basic pipeline\norder in place, it has to wait for all three jobs in the first stage to\ncomplete before it can start executing, which slows down the overall\npipeline considerably. However, by using `needs:` keywords, you can define a\ndirect dependency for the jobs and they would only have to wait on the job\nthey depend on to get started. By using the [DAG\nstrategy](https://docs.gitlab.com/ee/ci/directed_acyclic_graph/), you could\nshed out a few minutes from the processes for a certain project, thereby\nincreasing the pipeline execution speed and bringing down the CI minutes\nconsumption.\n\n\nBy using `needs: []` you can make the job in any stage run immediately, as\nit doesn't have to wait on any other job to finish.\n\n\n## Why parallel jobs increase productivity\n\n\nNot all the jobs in a pipeline have an equal run-time. While some may take\njust a few seconds, some take much longer to finish. When there are many\nteam members waiting on a running pipeline to finish to be able to make a\ncontribution to the project, the productivity of the team takes a hit.\n\n\nGitLab provides a method to make clones of a job and run them in parallel\nfor faster execution using the `parallel:` keyword. While [parallel\njobs](https://docs.gitlab.com/ee/ci/yaml/#parallel) may not help in reducing\nthe consumption of [CI minutes](/pricing/faq-compute-minutes/), they\ndefinitely help increase work productivity.\n\n\n## Break down big pipelines with parallel matrix Jobs\n\n\nBefore the release of [parallel matrix\njobs](https://docs.gitlab.com/ee/ci/yaml/#parallel-matrix-jobs), in order to\nrun multiple instances of a job with different variable values, the jobs had\nto be manually defined in the `.gitlab-ci-yml` like this:\n\n\n```yaml\n\n.run-test:\n  script: run-test $PLATFORM\n  stage: test\n\ntest-win:\n  extends: .run-test\n  variables:\n    - PLATFORM: windows\ntest-mac:\n  extends: .run-test\n  variables:\n    - PLATFORM: mac\ntest-linux:\n  extends: .run-test\n  variables:\n    - PLATFORM: linux\n```\n\n\nParallel matrix jobs were released with GitLab 13.3 and allow you to create\njobs at runtime based on specified variables. Let's say there is a need to\nrun multiple instances a job with different variables values for each\ninstance — with a combination of `parallel:` and `matrix:` you accomplish\njust that.\n\n\n```yaml\n\ntest:\n  stage: test\n  script: run-test $PLATFORM\n  parallel:\n    matrix:\n      - PLATFORM: [windows, mac, linux]\n```\n\n\nBy using `parallel:` and `matrix:`, big pipelines can be broken down into\nmanageable parts for efficient maintainance.\n\n\n## Reduce the risk of merge conflicts with parent/child pipelines\n\n\n![Parent-child pipelines can include external YAML files in you\nconfiguration](https://about.gitlab.com/images/blogimages/parent-child-explained.jpeg)\n\nThe parent pipeline generates a child pipeline via the trigger:include\nkeywords.\n\n{: .note.text-center}\n\n\nFor better management of dependencies, many organizations prefer a mono-repo\nsetup for their projects. But mono-repos have a flip side too. If a\nrepository hosts a large number of projects and a single pipeline definition\nis used to trigger different automated processes for different components,\nthe pipeline performance is negatively affected. By using [parent-child\npipelines](https://docs.gitlab.com/ee/ci/pipelines/downstream_pipelines.html)\nyou can design more efficient pipelines, since you can have multiple\nchild-pipelines that run in parallel. The keyword `include:` is used to\ninclude external YAML files in your CI/CD configuration for this purpose. In\nthe image above a pipeline (the parent) generates a child pipeline via the\ntrigger:include keywords.\n\n\nThis approach also reduces the chances of merge conflicts from happening, as\nit allows to only edit a section of the pipeline if necessary.\n\n\n## Merge trains help the target branch stay stable\n\n\nWhen there's a lot of merge requests flowing into a project, there is a risk\nof merge conflicts. [Merge\ntrains](https://docs.gitlab.com/ee/ci/pipelines/merge_trains.html) is a\npowerful feature by GitLab that allows users to automatically merge a series\nof (queued) merge requests without breaking the target branch. Using this\nfeature, you can add an MR to the train, and it would take care of it until\nit is merged.\n\n\n## Use multiple caches in the same job\n\n\nStarting 13.11, GitLab CI/CD provides the ability to [configure multiple\ncache keys in a single\njob](/releases/2021/04/22/gitlab-13-11-released/#use-multiple-caches-in-the-same-job)\nwhich will help you increase your pipeline performance. This functionality\ncould help you save precious development time when the jobs are running.\n\n\n## How can an efficient pipeline save you money?\n\n\nBy using CI/CD strategies that ensure safe merging of new changes and a\ngreen master, organizations can worry less about unanticipated downtimes\ncaused by infrastructural failures and code conflicts.\n\n\nWith faster pipelines, developers end up spending lesser time in maintenance\nand find time and space to bring in more thoughtfulness and creativity in\ntheir work, leading to improvements in code quality and the company\natmosphere and morale.\n\n\nIf you are looking to bring down the cost of running your CI/CD pipelines\nfor a large project, look up the [Artifact and cache\nsettings](https://docs.gitlab.com/ee/ci/runners/configure_runners.html#artifact-and-cache-settings)\nand [Optimizing GitLab for large\nrepositories](https://docs.gitlab.com/ee/ci/large_repositories/) sections in\nthe documentation.\n",[9,696,983,915],{"slug":1875,"featured":6,"template":699},"how-to-become-more-productive-with-gitlab-ci","content:en-us:blog:how-to-become-more-productive-with-gitlab-ci.yml","How To Become More Productive With Gitlab Ci","en-us/blog/how-to-become-more-productive-with-gitlab-ci.yml","en-us/blog/how-to-become-more-productive-with-gitlab-ci",{"_path":1881,"_dir":246,"_draft":6,"_partial":6,"_locale":7,"seo":1882,"content":1888,"config":1894,"_id":1896,"_type":14,"title":1897,"_source":16,"_file":1898,"_stem":1899,"_extension":19},"/en-us/blog/how-to-bring-devops-to-the-database-with-gitlab-and-liquibase",{"title":1883,"description":1884,"ogTitle":1883,"ogDescription":1884,"noIndex":6,"ogImage":1885,"ogUrl":1886,"ogSiteName":686,"ogType":687,"canonicalUrls":1886,"schema":1887},"How to bring DevOps to the database with GitLab and Liquibase","Learn how to build a continuous delivery pipeline for database code changes with this tutorial.","https://res.cloudinary.com/about-gitlab-com/image/upload/v1749672677/Blog/Hero%20Images/metalgears_databasecasestudy.jpg","https://about.gitlab.com/blog/how-to-bring-devops-to-the-database-with-gitlab-and-liquibase","\n                        {\n        \"@context\": \"https://schema.org\",\n        \"@type\": \"Article\",\n        \"headline\": \"How to bring DevOps to the database with GitLab and Liquibase\",\n        \"author\": [{\"@type\":\"Person\",\"name\":\"Tsvi Zandany\"}],\n        \"datePublished\": \"2022-01-05\",\n      }",{"title":1883,"description":1884,"authors":1889,"heroImage":1885,"date":1891,"body":1892,"category":717,"tags":1893},[1890],"Tsvi Zandany","2022-01-05","In the [Accelerate State of DevOps 2021\nReport](https://cloud.google.com/devops/state-of-devops/), the DevOps\nResearch and Assessment (DORA) team reveals “elite DevOps performers are 3.4\ntimes more likely to exercise database change management compared to their\nlow-performing counterparts.” Tracking changes with version control is not\njust for application code, though. It’s crucial for managing changes for one\nof your most important assets: your database.   \n\n\nThe GitLab DevOps platform enables database management teams to leverage\nCI/CD to track, manage, and deploy database changes, along with application\ndevelopment and automation and infrastructure as code. Database change\nmanagement tools have become more advanced in recent years, supporting\neasier collaboration and communication, which are the keys to successful\nDevOps. In this blog post, I’ll take you through a tutorial using\n[Liquibase](https://www.liquibase.com), a tool that integrates seamlessly\ninto the GitLab DevOps platform so your teams can deliver database code\nchanges as fast as application code changes (without compromising on quality\nand security). \n\n\n## What is Liquibase?\n\n\nLiquibase was founded as an open source project over 15 years ago to address\ngetting database changes into version control. With more than 75 million\ndownloads, the company behind Liquibase expanded to paid editions and\nsupport to help teams release software faster and safer by bringing the\ndatabase change process into their existing CI/CD automation.  \n\n\nIntegrating Liquibase with GitLab CI/CD enables database teams to leverage\nDevOps automation and best practices for database management. Liquibase\nhelps teams build automated database scripts and gain insights into when,\nwhere, and how database changes are deployed. In this tutorial, we’ll\ndemonstrate how to check database scripts for security and compliance\nissues, speed up database code reviews, perform easy rollbacks, and provide\ndatabase snapshots to check for malware.\n\n\n## Adding Liquibase to GitLab’s DevOps Platform\n\n\nTeams can add Liquibase to GitLab to enable true CI/CD for the database.\nIt’s easy to integrate Liquibase into your GitLab CI/CD pipeline. Before\njumping into the tutorial, let’s take a look at the [example Liquibase\nGitLab project\nrepository](https://gitlab.com/gitlab-com/alliances/liquibase/sandbox-projects/sql_server)\nyou’ll be using.\n\n\n### Understanding the example Liquibase GitLab project repository\n\n\n![A CI/CD pipeline\ndiagram](https://about.gitlab.com/images/blogimages/1_CICD_Pipeline_Diagram.png){:\n.shadow.small.center}\n\n\nFor this example, the GitLab CI/CD pipeline environments include DEV, QA,\nand PROD. This pipeline goes through several stages: build, test, deploy,\nand compare. A post stage comes into play later to capture a snapshot of\nyour database in Production.\n\n\nStages:\n  - build\n  - test\n  - deploy\n  - compare\n\n### Liquibase commands in the pipeline\n\n\nFor each of the predefined jobs in the GitLab repository, you’ll be using\nseveral Liquibase commands to help manage database changes quickly and\nsafely:\n\n\n- liquibase_job:\n\n  before_script:\n    - functions\n    - isUpToDate\n    - liquibase checks run\n    - liquibase updateSQL\n    - liquibase update\n    - liquibase rollbackOneUpdate --force\n    - liquibase tag $CI_PIPELINE_ID\n    - liquibase --logFile=${CI_JOB_NAME}_${CI_PIPELINE_ID}.log --logLevel=info update\n    - liquibase history\n\n  script:\n    - echo \"Comparing databases DEV --> QA\"\n    - liquibase diff\n    - liquibase --outputFile=diff_between_DEV_QA.json diff --format=json\n\n  script:\n    - echo \"Snapshotting database PROD\"\n    - liquibase --outputFile=snapshot_PROD.json snapshot --snapshotFormat=json\n\nLearn more about each of these commands in the [README file in the GitLab\nrepository](https://gitlab.com/gitlab-com/alliances/liquibase/sandbox-projects/sql_server/-/blob/main/README.md). \n\n\n## Tutorial\n\n\nThe following tutorial demonstrates how to run Liquibase in a GitLab CI/CD\npipeline. Follow along by watching this companion video:\n\n\n\u003C!-- blank line -->\n\n\u003Cfigure class=\"video_container\">\n  \u003Ciframe src=\"https://www.youtube.com/embed/ZBFhDayoRYo\" frameborder=\"0\" allowfullscreen=\"true\"> \u003C/iframe>\n\u003C/figure>\n\n\u003C!-- blank line -->\n\n\n### Prerequisites\n\n\nTo start, I’m using a Linux machine with the following:\n\n\n- [A GitLab account](https://www.gitlab.com)\n\n- Self-managed Runner on a Linux machine\n\n- Git\n\n- Java 11\n\n- Access to a SQL Server database with multiple environments\n\n\n### Download, install, and configure Liquibase\n\n\n[Download Liquibase v4.6.1+](https://www.liquibase.org/download)\n\n\n[Install\nLiquibase](https://docs.liquibase.com/concepts/installation/installation-linux-unix-mac.html)\n\n\n[Get a free Liquibase Pro license key](https://www.liquibase.com/trial). No\ncredit card is required, so you can play with all the advanced features and\nget support for 30 days. You’ll use this key later when you configure\nenvironment variables within GitLab.\n\n\nEnsure Liquibase is installed properly by running the liquibase --version\ncommand. If everything is good you’ll see the following:\n\n\nStarting Liquibase at 18:10:06 (version 4.6.1 #98 built at 2021-11-04\n20:16+0000)\n\nRunning Java under /usr/lib/jvm/java-11-openjdk-11.0.13.0.8-1.el7_9.x86_64\n(Version 11.0.13)\n\n\nLiquibase Version: 4.6.1\n\nLiquibase Community 4.6.1 by Liquibase\n\n\n### Prepare your GitLab project\n\n\nFork this [example GitLab project\nrepository](https://gitlab.com/gitlab-com/alliances/liquibase/sandbox-projects/sql_server).\n([See more information about forking a\nrepository](https://docs.gitlab.com/ee/user/project/repository/forking_workflow.html).)\n\n\n[Create a self-managed GitLab Runner](https://docs.gitlab.com/runner/) on\nyour Linux instance with your newly forked GitLab project.\n\n\nClone your newly forked project repository:\n\ngit clone https://gitlab.com/\u003Cusername>/sql_server.git\n\n\nGo to the “sql_server” project folder.\n\ncd sql_server\n\n\nRun the following command to change your git branch to staging:\n\ngit checkout staging\n\n\nConfigure the GitLab CI/CD pipeline environment variables.\n\n\nYour configuration will include [CI/CD\nvariables](https://docs.gitlab.com/ee/ci/variables/#add-a-cicd-variable-to-a-project),\n[Liquibase\nproperties](https://www.liquibase.com/blog/secure-database-developer-flow-using-gitlab-pipelines),\ndatabase credentials, and the Liquibase Pro trial license key so you can use\nall the advanced Liquibase commands.\n\n\nFrom the main sql_server project, go to Settings → CI/CD\n\n\nUnder Variables, click Expand and add the following variables:\n\n\n![A CI/CD pipeline\ndiagram](https://about.gitlab.com/images/blogimages/liquibasevariables.png){:\n.shadow.small.center}\n\n\n![A CI/CD pipeline\ndiagram](https://about.gitlab.com/images/blogimages/liquibasevariables2.png){:\n.shadow.small.center}\n\n\n### Configure the self-managed GitLab runner\n\n\nFrom the main sql_server project, go to Settings → CI/CD\n\n\nExpand the runners section, click the pencil edit icon, and add the\nfollowing runner tags (comma separated):\n\n\ndev_db,prod_db,test_db\n\n\nNote: Tags are created to help choose which runner will do the job. In this\nexample, we are associating all tags to one runner. Learn more about\n[configuring\nrunners](https://docs.gitlab.com/ee/ci/runners/configure_runners.html). \n\n\n### Make changes to the database\n\n\nEdit the changelog.sql file and add the following changeset after \n\n\n```\n\nliquibase formatted sql:\n\n-- changeset SteveZ:createTable_salesTableZ\n\nCREATE TABLE salesTableZ (\n   ID int NOT NULL,\n   NAME varchar(20) COLLATE SQL_Latin1_General_CP1_CI_AS NULL,\n   REGION varchar(20) COLLATE SQL_Latin1_General_CP1_CI_AS NULL,\n   MARKET varchar(20) COLLATE SQL_Latin1_General_CP1_CI_AS NULL\n)\n\n--rollback DROP TABLE salesTableZ\n\nAdd, commit, and push all new database changes.\n\ngit add changelog.sql\n\ngit commit -m “added changelog id and a create table salesTableZ changeset”\n\ngit push -u origin staging\n\n```\n\n\n### Merge the changes and run the pipeline\n\n\nLet’s merge the changes from branch staging → main to trigger the pipeline\nto run all jobs.\n\n\nClick Merge requests → New merge request\n\n\nSelect staging as Source branch and main as Target branch\n\n\nClick Compare branches and continue\n\n\nOn the next screen, click Create merge request\n\n\nClick Merge to finish merging the changes\n\n\n![A look at the merge\nrequest](https://about.gitlab.com/images/blogimages/2_Merge_Request1.png){:\n.shadow.small.center}\n\n\n![Another look at the merge\nrequestt](https://about.gitlab.com/images/blogimages/3_Merge_Request2.png){:\n.shadow.small.center}\n\n\nOnce these steps are completed, the code is merged into main and the\npipeline is triggered to run.\n\n\n![The pipeline is\ntriggered](https://about.gitlab.com/images/blogimages/4_Merge_Request3.png){:\n.shadow.small.center}\n\n\nTo see the pipeline running, click Pipelines.\n\n\nTo view the pipeline progress, click the pipeline ID link. You can view each\njob’s log output by clicking on each job name.\n\n\n![The pipeline in\nprogress](https://about.gitlab.com/images/blogimages/5_Pipeline_Progress.png){:\n.shadow.small.center}\n\n\nClicking into the build-job example:\n\n\nThe liquibase checks run command validates the SQL for any violations.\n\n\n```\n\n57Starting Liquibase at 22:19:14 (version 4.6.1 #98 built at 2021-11-04\n20:16+0000)\n\n58Liquibase Version: 4.6.1\n\n59Liquibase Pro 4.6.1 by Liquibase licensed to customersuccess until Mon Jun\n27 04:59:59 UTC 2022\n\n60Executing Quality Checks against changelog.sql\n\n61Executing all checks because a valid Liquibase Pro license was found!\n\n62Changesets Validated:\n\n63  ID: createTable_salesTableZ; Author: SteveZ; File path: changelog.sql\n\n64Checks run against each changeset:\n\n65  Warn on Detection of 'GRANT' Statements\n\n66  Warn on Detection of 'REVOKE' Statements\n\n67  Warn when 'DROP TABLE' detected\n\n68  Warn when 'DROP COLUMN' detected\n\n69  Check for specific patterns in sql (Short Name: SqlCreateRoleCheck)\n\n70  Warn when 'TRUNCATE TABLE' detected\n\n71  Warn on Detection of grant that contains 'WITH ADMIN OPTION'\n\n72Liquibase command 'checks run' was executed successfully.\n\n```\n\n\nThe liquibase update command deploys the changes. If you choose, you can\nview a full report of your changes in [Liquibase\nHub](https://docs.liquibase.com/tools-integrations/liquibase-hub/setup.html).\nThe update command also saves the deployment log output file as an artifact.\n\n\n```\n\n227Starting Liquibase at 22:19:34 (version 4.6.1 #98 built at 2021-11-04\n20:16+0000)\n\n228Liquibase Version: 4.6.1\n\n229Liquibase Pro 4.6.1 by Liquibase licensed to customersuccess until Mon\nJun 27 04:59:59 UTC 2022\n\n230----------------------------------------------------------------------\n\n231View a report of this operation at https://hub.liquibase.com/r/I7ens13ooM\n\n232* IMPORTANT: New users of Hub first need to Sign In to your account\n\n233with the one-time password sent to your email, which also serves as\n\n234your username.\n\n235----------------------------------------------------------------------\n\n236Logs saved to\n/home/gitlab-runner/builds/3-UvD4aX/0/szandany/sql_server/build-job_405710044.log\n\n237Liquibase command 'update' was executed successfully.\n\n```\n\n\nHere’s what your Liquibase Hub report will look like:\n\n\n![The hub report, part\none](https://about.gitlab.com/images/blogimages/6_LiquibaseHub_Report.png){:\n.shadow.small.center}\n\n\n![The hub report, part\ntwot](https://about.gitlab.com/images/blogimages/7_LiquibaseHub_Report.png){:\n.shadow.small.center}\n\n\nThe Liquibase history command will show what changes are currently in the\ndatabase.\n\n\n```\n\n255Starting Liquibase at 22:19:40 (version 4.6.1 #98 built at 2021-11-04\n20:16+0000)\n\n256Liquibase Version: 4.6.1\n\n257Liquibase Pro 4.6.1 by Liquibase licensed to customersuccess until Mon\nJun 27 04:59:59 UTC 2022\n\n258Liquibase History for\njdbc:sqlserver://localhost:1433;sendTemporalDataTypesAsStringForBulkCopy=true;delayLoadingLobs=true;useFmtOnly=false;useBulkCopyForBatchInsert=false;cancelQueryTimeout=-1;sslProtocol=TLS;jaasConfigurationName=SQLJDBCDriver;statementPoolingCacheSize=0;serverPreparedStatementDiscardThreshold=10;enablePrepareOnFirstPreparedStatementCall=false;fips=false;socketTimeout=0;authentication=NotSpecified;authenticationScheme=nativeAuthentication;xopenStates=false;sendTimeAsDatetime=true;trustStoreType=JKS;trustServerCertificate=false;TransparentNetworkIPResolution=true;serverNameAsACE=false;sendStringParametersAsUnicode=true;selectMethod=direct;responseBuffering=adaptive;queryTimeout=-1;packetSize=8000;multiSubnetFailover=false;loginTimeout=15;lockTimeout=-1;lastUpdateCount=true;encrypt=false;disableStatementPooling=true;databaseName=DEV;columnEncryptionSetting=Disabled;applicationName=Microsoft\nJDBC Driver for SQL Server;applicationIntent=readwrite;\n\n259- Database updated at 11/9/21, 10:19 PM. Applied 1 changeset(s),\nDeploymentId: 6496372605\n\n260  liquibase-internal::1636496372758::liquibase\n\n261- Database updated at 11/9/21, 10:19 PM. Applied 1 changeset(s),\nDeploymentId: 6496375151\n\n262  changelog.sql::createTable_salesTableZ::SteveZ\n\n263Liquibase command 'history' was executed successfully.\n\n```\n\n\n### Clicking into the DEV->QA job example from your pipeline\n\n\nWe run the liquibase diff command to compare the DEV and QA databases. This\nhelps detect any drift between the databases.\n\n\nNotice in the log output that there are some unexpected changes: \n\n\ntable named bad_table\n\n\nprocedure named bad_proc\n\n\n![The diff\nreport](https://about.gitlab.com/images/blogimages/8_LiquibaseDiff_Report.png){:\n.shadow.small.center}\n\n\nBy using the [Liquibase Pro trial license\nkey](https://www.liquibase.com/trial), you’re able to detect any stored\nlogic objects included in the diff report. Liquibase Pro also allows you to\ngenerate a parsable JSON output file and save it as an artifact for later\nuse.\n\n\n```\n\n137Starting Liquibase at 22:21:10 (version 4.6.1 #98 built at 2021-11-04\n20:16+0000)\n\n138Liquibase Version: 4.6.1\n\n139Liquibase Pro 4.6.1 by Liquibase licensed to customersuccess until Mon\nJun 27 04:59:59 UTC 2022\n\n140Output saved to\n/home/gitlab-runner/builds/3-UvD4aX/0/szandany/sql_server/diff_between_DEV_QA.json\n\n141Liquibase command 'diff' was executed successfully.\n\n```\n\n\nJSON artifact output file example:\n\n\n```\n\n{\n    \"diff\": {\n        \"diffFormat\": 1,\n        \"created\": \"Wed Dec 08 20:16:53 UTC 2021\",\n        \"databases\": {\n            \"reference\": {\n                \"majorVersion\": \"14\",\n                \"minorVersion\": \"00\",\n                \"name\": \"Microsoft SQL Server\",\n                \"url\": \"jdbc:sqlserver://localhost:1433;databaseName=DEV; ...\"\n            },\n            \"target\": {\n                \"majorVersion\": \"14\",\n                \"minorVersion\": \"00\",\n                \"name\": \"Microsoft SQL Server\",\n                \"url\": \"jdbc:sqlserver://localhost:1433;databaseName=QA; ...\"\n            }\n        },\n        \"unexpectedObjects\": [\n            {\n                \"unexpectedObject\": {\n                    \"name\": \"bad_proc\",\n                    \"type\": \"storedProcedure\",\n                    \"schemaName\": \"dbo\",\n                    \"catalogName\": \"QA\"\n                }\n            },\n            {\n                \"unexpectedObject\": {\n                    \"name\": \"bad_table\",\n                    \"type\": \"table\",\n                    \"schemaName\": \"dbo\",\n                    \"catalogName\": \"QA\"\n                }\n            },\n            {\n                \"unexpectedObject\": {\n                    \"name\": \"MARKET\",\n                    \"type\": \"column\",\n                    \"relationName\": \"bad_table\",\n                    \"schemaName\": \"dbo\",\n                    \"catalogName\": \"QA\"\n                }\n            },\n            {\n                \"unexpectedObject\": {\n                    \"name\": \"ID\",\n                    \"type\": \"column\",\n                    \"relationName\": \"bad_table\",\n                    \"schemaName\": \"dbo\",\n                    \"catalogName\": \"QA\"\n                }\n            },\n            {\n                \"unexpectedObject\": {\n                    \"name\": \"NAME\",\n                    \"type\": \"column\",\n                    \"relationName\": \"bad_table\",\n                    \"schemaName\": \"dbo\",\n                    \"catalogName\": \"QA\"\n                }\n            },\n            {\n                \"unexpectedObject\": {\n                    \"name\": \"REGION\",\n                    \"type\": \"column\",\n                    \"relationName\": \"bad_table\",\n                    \"schemaName\": \"dbo\",\n                    \"catalogName\": \"QA\"\n                }\n            }\n        ],\n        \"changedObjects\": [\n            {\n                \"changedObject\": {\n                    \"name\": \"QA\",\n                    \"type\": \"catalog\",\n                    \"differences\": [\n                        {\n                            \"difference\": {\n                                \"comparedValue\": \"QA\",\n                                \"field\": \"name\",\n                                \"message\": \"name changed from 'DEV' to 'QA'\",\n                                \"referenceValue\": \"DEV\"\n                            }\n                        }\n                    ]\n                }\n            }\n        ]\n    }\n}\n\n\n```\n\n\nNote that the [Liquibase\ndiffChangelog](https://docs.liquibase.com/commands/diffchangelog.html) can\nhelp any baseline environments that have drifted. \n\n\nClicking into the snapshot PROD job example, the snapshot file contains all\nthe current schema changes represented in a JSON file. You can obtain the\nPROD database snapshot file to compare two states of the same database to\nprotect against malware with drift detection.\n\n\n```\n\n58Starting Liquibase at 22:21:32 (version 4.6.1 #98 built at 2021-11-04\n20:16+0000)\n\n59Liquibase Version: 4.6.1\n\n60Liquibase Pro 4.6.1 by Liquibase licensed to customersuccess until Mon Jun\n27 04:59:59 UTC 2022\n\n61Output saved to\n/home/gitlab-runner/builds/3-UvD4aX/0/szandany/sql_server/snapshot_PROD.json\n\n62Liquibase command 'snapshot' was executed successfully. \n\n64Uploading artifacts for successful job00:01\n\n70Cleaning up project directory and file based variables00:00\n\n72Job succeeded\n\n```\n\n\n### Congratulations! The pipeline ran successfully.\n\n\nIf all the jobs are successful, you’ll see a green checkmark right next to\neach one.\n\n\nHere’s what your database changes will look like with a database SQL query\ntool.\n\n\n![The\ndatabase](https://about.gitlab.com/images/blogimages/9_Database_Changes_SQL_Query_Tool.png){:\n.shadow.small.center}\n\n\n## Summing it up\n\n\nYou’ve now successfully run Liquibase in a GitLab pipeline to enable true\nCI/CD for the database. You can easily keep adding more changes to the\ndatabase by adding more Liquibase changesets to the changelog, commit them\nto GitLab version control, and repeat the merge request process described in\nthis tutorial to add the changes. \n\n\nStill have questions or want support integrating Liquibase with your Gitlab\nCI/CD Pipeline? Our team of database DevOps experts is happy to help! \n\n\n[Contact Liquibase](https://www.liquibase.com/contact)\n\n\n[Contact GitLab](/sales/)\n\n\nContact a [certified GitLab channel\npartner](https://www.google.com/url?q=https://partners.gitlab.com/English/directory/&sa=D&source=docs&ust=1641393355697069&usg=AOvVaw0R5mPukwMBR2dKsn3eQzqp)\n\n\nContact a [Liquibase channel partner](https://www.liquibase.com/partners)\n\n\nOther useful links: \n\n\n[Gitlab CI/CD setup Liquibase\ndocumentation](https://docs.liquibase.com/concepts/installation/setup-gitlab-cicd.html)\n\n\n[GitLab - Liquibase\nrepository](https://gitlab.com/gitlab-com/alliances/liquibase/sandbox-projects/liquibasegitlabcicd/-/blob/master/README.md) \n\n\nGet a [speedy, secure database developer\nflow](https://www.liquibase.com/blog/secure-database-developer-flow-using-gitlab-pipelines)\nusing GitLab pipelines & Liquibase\n\n\n_Author Tsvi Zandany is a Senior Solutions Architect at Liquibase_\n",[233,9,696],{"slug":1895,"featured":6,"template":699},"how-to-bring-devops-to-the-database-with-gitlab-and-liquibase","content:en-us:blog:how-to-bring-devops-to-the-database-with-gitlab-and-liquibase.yml","How To Bring Devops To The Database With Gitlab And Liquibase","en-us/blog/how-to-bring-devops-to-the-database-with-gitlab-and-liquibase.yml","en-us/blog/how-to-bring-devops-to-the-database-with-gitlab-and-liquibase",{"_path":1901,"_dir":246,"_draft":6,"_partial":6,"_locale":7,"seo":1902,"content":1907,"config":1912,"_id":1914,"_type":14,"title":1915,"_source":16,"_file":1916,"_stem":1917,"_extension":19},"/en-us/blog/how-to-continously-test-web-apps-apis-with-hurl-and-gitlab-ci-cd",{"title":1903,"description":1904,"ogTitle":1903,"ogDescription":1904,"noIndex":6,"ogImage":1826,"ogUrl":1905,"ogSiteName":686,"ogType":687,"canonicalUrls":1905,"schema":1906},"How to continuously test web apps and APIs with Hurl and GitLab CI/CD","Hurl as a CLI tool can be integrated into the DevSecOps platform to continuously verify, test, and monitor targets. It also offers integrated unit test reports in GitLab CI/CD.","https://about.gitlab.com/blog/how-to-continously-test-web-apps-apis-with-hurl-and-gitlab-ci-cd","\n                        {\n        \"@context\": \"https://schema.org\",\n        \"@type\": \"Article\",\n        \"headline\": \"How to continuously test web apps and APIs with Hurl and GitLab CI/CD\",\n        \"author\": [{\"@type\":\"Person\",\"name\":\"Michael Friedrich\"}],\n        \"datePublished\": \"2022-12-14\",\n      }",{"title":1903,"description":1904,"authors":1908,"heroImage":1826,"date":1909,"body":1910,"category":717,"tags":1911},[1252],"2022-12-14","Testing websites, web applications, or generally everything reachable with\nthe HTTP protocol, can be a challenging exercise. Thanks to tools like\n`curl` and `jq`, [DevOps workflows have become more\nproductive](/blog/devops-workflows-json-format-jq-ci-cd-lint/)\nand even simple monitoring tasks can be automated with CI/CD pipeline\nschedules. Sometimes, use cases require specialized tooling with custom HTTP\nheaders, parsing expected responses, and building end-to-end test pipelines.\nStressful incidents also need good and fast tools that help analyze the root\ncause and quickly mitigate and fix problems.\n\n\n[Hurl](https://hurl.dev) is an open-source project developed and maintained\nby Orange, and uses libcurl from curl to provide HTTP test capabilities. It\naims to tackle complex HTTP test challenges by providing a simple plain text\nconfiguration to describe HTTP requests. It can chain requests, capture\nvalues, and evaluate queries on headers and body responses. So far, so good:\nHurl does not only support fetching data, it can be used to test HTTP\nsessions and XML (SOAP) and JSON (REST) APIs.\n\n\n## Getting Started\n\n\nHurl comes in various package formats to\n[install](https://hurl.dev/docs/installation.html). On macOS, a Homebrew\npackage is available.\n\n\n```sh\n\n$ brew install hurl\n\n```\n\n\n## First steps with Hurl\n\n\nHurl proposes to start with the configuration file format first, which is a\ngreat way to learn the syntax step by step. The following example creates a\nnew `gitlab-contribute.hurl` configuration file that will do two things:\nexecute a GET HTTP request on\n`https://about.gitlab.com/community/contribute/` and check whether its HTTP\nresponse contains the HTTP protocol `2` and status code `200` (OK).\n\n\n```sh\n\n$ vim gitlab-contribute.hurl\n\n\nGET https://about.gitlab.com/community/contribute/\n\n\nHTTP/2 200\n\n$ hurl --test gitlab-contribute.hurl\n\ngitlab-contribute.hurl: Running [1/1]\n\ngitlab-contribute.hurl: Success (1 request(s) in 413 ms)\n\n--------------------------------------------------------------------------------\n\nExecuted files:  1\n\nSucceeded files: 1 (100.0%)\n\nFailed files:    0 (0.0%)\n\nDuration:        415 ms\n\n```\n\n\nInstead of creating configuration files, you can also use the `echo “...” |\nhurl` command pattern. The following command tests against about.gitlab.com\nand checks whether the HTTP response protocol is 1.1 and the status is OK\n(200). The two newline characters `\\n` are required for separation.\n\n\n```sh\n\n$ echo \"GET https://about.gitlab.com\\n\\nHTTP/1.1 200\" | hurl --test\n\n```\n\n\n![hurl CLI run against about.gitlab.com, failed\nrequest](https://about.gitlab.com/images/blogimages/hurl-continuous-website-testing/hurl_assert_failure.png)\n\n\nThe command failed, and it says that the response protocol version is\nactually `2`. Let's adjust the test run to expect `HTTP/2`:\n\n\n```sh\n\necho \"GET https://about.gitlab.com\\n\\nHTTP/2 200\" | hurl --test\n\n```\n\n## Asserting HTTP responses\n\n\nHurl allows defining\n[assertions](https://hurl.dev/docs/asserting-response.html) to control when\nthe tests fail. These can be defined for different HTTP response types:\n\n\n- Expected HTTP protocol version and status\n\n- Headers\n\n- Body\n\n\nThe configuration language allows users to define queries with predicates\nthat allow to compare, chain, and execute different assertions.\n\n\nThis is the easiest way to verify that the HTTP response contains what is\nexpected to be a string or sentence on the website, for example. If the\nstring does not exist, this can indicate that it was changed unexpectedly,\nor that the website is down. Let's revisit the example with testing GET\nhttps://about.gitlab.com/community/contribute/ and add an expected string\n`Everyone can contribute` as a new assertion, `body contains \u003Cstring>` is\nthe expected configuration syntax for [body\nasserts](https://hurl.dev/docs/asserting-response.html#body-assert).\n\n\n```sh\n\n$ vim gitlab-contribute.hurl\n\n\nGET https://about.gitlab.com/community/contribute/\n\n\nHTTP/2 200\n\n\n[Asserts]\n\nbody contains \"Everyone should contribute\"\n\n\n$ hurl --test gitlab-contribute.hurl\n\n```\n\n\n**Exercise:** Fix the test by updating the asserts line to `Everyone can\ncontribute` and run Hurl again.\n\n\n### Asserting responses: JSON and XML\n\n\n[JSONPath](https://hurl.dev/docs/asserting-response.html#jsonpath-assert)\nautomatically parses the JSON response (a built-in `jq with curl` parser so\nto speak), and allows users to compare the value to verify the asserts (more\nbelow). The XML format can be found in an [RSS feed on\nabout.gitlab.com](https://about.gitlab.com/atom.xml) and parsed using\n[XPath](https://hurl.dev/docs/asserting-response.html#xpath-assert). The\nfollowing example from `atom.xml` should be verified with Hurl:\n\n\n```xml\n\n\u003Cfeed xmlns=\"http://www.w3.org/2005/Atom\">\n\n\u003Ctitle>GitLab\u003C/title>\n\n\u003Cid>https://about.gitlab.com/blog\u003C/id>\n\n\u003Clink href=\"https://about.gitlab.com/blog/\"/>\n\n\u003Cupdated>2022-11-21T00:00:00+00:00\u003C/updated>\n\n\u003Cauthor>\n\n\u003Cname>The GitLab Team\u003C/name>\n\n\u003C/author>\n\n\u003Centry>\n\n...\n\n\u003C/entry>\n\n\u003Centry>\n\n...\n\n\u003C/entry>\n\n\u003Centry>\n\n…\n\n```\n\n\nIt is important to note that XML namespaces need to be specified for\nparsing. Hurl allows users to replace the first default namespace with the\n`_` character to avoid adding `http://www.w3.org/2005/Atom` everywhere, the\nXPath is now shorter with `string(//_:feed/_:entry)` to get a list of all\nentries. This value is captured in the `entries` variable, which can be\ncompared to match a specific string, `GitLab` in this example. Additionally,\nthe feed id and author name is checked.\n\n\n```\n\n$ vim gitlab-rss.hurl\n\n\nGET https://about.gitlab.com/atom.xml\n\n\nHTTP/2 200\n\n\n[Captures]\n\nentries: xpath \"string(//_:feed/_:entry)\"\n\n\n[Asserts]\n\nvariable \"entries\" matches \"GitLab\"\n\n\nxpath \"string(//_:feed/_:id)\" == \"https://about.gitlab.com/blog\"\n\nxpath \"string(//_:feed/_:author/_:name)\" == \"The GitLab Team\"\n\n\n$ hurl –test gitlab-rss.hurl\n\n```\n\n\nHurl allows users to capture the value from responses into\n[variables](https://hurl.dev/docs/templates.html#variables) that can be used\nlater. This method can also be helpful to model end-to-end testing\nworkflows: First, check the website health status and retrieve a CSRF token,\nand then try to log into the website by sending the token again.\n\n\nREST APIs that are expected to always return a specified field, or\nmonitoring a website health state [becomes a breeze using\nHurl](https://hurl.dev/docs/tutorial/chaining-requests.html#test-rest-api).\n\n\n## Use Hurl in GitLab CI/CD jobs\n\n\nThe easiest way to integrate Hurl into GitLab CI/CD is to use the official\ncontainer image. The Hurl project provides a [container image on Docker\nHub](https://hub.docker.com/r/orangeopensource/hurl), which did not work in\nCI/CD at first glance. After talking with the maintainers, the [entrypoint\noverride](https://docs.gitlab.com/ee/ci/docker/using_docker_images.html#override-the-entrypoint-of-an-image)\nwas identified as a solution for using the image in GitLab CI/CD. Note that\nthe Alpine based image uses the libcurl library that does not support HTTP/2\nyet - the test results are different to a Debian base image (follow [this\nissue report](https://github.com/Orange-OpenSource/hurl/issues/1082) for the\nproblem analysis).\n\n\nThe following example is kept short to run the container image, override the\nentrypoint, and run Hurl with passing in the test using the `echo` CLI\ncommand.\n\n\n```yaml\n\nhurl-standalone:\n  image:\n    name: ghcr.io/orange-opensource/hurl:latest\n    entrypoint: [\"\"]\n  script:\n    - echo -e \"GET https://about.gitlab.com/community/contribute/\\n\\nHTTP/1.1 200\" | hurl --test --color\n```\n\n\nThe Hurl test report is printed into the CI/CD job trace log, and returns\nsuccesfully.\n\n\n```sh\n\n$ echo -e \"GET https://about.gitlab.com/community/contribute/\\n\\nHTTP/1.1\n200\" | hurl --test --color\n\n-: Running [1/1]\n\n-: Success (1 request(s) in 280 ms)\n\n--------------------------------------------------------------------------------\n\nExecuted files:  1\n\nSucceeded files: 1 (100.0%)\n\nFailed files:    0 (0.0%)\n\nDuration:        283 ms\n\nCleaning up project directory and file based variables\n\n00:00\n\nJob succeeded\n\n```\n\n\nThe next iteration is to create a CI/CD job template that provides generic\nattributes, and allows users to dynamically run the job with an environment\nvariable called `HURL_URL`.\n\n\n```yaml\n\n# Hurl job template\n\n.hurl-tmpl:\n  # Use the upstream container image and override the ENTRYPOINT to run CI/CD script\n  # https://docs.gitlab.com/ee/ci/docker/using_docker_images.html#override-the-entrypoint-of-an-image\n  image:\n    name: ghcr.io/orange-opensource/hurl:1.8.0\n    entrypoint: [\"\"]\n  variables:\n    HURL_URL: \"about.gitlab.com/community/contribute/\"\n  script:\n    - echo -e \"GET https://${HURL_URL}\\n\\nHTTP/1.1 200\" | hurl --test --color\n\nhurl-about-gitlab-com:\n  extends: .hurl-tmpl\n  variables:\n    HURL_URL: \"about.gitlab.com/jobs/\"\n```\n\n\nRunning GET commands with expected HTTP results is not the only use case,\nand the Hurl maintainers thought about this already. The next section\nexplains how to create a custom container image; you can skip to the\n[DevSecOps workflows](#devSecOps-workflows-with-hurl) section to learn more\nabout efficient Hurl configuration use cases.\n\n\n### Custom container image with Hurl\n\n\nMaintaining and building a custom container image adds more work, but also\nhelps with avoiding running unknown container images in CI/CD pipelines. The\nlatter is often a requirement for compliance and security. _Since the Hurl\nDebian package supports detecting HTTP/2 as a protocol, this blog post will\nfocus on building a custom image, and run all tests using this image. If you\nplan on using the upstream container image, make sure to review the test\nconfiguration for the HTTP protocol version detection._\n\n\nThe Hurl documentation provides multiple ways to install Hurl. For this\nexample, Debian 11 Bullseye (slim) is used. Hurl comes with a package\ndependency on `libxml2` which can either be installed manually with then\nrunning the `dpkg` command, or by using `apt install` to install a local\npackage and automatically resolve the dependencies.\n\n\nThe following CI/CD example uses a job template which defines the Hurl\nversion as environment variable to avoid repetitive use, and downloads and\ninstalls the Hurl Debian package. The `hurl-gitlab-com` job extends the\nCI/CD job template and runs a one-line test against `https://gitlab.com` and\nexpects to return `HTTP/2` as HTTP protocol version, and `200` as status.\n\n\n```yaml\n\n# CI/CD job template\n\n.hurl-tmpl:\n  variables:\n    HURL_VERSION: 1.8.0\n  before_script:\n    - DEBIAN_FRONTEND=noninteractive apt update && apt -y install jq curl ca-certificates\n    - curl -LO \"https://github.com/Orange-OpenSource/hurl/releases/download/${HURL_VERSION}/hurl_${HURL_VERSION}_amd64.deb\"\n    - DEBIAN_FRONTEND=noninteractive apt -y install \"./hurl_${HURL_VERSION}_amd64.deb\"\n\nhurl-gitlab-com:\n  extends: .hurl-tmpl\n  script:\n    - echo -e \"GET https://gitlab.com\\n\\nHTTP/2 200\" | hurl --test --color\n```\n\n\nThe next section describes how to optimize the CI/CD pipelines for more\nefficient schedules and runs to monitor websites and not waste too many\nresources and CI/CD minutes. You can also skip it and [scroll down to more\nadvanced Hurl examples in GitLab CI/CD](#devsecops-workflows-with-hurl).\n\n\n### CI/CD efficiency: Hurl container image\n\n\nThe installation steps for Hurl, and its dependencies, can waste resources\nand increase the pipeline job runtime every time. To make the CI/CD\npipelines more efficient, we want to use a container image that already\nprovides Hurl pre-installed. The following steps are required for creating a\ncontainer image:\n\n\n- Use Debian 11 Slim (FROM).\n\n- Install dependencies to download Hurl (`curl`, `ca-certificates`). `jq` is\ninstalled for convenience to access it from CI/CD commands when needed\nlater.\n\n- Download the Hurl Debian package, and use `apt install` to install its\ndependencies automatically.\n\n- Clear the apt lists cache to enforce apt update again, and avoid security\nissues.\n\n- Hurl is installed into the PATH, specify the default command being run.\nThis allows running the container without having to specify a command.\n\n\nThe steps to install the packages are separated for better readability; an\noptimization for the `docker-build` job can happen by chaining the `RUN`\ncommands into one long command.\n\n\n`Dockerfile`\n\n```\n\nFROM debian:11-slim\n\n\nENV DEBIAN_FRONTEND noninteractive\n\n\nARG HURL_VERSION=1.8.0\n\n\nRUN apt update && apt install -y curl jq ca-certificates\n\nRUN curl -LO\n\"https://github.com/Orange-OpenSource/hurl/releases/download/${HURL_VERSION}/hurl_${HURL_VERSION}_amd64.deb\"\n\n# Use apt install to determine package dependencies instead of dpkg\n\nRUN apt -y install \"./hurl_${HURL_VERSION}_amd64.deb\"\n\nRUN rm -rf /var/lib/apt/lists/*\n\n\nCMD [\"hurl\"]\n\n```\n\n\nNote that the `HURL_VERSION` variable can be overridden by passing the\nvariable and value into the container build job later. It is intentionally\nnot using an automated script that always uses the [latest\nrelease](https://github.com/Orange-OpenSource/hurl/releases) to avoid\nbreaking the behavior, and enforces a controlled upgrade cycle for container\nimages in production.\n\n\nOn GitLab.com SaaS, you can include the `Docker.gitlab-ci.yml` CI/CD\ntemplate which will automatically detect the `Dockerfile` file and start\nbuilding the image using the shared runners, and push it to the [GitLab\ncontainer\nregistry](https://docs.gitlab.com/ee/user/packages/container_registry/). For\nself-managed instances or own runners on GitLab.com SaaS, it is recommended\nto decide whether to use and setup\n[Docker-in-Docker](https://docs.gitlab.com/ee/ci/docker/using_docker_build.html)\nor [Kaniko](https://docs.gitlab.com/ee/ci/docker/using_kaniko.html), Podman,\nor other container image build tools.\n\n\n```yaml\n\ninclude:\n  - template: Docker.gitlab-ci.yml\n```\n\n\nTo avoid running the Docker image build job every time, the job override\ndefinition specifies to [run it\nmanually](https://docs.gitlab.com/ee/ci/yaml/#when). You can also use rules\nto [choose when to run the\njob](https://docs.gitlab.com/ee/ci/jobs/job_control.html), only when a Git\ntag is pushed for example.\n\n\n```yaml\n\ninclude:\n  - template: Docker.gitlab-ci.yml\n\n# Change Docker build to manual non-blocking\n\ndocker-build:\n  rules:\n    - if: '$CI_COMMIT_REF_NAME == $CI_DEFAULT_BRANCH'\n      when: manual\n      allow_failure: true\n```\n\n\nOnce the container image is pushed to the registry, navigate into `Packages\nand Registries > Container Registries` and inspect the tagged image. Copy\nthe image path for the latest tagged version and use it for the `image`\nattribute in the CI/CD job configuration.\n\n\n### Hurl container image in GitLab CI/CD example\n\n\nThe full example uses the previously built container image, and specifies\nthe default `HURL_URL` variable. This can later be overridden by job\ndefinitions.\n\n\n_Please note that the image URL\n`registry.gitlab.com/everyonecancontribute/dev/hurl-playground:latest` is\nonly used for demo purposes and not actively maintained or updated._\n\n\n```yaml\n\ninclude:\n  - template: Docker.gitlab-ci.yml\n\n# Change Docker build to manual non-blocking\n\ndocker-build:\n  rules:\n    - if: '$CI_COMMIT_REF_NAME == $CI_DEFAULT_BRANCH'\n      when: manual\n      allow_failure: true\n\n# Hurl job template\n\n.hurl-tmpl:\n  image: registry.gitlab.com/everyonecancontribute/dev/hurl-playground:latest\n  variables:\n    HURL_URL: gitlab.com\n\n# Hurl jobs that check websites\n\nhurl-dnsmichi-at:\n  extends: .hurl-tmpl\n  variables:\n    HURL_URL: dnsmichi.at\n  script:\n    - echo -e \"GET https://${HURL_URL}\\n\\nHTTP/1.1 200\" | hurl --test --color\n\nhurl-opsindev-news:\n  extends: .hurl-tmpl\n  variables:\n    HURL_URL: opsindev.news\n  script:\n    - echo -e \"GET https://${HURL_URL}\\n\\nHTTP/2 200\" | hurl --test --color\n```\n\n\nThe CI/CD configuration can further be optimized:\n\n\n- Create job templates that execute the same scripts and only differ in the\n`HURL_URL` variable.\n\n- Use Hurl configuration files that allow specifying variables on the CLI or\nas environment variables. More on this in the next section.\n\n\n## DevSecOps workflows with Hurl\n\n\nHurl allows users to describe HTTP instructions in a configuration file with\nthe `.hurl` suffix. You can add the configuration files to Git, and review\nand approve changes in merge requests - with the changes run in CI/CD and\nreporting back any failures before merging.\n\n\nInspect the `use-cases/` directory in the [example\nproject](https://gitlab.com/everyonecancontribute/dev/hurl-playground), and\nfork it to make changes and commit and run the CI/CD pipelines and reports.\nYou can also clone the project and run the `tree` command in the terminal.\n\n\n```sh\n\n$ tree use-cases\n\nuse-cases\n\n├── dnsmichi.at.hurl\n\n├── gitlab-com-api.hurl\n\n├── gitlab-contribute.hurl\n\n└── hackernews.hurl\n\n```\n\n\nHurl supports the glob option which collects all configuration files\nmatching a specific pattern.\n\n\n![Hurl configuration file\nrun](https://about.gitlab.com/images/blogimages/hurl-continuous-website-testing/hurl_multiple_config_files_run.png)\n\n\n### Chaining requests\n\n\nSimilar to CI/CD pipelines, jobs, and stages, testing HTTP endpoints with\nHurl can require multiple steps. First, ping the website for being\nreachable, and then try parsing expected results. Separating the\nrequirements into two steps helps to analyze errors.\n\n\n- HTTP endpoint reachable, but expected string not in response - static\nwebsite was changed, REST API misses a field, etc.\n\n- HTTP endpoint is unreachable, don’t try to understand why the follow-up\ntests fail.\n\n\nThe following example first sends a ping probe to the dev instance, and a\ncheck towards the production environment in the second request.\n\n\n```sh\n\n$ vim use-cases/everyonecancontribute-com.hurl\n\n\nGET https://everyonecancontribute.dev\n\n\nHTTP/2 200\n\n\nGET https://everyonecancontribute.com\n\n\nHTTP/2 200\n\n$ hurl --test use-cases/everyonecancontribute-com.hurl\n\n```\n\n\nIn this scenario, the TLS certificate of the dev instance expired, and Hurl\nhalts the test immediately.\n\n\n![Hurl chained requests, failing the first test with TLS certificate\nproblems](https://about.gitlab.com/images/blogimages/hurl-continuous-website-testing/hurl_chained_request_fail.png)\n\n\n### Hurl reports as JUnit test reports\n\n\nTreat website monitoring and web app tests as unit and end-to-end tests. The\nHurl developers thought of that too - the CLI command provides different\noutput options for the report: `--report-junit \u003Coutputpath>` integrates with\n[GitLab JUnit\nreport](https://docs.gitlab.com/ee/ci/testing/unit_test_reports.html)\nsupport into merge requests and pipeline views.\n\n\nThe following configuration generates a JUnit report file into the value of\nthe `HURL_JUNIT_REPORT` variable. It exists to avoid typing the path three\ntimes. The Hurl tests are run from the `use-cases/` directory using a glob\npattern.\n\n\n```yaml\n\n# Hurl job template\n\n.hurl-tmpl:\n    image: registry.gitlab.com/everyonecancontribute/dev/hurl-playground:latest\n    variables:\n        HURL_URL: gitlab.com\n        HURL_JUNIT_REPORT: hurl_junit_report.xml\n\n# Hurl tests from configuration file, generating JUnit report integration in\nGitLab CI/CD\n\nhurl-report:\n    extends: .hurl-tmpl\n    script:\n      - hurl --test use-cases/*.hurl --report-junit $HURL_JUNIT_REPORT\n    after_script:\n      # Hack: Workaround for 'id' instead of 'name' in JUnit report from Hurl. https://gitlab.com/gitlab-org/gitlab/-/issues/299086\n      - sed -i 's/id/name/g' $HURL_JUNIT_REPORT\n    artifacts:\n      when: always\n      paths:\n        - $HURL_JUNIT_REPORT\n      reports:\n        junit: $HURL_JUNIT_REPORT\n```\n\n\nThe JUnit format returned by Hurl 1.8.0 defines the `id` attribute, but the\nGitLab JUnit integration expects the `name` attribute to be present. While\nwriting this blog post, [the problem was\ndiscussed](https://github.com/Orange-OpenSource/hurl/issues/1067#issuecomment-1343264751)\nwith the maintainers, and [the `name` attribute was\nimplemented](https://github.com/Orange-OpenSource/hurl/issues/1078) and will\nbe available in future releases. As a workaround with Hurl 1.8.0, the CI/CD\n[after_script](https://docs.gitlab.com/ee/ci/yaml/#after_script) section\nuses `sed` to replace the attributes after generating the report.\n\n\nThe [following\nexample](https://gitlab.com/everyonecancontribute/dev/hurl-playground/-/merge_requests/10)\nfails on purpose with checking a different HTTP protocol version.\n\n\n```\n\nGET https://opsindev.news\n\n\n# This will fail on purpose\n\nHTTP/1.1 200\n\n\n[Asserts]\n\nbody contains \"Michael Friedrich\"\n\n```\n\n\n![Hurl test report in JUnit format integrated into\nGitLab](https://about.gitlab.com/images/blogimages/hurl-continuous-website-testing/hurl_gitlab_junit_integration_merge_request_widget_overlay.png)\n\n\nOnce the JUnit integration with Hurl tests from a glob pattern work, you can\ncontinue adding new `.hurl` configuration files to the GitLab repository and\nstart testing in MRs, which will require review and approval workflows for\nproduction then.\n\n\n### Web review apps\n\n\nWebsite monitoring is only one aspect of using Hurl: Testing web\napplications deployed in review environments in the cloud, and in\ncloud-native clusters provides a native integration into\n[DevSecOps](https://about.gitlab.com/topics/devsecops/) workflows. The CI/CD\npipelines will fail when Hurl tests are failing, and more insights are\nprovided using merge request widgets reports.\n\n\n[Cloud Seed](https://docs.gitlab.com/ee/cloud_seed/index.html) provides the\nability to deploy a web application to a major cloud provider, for example\nGoogle Cloud. After the deployment is successful, additional CI/CD jobs can\nbe configured that verify that the deployed web app version does not\nintroduce a regression, and provides all required data elements, API\nendpoints, etc. A similar workflow can be achieved by using review app\nenvironments with [webservers (Nginx, etc.), Docker, AWS, and\nKubernetes](https://docs.gitlab.com/ee/ci/review_apps/#review-apps-examples).\nThe review app [environment\nURL](https://docs.gitlab.com/ee/ci/environments/#create-a-dynamic-environment)\nis important for instrumenting the Hurl tests dynamically. The CI/CD\nvariable\n[`CI_ENVIRONMENT_URL`](https://docs.gitlab.com/ee/ci/variables/predefined_variables.html)\nis available when `environment:url` is specified in the review app\nconfiguration.\n\n\nThe following example tests the review app for [this blog post when written\nin a merge\nrequest](https://gitlab.com/gitlab-com/www-gitlab-com/-/merge_requests/115548):\n\n\n```yaml\n\n# Test review apps with hurl for this blog post.\n\nhurl-review-test:\n  extends: .review-environment # inherits the environment settings\n  needs: [uncategorized-build-and-review-deploy] # waits until the website (sites/uncategorized) is deployed\n  stage: test\n  rules: # YAML anchor that runs the job only on merge requests\n    - \u003C\u003C: *if-merge-request-original-repo\n  image:\n    name: ghcr.io/orange-opensource/hurl:1.8.0\n    entrypoint: [\"\"]\n  script:\n    - echo -e \"GET ${CI_ENVIRONMENT_URL}\\n\\nHTTP/1.1 200\" | hurl --test --color\n```\n\n\nThe environment is specified in the [.review-environment job\ntemplate](https://gitlab.com/gitlab-com/www-gitlab-com/-/blob/91d6fd72a424a3d913e79ebc2aefb23bbab85863/.gitlab-ci.yml#L332)\nand used to [deploy the website review\njob](https://gitlab.com/gitlab-com/www-gitlab-com/-/blob/91d6fd72a424a3d913e79ebc2aefb23bbab85863/.gitlab-ci.yml#L532).\nThe relevant configuration snippet is shown here:\n\n\n```yaml\n\n.review-environment:\n  variables:\n    DEPLOY_TYPE: review\n  environment:\n    name: review/$CI_COMMIT_REF_SLUG\n    url: https://$CI_COMMIT_REF_SLUG.about.gitlab-review.app\n    on_stop: review-stop\n    auto_stop_in: 30 days\n```\n\n\nThe deployment of the www-gitlab-com project [uses buckets in Google\nCloud](https://gitlab.com/gitlab-com/www-gitlab-com/-/blob/91d6fd72a424a3d913e79ebc2aefb23bbab85863/scripts/deploy)\nthat serve the website content in the review app. There are different types\nof web applications that require different deployment methods - as long as\nthe environment URL variable is available in CI/CD and the deployment URL is\naccessible from the GitLab Runner executing the CI/CD job, you can\ncontinously test web apps with Hurl!\n\n\n![Hurl test in GitLab CI/CD for review app\nenvironments](https://about.gitlab.com/images/blogimages/hurl-continuous-website-testing/hurl_gitlab_cicd_review_app_environment_tests_www-gitlab-com.png)\n\n\n## Development tips\n\n\nUse the [`--verbose`\nparameter](https://hurl.dev/docs/tutorial/debug-tips.html) to see the full\nrequest and response flow. Hurl also provides tips which `curl` command\ncould be run to fetch more data. This can be helpful when starting to use or\ndevelop a new REST API, or learning to understand the JSON structure of HTTP\nresponses. Chaining the `curl` command with `jq` (the `curl ... | jq`\npattern) can still be helpful to fetch data, and build the HTTP tests in a\nsecond terminal or editor window.\n\n\n```sh\n\n$ curl -s 'https://gitlab.com/api/v4/projects' | jq\n\n$ curl -s 'https://gitlab.com/api/v4/projects' | jq -c '.[]' | jq\n\n\n{\"id\":41375401,\"description\":\"An example project for a GitLab\npipeline.\",\"name\":\"Calculator\",\"name_with_namespace\":\"Iva Tee /\nCalculator\",\"path\":\"calculator\",\"path_with_namespace\":\"snufkins_hat/calculator\",\"created_at\":\"2022-11-26T00:32:33.825Z\",\"default_branch\":\"master\",\"tag_list\":[],\"topics\":[],\"ssh_url_to_repo\":\"git@gitlab.com:snufkins_hat/calculator.git\",\"http_url_to_repo\":\"https://gitlab.com/snufkins_hat/calculator.git\",\"web_url\":\"https://gitlab.com/snufkins_hat/calculator\",\"readme_url\":\"https://gitlab.com/snufkins_hat/calculator/-/blob/master/README.md\",\"avatar_url\":null,\"forks_count\":0,\"star_count\":0,\"last_activity_at\":\"2022-11-26T00:32:33.825Z\",\"namespace\":{\"id\":58849237,\"name\":\"Iva\nTee\",\"path\":\"snufkins_hat\",\"kind\":\"user\",\"full_path\":\"snufkins_hat\",\"parent_id\":null,\"avatar_url\":\"https://secure.gravatar.com/avatar/a3efe834950275380d5f19c9b17c922c?s=80&d=identicon\",\"web_url\":\"https://gitlab.com/snufkins_hat\"}}\n\n```\n\n\nThe GitLab projects API returns an array of elements, where we can inspect\nthe `id` and `name` attributes for a simple test - the first element’s name\nmust not be empty, the second element’s id needs to be greater than 0.\n\n\n```sh\n\n$ vim gitlab-com-api.hurl\n\n\nGET https://gitlab.com/api/v4/projects\n\n\nHTTP/2 200\n\n\n[Asserts]\n\njsonpath \"$[0].name\" != \"\"\n\njsonpath \"$[1].id\" > 0\n\n\n$ hurl --test gitlab-com-api.hurl\n\n\ngitlab-com-api.hurl: Running [1/1]\n\ngitlab-com-api.hurl: Success (1 request(s) in 728 ms)\n\n--------------------------------------------------------------------------------\n\nExecuted files:  1\n\nSucceeded files: 1 (100.0%)\n\nFailed files:    0 (0.0%)\n\nDuration:        730 ms\n\n```\n\n\n## More use cases\n\n\n- Work with HTTP sessions and\n[cookies](https://hurl.dev/docs/request.html#cookies), test [forms with\nparameters](https://hurl.dev/docs/request.html#form-parameters).\n\n- Review existing API tests of your applications.\n\n- Build advanced chained workflows with GET, POST, PUT, DELETE, and more\nHTTP methods.\n\n- Integrate simple ping/HTTP monitoring health checks into the DevSecOps\nPlatform using alerts and incident management.\n\n\nIf the Hurl checks cannot be integrated directly inside the project where\nthe application is developed and deployed, another idea could be to create a\nstandalone GitLab project that has CI/CD pipeline schedules enabled. It can\ncontinuously run the Hurl tests, and parse the reports or trigger an event\nwhen the pipeline is failing, and [create an\nalert](https://docs.gitlab.com/ee/operations/incident_management/alerts.html)\nby sending a JSON payload from the Hurl results to the [HTTP\nendpoint](https://docs.gitlab.com/ee/operations/incident_management/integrations.html#single-http-endpoint).\nDevelopers can send MRs to update the Hurl tests, and maintainers review and\napprove the new test suites being rolled out into production. Alternatively,\nmove the complete CI/CD configuration into a group/project with different\npermissions, and specify the CI/CD configuration as remote URL in the web\napplication project. This compliance level helps to control who can make\nchanges to important tests and CI/CD configuration.\n\n\nHurl supports `--json` as parameter to only return the JSON formatted test\nresult and build own custom reports and integrations.\n\n\n```sh\n\n$ echo -e \"GET https://about.gitlab.com/teamops/\\n\\nHTTP/2 200\" | hurl\n--json | jq\n\n```\n\n\nFor folks in DevRel, monitoring certain websites for keywords or checking\nAPIs whether values increase a certain threshold can be interesting. Here is\nan example for monitoring Hacker News using the Algolia search API, inspired\nby the [Zapier integration used for GitLab\nSlack](/handbook/marketing/developer-relations/workflows-tools/zapier/#zaps-for-hacker-news).\nThe `QueryStringParams` section allows users to define the query parameters\nas a readable list, which is easier to modify. The `jsonpath` checks\nsearches for the `hits` key and its count being zero (not on the Hacker News\nfront page means OK in this example).\n\n\n```\n\n$ vim hackernews.hurl\n\n\nGET https://hn.algolia.com/api/v1/search\n\n[QueryStringParams]\n\nquery: gitlab\n\n#query: hurl\n\ntags: front_page\n\n\nHTTP/2 200\n\n\n[Asserts]\n\njsonpath \"$.hits\" count == 0\n\n\n$ hurl --test hackernews.hurl\n\n```\n\n\n## Limitations\n\n\nHurl works great for testing websites and web applications that serve static\ncontent, and by sending different HTTP request types, data, etc., and\nensuring that responses match expectations. Compared to other end-to-end\ntesting solutions (Selenium, etc.), Hurl does not provide a JavaScript\nengine and only can parse the raw DOM or JSON response. It does not support\na DOM managed and rendered by JavaScript front-end frameworks. UI\nintegration tests also need to be performed with different tools, similar to\nfull end-to-end test workflows. Other examples are [accessibility\ntesting](https://docs.gitlab.com/ee/ci/testing/accessibility_testing.html)\nand [browser performance\ntesting](https://docs.gitlab.com/ee/ci/testing/browser_performance_testing.html).\nIf you are curious how end-to-end testing is done for GitLab, the product,\npeek into the [development\ndocumentation](https://docs.gitlab.com/ee/development/testing_guide/end_to_end/).\n\n\n## Conclusion\n\n\nHurl provides an easy way to test HTTP endpoints (such as websites and APIs)\nin a fast and reliable way. The CLI commands can be integrated into CI/CD\nworkflows, and the configuration syntax and files provide a single source of\ntruth for everything. Additional support for JUnit report formats ensure\nthat website testing is fully integrated into the\n[DevSecOps](https://about.gitlab.com/topics/devsecops/) platform, and\nincreases visibility and extensibility with automating tests, and\nmonitoring. There are known limitations with dynamic JavaScript websites and\nadvanced UI/end-to-end testing workflows.\n\n\nHurl is open source, [created and maintained by\nOrange](https://opensource.orange.com/en/open-source-orange/), and written\nin Rust. This blog post inspired contributions to the [Debian/Ubuntu\ninstallation\ndocumentation](https://github.com/Orange-OpenSource/hurl/pull/1084) and\n[default issue\ntemplates](https://github.com/Orange-OpenSource/hurl/pull/1083).\n\n\n**Tip:** Practice using Hurl on the command line, and remember it when the\nnext production incident shows a strange API behavior with POST requests.\n\n\nThanks to [Lee Tickett](/company/team/#leetickett-gitlab) who inspired me to\ntest Hurl in GitLab CI/CD and write this blog post after seeing huge\ninterest in a [Twitter\nshare](https://twitter.com/dnsmichi/status/1595820546062778369).\n\n\nCover image by [Aaron Burden](https://unsplash.com/@aaronburden) on\n[Unsplash](https://unsplash.com)\n\n{: .note}\n",[851,9,741],{"slug":1913,"featured":6,"template":699},"how-to-continously-test-web-apps-apis-with-hurl-and-gitlab-ci-cd","content:en-us:blog:how-to-continously-test-web-apps-apis-with-hurl-and-gitlab-ci-cd.yml","How To Continously Test Web Apps Apis With Hurl And Gitlab Ci Cd","en-us/blog/how-to-continously-test-web-apps-apis-with-hurl-and-gitlab-ci-cd.yml","en-us/blog/how-to-continously-test-web-apps-apis-with-hurl-and-gitlab-ci-cd",{"_path":1919,"_dir":246,"_draft":6,"_partial":6,"_locale":7,"seo":1920,"content":1926,"config":1933,"_id":1935,"_type":14,"title":1936,"_source":16,"_file":1937,"_stem":1938,"_extension":19},"/en-us/blog/how-to-create-a-ci-cd-pipeline-with-auto-deploy-to-kubernetes-using-gitlab",{"title":1921,"description":1922,"ogTitle":1921,"ogDescription":1922,"noIndex":6,"ogImage":1923,"ogUrl":1924,"ogSiteName":686,"ogType":687,"canonicalUrls":1924,"schema":1925},"CI/CD pipeline: GitLab & Helm for Kubernetes Auto Deploy","One user walks through how he tried GitLab caching and split the job into multiple steps to get better feedback.","https://res.cloudinary.com/about-gitlab-com/image/upload/v1749664472/Blog/Hero%20Images/gitlabflatlogomap.png","https://about.gitlab.com/blog/how-to-create-a-ci-cd-pipeline-with-auto-deploy-to-kubernetes-using-gitlab","\n                        {\n        \"@context\": \"https://schema.org\",\n        \"@type\": \"Article\",\n        \"headline\": \"How to create a CI/CD pipeline with Auto Deploy to Kubernetes using GitLab and Helm\",\n        \"author\": [{\"@type\":\"Person\",\"name\":\"Sergey Nuzhdin\"}],\n        \"datePublished\": \"2017-09-21\",\n      }",{"title":1927,"description":1922,"authors":1928,"heroImage":1923,"date":1930,"body":1931,"category":717,"tags":1932},"How to create a CI/CD pipeline with Auto Deploy to Kubernetes using GitLab and Helm",[1929],"Sergey Nuzhdin","2017-09-21","Recently, I started working on a few Golang\n[microservices](/topics/microservices/). I decided to try GitLab’s caching\nand split the job into multiple steps for better feedback in the UI.\n\n\n\u003C!-- more -->\n\n\nSince my previous\nposts[[1](http://blog.lwolf.org/post/how-to-build-tiny-golang-docker-images-with-gitlab-ci/)][[2](http://blog.lwolf.org/post/continuous-deployment-to-kubernetes-from-gitlab-ci/)]\nabout [CI/CD](/topics/ci-cd/), a lot has changed. I started using Helm\ncharts for packaging applications, and stopped using docker-in-docker in\ngitlab-runner.\n\n\nHere are a few of the main changes to my `.gitlab-ci.yml` file since my\nprevious post:\n\n\n* no docker-in-docker\n\n* using cache for packages instead of a prebuilt image with dependencies\n\n* splitting everything into multiple steps\n\n* autodeploy to staging environment using Helm, a package manager for\nKubernetes\n\n\n### Building Golang image\n\n\nSince Golang is very strict about the location of the project, we need to\nmake some adjustments to the CI job. This is done in the `before_script`\nblock. Simply create needed directories and link source code in there.\nAssuming that the official repository of the project is\n`gitlab.example.com/librerio/libr_files` it should look like this.\n\n\n```\n\nvariables:\n  APP_PATH: /go/src/gitlab.example.com/librerio/libr_files\n\nbefore_script:\n  - mkdir -p /go/src/gitlab.example.com/librerio/\n  - ln -s $PWD ${APP_PATH}\n  - mkdir -p ${APP_PATH}/vendor\n  - cd ${APP_PATH}\n```\n\n\nWith this in place, we can install dependencies and build our binaries. To\navoid the download of all packages on each build we need to configure\ncaching. Due to the strange caching rules of GitLab, we need to add vendor\ndirectory to both cache and artifacts. Cache will give us an ability to use\nit between build jobs and artifacts will allow us to use it inside the same\njob.\n\n\n```\n\n\ncache:\n  untracked: true\n  key: \"$CI_BUILD_REF_NAME\"\n  paths:\n    - vendor/\n\nsetup:\n  stage: setup\n  image: lwolf/golang-glide:0.12.3\n  script:\n    - glide install -v\n  artifacts:\n    paths:\n     - vendor/\n\n```\n\n\nBuild step didn’t change, it’s still about building the binary. I add binary\nto artifacts.\n\n\n```\n\nbuild:\n  stage: build\n  image: lwolf/golang-glide:0.12.3\n  script:\n    - cd ${APP_PATH}\n    - GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go build -o release/app -ldflags '-w -s'\n    - cd release\n  artifacts:\n    paths:\n     - release/\n```\n\n\n###  Test stage\n\n\nTo run golang tests with coverage reports I’m using the variation of [this\nshell\nscript](https://github.com/mlafeldt/chef-runner/blob/v0.7.0/script/coverage).\nIt runs all tests in project subdirectories and creates a [coverage\nreport](/blog/publish-code-coverage-report-with-gitlab-pages/). I changed it\na bit before putting into a gist. I exclude vendor directory from tests.\n\n\n* coverage regexp for gitlab-ci: `^total:\\s*\\(statements\\)\\s*(\\d+.\\d+\\%)`\n\n\n### Deploy stage\n\n\nI don’t use native GitLab’s integration with Kubernetes.\n\n\nFirst I thought about creating Kubernetes secrets and mounting it to the\ngitlab-runner pod. But it’s very complicated. You need to upgrade deployment\nevery time you want to add new Kubernetes cluster configurations. So I’m\nusing GitLab’s CI/CD variables with base64 encoded Kubernetes config. Each\nproject can have any number of configurations. The process is easy – create\nbase64 string from the configuration file and copy it to the clipboard.\nAfter this, put it into `kube_config` variable (name it whatever you like).\n\n\n`cat ~/.kube/config | base64 | pbcopy`\n\n\nIf you do not own a full GitLab installation, consider creating a Kubernetes\nuser with restricted permissions.\n\n\nThen on the deploy stage, we can decode this variable back into the file and\nuse it with kubectl.\n\n\n```\n\nvariables:\n  KUBECONFIG: /etc/deploy/config\n\ndeploy:\n  ...\n  before_script:\n    - mkdir -p /etc/deploy\n    - echo ${kube_config} | base64 -d > ${KUBECONFIG}\n    - kubectl config use-context homekube\n    - helm init --client-only\n    - helm repo add stable https://kubernetes-charts.storage.googleapis.com/\n    - helm repo add incubator https://kubernetes-charts-incubator.storage.googleapis.com/\n    - helm repo update\n```\n\n\nDeploy stage also covers the case when you have several versions of the same\napplication.\n\n\nFor example, you have two versions of API: v1.0 and v1.1. All you need to do\nis set `appVersion` in Chart.yaml file. Build system will check API version\nand either deploy or upgrade needed release.\n\n\n```\n\n- export API_VERSION=\"$(grep \"appVersion\" Chart.yaml | cut -d\" \" -f2)\"\n\n- export RELEASE_NAME=\"libr-files-v${API_VERSION/./-}\"\n\n- export DEPLOYS=$(helm ls | grep $RELEASE_NAME | wc -l)\n\n- if [ ${DEPLOYS}  -eq 0 ]; then helm install --name=${RELEASE_NAME} .\n--namespace=${STAGING_NAMESPACE}; else helm upgrade ${RELEASE_NAME} .\n--namespace=${STAGING_NAMESPACE}; fi\n\n```\n\n\n### tl;dr\n\n\n```\n\nHere is complete `.gitlab-ci.yaml` file for reference.\n\n\ncache:\n  untracked: true\n  key: \"$CI_BUILD_REF_NAME\"\n  paths:\n    - vendor/\n\nbefore_script:\n  - mkdir -p /go/src/gitlab.example.com/librerio/\n  - ln -s $PWD ${APP_PATH}\n  - mkdir -p ${APP_PATH}/vendor\n  - cd ${APP_PATH}\n\nstages:\n  - setup\n  - test\n  - build\n  - release\n  - deploy\n\nvariables:\n  CONTAINER_IMAGE: ${CI_REGISTRY}/${CI_PROJECT_PATH}:${CI_BUILD_REF_NAME}_${CI_BUILD_REF}\n  CONTAINER_IMAGE_LATEST: ${CI_REGISTRY}/${CI_PROJECT_PATH}:latest\n  DOCKER_DRIVER: overlay2\n\n  KUBECONFIG: /etc/deploy/config\n  STAGING_NAMESPACE: app-stage\n  PRODUCTION_NAMESPACE: app-prod\n\n  APP_PATH: /go/src/gitlab.example.com/librerio/libr_files\n  POSTGRES_USER: gorma\n  POSTGRES_DB: test-${CI_BUILD_REF}\n  POSTGRES_PASSWORD: gorma\n\nsetup:\n  stage: setup\n  image: lwolf/golang-glide:0.12.3\n  script:\n    - glide install -v\n  artifacts:\n    paths:\n     - vendor/\n\nbuild:\n  stage: build\n  image: lwolf/golang-glide:0.12.3\n  script:\n    - cd ${APP_PATH}\n    - GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go build -o release/app -ldflags '-w -s'\n    - cd release\n  artifacts:\n    paths:\n     - release/\n\nrelease:\n  stage: release\n  image: docker:latest\n  script:\n    - cd ${APP_PATH}/release\n    - docker login -u gitlab-ci-token -p ${CI_BUILD_TOKEN} ${CI_REGISTRY}\n    - docker build -t ${CONTAINER_IMAGE} .\n    - docker tag ${CONTAINER_IMAGE} ${CONTAINER_IMAGE_LATEST}\n    - docker push ${CONTAINER_IMAGE}\n    - docker push ${CONTAINER_IMAGE_LATEST}\n\ntest:\n  stage: test\n  image: lwolf/golang-glide:0.12.3\n  services:\n    - postgres:9.6\n  script:\n    - cd ${APP_PATH}\n    - curl -o coverage.sh https://gist.githubusercontent.com/lwolf/3764a3b6cd08387e80aa6ca3b9534b8a/raw\n    - sh coverage.sh\n\ndeploy_staging:\n  stage: deploy\n  image: lwolf/helm-kubectl-docker:v152_213\n  before_script:\n    - mkdir -p /etc/deploy\n    - echo ${kube_config} | base64 -d > ${KUBECONFIG}\n    - kubectl config use-context homekube\n    - helm init --client-only\n    - helm repo add stable https://kubernetes-charts.storage.googleapis.com/\n    - helm repo add incubator https://kubernetes-charts-incubator.storage.googleapis.com/\n    - helm repo update\n  script:\n    - cd deploy/libr-files\n    - helm dep build\n    - export API_VERSION=\"$(grep \"appVersion\" Chart.yaml | cut -d\" \" -f2)\"\n    - export RELEASE_NAME=\"libr-files-v${API_VERSION/./-}\"\n    - export DEPLOYS=$(helm ls | grep $RELEASE_NAME | wc -l)\n    - if [ ${DEPLOYS}  -eq 0 ]; then helm install --name=${RELEASE_NAME} . --namespace=${STAGING_NAMESPACE}; else helm upgrade ${RELEASE_NAME} . --namespace=${STAGING_NAMESPACE}; fi\n  environment:\n    name: staging\n    url: https://librerio.example.com\n  only:\n  - master\n\n```\n\n\n_[How to create a CI/CD pipeline with Auto Deploy to Kubernetes using GitLab\nand\nHelm](http://blog.lwolf.org/post/how-to-create-ci-cd-pipeline-with-autodeploy-k8s-gitlab-helm/)\nwas originally published on Lwolfs Blog._\n\n\nPhoto by C Chapman on [Unsplash](https://unsplash.com/)\n",[9,696,742,1005],{"slug":1934,"featured":6,"template":699},"how-to-create-a-ci-cd-pipeline-with-auto-deploy-to-kubernetes-using-gitlab","content:en-us:blog:how-to-create-a-ci-cd-pipeline-with-auto-deploy-to-kubernetes-using-gitlab.yml","How To Create A Ci Cd Pipeline With Auto Deploy To Kubernetes Using Gitlab","en-us/blog/how-to-create-a-ci-cd-pipeline-with-auto-deploy-to-kubernetes-using-gitlab.yml","en-us/blog/how-to-create-a-ci-cd-pipeline-with-auto-deploy-to-kubernetes-using-gitlab",{"_path":1940,"_dir":246,"_draft":6,"_partial":6,"_locale":7,"seo":1941,"content":1947,"config":1953,"_id":1955,"_type":14,"title":1956,"_source":16,"_file":1957,"_stem":1958,"_extension":19},"/en-us/blog/how-to-keep-up-with-ci-cd-best-practices",{"title":1942,"description":1943,"ogTitle":1942,"ogDescription":1943,"noIndex":6,"ogImage":1944,"ogUrl":1945,"ogSiteName":686,"ogType":687,"canonicalUrls":1945,"schema":1946},"How to keep up with CI/CD best practices","In this post, we look at continuous integration/continuous delivery (CI/CD), how to implement some best practices, and why it is important.","https://res.cloudinary.com/about-gitlab-com/image/upload/v1756989645/fojzxakmfdea6jfqjkrl.png","https://about.gitlab.com/blog/how-to-keep-up-with-ci-cd-best-practices","\n                        {\n        \"@context\": \"https://schema.org\",\n        \"@type\": \"Article\",\n        \"headline\": \"How to keep up with CI/CD best practices\",\n        \"author\": [{\"@type\":\"Person\",\"name\":\"Itzik Gan baruch\"}],\n        \"datePublished\": \"2025-09-20\",\n      }",{"title":1942,"description":1943,"authors":1948,"heroImage":1944,"date":1949,"updatedDate":1950,"body":1951,"category":849,"tags":1952},[934],"2022-02-03","2025-09-29","\nContinuous integration and continuous delivery (CI/CD) are at the heart of any successful DevSecOps practice. Teams wanting to achieve modern software development must keep up with [CI/CD](https://about.gitlab.com/topics/ci-cd/) best practices. Here's what you need to know to make sure your team is on the right track.\n\n## What is CI/CD?\nIt's a tech process, it's a mindset, it's a series of steps... CI/CD is all of those things. Put simply, continuous integration (CI) enables DevSecOps teams to streamline code development using automation. CI simplifies software builds and source code integration, enables version control, and promotes greater collaboration via automation. Where CI leaves off, continuous delivery (CD) kicks in with automated testing and deployment. Not only does CD reduce the amount of hands-on time ops pros need to spend on delivery and deployment, it also enables teams to drastically reduce the number of tools required to manage the lifecycle. By integrating security scanning and compliance checks early in the pipeline, CI/CD enables a shift-left security approach that identifies and resolves issues before they reach production.\n\n## What are the best practices for CI/CD?\nIf you want to be successful with CI/CD, make continuous integration, delivery, and deployment your mantra as they are the cornerstones of software development practices. The goal of DevSecOps is to get software to users more quickly than traditional methods, and these development practices will help make that happen.\nHere are the most essential practices, ranked by importance:\n1. **Use a unified DevSecOps platform:** Consolidate your CI/CD tools into a single platform to reduce maintenance overhead, minimize context switching, and improve collaboration. Fewer tools mean less integration complexity and better user experience across development, security, and operations teams.\n2. **Automate everything:** Keep tweaking the CI/CD pipeline to ensure the \"continuous automation\" state is achieved. This includes automated testing, security scanning, deployment, and infrastructure provisioning.\n3. **Fail fast:** On the CI side, developers committing code need to know as quickly as possible if there are issues so they can roll the code back and fix it while it's fresh in their minds. The idea of \"fail fast\" helps reduce developer context switching too, which makes for happier DevSecOps professionals.\n4. **Commit frequently:** The more regular the code commits, the more benefit DevSecOps teams will see. Small, frequent changes are easier to review, test, and deploy safely.\n5. **Shift left on security:** CI/CD offers a good opportunity to integrate security scanning, vulnerability assessments, and compliance checks earlier in the process.\n6. **Leverage AI for pipeline troubleshooting:** Use AI-powered tools to automatically diagnose pipeline failures, identify root causes, and suggest fixes, reducing resolution time from hours to minutes.\n7. **Use feature flags/toggles:** Decouple deployment from release by using feature flags. This allows you to deploy code to production without exposing new features to users, enabling safer rollouts and instant rollbacks.\n8. **Monitor everything:** Implement comprehensive observability including application metrics, logs, and business KPIs. Set up alerts for both technical and business-critical thresholds.\n9. **Maintain pipeline as code:** Store your CI/CD pipeline configuration in version control alongside your application code. This ensures pipeline changes are tracked, reviewed, and can be rolled back.\n10. **Enable feedback loops:** Make sure there's an easy way for the entire team to receive (and contribute to) feedback. This includes monitoring, alerting, post-deployment validation, and continuous improvement processes.\n    \n## Continuous delivery best practices\nContinuous delivery/deployment feels like it deserves its own deep dive into best practices because CI often steals most of the headlines. Here are essential CD best practices:\n- **Start with your current setup:** Don't wait for a perfect platform. Begin by identifying bottlenecks in your existing deployment process and automate the most painful manual steps first.\n- **Embrace deployment strategies:** Implement progressive delivery techniques like [**blue-green deployments**](https://docs.gitlab.com/ci/environments/incremental_rollouts/#blue-green-deployment), [**canary releases**](https://docs.gitlab.com/user/project/canary_deployments/), or [**feature flags**](https://docs.gitlab.com/operations/feature_flags/) to reduce risk and enable safe rollbacks.\n- **Maintain environment parity:** Ensure development, staging, and production environments are as similar as possible. Use infrastructure as code to eliminate configuration drift.\n- **Automate deployment validation:** Build automated smoke tests and health checks that verify deployments immediately after release. Failed validations should trigger automatic rollbacks.\n- **Implement comprehensive monitoring:** Monitor both technical metrics (response times and error rates) and business KPIs (user engagement and conversion rates) to catch issues early.\n- **Practice zero-downtime deployments:** Design your applications and deployment process to handle updates without service interruption, using techniques like rolling updates or load balancer switches.\n- **Keep rollbacks simple:** Ensure you can quickly revert to the previous version. Test your rollback process regularly and make it a one-click operation when possible.\n- **Separate deployment from release:** Use feature flags to deploy code without immediately exposing new functionality to users, allowing for safer testing in production.\n## How to optimize the CI/CD pipeline\nA CI/CD pipeline is the automated series of steps that takes code from development to production. The typical pipeline includes: **build**, **test**, **security scan**, **deploy**, and **monitor**. While these steps can be done manually, automation is what makes CI/CD truly valuable. If it's time to optimize your CI/CD pipeline, consider these performance enhancements:\n### Optimize pipeline performance\n- Implement [build caching](https://docs.gitlab.com/ci/caching/) to avoid rebuilding unchanged components.\n- Use [conditional job](https://docs.gitlab.com/ci/jobs/job_rules/#rules-examples) execution to skip unnecessary steps when code hasn't changed\n- Optimize Docker images with multi-stage builds and smaller base images.\n### Enhance pipeline visibility\n- Add pipeline duration tracking to identify slow stages.\n- Implement detailed logging and artifact collection for troubleshooting.\n- Leverage [pipeline dashboards](https://docs.gitlab.com/user/operations_dashboard/) showing success rates and performance trends.\n### Optimize resource efficiency\n- Right-size your CI runners based on actual resource consumption.\n- Use spot instances or [auto-scaling](https://docs.gitlab.com/runner/runner_autoscale/) for cost-effective compute resources.\n- Clean up temporary resources and artifacts after pipeline completion.\n### Scale for team growth\n- Use [pipeline components](https://docs.gitlab.com/ci/components/) for consistency across projects.\n- Use dynamic environments that spin up and tear down automatically.\n- Set up [pipeline approval](https://docs.gitlab.com/ci/environments/deployment_approvals/) workflows for production deployments.\n## CI/CD deployment strategy\nRemember that CI/CD is about getting a software application into the hands of a customer that is better and done quicker than before. Organizations that adopt CI/CD find their productivity improves significantly. The trick is coming up with a deployment strategy that works for the individual organization. \nHere are some strategies to help make a deployment successful:\n- **Commit small changes:** Deploy small, incremental changes frequently rather than large releases. Small changes are easier to test, review, and roll back if issues arise, reducing deployment risk.\n- **Start small and scale:** Begin with less critical applications to build confidence and refine processes before tackling mission-critical systems\n- **Implement automated rollbacks:** Define clear criteria for automatic rollbacks based on error rates, performance metrics, or health checks\n- **Practice deployment rehearsals:** Regularly test your deployment process in staging environments that mirror production\n- **Establish deployment windows:** Schedule deployments during low-traffic periods initially, then move toward continuous deployment as confidence grows\n- **Monitor deployment impact:** Track both technical metrics (response time, error rates) and business metrics (user engagement, conversion rates) after each deployment\n## How to measure the success of CI/CD\nDevSecOps teams can't know how well their CI/CD practices are going unless they measure them. Metrics play an important role in improving system performance and helping to identify where value can be added. Here are the essential metrics to employ:\n### The four DORA metrics\nModern teams rely on the [DORA framework](https://docs.gitlab.com/user/analytics/dora_metrics/), which identifies four key metrics that correlate strongly with organizational performance:\n### Deployment frequency\nHow often your team successfully releases code to production. Elite performers deploy multiple times per day, while high performers deploy daily to weekly. This metric indicates your team's ability to deliver value and respond to market demands.\n### Lead time for changes\nTime from code commit to production deployment. Elite performers achieve lead times under one day, high performers range from one day to one week. If commits take weeks to reach production, your pipeline needs optimization.\n### Change failure rate\nPercentage of deployments causing production failures requiring hotfixes or rollbacks. Elite performers maintain up to 15% failure rates, high performers see 16%-30%. High failure rates indicate quality problems in your testing and deployment practices.\n### Mean time to recovery\nHow quickly your team restores service after deployment failures. Elite performers recover in under one hour, high performers in under one day. Fast recovery requires robust monitoring and automated rollback capabilities.\n### Additional CI/CD metrics\nBeyond the core DORA metrics, teams often track these operational indicators:\n### Infrastructure costs\nCloud-native CI/CD can result in significant expenses if not managed properly. Efficient practices that reduce build times and optimize resource usage directly impact operational costs.\n### Team retention\nHappy developers stick around. When teams collaborate effectively on CI/CD practices, retention follows. Declining retention rates can signal problems with tooling or deployment workflows that developers may hesitate to voice directly.\n### Business impact of CI/CD metrics\nThese technical metrics directly translate to business outcomes:\n- Faster deployment frequency reduces time-to-market and improves customer responsiveness. This represents the **speed of innovation and market responsiveness**.\n- Shorter lead times enable quicker feature delivery and bug fixes, enhancing customer satisfaction. This measures the **time from idea to customer value**.\n- Lower change failure rates reduce support costs and minimize customer impact from production issues. This indicates **quality and customer experience reliability**.\n- Improved mean time to recovery protects revenue during outages and maintains service reliability. This reflects **business continuity and risk mitigation capability**.\n### Competitive advantage\n\n The [DORA research program](https://cloud.google.com/blog/products/ai-machine-learning/announcing-the-2025-dora-report) has found that high-performing organizations consistently demonstrate better business outcomes across multiple dimensions. \n\n> **Teams with strong CI/CD metrics report improved productivity, better customer satisfaction, and enhanced ability to compete in fast-moving markets**.\n## What are the benefits of following CI/CD best practices?\nWhen best practices are followed, the benefits of CI/CD are felt most importantly by your customers, and then throughout your organization. Here are the key benefits:\n- **Customers get features and fixes faster.** CI/CD enables rapid delivery of new functionality and bug fixes, directly improving the user experience and keeping customers satisfied with continuous improvements.\n- **Higher quality, more reliable software.** Automated testing and gradual rollouts mean fewer bugs reach production, resulting in more stable applications that customers can depend on.\n- **Faster response to customer feedback.** Short development cycles allow teams to quickly implement customer requests and address issues, showing customers their input is valued and acted upon rapidly.\n- **Better uptime and performance.** Automated monitoring and quick rollback capabilities mean customer-facing issues are resolved faster, minimizing service disruptions.\n- **More innovative features.** When developers spend less time on manual processes and firefighting, they can focus on building features that truly add customer value and differentiate your product.\n- **Improved customer support.** Faster deployment cycles mean customer-reported issues can be fixed and deployed quickly, rather than waiting for the next major release cycle.\n\nThe internal benefits naturally follow: happier developers, better collaboration between teams, reduced operational overhead, and easier talent retention — all of which ultimately contribute to delivering better customer experiences.\n    \n## How can I implement CI/CD in my organization?\nBefore any software is implemented, it's key to determine what the business drivers are and the same goes for adopting CI/CD. All development stakeholders should be involved early on in the implementation process. Developers should provide input since they will be the main users of a product. \n\nMake sure to do your due diligence when researching software that enables CI/CD, and ask about free trials. \n\nWhile it may seem counterintuitive since CI/CD is about accelerating the pace of software delivery in an automated fashion, start the process with a mentality of slow and steady. The boost in efficiency will decline if bugs are steadily moving into the finished application. \n\nIt's important to have consistency in the integration process. Perform unit tests, trigger releases manually and track metrics. Then determine what can and should be automated.\n> Get started today with CI/CD by signing up for [a free trial of GitLab Ultimate with Duo Enterprise](https://about.gitlab.com/free-trial/devsecops/).\n",[9,696,786],{"slug":1954,"featured":6,"template":699},"how-to-keep-up-with-ci-cd-best-practices","content:en-us:blog:how-to-keep-up-with-ci-cd-best-practices.yml","How To Keep Up With Ci Cd Best Practices","en-us/blog/how-to-keep-up-with-ci-cd-best-practices.yml","en-us/blog/how-to-keep-up-with-ci-cd-best-practices",{"_path":1960,"_dir":246,"_draft":6,"_partial":6,"_locale":7,"seo":1961,"content":1967,"config":1972,"_id":1974,"_type":14,"title":1975,"_source":16,"_file":1976,"_stem":1977,"_extension":19},"/en-us/blog/how-to-translate-bamboo-agent-capabilities-to-gitlab-runner-tags",{"title":1962,"description":1963,"ogTitle":1962,"ogDescription":1963,"noIndex":6,"ogImage":1964,"ogUrl":1965,"ogSiteName":686,"ogType":687,"canonicalUrls":1965,"schema":1966},"How to translate Bamboo agent capabilities to GitLab Runner tags  ","This tutorial demonstrates how to use tags to organize GitLab Runners when building complex CI/CD pipelines.","https://res.cloudinary.com/about-gitlab-com/image/upload/v1749663019/Blog/Hero%20Images/AdobeStock_519147119.jpg","https://about.gitlab.com/blog/how-to-translate-bamboo-agent-capabilities-to-gitlab-runner-tags","\n                        {\n        \"@context\": \"https://schema.org\",\n        \"@type\": \"Article\",\n        \"headline\": \"How to translate Bamboo agent capabilities to GitLab Runner tags  \",\n        \"author\": [{\"@type\":\"Person\",\"name\":\"Abubakar Siddiq Ango\"}],\n        \"datePublished\": \"2024-02-22\",\n      }",{"title":1962,"description":1963,"authors":1968,"heroImage":1964,"date":1969,"body":1970,"category":717,"tags":1971},[1311],"2024-02-22","CI pipelines often start simple – a single job building a binary and pushing\nit to an artifact repository or to some production environment.\nEver-changing software requirements introduce more complexities, such as\nadding more jobs to perform certain checks and reviewing the output before\nthe final build job is executed.  \n\n\nThese complexities increase exponentially when builds are expected to target\nvarying systems with different system architectures or resource needs. This\nis evident in projects like operating systems, mobile apps, or software\ndistributions that support multiple deployment platforms. To account for the\nvarying needs of builds in these types of environments, having multiple\nrunners that match needed requirements is key, and that's where [GitLab\nRunner](https://docs.gitlab.com/runner/) tags come in. If you are coming\nfrom Atlassian's Bamboo, they are called \"agent capabilities.\"\n\n\nRunner tags allow organizing runners by a tag that signifies a specific use\ncase they support; these tags are then used to make sure CI jobs run on a\nrunner that meets their requirements. A job can require GPU resources that\nare only available on a handful of runners; tagging the job to the tags of\nthe runner allows it to be scheduled on the runner with GPUs.\n\n\nAgent capabilities on Bamboo are used to achieve the same functionality by\nspecifying binaries or custom identifiers that must be matched or available\nfor a job to run on a Bamboo agent. In this blog post, we will be looking at\nhow to translate Bamboo agent capabilities to GitLab Runner tags. \n\n\nBamboo has varying agent capabilities:\n\n- Executable capability specifies executables that are available on an\nagent.\n\n- JDK capability specifies that the Java Development Kit is installed and\navailable for builds.\n\n- Version Control capability lets Bamboo know the version control systems\nset up on an agent and where the client application is located.\n\n- Docker capability is used to define the agents where Docker is installed\nfor Docker tasks\n\n- Custom capability uses key/value identifiers to identify a unique\nfunctionality an agent provides.\n\n\nGitLab makes the process easier by using tags to identify Runners, some of\nwhich can be assigned multiple tags to denote the varying functionalities\nthey can provide to jobs. Let's look at how you can use Runner tags in\nGitLab.\n\n\n## Adding tags to GitLab Runner\n\n\nWhen [registering a\nrunner](https://docs.gitlab.com/runner/register/index.html) after\ninstallation, one of the steps requires providing a list of comma-separated\ntags that can be used. If none are provided at this stage, you can always\nedit the `/etc/gitlab-runner/config.toml` file and add any missing tags.\n\n\nYou can also manage the tags of a runner in GitLab by accessing the runner's\nedit page and updating the `Tags` field. You have the option for the runner\nto be exclusive to jobs that are tagged appropriately, or when there are no\ntagged jobs to run, it should run untagged jobs, too. Checking `Run untagged\njobs` enables this behavior.\n\n\n## Using tags in .gitlab-ci.yaml file\n\n\nTo run a job on a specific runner, add the relevant tags to the job's\nconfiguration, as shown below:\n\n\n```yaml\n\nbuild_ios:\n  image: macos-13-xcode-14\n  stage: build\n  script:\n    - bundle check --path vendor/bundle || bundle install --path vendor/bundle --jobs $(nproc)\n    - bundle exec fastlane build\n  tags: \n    - saas-macos-medium-m1\n```\n\nIn the example above, the job builds an iOS application only on runners\noperating on a macOS device with an M1 chip and tagged\n`saas-macos-medium-m1`.\n\n\n## Using multiple tags\n\n\nA job can specify multiple tags to target a diverse range of runners,\nespecially in organizations that run several fleets of runners as part of\ntheir software development lifecycle. A job will only run if a runner is\nfound that has all the tags the job has been tagged with. For example, if a\njob has `[linux, android, fastlane]` tags, a runner with `[ android,\nfastlane]` or `[ linux, android]` will not execute the job because the full\nset of tags does not match the runner.\n\n\n## Dynamic jobs with tags and variables\n\n\nYou can use variables to determine the values of tags and thus dynamically\ninfluence which runners pick up the jobs. For example:\n\n\n```\n\nvariables:\n  KUBERNETES_RUNNER: kubernetes\n\n  job:\n    tags:\n      - docker\n      - $KUBERNETES_RUNNER\n    script:\n      - echo \"Hello runner selector feature\"\n\n``` \n\n\nIn this example, only runners tagged with `kubernetes` will execute the job.\nYou can take this further in more complex pipelines with [`parallel:\nmatrix`](https://docs.gitlab.com/ee/ci/yaml/index.html#parallelmatrix). Here\nis an example:\n\n\n```\n\ndeploystacks:\n  stage: deploy\n  parallel:\n    matrix:\n      - PROVIDER: aws\n        STACK: [monitoring, app1]\n      - PROVIDER: gcp\n        STACK: [data]\n  tags:\n    - ${PROVIDER}-${STACK}\n  environment: $PROVIDER/$STACK\n\n```\n\n\nThis example ends up with three parallel jobs with three different tags for\neach: `aws-monitoring`, `aws-app1` and `gcp-data`, thus targeting possibly\nthree different runners.\n\n\nUsing tags in your GitLab CI configuration gives you the flexibility to\ndetermine where and how your applications are built, to use resources more\nefficiently as scarce resources can be limited to certain runners, and to\ndetermine how jobs are allocated to those runners.\n\n\n> Learn more about [how to make the move from Atlassian to\nGitLab](https://about.gitlab.com/move-to-gitlab-from-atlassian/).\n",[109,9,742],{"slug":1973,"featured":91,"template":699},"how-to-translate-bamboo-agent-capabilities-to-gitlab-runner-tags","content:en-us:blog:how-to-translate-bamboo-agent-capabilities-to-gitlab-runner-tags.yml","How To Translate Bamboo Agent Capabilities To Gitlab Runner Tags","en-us/blog/how-to-translate-bamboo-agent-capabilities-to-gitlab-runner-tags.yml","en-us/blog/how-to-translate-bamboo-agent-capabilities-to-gitlab-runner-tags",{"_path":1979,"_dir":246,"_draft":6,"_partial":6,"_locale":7,"seo":1980,"content":1986,"config":1991,"_id":1993,"_type":14,"title":1994,"_source":16,"_file":1995,"_stem":1996,"_extension":19},"/en-us/blog/how-to-use-agent-based-gitops",{"title":1981,"description":1982,"ogTitle":1981,"ogDescription":1982,"noIndex":6,"ogImage":1983,"ogUrl":1984,"ogSiteName":686,"ogType":687,"canonicalUrls":1984,"schema":1985},"How to use a pull-based (agent-based) approach for GitOps","Learn how GitLab supports agent-based approach for GitOps","https://res.cloudinary.com/about-gitlab-com/image/upload/v1749682037/Blog/Hero%20Images/agent-based-gitops-cover-880x587.jpg","https://about.gitlab.com/blog/how-to-use-agent-based-gitops","\n                        {\n        \"@context\": \"https://schema.org\",\n        \"@type\": \"Article\",\n        \"headline\": \"How to use a pull-based (agent-based) approach for GitOps\",\n        \"author\": [{\"@type\":\"Person\",\"name\":\"Cesar Saavedra\"}],\n        \"datePublished\": \"2021-06-23\",\n      }",{"title":1981,"description":1982,"authors":1987,"heroImage":1983,"date":1988,"body":1989,"category":717,"tags":1990},[1025],"2021-06-23","\n\nIn the previous post, titled [3 ways to approach GitOps](https://about.gitlab.com/blog/gitops-done-3-ways/), we discussed the many benefits and options that GitLab supports for fulfilling the [GitOps](/topics/gitops/) requirements of customers, whose IT environments are composed of heterogeneous technologies and infrastructures. This post is a 3-part series, in which we delve deeper into these options. In this first part, we cover the pull-based or agent-based approach.\n\n## About a pull-based or agent-based approach\n\nIn this approach, an agent is installed in your infrastructure components to pull changes whenever there is a drift from the desired configuration, which resides in GitLab. Although the infrastructure components could be anything from a physical server or router to a VM or a database, we will focus on a Kubernetes cluster in this section.\n\nIn the following example, the [reconciliation loop](https://about.gitlab.com/solutions/gitops/) is made up of two components: an agent running on the Kubernetes cluster and a server-side service running on the GitLab instance. One of the benefits of this approach is that you don’t have to expose your Kubernetes clusters outside your firewall. Another benefit is its distributed architecture, in that agents running on the infrastructure components are in charge of correcting any drift relieving the server-side from resource consumption. This approach requires the maintenance and installation of agents on all infrastructure components you want to be part of your GitOps flows.\n\n### GitLab Agent for Kubernetes as a pull-based approach\n\n[Introduced](https://about.gitlab.com/releases/2020/09/22/gitlab-13-4-released/#introducing-the-gitlab-kubernetes-agent) as part of GitLab 13.4, the GitLab Agent for Kubernetes runs on your Kubernetes cluster and pulls changes in your infrastructure configuration from GitLab to your cluster keeping your infrastructure configuration from drifting away from its desired state.\n\nGitLab Agent for Kubernetes (the feature) is currently implemented as two components ([architecture doc](https://gitlab.com/gitlab-org/cluster-integration/gitlab-agent/-/blob/master/doc/architecture.md)):\n\n- GitLab Agent for Kubernetes (agentk program): The component that users install into their cluster.\n\n- GitLab Agent for Kubernetes Server (kas program): The server-side counterpart, that runs \"next to GitLab.\"\n\nThe high-level architecture of the GitLab Agent for Kubernetes is depicted below:\n\n![GitLab K8s agent high-level architecture](https://about.gitlab.com/images/blogimages/how-to-use-agent-based-gitops/0-K8s-agent-arch.png){: .shadow.small.center.wrap-text}\nGitLab K8s agent high-level architecture.\n{: .note.text-center}\n\nThe **agentk** is installed on your Kubernetes cluster and it is the component that applies updates to the infrastructure. The **kas** is installed on the GitLab instance and it manages the authentication and authorization between **agentk** instances and GitLab, monitors projects for any changes and gathers latest project manifests to send to **agentk** instances.\n\n> **NOTE:** on Gitlab.com, the **kas** is installed and maintained by GitLab. On self-managed instances, the customer needs to install it.\n\nIn the following self-managed instance example, we go through a GitOps flow that leverages the pull-based approach to GitOps.  After the **agentk** component has already been installed on the K8s cluster, the user proceeds to log on to the GitLab instance and creates a project called **gitops-project**:\n\n![Creating the gitops-project](https://about.gitlab.com/images/blogimages/how-to-use-agent-based-gitops/1-create-gitops-proj.png){: .shadow.medium.center.wrap-text}\nCreating the gitops-project.\n{: .note.text-center}\n\nThe project **gitops-project** will be the one that will be monitored or observed by the **kas** component. Then, under **gitops-project**, the user creates an empty manifest file called **manifest.yaml**. This is the manifest file that will contain the Infrastructure as Code configuration for this project:\n\n![Manifest file created](https://about.gitlab.com/images/blogimages/how-to-use-agent-based-gitops/2-manifest-file-created.png){: .shadow.medium.center.wrap-text}\nManifest file created.\n{: .note.text-center}\n\nNext, the user creates a Kubernetes agent configuration repository project, **kubernetes-agent**, which will contain information pertinent to the **kas** component.\n\n![Creating the kubernetes-agent project](https://about.gitlab.com/images/blogimages/how-to-use-agent-based-gitops/3-create-K8s-agent-proj.png){: .shadow.medium.center.wrap-text}\nCreating the kubernetes-agent project.\n{: .note.text-center}\n\nWithin the **kubernetes-agent** project, the user creates a subdirectory **.gitlab/agents/agent1**, where **agent1** is the name given to this specific agent:\n\n![Config.yaml file created](https://about.gitlab.com/images/blogimages/how-to-use-agent-based-gitops/4-config-yaml-created.png){: .shadow.medium.center.wrap-text}\nConfig.yaml file created.\n{: .note.text-center}\n\nNotice that in the screenshot above, the project to be observed, **gitops-project**, was created in an earlier step.\n\nThe next step consists of the creation of a GitLab Rails Agent record to associate it with the Kubernetes agent configuration repository project. In the following screenshot, you see the commands that the user enters to first identify the task-runner pod, to log into it, to enter the Rails Console, and finally to create the agent record and a token for it:\n\n![Agent record created](https://about.gitlab.com/images/blogimages/how-to-use-agent-based-gitops/5-agent-record-created.png){: .shadow.medium.center.wrap-text}\nAgent record created.\n{: .note.text-center}\n\nIn the above screenshot, the last command uses the agent token to create a secret on the K8s cluster for secured communication between the **agentk** and the **kas** components.\n\nThe **agentk** pod creation on the K8s cluster is the next step. For this, the user creates a **resources.yml** file, in which the secured communication protocol between the **agentk** and the **kas** is specified as shown in the following snippet:\n\n![Websockets line](https://about.gitlab.com/images/blogimages/how-to-use-agent-based-gitops/6-wss-line-in-resources-yml.png){: .shadow.medium.center.wrap-text}\nWebSockets communication specified in the resources.yml file.\n{: .note.text-center}\n\nIn the above snippet, secured WebSockets protocol is being used. GitLab also supports gRPC.\n\nOnce the **resources.yml** file is updated with the corresponding GitLab instance information, the user proceeds to create the pod:\n\n![Agentk pod created](https://about.gitlab.com/images/blogimages/how-to-use-agent-based-gitops/7-agentk-created.png){: .shadow.medium.center.wrap-text}\nCreation of the **agentk** pod.\n{: .note.text-center}\n\nIn the screenshot above, you can see the execution of the **kubectl apply** that created the **agentk** pod in the K8s cluster.\n\nNow that the **agentk** and **kas** have been installed and are communicating securely with each other, the user can start performing some GitOps flows. Although the [GitLab Flow](https://about.gitlab.com/topics/version-control/what-is-gitlab-flow/) is the recommended approach for DevOps, it is also applicable to GitOps flows; after all GitOps is all about applying the goodness of DevOps to managing [Infrastructure as Code](/topics/gitops/infrastructure-as-code/).\n\nThis means that the user should create an issue and then a merge request, in which all stakeholders can collaborate towards the resolution of the issue. For the sake of brevity, in this technical blog post, we will skip all these steps and show you how updates to the Infrastructure as Code configuration files are automatically applied to the infrastructure components.\n\nNOTE: Fostering Collaboration is a great benefit of GitOps. For more information on this, check out this short [tech video](https://youtu.be/onFpj_wvbLM).\n\nFor example, the user can start making updates to the **manifest.yaml** file under the **gitops-project**, which is being observed by the kas component. Here you can see the user has pasted content into this file:\n\n![Manifest.yaml file updated](https://about.gitlab.com/images/blogimages/how-to-use-agent-based-gitops/8-manifest-yaml-updated.png){: .shadow.medium.center.wrap-text}\nManifest.yaml file updated.\n{: .note.text-center}\n\nRemember that this file had been created as an empty file. As soon as the user commits the changes displayed above, the **kas** component will detect the changes and communicate these to the **agentk** component, which is running on the K8s cluster. The **agentk** will immediately apply these changes to the infrastructure. In this example, the user has updated the infrastructure configuration file to have 2 instances of an nginx. As shown in the screenshot below, the **agentk** has applied these updates by the instantiation of 2 nginx pods in the K8s cluster:\n\n![Two nginx pods up and running](https://about.gitlab.com/images/blogimages/how-to-use-agent-based-gitops/9-two-nginx-running.png){: .shadow.medium.center.wrap-text}\nGitOps flow instantiates two nginx pods.\n{: .note.text-center}\n\nIf the user were to change the **manifest.yaml** file one more time and increment the replicas of the nginx pod to 3:\n\n![Manifest.yaml file updated with 3 nginx](https://about.gitlab.com/images/blogimages/how-to-use-agent-based-gitops/10-manifest-yaml-updated-again.png){: .shadow.medium.center.wrap-text}\nManifest.yaml file updated with 3 nginx instances.\n{: .note.text-center}\n\nAgain, as soon as the commit takes place, the **kas** component detects the update and communicates this to the **agentk** component, which in turn, spins up a third nginx pod in the K8s cluster:\n\n![Three nginx pods up and running](https://about.gitlab.com/images/blogimages/how-to-use-agent-based-gitops/11-three-nginx-running.png){: .shadow.medium.center.wrap-text}\nGitOps flow instantiates a third nginx pod.\n{: .note.text-center}\n\nLastly, the user can check the log files of the different components running on GKE, in this example. In the following screenshot, the user can see the **kas** component running on the GitLab instance:\n\n![kas running on GKE](https://about.gitlab.com/images/blogimages/how-to-use-agent-based-gitops/12-kas-on-GKE.png){: .shadow.medium.center.wrap-text}\nThe **kas** component running on GKE.\n{: .note.text-center}\n\nAnd then the user can drill down into the log of the **kas** component, and see how it is detecting commits on the project it is observing:\n\n![kas log on GKE](https://about.gitlab.com/images/blogimages/how-to-use-agent-based-gitops/13-kas-log-on-GKE.png){: .shadow.medium.center.wrap-text}\nThe **kas** log output on GKE.\n{: .note.text-center}\n\nLikewise, the user can navigate to the **agentk** component of the K8s cluster:\n\n![agentk running on GKE](https://about.gitlab.com/images/blogimages/how-to-use-agent-based-gitops/14-agentk-on-GitLab.png){: .shadow.medium.center.wrap-text}\nThe **agentk** component running on GKE.\n{: .note.text-center}\n\nAnd, again drill down to its log to see, how the **agentk** component runs synchronizations with the **kas** component:\n\n![agentk log on GKE](https://about.gitlab.com/images/blogimages/how-to-use-agent-based-gitops/15-agentk-log-top-on-GitLab.png){: .shadow.medium.center.wrap-text}\nThe **agentk** log output on GKE.\n{: .note.text-center}\n\nIn the following screenshot, the user sees the log statements indicating that the **agentk** is instantiating a third instance of an nginx pod:\n\n![agentk instantiating a third nginx pod](https://about.gitlab.com/images/blogimages/how-to-use-agent-based-gitops/16-agentk-log-synced-on-GitLab.png){: .shadow.medium.center.wrap-text}\nThe **agentk** instantiating a third nginx pod.\n{: .note.text-center}\n\nThe above sections described an example of the setup needed to install and run the GitLab Agent for Kubernetes as well as how projects are monitored and synchronized from GitLab to a running K8s cluster.\n\n## Conclusion\n\nWe have gone over the setup and use of the Agent, which is an integral part of our pull-based or agent-based approach to GitOps. We also covered a GitOps flow that leveraged this agent-based approach, which is a good choice for Kubernetes shops that need to keep their clusters secured and behind their firewall. This approach comes with its drawbacks in that you need to maintain the agents, which also consume the resources of your infrastructure components. In part two of this series, we will discuss the push-based or agentless approach to GitOps.\n\nCover image by [Vincent Ledvina](https://unsplash.com/@vincentledvina?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText) on [Unsplash](https://unsplash.com/s/photos/grand-tetons?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText)\n{: .note}\n",[550,983,9,741,913],{"slug":1992,"featured":6,"template":699},"how-to-use-agent-based-gitops","content:en-us:blog:how-to-use-agent-based-gitops.yml","How To Use Agent Based Gitops","en-us/blog/how-to-use-agent-based-gitops.yml","en-us/blog/how-to-use-agent-based-gitops",{"_path":1998,"_dir":246,"_draft":6,"_partial":6,"_locale":7,"seo":1999,"content":2005,"config":2011,"_id":2013,"_type":14,"title":2014,"_source":16,"_file":2015,"_stem":2016,"_extension":19},"/en-us/blog/how-we-designed-the-gitlab-reference-architectures",{"title":2000,"description":2001,"ogTitle":2000,"ogDescription":2001,"noIndex":6,"ogImage":2002,"ogUrl":2003,"ogSiteName":686,"ogType":687,"canonicalUrls":2003,"schema":2004},"How we designed the GitLab Reference Architectures","Take a look back with us as we dive into our Reference Architectures design journey to help users easily deploy GitLab at scale. Learn our goals, process, and what's happened in the five years since.","https://res.cloudinary.com/about-gitlab-com/image/upload/v1750098651/Blog/Hero%20Images/Blog/Hero%20Images/blog-image-template-1800x945%20%282%29_52vS9ne2Hu3TElOeHep0AF_1750098651525.png","https://about.gitlab.com/blog/how-we-designed-the-gitlab-reference-architectures","\n                        {\n        \"@context\": \"https://schema.org\",\n        \"@type\": \"Article\",\n        \"headline\": \"How we designed the GitLab Reference Architectures\",\n        \"author\": [{\"@type\":\"Person\",\"name\":\"Grant Young\"}],\n        \"datePublished\": \"2024-10-02\",\n      }",{"title":2000,"description":2001,"authors":2006,"heroImage":2002,"date":2008,"body":2009,"category":717,"tags":2010},[2007],"Grant Young","2024-10-02","We introduced the first [GitLab Reference Architectures](https://docs.gitlab.com/ee/administration/reference_architectures) five years ago. Originally developed as a partnership between the GitLab Test Platform (formally Quality Engineering) and Support teams, along with other contributors, these architectures aim to provide scalable and elastic starting points to deploy GitLab at scale, tailored to an organization's target load.\n\nSince their debut, we've been thrilled to see the impact these architectures have had on our customers as they navigate their DevSecOps journey. We continue to iterate, expand, and refine the architectures, reflecting our commitment to providing you with the latest, best-in-class guidance on deploying, scaling, and maintaining your GitLab environments.\n\nIn recognition of the five-year milestone, here is a peek behind the curtain on _how_ we designed the Reference Architectures and how that design still applies today.\n\n## The problem\n\nBefore introducing the Reference Architectures, we frequently heard from our customers about the hurdles they faced when deploying GitLab at scale to meet their performance and availability goals.\n\nWhile every GitLab environment can be considered a little unique because of the need to meet a customer's own requirements, we recognized from running GitLab.com, as well as from our larger customers, that there were common fundamentals to deploying GitLab at scale that were worth sharing. Our objective was to address customer needs while promoting deployment best practices to reduce drift and increase alignment.\n\nSimultaneously, we wanted to significantly expand our performance testing efforts. The goals of this expansion were to provide our engineering teams with a deeper understanding of performance bottlenecks, to drive improvements in GitLab's performance, and to continuously test the application moving forward to ensure it remained performant. However, to conduct meaningful performance tests, we needed a standardized GitLab environment design capable of handling the target loads.\n\nEnter the Reference Architectures.\n\n## The goals\n\nWith the need for a common architecture clear, we turned next to set the goals of this initiative, which ultimately became the following:\n\n- Performance: Ensure the architecture can handle the target load efficiently.\n- Availability: Maximize uptime and reliability wherever possible.\n- Scalability and elasticity: Ensure the architecture is scalable and elastic to meet individual customer needs.\n- Cost-effectiveness: Optimize resource allocation to avoid unnecessary expenses.\n- Maintainability: Make the architecture deployment and management as straightforward as possible with standardized configurations.\n\nIt's crucial to note that these goals were not in order and they are goals we stay true to today.\n\n## The process\n\nOnce the goals were set, we faced the challenge of designing an architecture, validating it, and making sure that it was fit for purpose and met those goals.\n\nThe process itself was relatively simple in design:\n\n- Gather metrics on existing environments and the loads they were able to handle.\n- Define a prototype architecture based on these metrics.\n- Build and test the environment to validate.\n- Adjust the environment iteratively based on the test results and metrics until we had a validated architecture that met the goals.\n\nWhile simple in design, this, of course, was not the case in practice so we got to work.\n\nFirst, we collected and reviewed the data. To that end, we reviewed metrics and logging data from GitLab.com as well as several participating large customers to correlate the environment sizes deployed to the load they were handling. To achieve this, we needed an objective and quantifiable way to measure that load across any environment, and for that we used **Requests per Seconds (RPS)**. With RPS we could see the concurrent load each environment handled and correlate this to the user count accordingly. Specifically, a user count would correlate to the full manual and automated load (such as continuous integration). From that data, we were able to correlate this across several environment sizes and start to pick out common patterns for the architectures.\n\nNext, we started with a prototype architecture that aimed to meet the goals while cross-referencing with the data we collected. In fact, we actually started this step in conjunction with the first step initially as we had a good enough idea of where to start: Taking the fundamental GitLab.com design and scaling it down for individual customer loads in cost-effective ways. This allowed us to start performance testing the prototype with the data we were analyzing to corroborate accordingly. After quite a few iterations, we had a starting point for our prototype architecture.\n\nTo thoroughly test and validate the architecture we needed to turn to performance testing and define our methodology. The approach was to target our most common endpoints with a representative test data set at RPS loads that were also representative. Then, although we had manually built the prototype architecture, we knew we needed tooling to automatically build environments and handle tasks such as updates. These efforts resulted in the [GitLab Performance Tool](https://about.gitlab.com/blog/how-were-building-up-performance-testing-of-gitlab/) and [GitLab Environment Toolkit](https://about.gitlab.com/blog/why-we-are-building-the-gitlab-environment-toolkit-to-help-deploy-gitlab-at-scale/), which I blogged about previously and which we continue to use to this day (and you can use too!).\n\nWith all the above in place we started the main work of validating the prototype architecture through multiple cycles of testing and iterating. In each cycle, we would performance test the environment, review the results and metrics, and adjust the environment accordingly. Through iteration we were able to identify what failures were real application performance issues and what were environmental, and eventually we had our first architecture. That architecture is now known as the [200 RPS or 10,000-user Reference Architecture](https://docs.gitlab.com/ee/administration/reference_architectures/10k_users.html).\n\n![GitLab Reference Architecture - 200 RPS](https://res.cloudinary.com/about-gitlab-com/image/upload/v1750098658/Blog/Content%20Images/Blog/Content%20Images/reference_architecture_aHR0cHM6_1750098658326.png)\n\n## Where Reference Architectures are today\n\nSince publishing our first validated Reference Architecture, the work has never stopped! We like to describe the architectures as living documentation, as they're constantly being improved and expanded with additions such as:\n\n- various Reference Architecture sizes based on common deployments\n- non-highly available sizes for smaller environments\n- full step-by-step documentation in collaboration with our colleagues in Technical Writing and Support\n- expanded guidance and new naming scheme to help with right sizing, scaling, and how to deal with outliers such as monorepos\n- cloud native hybrid variants where select components are run in Kubernetes\n- recommendations and guidance for cloud provider services\n- and more! Check out the [update history](https://docs.gitlab.com/ee/administration/reference_architectures/#update-history) section in the Reference Architecture documentation!\n\nAll this is driven by our [comprehensive testing program](https://docs.gitlab.com/ee/administration/reference_architectures/#validation-and-test-results) that we built alongside the Reference Architectures to continuously test that they remain fit for purpose against the latest GitLab code _every single week_ and to catch any unexpected performance issues early.\n\nAnd we're thrilled to see these efforts have helped numerous customers to date as well as our own engineering teams deliver new, exciting services. In fact, our engineering teams used the Reference Architectures to develop [GitLab Dedicated](https://about.gitlab.com/dedicated/). Five years on, our commitment is stronger than ever. The work very much continues in the same way it started to ensure you have the best-in-class guidance for your DevSecOps journey.\n\n> Learn more about [GitLab Reference Architectures](https://docs.gitlab.com/ee/administration/reference_architectures/).\n",[9,786,719,915,1007],{"slug":2012,"featured":91,"template":699},"how-we-designed-the-gitlab-reference-architectures","content:en-us:blog:how-we-designed-the-gitlab-reference-architectures.yml","How We Designed The Gitlab Reference Architectures","en-us/blog/how-we-designed-the-gitlab-reference-architectures.yml","en-us/blog/how-we-designed-the-gitlab-reference-architectures",{"_path":2018,"_dir":246,"_draft":6,"_partial":6,"_locale":7,"seo":2019,"content":2025,"config":2031,"_id":2033,"_type":14,"title":2034,"_source":16,"_file":2035,"_stem":2036,"_extension":19},"/en-us/blog/humangeo-switches-jenkins-gitlab-ci",{"title":2020,"description":2021,"ogTitle":2020,"ogDescription":2021,"noIndex":6,"ogImage":2022,"ogUrl":2023,"ogSiteName":686,"ogType":687,"canonicalUrls":2023,"schema":2024},"HumanGeo switched from Jenkins to GitLab and cut costs by 1/3","Management overhead was bogging down the team at HumanGeo. GitLab freed up more than just cash.","https://res.cloudinary.com/about-gitlab-com/image/upload/v1749680315/Blog/Hero%20Images/humangeo-switches-jenkins-to-gitlab.jpg","https://about.gitlab.com/blog/humangeo-switches-jenkins-gitlab-ci","\n                        {\n        \"@context\": \"https://schema.org\",\n        \"@type\": \"Article\",\n        \"headline\": \"HumanGeo switched from Jenkins to GitLab and cut costs by 1/3\",\n        \"author\": [{\"@type\":\"Person\",\"name\":\"William Chia\"}],\n        \"datePublished\": \"2017-11-14\",\n      }",{"title":2020,"description":2021,"authors":2026,"heroImage":2022,"date":2028,"body":2029,"category":717,"tags":2030},[2027],"William Chia","2017-11-14","\n\nAs a software development company, [HumanGeo](http://www.thehumangeo.com/) ships a lot of code. Specializing in geospatial visualization, they have clients in every sector from video game companies to government agencies. The ability to manage multiple projects, iterate quickly, and operate at scale is critical to their success. Over time, a robust DevOps practice has evolved to allow them to quicken their pace of innovation. But traditional tools in their stack, like Jenkins CI, haven’t be able to deliver.\n\n\u003C!-- more -->\n\nI recently caught up with [Justin Shelton](https://twitter.com/kwonstant), an engineer at HumanGeo, to talk about their expanded use of GitLab and how it’s improved both their workflow and budget. Here’s what he had to say:\n\n## Ease of use cuts admin time by 96%\n\n**William**: Can you tell me about the benefits you’ve seen from GitLab in terms of ease-of-use?\n\n**Justin**: Defining CI as code fits great with the \"Infrastructure as Code\" philosophy. We already push hard to have AWS environments expressed in CloudFormation templates, provisioning via Ansible, and so on. With GitLab CI, we can manage our CI pipeline the same way – with code.\n\nManaging YAML for Domain Specific Language (DSL) is way easier than managing Groovy for Jenkinsfiles (or most other config formats, for that matter). YAML is far more widespread and easy to understand, so more developers at junior and senior levels are exposed to it. The path to getting smart on writing GitLab CI DSL is much faster than coming up to speed on Groovy. While Jenkins is overwhelmingly customizable and familiar, it became Yet Another Thing to Manage™. In the end, GitLab CI shares a lot of the same (and in some cases more) configuration options.\n\nAs full stack engineers we do a lot of our own systems administration. Reducing our platform management burden is a huge plus. We used to spend a 5-6 hours each month managing Jenkins and keeping it running. Now, I might spend 10-15 minutes a month managing GitLab CI.\n\n## Flexible CI runners cuts costs 33%\n\n**William**: In [your blog post](http://blog.thehumangeo.com/gitlab-autoscale-runners.html) you shared that GitLab helped to cut infrastructure costs. How did that work in practice?\n\n**Justin**: The ability to integrate with handlers, like the Docker Machine interface I talk about in the post, is huge for helping to manage costs. We get resources when we need them, and can spin them down when we don't. That saves big money compared to maintaining a large instance and having to manage the JVM size and other factors whenever we run out of space. With Jenkins we used to run a dedicated m2.xlarge on AWS all the time for CI purposes. Now, with GitLab, we are able to run spot instances for only around 40 hours a week, resulting in about 1/3 cost savings. Engineers can change a few config items, and managers can see savings. Win!\n\n## Increasing the pace of innovation\n\n**William**: How else has GitLab adoption impacted your workflow?\n\n**Justin**: The speed of development is huge – new features get added every month, and I get genuinely excited to check out the release notes and update our instance every month. (Another perk is how simple this is, upgrading with two apt commands is as easy as it gets.)\n\n[Auto DevOps](https://docs.gitlab.com/ee/topics/autodevops/) is the thing I'm most excited to dig into further that's come out recently. I'm excited about taking some of our bespoke release processes and tightening them up using this process. We're sticklers for code quality, so the Code Quality features were big, and we want to start utilizing Auto DevOps for canary releases as well.\n\n## Learn exactly how they did it\n\nAt HumanGeo using Jenkins CI proved to be costly in both time and money. Switching to GitLab reduced administration overhead, lowered spend, and increased development velocity. Justin wrote up a post to share all the technical details on [how HumanGeo scaled GitLab CI runners](http://blog.thehumangeo.com/gitlab-autoscale-runners.html). Check it out and let know us know what you think in the comments or on Twitter.\n\n\"[Pipe Dream](https://unsplash.com/photos/T7s_TnKO-dk)\" by [Sharosh Rajasekher](https://unsplash.com/@sharosh) on Unsplash\n{: .note}\n",[1007,9,1005],{"slug":2032,"featured":6,"template":699},"humangeo-switches-jenkins-gitlab-ci","content:en-us:blog:humangeo-switches-jenkins-gitlab-ci.yml","Humangeo Switches Jenkins Gitlab Ci","en-us/blog/humangeo-switches-jenkins-gitlab-ci.yml","en-us/blog/humangeo-switches-jenkins-gitlab-ci",{"_path":2038,"_dir":246,"_draft":6,"_partial":6,"_locale":7,"seo":2039,"content":2044,"config":2049,"_id":2051,"_type":14,"title":2052,"_source":16,"_file":2053,"_stem":2054,"_extension":19},"/en-us/blog/impact-of-the-file-type-variable-change-15-7",{"title":2040,"description":2041,"ogTitle":2040,"ogDescription":2041,"noIndex":6,"ogImage":974,"ogUrl":2042,"ogSiteName":686,"ogType":687,"canonicalUrls":2042,"schema":2043},"Understanding the file type variable expansion change in GitLab 15.7","Learn how the change to file type variable expansion can impact CI jobs that rely on the file contents and what to do.","https://about.gitlab.com/blog/impact-of-the-file-type-variable-change-15-7","\n                        {\n        \"@context\": \"https://schema.org\",\n        \"@type\": \"Article\",\n        \"headline\": \"Understanding the file type variable expansion change in GitLab 15.7\",\n        \"author\": [{\"@type\":\"Person\",\"name\":\"Darren Eastman\"}],\n        \"datePublished\": \"2023-02-13\",\n      }",{"title":2040,"description":2041,"authors":2045,"heroImage":974,"date":2046,"body":2047,"category":805,"tags":2048},[979],"2023-02-13","In GitLab 15.7, we stopped expanding `file type` variables in CI jobs. CI\njobs that rely on the old expansion method will generate errors and not\nwork. Here is a look at how this change came about, the difference in job\noutputs, and what to do next.\n\n\n## Background\n\n\nGitLab CI has long-supported file type CI/CD variables. This is a helpful\nfeature for CI jobs, as a file variable is a simple way to pass values to an\nexternal system. In cases where there is a concern about environment\nvariable size limits, putting the information in a file and using an\nenvironment variable to reference the file is a good option.\n\n\nBefore 15.7, variable expansion expanded the contents of the file referenced\nin a file type variable. Some users found this expansion behavior to be\nquite valuable. In looking at some metrics on GitLab.com, for example, we\nsaw over 1,000 unique projects that used a file variable inside another\nvariable. However, other users did not find this unintended behavior helpful\nand implemented workarounds.\n\n\nAs expected, a file referenced in a file type variable may contain sensitive\ndata. So performing variable expansion on the file contents could expose\nthat data in the build environment. Even though the risk could be somewhat\nmitigated, continuing to expand file type variables was not the right\napproach to ensure the most secure system.\n\n\n## Example of the job output before and after 15.7\n\n\n1. Create a file variable via the GitLab UI. For example: `A_FILE_VAR` with\nthe value `this is some super secret content`.\n\n1. Create a CI job with this content:\n\n\n```\n\ntest_job:\n   stage: test\n   variables:\n     REF_FILE_VAR: $A_FILE_VAR\n   script:\n     - echo $A_FILE_VAR\n     - cat $A_FILE_VAR\n     - echo $REF_FILE_VAR\n     - cat $REF_FILE_VAR\n\n```\n\n\n**Results before 15.7:**\n\n\n```\n\n$ echo $A_FILE_VAR\n\n/builds/test-project-repo/test-project.tmp/A_FILE_VAR\n\n$ cat $A_FILE_VAR\n\nthis is some super secret content\n\n$ echo $REF_FILE_VAR\n\nthis is some super secret content\n\n$ cat $REF_FILE_VAR\n\ncat: can't open 'this': No such file or directory\n\ncat: can't open 'is': No such file or directory\n\ncat: can't open 'some': No such file or directory\n\ncat: can't open 'super': No such file or directory\n\ncat: can't open 'secret': No such file or directory\n\ncat: can't open 'content': No such file or directory\n\n\n```\n\n\n**Results after 15.7:**\n\n\n```\n\n$ echo $A_FILE_VAR\n\n/builds/test-project-repo/test-project.tmp/A_FILE_VAR\n\n$ cat $A_FILE_VAR\n\nthis is some super secret content\n\n$ echo $REF_FILE_VAR\n\n/builds/test-project-repo/test-project.tmp/A_FILE_VAR\n\n$ cat $REF_FILE_VAR\n\nthis is some super secret content\n\n\n```\n\n\nYou will notice in the 15.7+ job output the echo command no longer prints\nthe contents of the file.\n\n\n## What is the current status of the change?\n\n\nWe\n[deprecated](https://docs.gitlab.com/ee/update/deprecations.html#file-type-variable-expansion-in-gitlab-ciyml)\nthis feature in 15.5 and removed it from the code base in\n[15.7](https://gitlab.com/gitlab-org/gitlab/-/issues/29407). However, we\nneglected to include a follow-up removal notice in the 15.7 release, so some\nself-managed customers now upgrading to 15.7+ may have missed the initial\ndeprecation notice.\n\n\n## What do I need to do before upgrading to 15.7 or higher?\n\n\n1. Check your CI jobs for any instances where a file variable is referenced\ninside another variable.\n\n2. Change the references and test the CI jobs.\n\n\n## What’s next\n\n\nSecrets and variable handling are likely some of the most complex areas in a\nDevSecOps platform. On our end, we are continuously refining our processes\nto effectively communicate the potential impact of new features or the\nremoval of existing ones. We also recommend that you reach out to us (the\nVerify team) directly on issues referenced in a release or deprecation\nnotice if it's not clear how a change might affect your CI workflows.\n",[9,696,983],{"slug":2050,"featured":6,"template":699},"impact-of-the-file-type-variable-change-15-7","content:en-us:blog:impact-of-the-file-type-variable-change-15-7.yml","Impact Of The File Type Variable Change 15 7","en-us/blog/impact-of-the-file-type-variable-change-15-7.yml","en-us/blog/impact-of-the-file-type-variable-change-15-7",{"_path":2056,"_dir":246,"_draft":6,"_partial":6,"_locale":7,"seo":2057,"content":2063,"config":2069,"_id":2071,"_type":14,"title":2072,"_source":16,"_file":2073,"_stem":2074,"_extension":19},"/en-us/blog/inside-the-improved-ci-logs-management-experience-for-multi-line-commands",{"title":2058,"description":2059,"ogTitle":2058,"ogDescription":2059,"noIndex":6,"ogImage":2060,"ogUrl":2061,"ogSiteName":686,"ogType":687,"canonicalUrls":2061,"schema":2062},"Inside the improved CI logs management experience for multi-line commands","Reviewing log output for CI/CD jobs with multi-line commands is now easier than ever. Find out why, how to configure your pipelines, and what's ahead.\n\n","https://res.cloudinary.com/about-gitlab-com/image/upload/v1750099499/Blog/Hero%20Images/Blog/Hero%20Images/AdobeStock_639935439_3oqldo5Yt5wPonEJYZOLTM_1750099498739.jpg","https://about.gitlab.com/blog/inside-the-improved-ci-logs-management-experience-for-multi-line-commands","\n                        {\n        \"@context\": \"https://schema.org\",\n        \"@type\": \"Article\",\n        \"headline\": \"Inside the improved CI logs management experience for multi-line commands\",\n        \"author\": [{\"@type\":\"Person\",\"name\":\"Romuald Atchadé\"}],\n        \"datePublished\": \"2024-01-25\",\n      }",{"title":2058,"description":2059,"authors":2064,"heroImage":2060,"date":2066,"body":2067,"category":717,"tags":2068},[2065],"Romuald Atchadé","2024-01-25","Improving the GitLab CI/CD log experience for jobs with multi-line commands\nhas been a long-requested feature. With the latest release of GitLab and\nGitLab Runner, it's now easier to work with the log section for jobs with\nmulti-line commands. In this post, we will describe the experience with the\nnew feature, show you how to enable the new log output in your pipelines,\nand discuss key points regarding CI/CD script execution and log output in\nvarious shells, such as Bash and Powershell.\n\n\n## Overview of multi-line commands\n\n\nFirst, it’s helpful to describe what we mean by a CI job with multi-line\ncommands. In GitLab CI the script keyword is used to specify commands to\nexecute for a CI job. In the example below, the build-job has a single\ncommand, a basic echo statement, to execute in the script block. \n\n\n```\n\n## A pipeline with a single line command in the script block for the\nbuild-job\n\n\nbuild-job:\n  stage: build\n  script:\n    - echo \"this is the script to run for the build job\"\n\n```\n\n\nIf you were to run this pipeline, then the log output in the UI would\ndisplay as follows:\n\nLine 17 - GitLab CI automatically generates a log entry for the command that\nyou specify in the script block.\n\nLine 18 - This is the output of the command that was executed.\n\n\n![Ci log management - image\n2](https://res.cloudinary.com/about-gitlab-com/image/upload/v1750099524/Blog/Content%20Images/Blog/Content%20Images/image5_aHR0cHM6_1750099524655.png)\n\n\nNow as you can imagine, the script that you define in the script block will\nlikely be more complex than the example provided and could very well span\nmultiple lines in the CI/CD pipeline file. \n\n\n```\n\n## A pipeline with a multi-line command in the script block for the\nbuild-job\n\n\nbuild-job:\n  stage: build\n  script:\n       - |\n         echo \"this is a multi-line command\"  # a simple echo statement\n         ls  \n\n```\n\n\nIf you were to run this pipeline, then the log output in the UI would\ndisplay as follows:\n\n\nLine 17 - As in the previous example, GitLab CI automatically generates a\nlog entry for the command that you specify in the script block. You will\nnotice that line 17 only includes the first command in the script block.\nThis makes it more difficult to debug an issue with script execution as you\nwill need to refer back to the source pipeline file to see exactly what\nscript was executed.\n\n\n![CI log management - image\n3](https://res.cloudinary.com/about-gitlab-com/image/upload/v1750099525/Blog/Content%20Images/Blog/Content%20Images/image6_aHR0cHM6_1750099524656.png)\n\n\n## So what’s new?\n\n\nStarting in GitLab 16.7 and GitLab Runner 16.7, you can now enable a feature\nflag titled FF_SCRIPT_SECTIONS, which will add a collapsible output section\nto the CI job log for multi-line command script blocks. This feature flag\nchanges the log output for CI jobs that execute within the Bash shell.\n\n\n![CI log management - image\n4](https://res.cloudinary.com/about-gitlab-com/image/upload/v1750099525/Blog/Content%20Images/Blog/Content%20Images/image3_aHR0cHM6_1750099524658.png)\n\n\nLine 17: Unlike the previous examples, the first thing you will notice in\nthe screenshot above is that by default the log entry for the multi-line\ncommand is collapsed by default.\n\n\nSingle-line commands do not display in a collapsible element.\n\n\nFor multi-line scripts the multi-line command is now a collapsible element,\nso now, when you uncollapse the log entry for line 17, then the log will\ndisplay all of the commands that were executed in the script block.\n\n\n![CI log management - image\n1](https://res.cloudinary.com/about-gitlab-com/image/upload/v1750099525/Blog/Content%20Images/Blog/Content%20Images/image2_aHR0cHM6_1750099524659.png)\n\n\nThere is also the [`custom collapsible\nsection`](https://docs.gitlab.com/ee/ci/jobs/#custom-collapsible-sections)\nfeature, which in combination with this new multi-command output capability\ndoes provide you additional flexibility for displaying log output in the UI.\nHere is how you can use the two features to change the log output. \n\n\n```\n\n## A pipeline with a multi-line command in the script block for the\nbuild-job\n\n\nvariables:\n  FF_PRINT_POD_EVENTS: \"true\"\n  FF_USE_POWERSHELL_PATH_RESOLVER: \"true\"\n  FF_SCRIPT_SECTIONS: \"true\"\n\ncollapsible_job_multiple:\n  stage: build\n  script:\n    - |\n      echo \"{\n        'test': 'data',\n        'test2': 'data2',\n      }\"\n    - |\n      echo \"{\n        'test': 'data',\n        'test2': 'data2',\n      }\"\n    - echo -e \"\\033[0Ksection_start:`date +%s`:my_first_section\\r\\033[0KHeader of the 1st collapsible section\"\n    - echo 'this line should be hidden when collapsed'\n    - |\n      echo \"{\n        'test': 'data',\n        'test2': 'data2',\n      }\"\n    - echo -e \"\\033[0Ksection_start:`date +%s`:second_section\\r\\033[0KHeader of the 2nd collapsible section\"\n    - echo 'this line should be hidden when collapsed'\n    - echo -e \"\\033[0Ksection_end:`date +%s`:second_section\\r\\033[0K\"\n    - echo -e \"\\033[0Ksection_end:`date +%s`:my_first_section\\r\\033[0K\"\n\n```\n\n\nIf you were to run this pipeline with the FF_SCRIPT_SECTIONS feature flag\nset to false, then the log output would be as depicted in the following\nscreenshot.\n\n\n![CI log management - image\n5](https://res.cloudinary.com/about-gitlab-com/image/upload/v1750099524/Blog/Content%20Images/Blog/Content%20Images/image1_aHR0cHM6_1750099524661.png)\n\n\nBut, if you were to run this pipeline with the FF_SCRIPT_SECTIONS feature\nflag set to true, then the log output would be as depicted in the following\nscreenshot.\n\n\n![CI log management - image\n6](https://res.cloudinary.com/about-gitlab-com/image/upload/v1750099525/Blog/Content%20Images/Blog/Content%20Images/image4_aHR0cHM6_1750099524663.png)\n\n\n## What about other shells?\n\n\nAs of the 16.7 release, the collapsible output section in the CI job log for\nmulti-line command script blocks is only visible for CI/CD jobs that are\nexecuted with the Bash shell. CI/CD jobs executed with Powershell is not\ncurrently supported. We plan to add this\n[capability](https://gitlab.com/gitlab-org/gitlab-runner/-/merge_requests/4494)\nin a future release. \n\n\n## What are our future plans?\n\n\nA few features are still needed to improve the CI/CD job log output, and the\n`timestamp` for each log line is one of them. This addition will add missing\nfeatures such as command/section duration.\n\n\n> To learn more about GitLab CI/CD features, refer to the official [CI/CD\ndocumentation](https://docs.gitlab.com/ee/ci/index.html). \n\n\n_Disclaimer: This blog contains information related to upcoming products,\nfeatures, and functionality. It is important to note that the information in\nthis blog post is for informational purposes only. Please do not rely on\nthis information for purchasing or planning purposes. As with all projects,\nthe items mentioned in this blog and linked pages are subject to change or\ndelay. The development, release, and timing of any products, features, or\nfunctionality remain at the sole discretion of GitLab._\n",[9,696,109,742],{"slug":2070,"featured":91,"template":699},"inside-the-improved-ci-logs-management-experience-for-multi-line-commands","content:en-us:blog:inside-the-improved-ci-logs-management-experience-for-multi-line-commands.yml","Inside The Improved Ci Logs Management Experience For Multi Line Commands","en-us/blog/inside-the-improved-ci-logs-management-experience-for-multi-line-commands.yml","en-us/blog/inside-the-improved-ci-logs-management-experience-for-multi-line-commands",{"_path":2076,"_dir":246,"_draft":6,"_partial":6,"_locale":7,"seo":2077,"content":2083,"config":2089,"_id":2091,"_type":14,"title":2092,"_source":16,"_file":2093,"_stem":2094,"_extension":19},"/en-us/blog/introducing-auto-breakfast-from-gitlab",{"title":2078,"description":2079,"ogTitle":2078,"ogDescription":2079,"noIndex":6,"ogImage":2080,"ogUrl":2081,"ogSiteName":686,"ogType":687,"canonicalUrls":2081,"schema":2082},"Introducing Auto Breakfast from GitLab (sort of)","GitLab can't make you breakfast? This is what happens when you tell a GitLab team member whose favorite catchphrase is \"Challenge accepted.\"","https://res.cloudinary.com/about-gitlab-com/image/upload/v1749680054/Blog/Hero%20Images/auto-breakfast.jpg","https://about.gitlab.com/blog/introducing-auto-breakfast-from-gitlab","\n                        {\n        \"@context\": \"https://schema.org\",\n        \"@type\": \"Article\",\n        \"headline\": \"Introducing Auto Breakfast from GitLab (sort of)\",\n        \"author\": [{\"@type\":\"Person\",\"name\":\"Brendan O'Leary\"}],\n        \"datePublished\": \"2018-06-29\",\n      }",{"title":2078,"description":2079,"authors":2084,"heroImage":2080,"date":2086,"body":2087,"category":717,"tags":2088},[2085],"Brendan O'Leary","2018-06-29","\n\nA big part of [GitLab's culture](/company/culture/) is [saying thanks](https://handbook.gitlab.com/handbook/communication/#say-thanks) to one another for doing a great job. That can be anything from helping with a tough technical problem to simply sharing a nice [coffee chat](/company/culture/all-remote/#coffee-chats) to break up the work day. One day a Sales team member thanked someone from Customer Success for a great demo of [GitLab CI/CD](/solutions/continuous-integration/). The customer commented afterwards, \"Okay, what doesn't GitLab do?\"\n\nPlenty of heart-themed emoji reactions followed. We've seen users do some pretty amazing things with GitLab CI/CD, from [ramping up to weekly mobile releases](/blog/continuous-integration-ticketmaster/) to [automating boring Git operations](/blog/automating-boring-git-operations-gitlab-ci/), to [saving 90 percent on EC2 costs](/blog/autoscale-ci-runners/). However, there was one thing we hadn't seen. So in addition to this love, the question also garnered a semi-sarcastic answer:\n\n> It won't make breakfast for you, unfortunately.\n\nNever one to let a Slack conversation go unnoticed, I replied with one of my favorite phrases:\n\n![Challenge Accepted](https://about.gitlab.com/images/blogimages/breakfast-challenge.png){: .shadow.center.medium}\n\nI have to admit that the fact that my status was [`:coffee_parrot:`](https://github.com/jmhobbs/cultofthepartyparrot.com/issues/55) could have been related to my enthusiastic reply...\n\n## The challenge\n\nAt the time I had only a vague idea of how I would accomplish this. Many suggestions about Internet of Things devices followed my comment. And while a toaster with a version of Linux that will never be patched was intriguing, I wanted to do something bigger.\n\nA few years ago some friends got together and bought me an [Anova Sous Vide](https://anovaculinary.com/), knowing that I loved to cook. What they failed to calculate was that having four kids in eight years was counterproductive to learning the time-tested [French cooking method of sous-vide](https://en.wikipedia.org/wiki/Sous-vide). As such, the tool has not had a whole lot of use in its time.\n\nHowever, at this point I thought of two things:\n\n1. I love a new sous-vide egg bite offering from a well-known coffee shop\n1. The Anova Sous Vide uses [bluetooth low energy (BLE)](https://en.wikipedia.org/wiki/Bluetooth_Low_Energy) to allow you to control it through an app\n\n## The recipe (culinary)\n\nWhile I did like the egg bites from a coffee shop that shall remain nameless, I don't have them all the time. I would give them a 5- _star_ rating, but they cost a few more _bucks_ then I’d like to spend 😉 So I found a [sous-vide egg bite recipe](https://recipes.anovaculinary.com/recipe/sous-vide-egg-bites-bacon-gruyere) on Anova's website.\n\n## The recipe (technology)\n\nOnce I had the recipe, all I needed was to reverse engineer the BLE connection, figure out how to get that to work from the command line, set up a project and get it integrated with GitLab CI/CD... no big deal. Luckily I found a fantastic project called PyCirculate that had already worked out a lot of the BLE connection issues with the Anova. It made me wonder if someone else had automated breakfast before... but I've yet to find them!\n\n![Ingredients...Pinterest picture](https://about.gitlab.com/images/blogimages/breakfast-pintrest.png){: .shadow.center.medium}\n\nNow that I had both recipes and all the ingredients, it was time to _*git*_ crackin'... (I can't tell you how happy I was when I thought of that joke. Did I mention I'm a dad?)\n\n### Setting up the breakfast pipeline\n\nOnce I had that project installed and working on my laptop, I uploaded the code to GitLab in the public repository in the [auto-breakfast group](https://gitlab.com/auto-breakfast/eggs/). Next, I installed [GitLab Runner](https://docs.gitlab.com/runner/) on a [RaspberryPi](https://www.raspberrypi.org/). I registered the Pi as a [specific runner](https://docs.gitlab.com/runner/register/) for my project. I used a [runner tag](https://docs.gitlab.com/ee/ci/runners/configure_runners.html#use-tags-to-control-which-jobs-a-runner-can-run) so that I could ensure the cooking job only ran on a device with a Bluetooth connection.\n\n![Specific runner](https://about.gitlab.com/images/blogimages/breakfast-runner.png){: .shadow.small.right.wrap-text}\n\nWhen I run a pipeline on `auto-breakfast/eggs` it uses the RaspberryPi to execute and thus can create the BLE connection to the Anova. With the click of a button in GitLab, my breakfast pipeline was running. All I had to do was sit back, relax, and let GitLab CI/CD do all the work.\n\n![Auto Breakfast pipeline](https://about.gitlab.com/images/blogimages/breakfast-1.JPG){: .shadow.center.medium}\n\n## The results\n\nThe egg bites were great! I even modified the recipe with some great Kerrygold Irish whiskey cheddar cheese. However, I would say that it did take a little more effort to get things set up. However, now that it's done, I have a repeatable, single-button way to cook the recipe again (minus the egg cracking and food processing). Just like CI/CD with a `.gitlab-ci.yml` can help make software build and deployment more reliable and repeatable, it can also make a fantastic breakfast 😎\n\nNot pictured: A very messy kitchen and a very perplexed wife.\n{: .alert .alert-gitlab-purple}\n\n[Photo](https://unsplash.com/photos/I-ykyShydj0?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText) by Leti Kugler on [Unsplash](https://unsplash.com/?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText)\n{: .note}\n",[9,1005],{"slug":2090,"featured":6,"template":699},"introducing-auto-breakfast-from-gitlab","content:en-us:blog:introducing-auto-breakfast-from-gitlab.yml","Introducing Auto Breakfast From Gitlab","en-us/blog/introducing-auto-breakfast-from-gitlab.yml","en-us/blog/introducing-auto-breakfast-from-gitlab",{"_path":2096,"_dir":246,"_draft":6,"_partial":6,"_locale":7,"seo":2097,"content":2103,"config":2108,"_id":2110,"_type":14,"title":2111,"_source":16,"_file":2112,"_stem":2113,"_extension":19},"/en-us/blog/introducing-ci-cd-steps-a-programming-language-for-devsecops-automation",{"title":2098,"description":2099,"ogTitle":2098,"ogDescription":2099,"noIndex":6,"ogImage":2100,"ogUrl":2101,"ogSiteName":686,"ogType":687,"canonicalUrls":2101,"schema":2102},"Introducing CI/CD Steps, a programming language for DevSecOps automation","Inside GitLab’s vision for CI/CD programmability and a look at how we simplified workflow automation.","https://res.cloudinary.com/about-gitlab-com/image/upload/v1749665151/Blog/Hero%20Images/blog-image-template-1800x945__27_.png","https://about.gitlab.com/blog/introducing-ci-cd-steps-a-programming-language-for-devsecops-automation","\n                        {\n        \"@context\": \"https://schema.org\",\n        \"@type\": \"Article\",\n        \"headline\": \"Introducing CI/CD Steps, a programming language for DevSecOps automation\",\n        \"author\": [{\"@type\":\"Person\",\"name\":\"Darren Eastman\"}],\n        \"datePublished\": \"2024-08-06\",\n      }",{"title":2098,"description":2099,"authors":2104,"heroImage":2100,"date":2105,"body":2106,"category":1477,"tags":2107},[979],"2024-08-06","For years, the DevOps industry has tried to simplify how developers create automation scripts or workflows to automatically test a code change and to perform a task with the resulting artifact or binary. Today, we are introducing [CI/CD Steps](https://docs.gitlab.com/ee/ci/steps/), a programming language for DevSecOps automation in experiment phase, as a solution to this challenge. With CI/CD Steps, software development teams can easily create complex automation workflows within GitLab.\n\n## The path to CI/CD Steps\n\nEarly in the company's history, GitLab founders and engineers decided that there must be a tight integration between source code management, the place you store your code, and continuous integration, the automation workflows that test your code changes. And we've continued to evolve that integration, focusing on workflow automation tasks and differentiating from the approaches of CI engines across the industry, including Jenkins CI's domain-specific language, GitHub Actions, and many more. \n\nAnd, yes, I did mean to use the term workflow automation tasks rather than [CI and continuous deployment (CD)](https://about.gitlab.com/topics/ci-cd/). This is simply a result of the code that I have seen our customers develop. In a lot of cases, the platform engineering teams that support development teams using GitLab are writing complex automation scripts (workflows). So we need to embrace a more expansive construct beyond simply CI and CD. In fact, I have seen some developers rave about the flexibility of new CI/CD solutions that allow for modularity and conditionals in writing automation workflows.\n\nAt GitLab, our initial approach for CI authoring was based on YAML. We can endlessly debate the pros and cons of such a choice, but for me, as a [DevOps](https://about.gitlab.com/topics/devops/) practitioner coming from a large Fortune 50 company with a moshpit of Jenkins Groovy code and hundreds of permutations of scripts basically performing the same job, the GitLab CI authoring and execution approach was a breath of fresh air. \n\nThe first time I read a GitLab CI file – this was back in mid-2019 – my first thought was, \"No, it could not be that simple.\" A non-developer can easily grasp the intent of a basic GitLab CI pipeline without prior knowledge of all of the intricacies of the syntax of the execution model. In fact, I had just spent a year working on a team that spent several hours each day helping other development teams debug Jenkins pipelines written in Groovy and trying to figure out how to test, and in some cases build, large Java monoliths; in other cases, tons of microservices.\n\nWhile there are benefits to a GitLab CI YAML-based authoring and a bash script execution type approach, there are also limitations. Limitations that developers or platform engineers bump into as they integrate more complex workflows into their CI pipelines. These issues seem to be amplified at enterprise scale as platform teams are trying to simplify or standardize workflows across multiple development teams. In fact, one of the quotes from a recent customer survey states: “GitLab needs to embrace a post-YAML world for CI.”\n\nSo, over the past two years, our pipeline authoring team, led by Product Manager [Dov Hershkovitch](https://gitlab.com/dhershkovitch), has been working extensively on improving the pipeline authoring experience. They've also been improving the management experience of the building blocks for workflow automation – especially at scale. In fact, a part of this work, the [GitLab CI/CD Catalog](https://about.gitlab.com/blog/ci-cd-catalog-goes-ga-no-more-building-pipelines-from-scratch/), recently became generally available.\n\nThe logical next step was to build a new language for workflow automation.\n\n## Understanding CI/CD Steps\n\nGitLab CI/CD Steps is a concept incubated by our top-notch engineers. In [our documentation](https://docs.gitlab.com/ee/ci/steps/), we describe CI/CD Steps as reusable and composable pieces of a CI job that can be referenced in a GitLab CI pipeline configuration. But what does that really mean and what is the long-term value proposition?\n\nAs I was giving this some thought, a comment from one of our customers (paraphrased here) came to mind:\n\n“CI/CD Steps enables you to compose inputs and outputs for a CI/CD job. With CI/CD Steps, developers can define inputs and outputs and, therefore, use CI/CD Steps as a function as we do in any modern programming language. A key differentiator to a normal CI/CD component is that CI/CD Steps allows the use of the outputs of other steps without GitLab having to know certain values before running the pipeline. With CI/CD Steps, you could more easily auto-cancel redundant jobs when all jobs are running as part of the parent pipeline versus having to use child pipelines.”\n\nHaving CI/CD Steps alongside the current GitLab CI/CD execution mechanism and the [CI/CD component catalog](https://docs.gitlab.com/ee/ci/components/index.html) unlocks so many possibilities for creating and maintaining the most complex CI/CD workflows. \n\nA key feature is reusability. Now, I am not suggesting that once we release CI/CD Steps as generally available, you would immediately start refactoring your currently working CI/CD jobs to CI/CD Steps. Instead, you likely will find opportunities to introduce CI/CD Steps to optimize complex pipeline workflows, and, in doing so, you will begin to reuse a CI/CD Step that you author in multiple pipelines.\n\nCI/CD Steps is a marathon, not a sprint. When we release this in beta (currently targeted for late 2024) and start getting feedback from you, we will learn new information that will guide the evolution of this new CI programming language as well as the new Step Runner, which is designed specifically to run CI/CD Steps alongside the current CI/CD jobs.\n\nI'm sure there will be questions about our strategy: Why did we make certain syntax choices? Why didn't we use Starlark as the basis for this new approach? Why did we create something new that we all have to learn? My boilerplate response is: At GitLab we develop our software in the open. More importantly, as a customer, user, and community member, if you have an idea of how to make it better, we invite you to create a merge request so we can improve this feature together.\n\nWe are the only enterprise software platform where, as users and customers, **you** have a direct say in how the platform evolves and **you** can see the changes happening transparently and in real time. That’s the power of GitLab – we iterate and we collaborate. You have invested in a platform and community that is able to evolve with the ever-changing software industry.\n\n## Create your own CI/CD step\n\nTo get a deeper understanding of CI Steps and our direction, take a look at the detailed refactoring proof-of-concept writeup in [this issue](https://gitlab.com/gitlab-org/step-runner/-/issues/85). [Principal engineer Joe Burnett](https://gitlab.com/josephburnett) walks through in great detail the thought process for refactoring a CI/CD job used as part of our GitLab Runner automated test framework. There are also recommendations noted at the end that will inform the evolution of the CI Steps syntax.\n\nThen check out the [CI/CD Steps tutorial](https://docs.gitlab.com/ee/tutorials/setup_steps/) and try creating your own CI/CD step. We recently released the `run` keyword, so testing out a CI/CD step will be simpler than previous examples that required using environment variables. This feature set is experimental so please share your experiences on the [feedback issue](https://gitlab.com/gitlab-org/gitlab/-/issues/460057). There also is a separate feedback issue if you are testing the [Run GitHub Actions with CI/CD Steps experimental feature](https://docs.gitlab.com/ee/ci/steps/#actions).\n\nWe look forward to working with you on this journey to continuously improve the GitLab CI/CD authoring experience.\n\n## Read more\n- [CI/CD Catalog goes GA](https://about.gitlab.com/blog/ci-cd-catalog-goes-ga-no-more-building-pipelines-from-scratch/)\n- [FAQ: GitLab CI/CD Catalog](https://about.gitlab.com/blog/faq-gitlab-ci-cd-catalog/)\n- [What is CI/CD?](https://about.gitlab.com/topics/ci-cd/)\n- [The basics of CI](https://about.gitlab.com/blog/basics-of-gitlab-ci-updated/)\n",[496,109,696,9,983],{"slug":2109,"featured":91,"template":699},"introducing-ci-cd-steps-a-programming-language-for-devsecops-automation","content:en-us:blog:introducing-ci-cd-steps-a-programming-language-for-devsecops-automation.yml","Introducing Ci Cd Steps A Programming Language For Devsecops Automation","en-us/blog/introducing-ci-cd-steps-a-programming-language-for-devsecops-automation.yml","en-us/blog/introducing-ci-cd-steps-a-programming-language-for-devsecops-automation",{"_path":2115,"_dir":246,"_draft":6,"_partial":6,"_locale":7,"seo":2116,"content":2122,"config":2128,"_id":2130,"_type":14,"title":2131,"_source":16,"_file":2132,"_stem":2133,"_extension":19},"/en-us/blog/keeping-your-development-dry",{"title":2117,"description":2118,"ogTitle":2117,"ogDescription":2118,"noIndex":6,"ogImage":2119,"ogUrl":2120,"ogSiteName":686,"ogType":687,"canonicalUrls":2120,"schema":2121},"DRY development: A cheatsheet on reusability throughout GitLab","How to follow the DevOps principle of 'don't repeat yourself' to optimize CI/CD.","https://res.cloudinary.com/about-gitlab-com/image/upload/v1749683555/Blog/Hero%20Images/drylights.jpg","https://about.gitlab.com/blog/keeping-your-development-dry","\n                        {\n        \"@context\": \"https://schema.org\",\n        \"@type\": \"Article\",\n        \"headline\": \"DRY development: A cheatsheet on reusability throughout GitLab\",\n        \"author\": [{\"@type\":\"Person\",\"name\":\"Noah Ing\"},{\"@type\":\"Person\",\"name\":\"Joe Randazzo\"}],\n        \"datePublished\": \"2023-01-03\",\n      }",{"title":2117,"description":2118,"authors":2123,"heroImage":2119,"date":2125,"body":2126,"category":717,"tags":2127},[781,2124],"Joe Randazzo","2023-01-03","More than 20 years ago, the book [The Pragmatic\nProgrammer](https://pragprog.com/titles/tpp20/the-pragmatic-programmer-20th-anniversary-edition/)\nbrought attention to the DRY principle, or “Don’t Repeat Yourself.\" This\nprinciple is defined as every piece of knowledge must have a single,\nunambiguous, authoritative representation within a system.\n\n\nThe main problem to solve here is minimizing duplication. As a development\nproject is bombarded with new requests or changing requirements, DevOps\nteams must balance between development of net-new features or maintaining\nexisting code. The important part is how to reduce duplicate knowledge\nacross projects.\n\n\nThis tutorial explores the mechanisms throughout GitLab that leverage the\nDRY principle to cut down on code duplication and standardize on knowledge.\nTo see working examples of reusability in action, take a look at this\n[repository](https://gitlab.com/guided-explorations/gitlab-ci-yml-tips-tricks-and-hacks/dry-repository-a-cheatsheet).\n\n\n## Minimizing duplication in CI/CD\n\n\n### include\n\n[`include`](https://docs.gitlab.com/ee/ci/yaml/index.html#include) can be\nused to transform a single .gitlab-ci.yml file into multiple files to\nimprove readability and minimize duplication. For example, testing,\nsecurity, or deployment workflows can be broken out into separate templates.\nThis also allows\n[ownership](https://docs.gitlab.com/ee/user/project/codeowners/) of the\nfiles.\n\n\n\n```yaml\n\ninclude:\n  - template: CI/Build.gitlab-ci.yml\n  - template: CI/Test.gitlab-ci.yml\n  - template: CI/Security.gitlab-ci.yml\n  - template: CD/Deploy.gitlab-ci.yml\n\n```\n\n\n### YAML anchors\n\n[YAML\nanchors](https://docs.gitlab.com/ee/ci/yaml/yaml_optimization.html#anchors)\ncan be used to reduce repeat syntax and extend blocks of CI workflow,\nincluding jobs, variables, and scripts.\n\n\n```yaml\n\n.test_template: &test_suite\n  image: ruby:2.6\n\nunit_test:\n  \u003C\u003C: *test_suite\n  script:\n    - echo \"Running a test here\"\n\nend_to_end_test:\n  \u003C\u003C: *test_suite\n  script:\n    - echo \"Running a test here\"\n\nsmoke_test:\n  \u003C\u003C: *test_suite\n  script:\n    - echo \"Running a test here\"\n```\n\n\n### extends\n\n[`extends`](https://docs.gitlab.com/ee/ci/yaml/index.html#extends) is\nsimilar to anchors with additional flexibility and readability. The major\ndifference is it can be used with `includes`.\n\n\n```yaml\n\n\n.prepare_deploy:\n  stage: deploy\n  script:\n    - echo \"I am preparing the deploy\"\n  only:\n    - main\n\ndeploy_to_dev:\n  extends: .prepare_deploy\n  script:\n    - echo \"Deploy to dev environment\"\n  environment: dev\n\ndeploy_to_production:\n  extends: .prepare_deploy\n  script:\n    - echo \"Deploy to production environment\"\n  when: manual\n  environment: production\n```\n\n\n### !reference\n\n[`!reference`](https://docs.gitlab.com/ee/ci/yaml/yaml_optimization.html#reference-tags)\nenables the selection of keyword configuration from other job sections and\nreuse in the current session.\n\n\n```yaml\n\n.vars:\n  variables:\n    DEV_URL: \"http://dev-url.com\"\n    STAGING_URL: \"http://staging-url.com\"\n\n.setup_env:\n  script:\n    - echo \"Creating Environment\"\n\n.teardown_env:\n  after_script:\n    - echo \"Deleting Environment\"\n\nintegration_test:\n  variables: !reference [.vars, variables, DEV_URL]\n  script:\n    - !reference [.setup_env, script]\n    - echo \"Run Test\"\n  after_script:\n    - !reference [.teardown_env, after_script]\n\nperformance_test:\n  variables: !reference [.vars, variables]\n  script:\n    - !reference [.setup_env, script]\n    - echo \"Run Test\"\n  after_script:\n    - !reference [.teardown_env, after_script]\n```\n\n\n### Downstream pipelines\n\n[Downstream\npipelines](https://docs.gitlab.com/ee/ci/pipelines/downstream_pipelines.html)\nenable the breakout of microservices and their pipelines. A .gitlab-ci.yml\nfile can be used for each service, and when a file or directory is changed,\nonly that pipeline needs to be triggered improving the awareness and\nreadability of what’s deploying.\n\n\n```yaml\n\nui:\n  trigger:\n    include: ui/.gitlab-ci.yml\n    strategy: depend\n  rules:\n    - changes: [ui/*]\n\nbackend:\n  trigger:\n    include: backend/.gitlab-ci.yml\n    strategy: depend\n  rules:\n    - changes: [backend/*]\n```\n\n\n![Dynamic child\npipeline](https://about.gitlab.com/images/blogimages/2022-02-01-parent-child-vs-multi-project-pipelines/parent-child.png){:\n.shadow}\n\n\n### CI/CD variables\n\n[CI/CD variables](https://docs.gitlab.com/ee/ci/variables/) can be scoped to\na specific level, including the project, group, instance level, or\n.gitlab-ci.yml level. The values can be stored and reused across a group for\nproject inheritance or overwritten at the project level.\n\n\n```yaml\n\nvariables:\n  PROJECT_LEVEL_VARIABLES: \"I am first in line in precedence\"\n  GROUP_LEVEL_VARIABLES: \"I am second in line\"\n  INSTANCE_LEVEL_VARIABLES: \"I am in third place\"\n  GITLAB_CI_YML_LEVEL_VARIABLES: \"I am last in line of precedence\"\n\n```\n\n\n## Creating consistent code reviews across multiple teams\n\n\n### Description templates\n\n[Description\ntemplates](https://docs.gitlab.com/ee/user/project/description_templates.html)\nenable teams to define a consistent workflow for issues or merge requests.\nFor example, the MR template can define a checklist for rolling out to a\nfeature to ensure it’s documented, quality tested, and reviewed by\nappropriate team members. Here are [MR\ntemplates](https://gitlab.com/gitlab-org/gitlab/-/tree/master/.gitlab/merge_request_templates)\nthat GitLab team members use daily.\n\n\n```md\n\n\u003C!-- These templates can be set at the instance or group level to share\namongst the organization:\nhttps://docs.gitlab.com/ee/user/project/description_templates.html#set-instance-level-description-templates\n-->\n\n\n## What does this MR do?\n\n\n\u003C!-- Briefly describe what this MR is about. -->\n\n\n## Related issues\n\n\n\u003C!-- Link related issues below. -->\n\n\n## Create a checklist for the author or reviewer\n\n- [ ] Optional. Consider taking this writing course before publishing a\nchange.\n\n- [ ] Follow the documentation process stated here.\n\n- [ ] Tag this user group if this applies.\n\n\n\n\u003C!-- Quick Actions - See\nhttps://docs.gitlab.com/ee/user/project/quick_actions.html#issues-merge-requests-and-epics\nfor a list of all the quick actions available. -->\n\n\n\u003C!-- Add a label to assign a specific workflow using scoped labels -->\n\n/label ~documentation ~\"type::maintenance\" ~\"docs::improvement\"\n~\"maintenance::refactor\"\n\n\n\u003C!-- Apply draft format automatically -->\n\n/draft\n\n\n\u003C!-- Assign myself or a usergroup -->\n\n/assign me\n\n```\n\n\n### Project templates\n\n[Project\ntemplates](https://docs.gitlab.com/ee/user/group/custom_project_templates.html)\ncan be used to define an initial project structure for when new services are\nbeing developed. This gives a consistent starting point for projects that\ncome equipped with the latest file configurations and defaults.\n\n\n### File templates\n\n[File\ntemplates](https://docs.gitlab.com/ee/administration/settings/instance_template_repository.html)\nare similar to project templates but are default files to choose from when\nadding a new file to your repository. The team then can quickly choose from\nfiles that have best practices baked in and organization defaults.\n\n\n## Defining a Pipeline Center of Excellence project for CI/CD workflows\n\n\nAs you 'productionize' your CI/CD workflows, it’s recommended to create a\n“Pipeline Center of Excellence” project that contains templates, containers,\nor other abstracted constructs that can be adopted throughout the\norganization. This project contains file or CI/CD templates that have the\nbest practices or well-formed workflows defined for development teams to\nquickly adopt (includes) without recreating the wheel. To explore this in\npractice, visit [Pipeline\nCOE](https://gitlab-org.gitlab.io/professional-services-automation/pipelinecoe/pipeline-templates/#/)\ndocumentation written by the GitLab Professional Services team.\n\n\nHave a reusable component to suggest or that we missed? Add a comment to\nthis blog post or suggest a change to this file!\n\n\n## Related posts\n\n- [How to keep up with CI/CD best\npractices](https://about.gitlab.com/blog/how-to-keep-up-with-ci-cd-best-practices/)\n\n- [How to become more productive with GitLab\nCI](https://about.gitlab.com/blog/how-to-become-more-productive-with-gitlab-ci/)\n\n- [A visual guide to GitLab CI/CD\ncaching](https://about.gitlab.com/blog/a-visual-guide-to-gitlab-ci-caching/)\n\n\nCover image by [Federico\nBeccari](https://unsplash.com/@federize?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText)\non [Unsplash](https://www.unsplash.com).\n",[9,696,741,786],{"slug":2129,"featured":6,"template":699},"keeping-your-development-dry","content:en-us:blog:keeping-your-development-dry.yml","Keeping Your Development Dry","en-us/blog/keeping-your-development-dry.yml","en-us/blog/keeping-your-development-dry",{"_path":2135,"_dir":246,"_draft":6,"_partial":6,"_locale":7,"seo":2136,"content":2141,"config":2147,"_id":2149,"_type":14,"title":2150,"_source":16,"_file":2151,"_stem":2152,"_extension":19},"/en-us/blog/little-things-make-a-difference",{"title":2137,"description":2138,"ogTitle":2137,"ogDescription":2138,"noIndex":6,"ogImage":754,"ogUrl":2139,"ogSiteName":686,"ogType":687,"canonicalUrls":2139,"schema":2140},"Little things make a difference","Let's celebrate the small UI refinements that add up to create a big impact","https://about.gitlab.com/blog/little-things-make-a-difference","\n                        {\n        \"@context\": \"https://schema.org\",\n        \"@type\": \"Article\",\n        \"headline\": \"Little things make a difference\",\n        \"author\": [{\"@type\":\"Person\",\"name\":\"Christie Lenneville\"}],\n        \"datePublished\": \"2021-02-12\",\n      }",{"title":2137,"description":2138,"authors":2142,"heroImage":754,"date":2144,"body":2145,"category":1088,"tags":2146},[2143],"Christie Lenneville","2021-02-12","\n\n{::options parse_block_html=\"true\" /}\n\n\n\nWhen you're busy focusing on the big picture of feature improvement work, it can be easy to forget the value of tiny refinements. But when you add them all up, fixing little \"paper cuts\" can have a meaningful impact on user experience. \n\nThat's why I was so excited to see the [GitLab UI Polish Gallery](https://nicolasdular.gitlab.io/gitlab-polish-gallery/) created by [Nicolas Dular](https://gitlab.com/nicolasdular), a Senior Fullstack Engineer on our Growth team. It highlights small refinement contributions &#151; like adjusting alignment, spacing, and type scale &#151; that are easy to overlook. But seeing them in aggregate, you quickly realize what a difference they make.\n\nFor me, the most inspiring part of the gallery was seeing such a diverse group of people contribute to making our product the best it can be. Developers, designers, and members of the wider GitLab community (special shout out to [Yogi](https://gitlab.com/yo)) all care enough about our product experience to put time into small changes.\n\nHere are a few examples, but I encourage you to check out the gallery for yourself!\n\n## Polishing the Jira Connect app\n\nOur [Jira Connect](https://marketplace.atlassian.com/apps/1221011/gitlab-com-for-jira-cloud?hosting=cloud&tab=overview) app helps customers use GitLab in coordination with Jira for a more seamless developer experience. [Libor Vanc](https://gitlab.com/lvanc) (Senior Product Designer) and [Justin Ho](https://gitlab.com/justin_ho) (Senior Frontend Engineer) on our Ecosystem team made some light changes to the app's type scale and CTAs that make the app much simpler to visually parse. What a nice change!\n\n![GitLab Jira Connect app](https://about.gitlab.com/images/blogimages/little-things-make-a-difference/jira-connect-gitlab.png)\n\n## Addressing alignment problems in the merge request widget\n\nMerge requests are central to our user experience, and we're working hard to make the experience exceptional. When Staff Product Designer, [Pedro Moreira da Silva](https://gitlab.com/pedroms), noticed alignment problems in the MR widget, he worked with Senior Frontend Engineer, [Jacques Erasmus](https://gitlab.com/jerasmus), to address them. It was a very subtle change that will impact millions of users.\n\n![Reply box in diffs](https://about.gitlab.com/images/blogimages/little-things-make-a-difference/widget-alignment.png)\n\n## Fixing the vertical alignment in card headers\n\nThis change is so subtle that it's hard to even notice, but the vertical alignment in the card header of our on-demand security scans was off by mere pixels. Product Designer, [Annabel Dunstone Gray](https://gitlab.com/annabeldunstone), noticed the [problem](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/50550#note_480509692) during an MR review, and Frontend Engineer, [Paul Gascou Vaillancourt](https://gitlab.com/pgascouvaillancourt), jumped in to fix it in the same release.\n\n![Card header vertical alignment](https://about.gitlab.com/images/blogimages/little-things-make-a-difference/card-header.png)\n\n## More to come!\n\nWe make visual refinements all of the time, so this is just a start to what you'll see in the [GitLab UI Polish Gallery](https://nicolasdular.gitlab.io/gitlab-polish-gallery/). I'll personally be checking in from time to time to remind myself of the little things that make a big difference.\n\n",[9,808],{"slug":2148,"featured":6,"template":699},"little-things-make-a-difference","content:en-us:blog:little-things-make-a-difference.yml","Little Things Make A Difference","en-us/blog/little-things-make-a-difference.yml","en-us/blog/little-things-make-a-difference",{"_path":2154,"_dir":246,"_draft":6,"_partial":6,"_locale":7,"seo":2155,"content":2161,"config":2167,"_id":2169,"_type":14,"title":2170,"_source":16,"_file":2171,"_stem":2172,"_extension":19},"/en-us/blog/manager-of-frances-fr-domain-selects-gitlab",{"title":2156,"description":2157,"ogTitle":2156,"ogDescription":2157,"noIndex":6,"ogImage":2158,"ogUrl":2159,"ogSiteName":686,"ogType":687,"canonicalUrls":2159,"schema":2160},"France's .fr domain manager selects GitLab for security","Afnic looks to The One DevOps Platform to modernize its software development with automation, security and compliance, and support for multi-cloud environments.","https://res.cloudinary.com/about-gitlab-com/image/upload/v1749667869/Blog/Hero%20Images/afniclogo.png","https://about.gitlab.com/blog/manager-of-frances-fr-domain-selects-gitlab","\n                        {\n        \"@context\": \"https://schema.org\",\n        \"@type\": \"Article\",\n        \"headline\": \"Manager of France's .fr domain selects GitLab for its DevSecOps capabilities\",\n        \"author\": [{\"@type\":\"Person\",\"name\":\"GitLab\"}],\n        \"datePublished\": \"2022-05-19\",\n      }",{"title":2162,"description":2157,"authors":2163,"heroImage":2158,"date":2164,"body":2165,"category":1131,"tags":2166},"Manager of France's .fr domain selects GitLab for its DevSecOps capabilities",[1474],"2022-05-19","Association Française pour le Nommage Internet en Coopération ([Afnic](https://www.afnic.fr/en/)) is a longstanding nonprofit in France that manages .fr domain names. Chosen 20 years ago by the French State to operate the .fr country code top-level domain, Afnic’s motto is “reliability first.” Afnic uses GitLab, The One DevOps Platform, to help sustain that motto through modernization of its software development environment.\n\nAfnic’s mission as the French National Top Level Domain Registry is to bring together public authorities, Internet users, and domain name professionals to build a secure and stable Internet, open to innovation and in which the French Internet community plays a leading role. Outages of such a digital service could prevent the provisioning of other services that rely on it and could thus have an impact on key economic and societal activities.\n\nAfnic started using GitLab about four years ago to build and secure the brand-new version of its Shared Registry System (SRS). The SRS is a platform that manages the domain names from the subscription of a domain name to the publication in the DNS database and all the updates during its life, including contacts, server names, and DNSSEC keys, according to Richard Coffre, Afnic’s principal product manager.\n\nSince the project began, all the technologies have changed. Previously, Afnic’s team was mainly using Java and Perl and now they use [Kubernetes](/solutions/kubernetes/), Angular, the latest version of Java, and Docker, among others. Security is paramount, and the team is using private clouds. That means Afnic has its own data centers in France and in colocation facilities all over the world.\n\n## Modernizing software development with automation and integration\n\nAfnic selected GitLab to automate and integrate processes during the deployment process. Previously, the majority of things were done manually and now Afnic’s team wants to follow [DevSecOps philosophy and governance](/topics/devsecops/). They wanted one DevOps platform with state-of-the-art [CI/CD](/topics/ci-cd/) capabilities, the ability to quickly onboard new developers, and features to improve compliance and monitoring functionality.\n\nNow, Gitlab is one of the core components of Afnic’s systems.\n\nThe company’s use of GitLab expanded as they deployed new versions of Java and Docker and other technologies. “We wanted to take a big step to align our technology with the state of the market,” Coffre says, and after surveying the development team, the choice was GitLab.\n\nThe team is integrating GitLab with Jira, which is providing a lot of value, he adds.\n\nNow, in addition to developers, Afnic’s database administrators and network administrators use GitLab. The team is using Docker for images and Ansible. Jira is used for ticketing issues and is linked to GitLab and Confluence as a wiki to create the documentation.\n\n## What GitLab brings to the table\n\nThe goal for Afnic is to increase automation and to have everything in the same place and for anyone to be able to get at the proper version anytime. “That's the strength of GitLab,\" Coffre says. “That's also why we chose it because it's one of the leaders. Like many modern source code management systems, GitLab allows our developers to concurrently create source code. But it does it easily, giving us the possibility to do it safely, remembering our motto.\"\n\nPreviously Afnic used only open source tools that they had to customize, which Coffre says was not efficient on a daily basis. To manage source code properly, the team syncs it to GitLab. The strong focus on community contributions “is a guarantee that its features match the developers’ needs, especially regarding CI/CD,” he adds. \n\nWhen new developers join Afnic, it is very easy to onboard them to GitLab, he says. Another benefit is the cost savings because developers don’t lose source code. There is a time-saving metric, too, because if there is an issue in GitLab, it just requires someone to patch it. \n\nNow developers can focus on higher-value strategic tasks like security and vulnerability compliance, and not manual tests and delays, etc. That frees up developers to focus on their job managing DNS databases because the GitLab platform manages the software development lifecycle end-to-end. Coffre says, “GitLab will provide the foundational platform for all Afnic’s software products moving forward. We have experienced great benefits so far and we are excited to expand our use of this platform into the future”.",[741,784,9,696],{"slug":2168,"featured":6,"template":699},"manager-of-frances-fr-domain-selects-gitlab","content:en-us:blog:manager-of-frances-fr-domain-selects-gitlab.yml","Manager Of Frances Fr Domain Selects Gitlab","en-us/blog/manager-of-frances-fr-domain-selects-gitlab.yml","en-us/blog/manager-of-frances-fr-domain-selects-gitlab",{"_path":2174,"_dir":246,"_draft":6,"_partial":6,"_locale":7,"seo":2175,"content":2180,"config":2185,"_id":2187,"_type":14,"title":2188,"_source":16,"_file":2189,"_stem":2190,"_extension":19},"/en-us/blog/managing-multiple-environments-with-terraform-and-gitlab-ci",{"title":2176,"description":2177,"ogTitle":2176,"ogDescription":2177,"noIndex":6,"ogImage":1589,"ogUrl":2178,"ogSiteName":686,"ogType":687,"canonicalUrls":2178,"schema":2179},"Managing multiple environments with Terraform and GitLab CI","This tutorial shows how to set up and manage three different environments in one project using GitLab CI and Terraform.","https://about.gitlab.com/blog/managing-multiple-environments-with-terraform-and-gitlab-ci","\n                        {\n        \"@context\": \"https://schema.org\",\n        \"@type\": \"Article\",\n        \"headline\": \"Managing multiple environments with Terraform and GitLab CI\",\n        \"author\": [{\"@type\":\"Person\",\"name\":\"Sophia Manicor\"},{\"@type\":\"Person\",\"name\":\"Noah Ing\"}],\n        \"datePublished\": \"2023-06-14\",\n      }",{"title":2176,"description":2177,"authors":2181,"heroImage":1589,"date":2182,"body":2183,"category":784,"tags":2184},[1771,781],"2023-06-14","\n\nUsing multiple environments ensures that your infrastructure as code (IaC) is rigorously tested before it is deployed. This tutorial will show a setup of how to manage **three different environments in one project** using GitLab CI and Terraform.\n\n## Prerequisites\n- Working knowledge of [GitLab CI/CD](https://docs.gitlab.com/ee/ci/introduction/index.html#continuous-integration)\n- An AWS / GCP account (where you will deploy to)\n- Working knowledge of Terraform\n- 5 minutes\n\n## Multiple environments\nIn this tutorial, we have three environments set up: dev, staging, and production.\n- Dev: This should be where all the experimental changes go. This environment is intended to develop new features and/or test out new changes.\n- Staging: After you have confirmed your changes in dev, this environment should have parity with the production environment.\n- Production: This environment has the latest versions of infrastructure and applications are live.\n\n## File structure\nFor each environment we set up a corresponding folder at the root level: folders are named dev, staging, and production respectively. Each folder stores all the Terraform infrastructure configuration for the corresponding environment. Within each of these folders, we created a CI file for that environment. \n\n## .gitlab-ci.yml\n\n### Environment-specific .gitlab-ci.yml\nThe file below is for the dev environment and is in the dev folder. Note that there is a rule with each job that only allows the jobs to run when a file in the dev folder is changed. There is a corresponding file in the staging and production folders that has the same rules to only allow jobs when those specific folders are changed. To keep each CI file running the same jobs we have made use of a helper file. \n\n[Environment-specific GitLab CI](https://gitlab.com/demos/infrastructure/terraform-multi-env/-/blob/main/dev/.gitlab-ci.yml)\n\n```yaml\ninclude:\n  - 'helper.yml'\n\nvariables:\n  TF_ROOT: ./dev  # The relative path to the root directory of the Terraform project\n  TF_STATE_NAME: default      # The name of the state file used by the GitLab-managed Terraform state backend\n  SECURE_ANALYZERS_PREFIX: \"$CI_TEMPLATE_REGISTRY_HOST/security-products\"\n  SAST_IMAGE_SUFFIX: \"\"\n  SAST_EXCLUDED_PATHS: \"spec, test, tests, tmp\"\n  PLAN: plan.cache\n  PLAN_JSON: plan.json\n\n\ncache:\n  key: \"${TF_ROOT}\"\n  paths:\n    - ${TF_ROOT}/.terraform/\n\nfmt-dev:\n  extends: .fmt\n  rules:\n      - changes:\n          - dev/**/*\n\nvalidate-dev:\n  extends: .validate\n  rules:\n      - changes:\n          - dev/**/*\n\nbuild-dev:\n  extends: .build\n  rules:\n      - changes:\n          - dev/**/*\n\nkics-iac-sast-dev:\n  extends: .kics-iac-sast\n  rules:\n      - changes:\n          - dev/**/*\n\ndeploy-dev:\n  extends: .deploy\n  rules:\n      - changes:\n          - dev/**/*\n\ndestroy-dev:\n  extends: .destroy\n  rules:\n      - changes:\n          - dev/**/*\n\n```\n\n### helper.yml\nThis helper file was created at the root level so that it could be referenced by all of the environment-specific files. The [helper.yml](https://gitlab.com/demos/infrastructure/terraform-multi-env/-/blob/main/helper.yml) is where all the heavy lifting is happening. This will make sure that all the jobs throughout the environment-specific file's configuration stays up to date and consistent. In the environment-specific files we 'included' the helper file and extended the jobs outlined below.\n\n```yaml\n\n.fmt:\n  stage: validate\n  script:\n    - cd \"${TF_ROOT}\"\n    - gitlab-terraform fmt\n  allow_failure: true\n\n\n.validate:\n  stage: validate\n  script:\n    - cd \"${TF_ROOT}\"\n    - gitlab-terraform validate\n\n\n.build:\n  stage: build\n  before_script:\n    - apk --no-cache add jq\n    - alias convert_report=\"jq -r '([.resource_changes[]?.change.actions?]|flatten)|{\\\"create\\\":(map(select(.==\\\"create\\\"))|length),\\\"update\\\":(map(select(.==\\\"update\\\"))|length),\\\"delete\\\":(map(select(.==\\\"delete\\\"))|length)}'\"\n  script:\n    - cd \"${TF_ROOT}\"\n    - gitlab-terraform plan -out=$PLAN\n    - gitlab-terraform plan-json | convert_report > $PLAN_JSON\n  resource_group: ${TF_STATE_NAME}\n  artifacts:\n    paths:\n      - ${TF_ROOT}/plan.cache\n    reports:\n      terraform: ${TF_ROOT}/$PLAN_JSON\n\n.kics-iac-sast:\n  stage: test\n  artifacts:\n    reports:\n      sast: gl-sast-report.json\n  image:\n    name: \"$SAST_ANALYZER_IMAGE\"\n  variables:\n    SEARCH_MAX_DEPTH: 4\n    SAST_ANALYZER_IMAGE_TAG: 3\n    SAST_ANALYZER_IMAGE: \"$SECURE_ANALYZERS_PREFIX/kics:$SAST_ANALYZER_IMAGE_TAG$SAST_IMAGE_SUFFIX\"\n  allow_failure: true\n  script:\n    - /analyzer run\n\n\n.deploy:\n  stage: deploy\n  script:\n    - cd \"${TF_ROOT}\"\n    - gitlab-terraform apply\n  resource_group: ${TF_STATE_NAME}\n  when: manual\n  rules:\n      - changes:\n          - ${TF_ENVIRONMENT}/**/*\n\n.destroy:\n  stage: cleanup\n  script:\n    - cd \"${TF_ROOT}\"\n    - gitlab-terraform destroy\n  resource_group: ${TF_STATE_NAME}\n  when: manual\n\n```\n\n### Root-level .gitlab-ci.yml\n[Root-level GitLab CI](https://gitlab.com/demos/infrastructure/terraform-multi-env/-/blob/main/.gitlab-ci.yml)\n\nThe file that brings everything above together is the root-level CI file. This will be what the pipeline initially references when run. The [root-level GitLab CI](https://gitlab.com/demos/infrastructure/terraform-multi-env/-/blob/main/.gitlab-ci.yml) is where all of the stages and container images are defined. Note that they are inheriting `.gitlab-ci.yml` from each of the individual folders themselves.\n\n```yaml\ninclude:\n  - 'dev/.gitlab-ci.yml'\n  - 'staging/.gitlab-ci.yml'\n  - 'production/.gitlab-ci.yml'\n  \nimage:\n  name: \"$CI_TEMPLATE_REGISTRY_HOST/gitlab-org/terraform-images/releases/1.1:v0.43.0\"\n\nstages:          \n  - validate\n  - build\n  - test\n  - deploy\n  - cleanup\n\nvariables:\n  # If not using GitLab's HTTP backend, remove this line and specify TF_HTTP_* variables\n  TF_STATE_NAME: default\n  TF_CACHE_KEY: default\n\n```\n\n## Merge request + promotion through environments\nWith the project set up and GitLab CI’s triggering only based off changes to the individual environment folders, you can now safely promote changes using merge requests. When you want to make a change:\n1. First create a merge request in the dev environment with your *.tf files.\n2. Review the [Terraform integration in merge requests](https://docs.gitlab.com/ee/user/infrastructure/iac/mr_integration.html) to see X changes, X to Add, and X to Remove.\n3. If your changes are as expected, request your team members to review the changes and Terraform code.\n4. Apply the changes to your dev environment and merge in the merge request.\n5. If everything worked as intended, then make the same merge up into the staging environment.\n6. If the staging environment remains stable, make a merge request up into the production environment.\n\n\n## Results\nVoila, and there you have it! **A single project to manage three different infrastructure environments** in a safe way to ensure that your changes to production are tested, reviewed, and approved by the rest of your team members.\n\n",[741,9,786,784],{"slug":2186,"featured":6,"template":699},"managing-multiple-environments-with-terraform-and-gitlab-ci","content:en-us:blog:managing-multiple-environments-with-terraform-and-gitlab-ci.yml","Managing Multiple Environments With Terraform And Gitlab Ci","en-us/blog/managing-multiple-environments-with-terraform-and-gitlab-ci.yml","en-us/blog/managing-multiple-environments-with-terraform-and-gitlab-ci",{"_path":2192,"_dir":246,"_draft":6,"_partial":6,"_locale":7,"seo":2193,"content":2199,"config":2204,"_id":2206,"_type":14,"title":2207,"_source":16,"_file":2208,"_stem":2209,"_extension":19},"/en-us/blog/merge-trains-explained",{"title":2194,"description":2195,"ogTitle":2194,"ogDescription":2195,"noIndex":6,"ogImage":2196,"ogUrl":2197,"ogSiteName":686,"ogType":687,"canonicalUrls":2197,"schema":2198},"How to use merge train pipelines with GitLab","Read here an introduction on what merge trains are, how to use them and how to incorporate them to your GitLab project.","https://res.cloudinary.com/about-gitlab-com/image/upload/v1749667210/Blog/Hero%20Images/merge-train-explained-banner.jpg","https://about.gitlab.com/blog/merge-trains-explained","\n                        {\n        \"@context\": \"https://schema.org\",\n        \"@type\": \"Article\",\n        \"headline\": \"How to use merge train pipelines with GitLab\",\n        \"author\": [{\"@type\":\"Person\",\"name\":\"Veethika Mishra\"}],\n        \"datePublished\": \"2020-12-14\",\n      }",{"title":2194,"description":2195,"authors":2200,"heroImage":2196,"date":2201,"body":2202,"category":717,"tags":2203},[1170],"2020-12-14","This blog post was originally published on the GitLab Unfiltered blog. It was reviewed and republished on 2021-01-20.\n{: .alert .alert-info .note}\n\n[Merge trains](https://docs.gitlab.com/ee/ci/pipelines/merge_trains.html) is a powerful GitLab feature that empowers users to harness the potential of [pipelines for merge results](https://docs.gitlab.com/ee/ci/pipelines/merge_request_pipelines.html) to the fullest and also automatically merge a series of (queued) merge requests (MRs) without breaking the target branch. However, due to the structural complexity of the concept, users are often unable to use it effectively for their projects and play it safe by restricting their usage to MRs that pose minimum or no conflict with the target branch.\n\nAs a [senior product designer for Continuous Integration (CI)](/company/team/#veethikaa), I often deconstruct certain concepts and logic for features related to CI so that I have a strong foundation of understanding when making design proposals. Recently, I had a chance to hold a discussion around a very interesting feature - merge trains — with the team. This post unpacks the concept of merge trains by explaining the difference between merge trains, pipelines for MRs, and pipelines for merge results.\n\n## Pipelines for merge requests\n\nGenerally, when a new merge request is created, a pipeline runs to check if the new changes are eligible to be merged to the target branch. This is called the pipeline for merge requests (MRs). A good practice is to only keep the necessary jobs for validating the changes at this step, so the pipeline doesn’t take a long time to complete and CI minutes are not overused. GitLab allows users to [configure the pipeline for MRs](https://docs.gitlab.com/ee/ci/pipelines/merge_request_pipelines.html) by adding `rules:if: $CI_MERGE_REQUEST_IID` to the jobs they wish to run for MRs.\n\n![Pipeline for merge request](https://about.gitlab.com/images/blogimages/merge-train-explained-pipeline-for-merge-requests.jpg)\n\n### Pipelines for merge results\n\nMerge request pipelines verify the branch in isolation. The target branch may change several times during the lifetime of the MR, and these changes are not taken into consideration. In the time during which the pipeline for the MR runs (and succeeds), if the target branch progresses in the background and a user merges the changes to the target branch, they might eventually end up with a broken target.\n\nWhen a [pipeline for merge results](https://docs.gitlab.com/ee/ci/pipelines/merge_request_pipelines.html) runs, GitLab CI performs a _pretend_ merge against the updated target branch by creating a commit on an internal ref from the source branch, and then runs a pipeline against it. This pipeline validates the result prior to merging, therefore increasing the chances of keeping the target branch green.\n\n![Pipeline for merge results](https://about.gitlab.com/images/blogimages/merge-train-explained-pipeline-for-merge-results.jpg)\n\nWe should keep in mind that this pipeline does not run automatically with every update to the target branch. To learn more about this feature in detail and understand the process of enabling it in your GitLab instance, you can refer to the [official documentation on merge results](https://docs.gitlab.com/ee/ci/pipelines/merged_results_pipelines.html).\n\nHowever, if a long time has passed since the last successful pipeline ran, by the time the MR is ready to be merged, the target branch may have already changed and advanced. If we go ahead and merge your MR without re-running the pipeline for MRs, we could end up with a broken target branch. Merge trains can prevent this from happening.\n\n### About merge trains\n\nPipeline for merge results is an extremely useful feature in itself, but tracking the right slot to merge the feature branch into the target and remembering to run the pipeline manually before doing so is a lot to expect from a developer buried in tasks that involve deep logical thinking.\n\nTo tackle this complexity in workflow, GitLab introduced [the merge trains feature](https://docs.gitlab.com/ee/ci/pipelines/merge_trains.html) in [GitLab Premium 12.0](/releases/2019/06/22/gitlab-12-0-released/#sequential-merge-trains). Merge trains allow users to capitalize on the capabilities of pipelines for merge results to automate the process of merging to the target branch with minimum chances of breaking it.\n\nWith merge trains enabled, a merge request can be added to the train, which takes care of it until merged.\nA merge train can be imagined as a queue of MRs that is automatically managed for you.\n\n#### How do merge trains work?\n\nWhen users queue up their MRs in a merge train, GitLab performs a pretend merge for each source branch on top of the previous branch in the queue, where the first branch on the train is merged against the target branch.\nBy creating a temporary commit for each of these merges, GitLab can run merged result pipelines.\nThe first MR in the queue, after having a successful pipeline run for MRs, gets merged to the target branch.\n\nEvery time a merge request is merged into the target branch, the pipelines for the newly added MRs in the train would run against the target branch and the newly added changes from the recently merged MR and changes that are from MRs already in the train.\n\n![Pipeline for merge results](https://about.gitlab.com/images/blogimages/merge-train-explained-working.gif)\n\nMerge trains carry an immense possibility for innovation with GitLab as a toolchain. But to be able to build upon the concept, it is imperative to have a holistic understanding of the same at the system level.\n\nHopefully, this post does the job of breaking down the concept into layman's terms, thereby opening doors for future collaboration within [stage groups](https://handbook.gitlab.com/handbook/product/categories/) at GitLab.\n\nHave suggestions around improving merge trains? please leave your thoughts on this [epic](https://gitlab.com/groups/gitlab-org/-/epics/5122).\n",[9,696,983,741,915],{"slug":2205,"featured":6,"template":699},"merge-trains-explained","content:en-us:blog:merge-trains-explained.yml","Merge Trains Explained","en-us/blog/merge-trains-explained.yml","en-us/blog/merge-trains-explained",{"_path":2211,"_dir":246,"_draft":6,"_partial":6,"_locale":7,"seo":2212,"content":2218,"config":2225,"_id":2227,"_type":14,"title":2228,"_source":16,"_file":2229,"_stem":2230,"_extension":19},"/en-us/blog/microservices-integrated-solution",{"title":2213,"description":2214,"ogTitle":2213,"ogDescription":2214,"noIndex":6,"ogImage":2215,"ogUrl":2216,"ogSiteName":686,"ogType":687,"canonicalUrls":2216,"schema":2217},"Tackling the microservices repository explosion challenge","Microservices have spawned an explosion of dependent projects with multiple repos, creating the need for an integrated solution – we're working on it right now.","https://res.cloudinary.com/about-gitlab-com/image/upload/v1749662898/Blog/Hero%20Images/microservices-explosion.jpg","https://about.gitlab.com/blog/microservices-integrated-solution","\n                        {\n        \"@context\": \"https://schema.org\",\n        \"@type\": \"Article\",\n        \"headline\": \"It's raining repos: The microservices repo explosion, and what we're doing about it\",\n        \"author\": [{\"@type\":\"Person\",\"name\":\"Aricka Flowers\"}],\n        \"datePublished\": \"2018-11-26\",\n      }",{"title":2219,"description":2214,"authors":2220,"heroImage":2215,"date":2222,"body":2223,"category":717,"tags":2224},"It's raining repos: The microservices repo explosion, and what we're doing about it",[2221],"Aricka Flowers","2018-11-26","\nGone are the days of \"set it and forget it\"-style software development. The increased demand for code and operations on all projects, especially [microservices](/topics/microservices/), means more repos. This calls for a more integrated solution to incorporate testing, security updates, monitoring, and more, says GitLab CEO [Sid Sijbrandij](/company/team/#sytses):\n\n>\"The bar's going up for software development. It's no longer enough to just write the code; you also have to write the tests. It's no longer enough to just ship it; you also have to monitor it. You can no longer make it once and forget about it; you have to stay current with security updates. For every product you make you have to integrate more of these tools. It used to be that only the big projects got all these things, but now every single service you ship should have these features, because other projects are dependent on it. One security vulnerability can be enough to take a company down.\"\n\nAn increasing number of project repos means exponential growth in the number of tools needed to handle them – bad news for those saddled managing project dependencies. A streamlined workflow is essential to alleviate this burden – here's how we want to help you get there.\n\n### Everything under one roof\n\n\"With GitLab, we want to enable you to simply commit your code and have all the tools you need integrated out of the box,\" Sid said. \"You don't have to do anything else. It's monitored; we measure whether your dependencies have a vulnerability and fix it for you automatically. I think that's the big benefit of GitLab; that you don't have to go into stitching together 10 tools for every project that you make.\"\n\nBy using an integrated solution to manage an ever-growing number of microservices, you can avoid having engineers siloed off with their respective teams and tools. Creating visibility among teams and getting rid of the need for handoffs leads to a faster [DevOps lifecycle](/topics/devops/) while also ensuring that your projects deploy and remain stable, Sid explains.\n\n\"Our customers that switched from a fragmented setup and were only able to get projects through that cycle a few times a year are now deploying a few times a week,\" Sid said. \"The ability to go from planning to monitoring it in production is what GitLab brings to the table. We have an ample amount of customer case studies showing how we helped improve their speed.\"\n\n### Better support for microservices\n\nWe are boning up our support of microservices, and have a number of features in the works to improve this area, including [group level Kubernetes clusters](https://gitlab.com/gitlab-org/gitlab-ce/issues/34758), a [global Docker registry browser](https://gitlab.com/gitlab-org/gitlab-ce/issues/49336), and adding the [ability to define multiple pipelines](https://gitlab.com/gitlab-org/gitlab-ce/issues/22972). This is to build on what's already there:\n\n\"We have great support for microservices. GitLab has [multi-project pipelines](/blog/use-multiproject-pipelines-with-gitlab-cicd/) and [can trigger pipelines from multi-projects via API](https://docs.gitlab.com/ee/ci/jobs/ci_job_token.html),\" Sid detailed. \"The CI Working Group of the CNCF (Cloud Native Computing Foundation), the most cloud native organization in the world probably, uses GitLab to test their projects. We've got great support for things like [Kubernetes](/solutions/kubernetes/) and cloud native technologies. In GitLab, every project you have can be attached to a Kubernetes cluster, and GitLab uses that to run everything that’s going on. We know that a lot of our users and customers are using microservices, and we work great with them.\"\n\n### Future focus: best-in-class solutions\n\nGitLab is much more than just version control. Having started with the planning, creating and verifying stages in 2011 and 2012, we’ve had time to make those capabilities very strong. We are now strengthening our offerings in the other steps of the DevOps lifecycle: managing, packaging, releasing, configuring, monitoring and security.\n\n\"We are seeing enormous progress in those areas, but they can't go head to head with the best-in-class solutions just yet. So that's going be the theme for GitLab next year, to make sure each of our solutions is best in class instead of just the three things we started with,\" Sid says. \"And we won't take our eyes off the ball.\"\n\n[Cover image](https://unsplash.com/photos/wplxPRCF7gA) by [Ruben Bagues](https://unsplash.com/@rubavi78) on Unsplash\n{: .note}\n",[9,233,914,784,851],{"slug":2226,"featured":6,"template":699},"microservices-integrated-solution","content:en-us:blog:microservices-integrated-solution.yml","Microservices Integrated Solution","en-us/blog/microservices-integrated-solution.yml","en-us/blog/microservices-integrated-solution",{"_path":2232,"_dir":246,"_draft":6,"_partial":6,"_locale":7,"seo":2233,"content":2239,"config":2244,"_id":2246,"_type":14,"title":2247,"_source":16,"_file":2248,"_stem":2249,"_extension":19},"/en-us/blog/migrate-from-jenkins-update",{"title":2234,"description":2235,"ogTitle":2234,"ogDescription":2235,"noIndex":6,"ogImage":2236,"ogUrl":2237,"ogSiteName":686,"ogType":687,"canonicalUrls":2237,"schema":2238},"How we're improving migrations from Jenkins to GitLab CI/CD","Learn more about our Jenkins Importer category and see what's in the works for easier Jenkins migrations.","https://res.cloudinary.com/about-gitlab-com/image/upload/v1749679556/Blog/Hero%20Images/insights.png","https://about.gitlab.com/blog/migrate-from-jenkins-update","\n                        {\n        \"@context\": \"https://schema.org\",\n        \"@type\": \"Article\",\n        \"headline\": \"How we're improving migrations from Jenkins to GitLab CI/CD\",\n        \"author\": [{\"@type\":\"Person\",\"name\":\"Chrissie Buchanan\"}],\n        \"datePublished\": \"2020-12-08\",\n      }",{"title":2234,"description":2235,"authors":2240,"heroImage":2236,"date":2241,"body":2242,"category":849,"tags":2243},[825],"2020-12-08","\nTeams that want to migrate from Jenkins to [GitLab CI/CD](/topics/ci-cd/) can run into roadblocks in the migration process. After all, going from a complicated plugin environment to GitLab CI/CD isn't exactly an apples to apples comparison. Teams that want to make the switch to GitLab will need help to ease the transition – so what are we doing to make that transition easier?\n\nWe created a Jenkins Importer category direction to bring together documentation and issues around improving the Jenkins migration process. We'll go over a few of the projects that are in progress and our vision for the future of Jenkins migrations.\n\n## What is the Jenkins Importer category?\n\nThe [Jenkins Importer](/direction/verify/jenkins_importer/) category is a collection of tools and documentation to help teams migrate from their Jenkins environment to GitLab CI/CD as easily as possible. Since we're a company that works [public by default](https://handbook.gitlab.com/handbook/values/#public-by-default), we use these category direction pages for information related to upcoming products, features, and functionality, not necessarily for purchasing or planning purposes.\n\nUltimately, our goal is to make at least 80% of the automated tasks easy. Having a Jenkins Importer category helps us organize issues and epics around helping unblock teams from migrating to GitLab CI/CD. This category is currently at a \"minimal\" [level of maturity](/direction/maturity/), meaning the features might be available in the product but are not necessarily in production yet.\n\nWith our work being public, you can see our progress and make contributions or comments on these issues.\n\n## Jenkins Importer: Top priority\n\nOur main epic is about [implementing a wrapper](https://gitlab.com/groups/gitlab-org/-/epics/2779) around the Jenkinsfile Runner. A wrapper is all about creating a minimum viable change (MVC) that will enable teams to run their Jenkins stack within GitLab while they complete their migration.\n\nConverting a complicated Jenkins enterprise environment into GitLab can be especially complex. For some Jenkins users, they may have thousands of pipelines that need to be converted. The idea of a wrapper came from [a comment](https://gitlab.com/groups/gitlab-org/-/epics/2735#note_295172334) on a different issue around improving our [Jenkins migration documentation](https://docs.gitlab.com/ee/ci/migration/jenkins.html). This process can be used to run Jenkins builds in GitLab CI while migrating Jenkinsfiles to the GitLab CI/CD syntax.\n\n## Migrating from Jenkins: Other works in progress\n\nAs we continue to receive feedback from the community and [conduct research](https://gitlab.com/gitlab-org/ux-research/-/issues/765) on use cases, those findings will impact the maturity of this category. While we're focusing on a wrapper because it will have the most initial value, we have other vision items for the Jenkins Importer category as well, which are summarized below.\n\n### Importer for declarative and imperative Jenkins configuration\n\nThis [first issue is a proposal to write a tool](https://gitlab.com/gitlab-org/gitlab/-/issues/208276) that can read the newer declarative or imperative syntax (as opposed to JenkinsFiles, a Groovy DSL) and convert it to a valid `.gitlab-ci.yml` file.\n\n### Importer for scripted Jenkins configuration\n\nThis [second issue is a proposal for a translator](https://gitlab.com/gitlab-org/gitlab/-/issues/208275) that can turn scripted Jenkinsfiles written in Groovy into a YAML syntax.\n\nAt GitLab, everyone can contribute. If this category interests you and you'd like to know how we're making migrations easier, feel free to comment on the public issues. If you're interested in helping GitLab test the Jenkins wrapper, join our [public testing issue](https://gitlab.com/gitlab-org/gitlab/-/issues/215675) for instructions and to provide your feedback.\n\nLearn more about the benefits of single application CI/CD and see how GitLab and Jenkins compare head-to-head.\n\nCover image by [Kenrick Mills](https://unsplash.com/@kenrickmills?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText) on [Unsplash](https://unsplash.com/s/photos/migrate?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText)\n",[9,696],{"slug":2245,"featured":6,"template":699},"migrate-from-jenkins-update","content:en-us:blog:migrate-from-jenkins-update.yml","Migrate From Jenkins Update","en-us/blog/migrate-from-jenkins-update.yml","en-us/blog/migrate-from-jenkins-update",{"_path":2251,"_dir":246,"_draft":6,"_partial":6,"_locale":7,"seo":2252,"content":2258,"config":2264,"_id":2266,"_type":14,"title":2267,"_source":16,"_file":2268,"_stem":2269,"_extension":19},"/en-us/blog/new-default-container-image-gitlab-saas-linux-runnners",{"title":2253,"description":2254,"ogTitle":2253,"ogDescription":2254,"noIndex":6,"ogImage":2255,"ogUrl":2256,"ogSiteName":686,"ogType":687,"canonicalUrls":2256,"schema":2257},"Using Ruby 3.1 as default on GitLab SaaS Linux runners","Learn about the new image and how to ensure CI job compatibility.","https://res.cloudinary.com/about-gitlab-com/image/upload/v1749670766/Blog/Hero%20Images/container-reg-cdn-blog.jpg","https://about.gitlab.com/blog/new-default-container-image-gitlab-saas-linux-runnners","\n                        {\n        \"@context\": \"https://schema.org\",\n        \"@type\": \"Article\",\n        \"headline\": \"How to use Ruby 3.1 as the default container image on GitLab SaaS Runners on Linux\",\n        \"author\": [{\"@type\":\"Person\",\"name\":\"Darren Eastman\"}],\n        \"datePublished\": \"2022-12-13\",\n      }",{"title":2259,"description":2254,"authors":2260,"heroImage":2255,"date":2261,"body":2262,"category":717,"tags":2263},"How to use Ruby 3.1 as the default container image on GitLab SaaS Runners on Linux",[979],"2022-12-13","\nOn January 12, 2023, we will change the [default container](https://docs.gitlab.com/ee/ci/runners/saas/linux_saas_runner.html) image used on GitLab Saas Runners on Linux from Ruby 2.5, which is end of life, to Ruby 3.1.\n\nIf you have specified a container image in your CI/CD job, then there is no impact to you. In other words, your GitLab SaaS CI/CD job will only run in the default container if no image is set for the job in the `.gitlab-ci.yml` pipeline file.\n\nTo check, open the log view of a CI job and note the image used. For example, if you have not added an image to your CI job on GitLab SaaS, then the job log will have the following:\n\n```\nUsing Docker executor with image ruby:2.5 ...\n\n```\n\nIf you have not set a container image in your CI job, then after this change, the job will run in a Ruby 3.1 container.\n\n## How can I check for any build issues on Ruby 3.1?\n\nWhile it is not expected that running a CI/CD job on Ruby 2.5 is incompatible with Ruby 3.1, to check, simply configure the job to run in a Ruby 3.1 container. To do so, edit the `.gitlab-ci.yml` and add the following:\n\n```\ndefault:\n  image: ruby:3.1\n```\n\n## Future plans\n\nIn addition to this change, we plan to [define](https://gitlab.com/gitlab-org/gitlab/-/issues/384992) a new container image maintenance process for GitLab SaaS Runners on Linux. The new policy aims to ensure that the default image used is updated so that it contains the latest security fixes.\n\n_This blog post and linked pages contain information related to upcoming products, features, and functionality. It is important to note that the information presented is for informational purposes only. Please do not rely on this information for purchasing or planning purposes. As with all projects, the items mentioned in this blog post and linked pages are subject to change or delay. The development, release, and timing of any products, features, or functionality remain at the sole discretion of GitLab Inc._\n\n",[741,915,9,696,233],{"slug":2265,"featured":6,"template":699},"new-default-container-image-gitlab-saas-linux-runnners","content:en-us:blog:new-default-container-image-gitlab-saas-linux-runnners.yml","New Default Container Image Gitlab Saas Linux Runnners","en-us/blog/new-default-container-image-gitlab-saas-linux-runnners.yml","en-us/blog/new-default-container-image-gitlab-saas-linux-runnners",{"_path":2271,"_dir":246,"_draft":6,"_partial":6,"_locale":7,"seo":2272,"content":2278,"config":2283,"_id":2285,"_type":14,"title":2286,"_source":16,"_file":2287,"_stem":2288,"_extension":19},"/en-us/blog/new-machine-types-for-gitlab-saas-runners",{"title":2273,"description":2274,"ogTitle":2273,"ogDescription":2274,"noIndex":6,"ogImage":2275,"ogUrl":2276,"ogSiteName":686,"ogType":687,"canonicalUrls":2276,"schema":2277},"GitLab introduces new machine types for GitLab SaaS Linux Runners","GitLab SaaS now offers larger machine types for running CI jobs on Linux.","https://res.cloudinary.com/about-gitlab-com/image/upload/v1749672836/Blog/Hero%20Images/multiple-machine-types-cover.png","https://about.gitlab.com/blog/new-machine-types-for-gitlab-saas-runners","\n                        {\n        \"@context\": \"https://schema.org\",\n        \"@type\": \"Article\",\n        \"headline\": \"GitLab introduces new machine types for GitLab SaaS Linux Runners\",\n        \"author\": [{\"@type\":\"Person\",\"name\":\"Darren Eastman\"}],\n        \"datePublished\": \"2022-09-22\",\n      }",{"title":2273,"description":2274,"authors":2279,"heroImage":2275,"date":2280,"body":2281,"category":1477,"tags":2282},[979],"2022-09-22","Our GitLab SaaS vision is to provide a solution where you can easily choose\nand use the correct type of public cloud-hosted compute resources for your\nCI/CD jobs. In this first iteration towards achieving that vision, we are\npleased to announce that two larger compute machines are generally available\nfor GitLab SaaS Runners on Linux.\n\n\nWith these two machine types, you can now access more choices for your\nGitLab SaaS CI/CD jobs. And with 100% job isolation on an ephemeral virtual\nmachine, and security and autoscaling fully managed by GitLab, you can\nconfidently run your critical [CI/CD](/topics/ci-cd/) jobs on GitLab SaaS.\n\n\n## New machine type details\n\n\nThe new [SaaS Runners on\nLinux](https://docs.gitlab.com/ee/ci/runners/saas/linux_saas_runner.html)\nare a 2 vCPU, 8GB RAM (`saas-linux-medium-amd64`), and a 4 vCPU, 16GB RAM\n(`saas-linux-large-amd64`) machine type. These machine types, powered by the\nlatest generation of Google Compute N2D virtual machines, deliver\nsignificant performance improvements for general-purpose CI workloads. The\nmedium machine type, `saas-linux-medium-amd64`,  is available to all\nsubscriptions (Free, Premium, Ultimate). The large machine type,\n`saas-linux-large-amd64` is only available to paid plans (Premium and\nUltimate) and GitLab for Open Source program members.\n\n\nNote: If you are in a Free plan and tag a CI job with the large machine\ntype, `saas-linux-large-amd64`, you will get an error at the job level and\nthe job will not run.\n\n\n```\n\nThis job is stuck because of one of the following problems. There are no\nactive runners online, no runners for the protected branch, or no runners\nthat match all of the job's tags: saas-linux-large-amd64\n\n\n```\n\n\n## Are the new machine types right for my CI job?\n\n\nThe answer is that it depends. If the CI job is compute-intensive, you will\nlikely see a performance improvement measured by reduced build times. We ran\na series of  [Linux\nkernel](https://gitlab.com/gitlab-org/ci-cd/gitlab-runner-stress/linux-kernel)\nbuilds on the medium machine type to test the potential performance gains\nfor compute-intensive CI jobs.\n\n\n![Linux kernel build CI job execution time\nbenchmark](https://about.gitlab.com/images/blogimages/new-machine-types-gitlab-saas-linux/linux-kernel-build-runner-saas-benchmark_2022-09-22.png)\n\n\nOur testing found an average 41% reduction in CI job execution time for the\nmedium machine types compared to the baseline small machine type. We\nrecommend you experiment with the new machine types for your CI jobs to\ndetermine the right choice based on your build workflows.\n\n\n## Getting started\n\n\nTo get started with the new machine types, simply add a tag to your CI file.\nWithout the tag, a job in your pipeline will automatically run on the small\nmachine type.\n\n\n### Example pipeline configuration\n\n\nIn this example pipeline configuration, `job_001` will run on the default\nLinux SaaS Runner as no machine type tag is defined. The subsequent job,\n`job_002`, in the build stage will run on the medium machine type, and\n`job_003` will run on the large machine type. So you have flexibility within\na GitLab CI/CD pipeline to choose the right machine type for each job.\n\n\n```\n\nstages:\n  - Prebuild\n  - Build\n  - Unit Test\n\njob_001:\n stage: Prebuild\n script:\n  - echo \"this job runs on the default (small) machine type\"\n\njob_002:\n tags: [ saas-linux-medium-amd64 ]\n stage: Build\n script:\n  - echo \"this job runs on the medium machine type\"\n\njob_003:\n tags: [ saas-linux-large-amd64 ]\n stage: Unit Test\n script:\n  - echo \"this job runs on the large machine type\"\n\n```\n\n\n## Understanding the new machine types and cost factors\n\n\nYou can start using the new machine types now with the CI minutes currently\navailable in your plan. The new machine types will consume your CI minutes\nat a different rate than the default (small) machine type based on an\napplied cost factor. If you are a GitLab for Open Source program member,\nthen refer to the [cost factor documentation\npage](https://docs.gitlab.com/ee/ci/pipelines/cicd_minutes.html#cost-factor)\nfor details on how cost factors are applied to your CI/CD jobs.\n\n\n|  | saas-linux-small-amd64 |saas-linux-medium-amd64 |saas-linux-large-amd64\n|\n\n| ------ | ------ |------ |------ |\n\n| CI minutes consumed per 1 minute of build time| 1 |2|3|\n\n\nToday your CI minutes usage report on GitLab SaaS will be an aggregate of\nall of the CI minutes consumed across all the machine types you select in\nyour jobs. In this\n[issue](https://gitlab.com/gitlab-org/gitlab/-/issues/356076), we are\nworking towards adding visibility into usage by each Runner type. So you\nwill soon have more granular reporting of use across the various Runner\nclasses (Linux, Windows, macOS) and machine types we plan to offer.\n\n\n## Feedback\n\n\nAt GitLab, we value your input and use it as a critical sensing mechanism in\nplanning roadmap investments. To provide feedback on the machine types you\nneed on GitLab SaaS Runners on Linux, add a comment to the respective\ncomment thread in this\n[issue](https://gitlab.com/gitlab-org/gitlab/-/issues/373196)\n\n\nCover image by [Julian Hochgesang](https://unsplash.com/@julianhochgesang)\non [Unsplash](https://unsplash.com)\n\n{: .note}\n",[9,696,719,1477],{"slug":2284,"featured":6,"template":699},"new-machine-types-for-gitlab-saas-runners","content:en-us:blog:new-machine-types-for-gitlab-saas-runners.yml","New Machine Types For Gitlab Saas Runners","en-us/blog/new-machine-types-for-gitlab-saas-runners.yml","en-us/blog/new-machine-types-for-gitlab-saas-runners",{"_path":2290,"_dir":246,"_draft":6,"_partial":6,"_locale":7,"seo":2291,"content":2297,"config":2302,"_id":2304,"_type":14,"title":2305,"_source":16,"_file":2306,"_stem":2307,"_extension":19},"/en-us/blog/oidc",{"title":2292,"description":2293,"ogTitle":2292,"ogDescription":2293,"noIndex":6,"ogImage":2294,"ogUrl":2295,"ogSiteName":686,"ogType":687,"canonicalUrls":2295,"schema":2296},"Secure GitLab CI/CD workflows using OIDC JWT on a DevSecOps platform","Learn a new method to authenticate using JWT to increase the security of CI/CD workflows.","https://res.cloudinary.com/about-gitlab-com/image/upload/v1749667094/Blog/Hero%20Images/container-security.jpg","https://about.gitlab.com/blog/oidc","\n                        {\n        \"@context\": \"https://schema.org\",\n        \"@type\": \"Article\",\n        \"headline\": \"Secure GitLab CI/CD workflows using OIDC JWT on a DevSecOps platform\",\n        \"author\": [{\"@type\":\"Person\",\"name\":\"Dov Hershkovitch\"}],\n        \"datePublished\": \"2023-02-28\",\n      }",{"title":2292,"description":2293,"authors":2298,"heroImage":2294,"date":2299,"body":2300,"category":1131,"tags":2301},[1150],"2023-02-28","\n\nSecuring CI/CD workflows can be challenging. This blog post walks you through the problem validation, explores the JWT token technology and how it can be used with OIDC authentication, and discusses implementation challenges with authorization realms. You will learn about the current possibilities and future plans with GitLab 16.0. \n\n### Variables vs. secrets\nVariables are an efficient way to control and inject parameters into your jobs and pipelines, making managing and configuring the CI/CD workflows easier. You can read more about [how to use CI/CD variables](https://about.gitlab.com/blog/demystifying-ci-cd-variables/). An extra layer of security on top of variables to mask and protect, for now, is our “best-effort” to prevent sensitive variables from being accidentally revealed. However, variables are not a drop-in replacement for secrets. [Securing secrets natively](https://gitlab.com/gitlab-org/gitlab/-/issues/217355) is a solution that GitLab aspires to provide. Meanwhile, we recommend storing sensitive information in a dedicated secrets management solution. As a company, we will provide you abilities to integrate and retrieve secrets as part of your CI/CD workflows. \n\n## Security shifting left\nSensitive information like passwords, secret tokens, or shared IDs required to access tools and platforms need to be securely stored. They must also be highly available to their owners and the teams who use them. There are various secrets management solutions and frameworks available. They have addressed one problem but created new problems. For example: \"Which tool is right for our needs?\" More importantly, in software development: \"What's the best way to integrate this into our DevOps processes so that we're secure but still operating as efficiently as possible?\" Ignoring the security protocols in your organization is not an option. However, sensitive information should be stored as securely as possible. Something as simple as an access token stored in plain text can lead to security leaks and business incidents in the worst-case scenarios.\n\n## Initial support for JWT\nThe [JSON Web Token (JWT)](https://en.wikipedia.org/wiki/JSON_Web_Token) aims to build the integration bridge as an open standard for security claims exchange. It is a signed, short-lived, contextualized token that allows everyone to implement authentication between different products securely. The JWT consists of three parts: a header, a payload, and a signature.\n\n- The header represents the type of the token and the encryption algorithm.\n- The signature ensures that the token hasn't been altered.\n- The payload comprises a series of claims representing the information exchanged between two parties, which includes information about a GitLab user (ID, email, login) and the pipeline information (pipeline ID, job ID, environment, and more).\n\n_Example of GitLab JWT payload_\n\n```\n{\n  \"jti\": \"c82eeb0c-5c6f-4a33-abf5-4c474b92b558\",\n  \"iss\": \"gitlab.example.com\",\n  \"iat\": 1585710286,\n  \"nbf\": 1585798372,\n  \"exp\": 1585713886,\n  \"sub\": \"job_1212\",\n  \"namespace_id\": \"1\",\n  \"namespace_path\": \"mygroup\",\n  \"project_id\": \"22\",\n  \"project_path\": \"mygroup/myproject\",\n  \"user_id\": \"42\",\n  \"user_login\": \"myuser\",\n  \"user_email\": \"myuser@example.com\",\n  \"pipeline_id\": \"1212\",\n  \"pipeline_source\": \"web\",\n  \"job_id\": \"1212\",\n  \"ref\": \"auto-deploy-2020-04-01\",\n  \"ref_type\": \"branch\",\n  \"ref_protected\": \"true\",\n  \"environment\": \"production\",\n  \"environment_protected\": \"true\"\n}\n```\nUsing this information (called \"claims\"), you can implement an authentication condition where the token will get rejected if one of those claims does not match. You can use this to restrict access to only the authorized users and jobs in your pipelines.\n\nGitLab 12.10 added [initial support for JWT token-based connections](https://about.gitlab.com/releases/2020/04/22/gitlab-12-10-released/#retrieve-cicd-secrets-from-hashicorp-vault), which was later [enhanced](https://about.gitlab.com/releases/2020/09/22/gitlab-13-4-released/#use-hashicorp-vault-secrets-in-ci-jobs) with the `secrets:` keyword, as well as the `CI_JOB_JWT` predefined CI/CD variable, which is automatically injected into every job in a pipeline. This implementation was restricted to Hashicorp Vault, and users can use it to read secrets directly from the vault as part of their CI/CD workflow.\n \n### OIDC (JWT Version 2)\nThe logic we used to build the initial support for JWT opened up the possibility of connecting to other providers as well, but the first iteration was still restricted to Hashicorp Vault users.\n\nThis problem was addressed in GitLab 14.7 when we [released](https://about.gitlab.com/releases/2022/01/22/gitlab-14-7-released/#openid-connect-support-for-gitlab-cicd) the first \"Alpha\" version of JWT V2, which provided [Open ID Connect (OIDC)](https://openid.net/connect/) support for CI/CD.\n\nOIDC is an identity layer implemented on top of the JSON web token. You can securely authenticate against many products and services that implement OIDC, including AWS, GCP, and many more, making better use of the token's potential. Similar to our first JWT iteration, we added another [predefined CI/CD variable](https://docs.gitlab.com/ee/ci/variables/predefined_variables.html) `CI_JOB_JWT_V2` which is also automatically injected into every job in a CI/CD pipeline.\n\n### Securely store your secrets \nYour software supply chain should include everything needed to deliver and run your software. Securing your supply chain means you need to secure your software and the surrounding (cloud-native) infrastructure. In [GitLab 15.9](https://about.gitlab.com/releases/2023/02/22/gitlab-15-9-released/), we've added additional layers of protection to move our OIDC token from an Experiment to General Availability, increasing the security of your CI/CD workflows. \n\n\n#### Opt-in JWT token\nJSON web tokens (V1 and V2) are stored in CI/CD variables, which are injected automatically into all jobs in a CI/CD pipeline. However, it is likely most jobs in your pipeline do not need the token. In addition to the inefficiency of injecting unused tokens into all jobs in a pipeline, there is a potential security vulnerability. All it takes is one compromised job for this token to be leaked and used by an attacker to retrieve sensitive information from your organization. To minimize this risk, we've added the ability to restrict the token variable from all jobs in your pipeline and expose it only to the specific jobs that need it.\n\nTo declare the JSON web token in a job that needs it, configure the job in the `.gitlab-ci.yml` configuration file following this example:\n\n```yaml\njob_name:\n  id_token:\n    MY_JOB_JWT: # or any other variable name\n  ...\n```\n\nYou can minimize the token exposure across your pipeline, but ensure it is available to the jobs that require it.\n\n#### Audience claim (`aud:`)\nClaims constitute the payload part of a JSON web token and represent a set of information exchanged between two parties. The JWT standard distinguishes between reserved, public, and private claims.\n\nThe audience (`aud:`) claim is a reserved claim, which identifies the audience that the JWT is intended for (the target of the token). In other words, which services, APIs, or products should accept this token. If the audience claim does not match, the token is rejected, so the audience claim is an essential part of software supply chain security.\n\nThe option to configure the audience claim is done in the CI/CD configuration when [declaring the usage of the JWT token](https://docs.gitlab.com/ee/ci/secrets/id_token_authentication.html#id-tokens), if we'll continue from the previous example:\n\n```yaml\njob_name:\n  id_token:\n    MY_JOB_JWT: # or any other variable name\n        aud: \"...\" # mandatory field\n  script:\n    - my-authentication-script.sh MY_JOB_JWT….. # use the declared variables in a script\n  ```\n\nConfiguring the audience claim is mandatory for Vault users that leverage the [GitLab/Vault native integration](https://docs.gitlab.com/ee/ci/secrets/#use-vault-secrets-in-a-ci-job) (using the 'secrets:' keyword).\n\n```yaml\njob_name:\n  secrets:\n    VAULT_JWT_1: # or any other variable name\n      id_token:\n        aud: 'devs' # audience claim configuration\n    STAGING_DATABASE_PASSWORD: # VAULT_JWT_1 is the token to be used\n      vault: staging/db/password@ops\n```\n\n### Breaking changes and backward compatibility \nWe understand the increasing demand to secure your software supply chain. We recognize that many of our current users already use the JWT in what will soon be the \"old JWT method\" (V1). To mitigate this conflict, we've decided that moving to the new (OIDC) JWT method is optional until the next major release (GitLab 16.0). To use the new (OIDC) token, users must opt-in to this change from the UI settings and update the pipeline configuration, as explained in the previous sections. Users can continue using the Experiment or the \"old method\" until GitLab 16.0. (At that point, only the \"new\" (OIDC) JWT token and method will be available.)\n\nSeveral breaking changes were announced for both [Vault users](https://docs.gitlab.com/ee/update/deprecations.html#hashicorp-vault-integration-will-no-longer-use-ci_job_jwt-by-default) and [users of the JWT \"old\" methods](https://docs.gitlab.com/ee/update/deprecations.html#old-versions-of-json-web-tokens-are-deprecated). Those changes are scheduled for GitLab 16.0.\n\n## Three ways to use the JWT token\nThere are three ways to use a JWT to authenticate against different products in your CI/CD pipeline:\n- The \"old\" method, using the `secrets:` keyword and the `CI_JOB_JWT` variable, which is mainly used to integrate with Hashicorp Vault.\n- An \"Alpha\" version that uses the `CI_JOB_JWT_V2` OIDC token to integrate with different cloud providers.\n- A production-ready OIDC token, which is a secured version of the `CI_JOB_JWT_V2` token, used to authenticate with a variety of different products, like Vault, GCP, AWS, and so on.\n\nAll three methods are available until the next major version (GitLab 16.0). At that point, only the secured OIDC token will be available.\n\nTo prepare for this change, you should:\n\n1. Configure your pipelines to use the fully configurable and more secure [id_token](https://docs.gitlab.com/ee/ci/yaml/index.html#id_tokens) keyword.\n2. Enable the [Limit JSON Web Token (JWT) access setting](https://docs.gitlab.com/ee/ci/secrets/id_token_authentication.html#enable-automatic-id-token-authentication), which prevents the old tokens from being exposed to any jobs. (This setting will be permanently enabled for all projects in GitLab 16.0).\n3. If you use GitLab/Hashicorp native integration (using the [secrets:vault](https://docs.gitlab.com/ee/ci/yaml/#secretsvault) keyword), ensure the bound audience is prefixed with `https://`.\n\nThis should ensure a smooth transition to [GitLab 16.0](/upcoming-releases/) without breaking your existing workflows.\n\n\n",[742,786,9,696],{"slug":2303,"featured":6,"template":699},"oidc","content:en-us:blog:oidc.yml","Oidc","en-us/blog/oidc.yml","en-us/blog/oidc",{"_path":2309,"_dir":246,"_draft":6,"_partial":6,"_locale":7,"seo":2310,"content":2316,"config":2323,"_id":2325,"_type":14,"title":2326,"_source":16,"_file":2327,"_stem":2328,"_extension":19},"/en-us/blog/parent-child-vs-multi-project-pipelines",{"title":2311,"description":2312,"ogTitle":2311,"ogDescription":2312,"noIndex":6,"ogImage":2313,"ogUrl":2314,"ogSiteName":686,"ogType":687,"canonicalUrls":2314,"schema":2315},"CI/CD patterns with parent-child and multi-project pipelines","Parent-child pipelines inherit a lot of the design from multi-project pipelines, but they also have differences that make them unique.","https://res.cloudinary.com/about-gitlab-com/image/upload/v1749659961/Blog/Hero%20Images/parent-child-multi-project-pipelines-unsplash.jpg","https://about.gitlab.com/blog/parent-child-vs-multi-project-pipelines","\n                        {\n        \"@context\": \"https://schema.org\",\n        \"@type\": \"Article\",\n        \"headline\": \"Breaking down CI/CD complexity with parent-child and multi-project pipelines\",\n        \"author\": [{\"@type\":\"Person\",\"name\":\"Fabio Pitino\"}],\n        \"datePublished\": \"2022-02-22\",\n      }",{"title":2317,"description":2312,"authors":2318,"heroImage":2313,"date":2320,"body":2321,"category":717,"tags":2322},"Breaking down CI/CD complexity with parent-child and multi-project pipelines",[2319],"Fabio Pitino","2022-02-22","Software requirements change over time. Customers request more features and\nthe application needs to scale well\n\nto meet user demands. As software grows in size, so does its complexity, to\nthe point where we might decide that it's\n\ntime to split the project up into smaller, cohesive components.\n\n\nAs we proceed to tackle this complexity we want to ensure that our CI/CD\npipelines continue to validate\n\nthat all the pieces work correctly together.\n\n\nThere are two typical paths to splitting up software projects:\n\n\n- **Isolating independent modules within the same repository**: For example,\nseparating the UI from the backend,\n  the documentation from code, or extracting code into independent packages.\n- **Extracting code into a separate repository**: For example, extracting\nsome generic logic into a library, or creating\n  independent microservices.\n\nWhen we pick a path for splitting up the project, we should also adapt the\nCI/CD pipeline to match.\n\n\nFor the first path, [GitLab CI/CD](/topics/ci-cd/) provides [parent-child\npipelines](https://docs.gitlab.com/ee/ci/pipelines/downstream_pipelines.html)\nas a feature that helps manage complexity while keeping it all in a\nmonorepo.\n\n\nFor the second path, [multi-project\npipelines](https://docs.gitlab.com/ee/ci/pipelines/downstream_pipelines.html)\n\nare the glue that helps ensure multiple separate repositories work together.\n\n\nLet's look into how these two approaches differ, and understand how to best\nleverage them.\n\n\n## Parent-child pipelines\n\n\nIt can be challenging to maintain complex CI/CD pipeline configurations,\nespecially when you need to coordinate many jobs that may relate\n\nto different components, while at the same time keeping the pipeline\nefficient.\n\n\nLet's imagine we have an app with all code in the same repository, but split\ninto UI and backend components. A \"one-size-fits-all\" pipeline for this app\nprobably would have all the jobs grouped into common stages that cover all\nthe components. The default is to use `build`, `test`, and `deploy` stages.\n\nUnfortunately, this could be a source of inefficiency because the UI and\nbackend represent two separate tracks of the pipeline.\n\nThey each have their own independent requirements and structure and likely\ndon't depend on each other.\n\nThe UI might not need the `build` stage at all, but it might instead need a\n`system-test` stage with jobs that test the app end-to-end.\n\nSimilarly, the UI jobs from `system-test` might not need to wait for backend\njobs to complete.\n\n\n[Parent-child\npipelines](https://docs.gitlab.com/ee/ci/pipelines/downstream_pipelines.html)\nhelp here,\n\nenabling you to extract cohesive parts of the pipeline into child pipelines\nthat runs in isolation.\n\n\nWith parent-child pipelines we could break the configurations down into two\nseparate\n\ntracks by having two separate jobs trigger child pipelines:\n\n\n- The `ui` job triggers a child pipeline that runs all the UI jobs.\n\n- The `backend` job triggers a separate child pipeline that runs all the\nbackend jobs.\n\n\n```yaml\n\nui:\n  trigger:\n    include: ui/.gitlab-ci.yml\n    strategy: depend\n  rules:\n    - changes: [ui/*]\nbackend:\n  trigger:\n    include: backend/.gitlab-ci.yml\n    strategy: depend\n  rules:\n    - changes: [backend/*]\n```\n\n\nThe modifier `strategy: depend`, which is also available for multi-project\npipelines, makes the trigger job reflect the status of the\n\ndownstream (child) pipeline and waits for it to complete. Without `strategy:\ndepend` the trigger job succeeds immediately after creating the downstream\npipeline.\n\n\nNow the frontend and backend teams can manage their CI/CD configurations\nwithout impacting each other's pipelines. In addition to that, we can now\nexplicitly visualize the two workflows.\n\n\n![example parent-child\npipeline](https://about.gitlab.com/images/blogimages/2022-02-01-parent-child-vs-multi-project-pipelines/parent-child.png){:\n.shadow.medium.center}\n\n\nThe two pipelines run in isolation, so we can set variables or configuration\nin one without affecting the other. For example, we could use\n`rules:changes` or `workflow:rules` inside `backend/.gitlab-ci.yml`, but use\nsomething completely different in `ui/.gitlab-ci.yml`.\n\n\nChild pipelines run in the same context of the parent pipeline, which is the\ncombination of project, Git ref and commit SHA. Additionally, the child\npipeline inherits some information from the parent pipeline, including Git\npush data like `before_sha`, `target_sha`, the related merge request, etc.\n\nHaving the same context ensures that the child pipeline can safely run as a\nsub-pipeline of the parent, but be in complete isolation.\n\n\nA programming analogy to parent-child pipelines would be to break down long\nprocedural code into smaller, single-purpose functions.\n\n\n## Multi-project pipelines\n\n\nIf our app spans across different repositories, we should instead leverage\n[multi-project\npipelines](https://docs.gitlab.com/ee/ci/pipelines/downstream_pipelines.html).\nEach repository defines a pipeline that suits the project's needs. Then,\nthese standalone and independent pipelines can be chained together to create\nessentially a much bigger pipeline that ensures all the projects are\nintegrated correctly.\n\n\nThere can be endless possibilities and topologies, but let's explore a\nsimple case of asking another project\n\nto run a service for our pipeline.\n\n\nThe app is divided into multiple repositories, each hosting an independent\ncomponent of the app.\n\nWhen one of the components changes, that project's pipeline runs.\n\nIf the earlier jobs in the pipeline are successful, a final job triggers a\npipeline on a different project, which is the project responsible for\nbuilding, running smoke tests, and\n\ndeploying the whole app. If the component pipeline fails because of a bug,\nthe process is interrupted and there is no\n\nneed to trigger a pipeline for the main app project.\n\n\nThe component project's pipeline:\n\n\n```yaml\n\nbuild:\n  stage: build\n  script: ./build_component.sh\n\ntest:\n  stage: test\n  script: ./test_component.sh\n\ndeploy:\n  stage: deploy\n  trigger:\n    project: myorg/app\n    strategy: depend\n```\n\n\nThe full app project's pipeline in `myorg/app` project:\n\n\n```yaml\n\nbuild:\n  stage: build\n  script: ./build_app.sh  # build all components\n\nqa-test:\n  stage: test\n  script: ./qa_test.sh\n\nsmoke-test:\n  stage: test\n  script: ./smoke_test.sh\n\ndeploy:\n  stage: deploy\n  script: ./deploy_app.sh\n```\n\n\n![example multi-project\npipeline](https://about.gitlab.com/images/blogimages/2022-02-01-parent-child-vs-multi-project-pipelines/multi-project.png){:\n.shadow.center}\n\n\nIn our example, the component pipeline (upstream) triggers a downstream\nmulti-project pipeline to perform a service:\n\nverify the components work together, then deploy the whole app.\n\n\nA programming analogy to multi-project pipelines would be like calling an\nexternal component or function to\n\neither receive a service (using `strategy:depend`) or to notify it that an\nevent occurred (without `strategy:depend`).\n\n\n## Key differences between parent-child and multi-project pipelines\n\n\nAs seen above, the most obvious difference between parent-child and\nmulti-project pipelines is the project\n\nwhere the pipelines run, but there are are other differences to be aware of.\n\n\nContext:\n\n\n- Parent-child pipelines run on the same context: same project, ref, and\ncommit SHA.\n\n- Multi-project pipelines run on completely separate contexts. The upstream\nmulti-project pipeline can indicate [a ref to\nuse](https://docs.gitlab.com/ee/ci/pipelines/downstream_pipelines.html),\nwhich can indicate what version of the pipeline to trigger.\n\n\nControl:\n\n\n- A parent pipeline _generates_ a child pipeline, and the parent can have a\nhigh degree of control over what the child pipeline\n  runs. The parent can even [dynamically generate configurations for child pipelines](https://docs.gitlab.com/ee/ci/pipelines/downstream_pipelines.html).\n- An upstream pipeline _triggers_ a downstream multi-project pipeline. The\nupstream (triggering) pipeline does not have much control over the structure\nof the downstream (triggered) pipeline.\n  The upstream project treats the downstream pipeline as a black box.\n  It can only choose the ref to use and pass some variables downstream.\n\nSide-effects:\n\n\n- The final status of a parent pipeline, like other normal pipelines,\naffects the status of the ref the pipeline runs against. For example, if a\nparent pipeline fails on the `main` branch, we say that `main` is broken.\n  The status of a ref is used in various scenarios, including [downloading artifacts](https://docs.gitlab.com/ee/api/job_artifacts.html#download-the-artifacts-archive) from the latest successful pipeline.\n\n  Child pipelines, on the other hand, run on behalf of the parent pipeline, and they don't directly affect the ref status. If triggered using `strategy: depend`, a child pipeline affects the status of the parent pipeline.\n  In turn, the parent pipeline can be configured to fail or succeed based on `allow_failure:` configuration on the job triggering the child pipeline.\n- A multi-project downstream pipeline may affect the status of the upstream\npipeline if triggered using `strategy: depend`,\n  but each downstream pipeline affects the status of the ref in the project they run.\n- Parent and child pipelines that are still running are all automatically\ncanceled if interruptible when a new pipeline is created for the same ref.\n\n- Multi-project downstream pipelines are not automatically canceled when a\nnew upstream pipeline runs for the same ref. The auto-cancelation feature\nonly works within the same project.\n  Downstream multi-project pipelines are considered \"external logic\". They can only be auto-canceled when configured to be interruptible\n  and a new pipeline is triggered for the same ref on the downstream project (not the upstream project).\n\nVisibility:\n\n\n- Child pipelines are not directly visible in the pipelines index page\nbecause they are considered internal\n  sub-components of the parent pipeline. This is to enforce the fact that child pipelines are not standalone and they are considered sub-components of the parent pipeline.\n  Child pipelines are discoverable only through their parent pipeline page.\n- Multi-project pipelines are standalone pipelines because they are normal\npipelines, but just happen to be triggered by an another project's pipeline.\nThey are all visible in the pipeline index page.\n\n\n## Conclusions\n\n\nParent-child pipelines inherit a lot of the design from multi-project\npipelines, but parent-child pipelines have differences that make them a very\nunique type\n\nof pipeline relationship.\n\n\nSome of the parent-child pipelines work we at GitLab will be focusing on is\nabout surfacing job reports generated in child pipelines as merge request\nwidgets,\n\ncascading cancelation and removal of pipelines as well as passing variables\nacross related pipelines.\n\nSome of the parent-child pipeline work we at GitLab plan to focus on relates\nto:\n\n\n- Surfacing job reports generated in child pipelines in merge request\nwidgets.\n\n- Cascading cancelation down to child pipelines.\n\n- Cascading removal down to child pipelines.\n\n- Passing variables across related pipelines.\n\n\nYou can check [this\nissue](https://gitlab.com/gitlab-org/gitlab/-/issues/336884) for planned\nfuture developments on parent-child and multi-project pipelines.\n\nLeave feedback or let us know how we can help.\n\n\nCover image by [Ravi\nRoshan](https://unsplash.com/@ravi_roshan_inc?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText)\non\n[Unsplash](https://unsplash.com/?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText)\n\n{: .note}\n",[9,696,983],{"slug":2324,"featured":6,"template":699},"parent-child-vs-multi-project-pipelines","content:en-us:blog:parent-child-vs-multi-project-pipelines.yml","Parent Child Vs Multi Project Pipelines","en-us/blog/parent-child-vs-multi-project-pipelines.yml","en-us/blog/parent-child-vs-multi-project-pipelines",{"_path":2330,"_dir":246,"_draft":6,"_partial":6,"_locale":7,"seo":2331,"content":2337,"config":2341,"_id":2343,"_type":14,"title":2344,"_source":16,"_file":2345,"_stem":2346,"_extension":19},"/en-us/blog/pipeline-editor-overview",{"title":2332,"description":2333,"ogTitle":2332,"ogDescription":2333,"noIndex":6,"ogImage":2334,"ogUrl":2335,"ogSiteName":686,"ogType":687,"canonicalUrls":2335,"schema":2336},"Meet Pipeline Editor, your one-stop shop for building a CI/CD pipeline","The Pipeline Editor reduces the complexity of configuring your CI/CD pipelines.","https://res.cloudinary.com/about-gitlab-com/image/upload/v1749665961/Blog/Hero%20Images/image_cover.jpg","https://about.gitlab.com/blog/pipeline-editor-overview","\n                        {\n        \"@context\": \"https://schema.org\",\n        \"@type\": \"Article\",\n        \"headline\": \"Meet Pipeline Editor, your one-stop shop for building a CI/CD pipeline\",\n        \"author\": [{\"@type\":\"Person\",\"name\":\"Dov Hershkovitch\"}],\n        \"datePublished\": \"2021-02-22\",\n      }",{"title":2332,"description":2333,"authors":2338,"heroImage":2334,"date":1086,"body":2339,"category":717,"tags":2340},[1150],"This blog post was originally published on the GitLab Unfiltered blog. It\nwas reviewed and republished on 2021-03-02.\n\n{: .note .alert-info .text-center}\n\n\nIn GitLab 13.8, we introduced the first iteration of the [Pipeline\nEditor](/releases/2021/01/22/gitlab-13-8-released/): a dedicated editor\ndesigned for authoring your CI/CD. It is your one-stop shop for everything\nyou need to configure your CI/CD pipelines.\n\n\n## Why do we need a dedicated editor for pipelines?\n\n\nGitLab's advanced syntax provides a high degree of customization for\nsophisticated and demanding CI/CD use cases. However, all of this power and\nflexibility comes with a fair bit of complexity. The Pipeline Editor helps\nyou mitigate this challenge and serves as a single solution that groups all\nexisting CI authoring features in a single location. It is our foundation,\nand we plan to build on it with enhancements in future iterations. \n\n\n## Getting started\n\n\nIn order for the pipeline editor to work, you'll first need to create a\n`.gitlab-ci.yml` file in your project. The `.gitlab-ci.yml` is a [YAML\nfile](https://en.wikipedia.org/wiki/YAML) where you configure specific\nGitLab CI/CD instructions. Check out how we are working on [improving the\nfirst-time experience of creating a `.gilab-ci.yml` file directly from the\nPipeline Editor](https://gitlab.com/groups/gitlab-org/-/epics/5276). \n\n\n### Continuous validation\n\nOnce you have created the `.gitlab-ci.yml` file and navigated to it in the\nPipeline Editor, you can begin editing your configuration. Writing YAML can\nbe error prone. No matter how technical or skilled you are, programming\nmistakes happen. Sometimes an indentation will be missed, the incorrect\nsyntax is used, or the wrong keyword is selected, and that's OK! As you\nstart authoring your pipeline, GitLab will inspect the pipeline\nconfiguration using our linting APIs and provide you with an indicator of\nwhether your pipeline configuration is valid or not. We will continuously\nvalidate your pipeline without making any changes to your pipeline\nconfiguration, so you can have confidence in hitting \"merge\" and running\nyour pipeline without any surprises. \n\n\n![Continuous validation of\npipelines](https://about.gitlab.com/images/blogimages/2020-02-08-Pipeline-editor-overview/image1.png){:\n.shadow.medium.center}\n\nContinuous validation of your pipelines\n\n{: .note.text-center}\n\n\n### Pipeline visualizer: Seeing is believing\n\nIt's practically impossible to envision what a pipeline should look like\nwhen you start writing from a blank YAML file. Luckily, GitLab provides you\nwith a full pipeline view for every running pipeline. But, what if you want\nto visualize your pipeline _before_ they begin to run? Well, you can do that\nnow by navigating to the \"Visualize\" tab in the Pipeline Editor. You'll find\nan illustration that shows how your pipeline should look as you write it,\nsimilar to the linter, and GitLab will display the visual before making any\ncommits, before running, or before altering your pipeline in any way.\n\n\nIn the visualization, we will group all your defined pipeline jobs by stages\nand add links between the jobs based on the\n[needs](https://docs.gitlab.com/ee/ci/yaml/#needs) relationships you've\nconfigured.\n\n\nIf we take a look at the example below, you can easily see that I've\nconfigured a three-stage pipeline, where the build stage has three jobs\n(step 1-3), and that step 4 needs steps 1 and 3.\n\n\n![Pipeline editor\noverview](https://about.gitlab.com/images/blogimages/2020-02-08-Pipeline-editor-overview/image2.png){:\n.shadow.medium.center}\n\nPipeline visualizer\n\n{: .note.text-center}\n\n\nHere is what the YAML looks like:\n\n ```yaml\nimage: alpine:latest\n\n\nstages:\n   - test\n   - build\n   - deploy\n\nprepare:\n   script: exit 0\n   stage: test\n\nstep1:\n   script: echo testo\n   stage: build\nstep2:\n   script: echo testo\n   stage: build\nstep3:\n   script: echo testo\n   stage: build\n\nstep4:\n   needs: ['step1', 'step3']\n   script: exit 0\n   stage: deploy\n ```\n\n### View an expanded version of the CI/CD configuration\n\nWhen configuring pipelines, you use keywords like 'include' and 'extends'\noften. These keywords help break down one long pipeline configuration file\ninto multiple files, which increases readability and reduces duplication.\nUnfortunately, those keywords can make a pipeline configuration hard to\nfollow. In some configurations, a pipeline configuration file can be mostly\ncomposed of a list of other included configuration files.\n\n\nTo make the configuration easier to follow, we've added the ability to view\na version of your pipeline configuration with all of the 'includes' and\n'extends' configurations merged together as a fourth tab in the Pipeline\nEditor. Now it's much easier to understand more complex pipeline flows and\nthis simplifies the debugging process.\n\n\nPipeline configuration example:\n\n\n![pipeline\nconfiguration](https://about.gitlab.com/images/blogimages/2020-02-08-Pipeline-editor-overview/image6.png){:\n.shadow.medium.center}\n\n\nThe expanded version of the pipeline configuration:\n\n\n![expanded pipeline\nconfiguration](https://about.gitlab.com/images/blogimages/2020-02-08-Pipeline-editor-overview/image7.png){:\n.shadow.medium.center}\n\n\n### Lint\n\n\nThe CI lint helps you validate your pipeline configuration and provides you\nwith additional information about it. That's why we've copied the existing\nCI linter (which was well hidden in our jobs page) to the Pipeline Editor as\na third tab.\n\n\nThe linter provides you with detailed information about every job you've\nconfigured in your pipeline. For each job, it provides the\n[before_script](https://docs.gitlab.com/ee/ci/yaml/#before_script),\n[after_script](https://docs.gitlab.com/ee/ci/yaml/#after_script), and\n[script](https://docs.gitlab.com/ee/ci/yaml/#script) fields, tags,\nenvironment names, branches it should run, and more…\n\n\nIf you look at the following example, just by looking at the linter tab\nyou'll know that the `prepare` job:\n\n* Runs in the `prepare` stage\n\n* Contains `before_script`, `script`, and `after_scripts` fields \n\n* Runs only on master \n\n* Runs upon failure\n\n* Tag as production\n\n* Has the environment set to production \n\n\n![image3](https://about.gitlab.com/images/blogimages/2020-02-08-Pipeline-editor-overview/image3.png){:\n.shadow.medium.center}\n\n\nIn this second example, you can see that the build job is a manual job that\nruns on all branches and is allowed to fail:\n\n\n![Manual build\njob](https://about.gitlab.com/images/blogimages/2020-02-08-Pipeline-editor-overview/image5.png){:\n.shadow.medium.center}\n\n\n## How the Pipeline Editor came about\n\n\nEarlier this year, we decided to split continuous integration into two\nseparate teams: [Continuous\nIntegration](/direction/verify/continuous_integration/), which is\nresponsible for improving the experience of running a CI/CD pipeline, and\n[Pipeline Authoring](/direction/verify/pipeline_composition/), responsible\nfor helping you author your pipeline. We've defined the Pipeline Authoring\nteam goal as, \"Making the authoring experience as easy as possible for both\nadvanced and novice users.\"\n\n\n![Verify\nGroups](https://about.gitlab.com/images/handbook/engineering/verify/verify_groups_banner.jpg){:\n.shadow.center}\n\n\nAs a team, we realized that a dedicated authoring area is needed to achieve\nour [ambitious roadmap](https://youtu.be/hInM7JUEH4Y) – this is when the\nPipeline Editor idea was formed. \n\n\n## Try out Pipeline Editor yourself\n\n\nThat's it! I hope you found this overview useful. To get started with GitLab\nCI, you can [try out our hosted GitLab.com solution](/free-trial/), or you\ncan [download GitLab Self-Managed](/free-trial/) and read its documentation\nfor more in-depth coverage of the functionality. \n\n\nIf you are using our Pipeline Editor, we would love it if you leave us a\nnote on our [feedback\nissue](https://gitlab.com/gitlab-org/gitlab/-/issues/298928)! If you'd like\nto learn more about the upcoming features, feel free to read through the\n[Pipeline Editor second iteration\nepic](https://gitlab.com/groups/gitlab-org/-/epics/4814), and tag\n`@dhershkovitch` if you have any questions.\n",[9,696,741,983],{"slug":2342,"featured":6,"template":699},"pipeline-editor-overview","content:en-us:blog:pipeline-editor-overview.yml","Pipeline Editor Overview","en-us/blog/pipeline-editor-overview.yml","en-us/blog/pipeline-editor-overview",{"_path":2348,"_dir":246,"_draft":6,"_partial":6,"_locale":7,"seo":2349,"content":2354,"config":2362,"_id":2364,"_type":14,"title":2365,"_source":16,"_file":2366,"_stem":2367,"_extension":19},"/en-us/blog/placebo-lines-on-the-pipeline-graph",{"title":2350,"description":2351,"ogTitle":2350,"ogDescription":2351,"noIndex":6,"ogImage":1687,"ogUrl":2352,"ogSiteName":686,"ogType":687,"canonicalUrls":2352,"schema":2353},"Placebo Lines on the Pipeline Graph","Have you noticed the connecting lines missing on your pipelines lately? Here's why","https://about.gitlab.com/blog/placebo-lines-on-the-pipeline-graph","\n                        {\n        \"@context\": \"https://schema.org\",\n        \"@type\": \"Article\",\n        \"headline\": \"Placebo Lines on the Pipeline Graph\",\n        \"author\": [{\"@type\":\"Person\",\"name\":\"Sam Beckham\"}],\n        \"datePublished\": \"2021-05-11\",\n      }",{"title":2350,"description":2351,"authors":2355,"heroImage":1687,"date":2357,"body":2358,"category":1088,"tags":2359},[2356],"Sam Beckham","2021-05-11","\n\n{::options parse_block_html=\"true\" /}\n\n\n\nHave you ever pressed the close door button on the elevator, in the hope that you'll save a few precious seconds?\nOr got frustrated at the person stood next to you at the cross-walk, neglecting to press the button?\nWell, maybe they know something you don't, or perhaps you know this already.\nMany buttons in our society lie to us.\n[David McRaney](https://youarenotsosmart.com/2010/02/10/placebo-buttons/) dubbed these, \"Placebo buttons\" and they're everywhere.\nThose elevator doors won't close any faster and the cross-walk button has no effect on the lights.\nThe only lights they control are the lights on the buttons themselves.\nThey give you the feedback you crave, but that's all they're doing.\n\nThese placebos aren't constrained to the physical world, they're prevalent in [UI design](/blog/the-evolution-of-ux-at-gitlab/) too.\nFrom literal placebo buttons like [YouTube's downvote](https://www.quora.com/Does-downvoting-a-comment-on-YouTube-even-do-anything), to more subtle effects like Instagram always [pretending to work](https://www.fastcompany.com/1669788/the-3-white-lies-behind-instagrams-lightning-speed), or progress bars that have a [fixed animation](https://www.theatlantic.com/technology/archive/2017/02/why-some-apps-use-fake-progress-bars/517233/).\nThey're everywhere if you know where to look.\n\nAt GitLab, we created a placebo of our own in one of our core features; the pipeline graph.\n\nThose of you who have used our pipeline graph, will be familiar with its appearance.\nThere's a series of jobs, grouped by stages, connected by a series of lines depicting the relationships between the jobs.\nBut these lines might be lying to you.\nThese lines are indiscriminately drawn between each job in a stage, regardless of their relationship.\nThese lines are placebos.\n\n![The old pipeline rendering with lines connecting every job in a stage](https://about.gitlab.com/images/blogimages/placebo-lines_old-graph.png)\n\nThis wasn't a problem to begin with.\nA basic pipeline has several jobs across a handful of stages.\nJobs in each stage would run parallel to each other, but each stage would run sequentially.\nIn the image shown above, all the jobs in the test stage would trigger at the same time. Once those jobs had finished, all the jobs in the build stage would trigger.\nWe used rudimentary CSS to draw lines connecting each job in one stage to each job in the next.\nThese lines weren't calculated based on their connections, but still reflected the story they were telling.\n\nSince the introduction of `needs` relationships in [v12.2](https://gitlab.com/gitlab-org/gitlab-foss/-/issues/47063), pipelines got a bit more complicated.\nNow you could configure a job in a later stage to trigger as soon as a job in an earlier stage completed.\nLooking at our old example, we could set the API deployment to run as soon as our spec tests passed.\nThis skips the remaining tests and the entire build stage, turning our lines into pretty little liars.\n\nWe had many internal discussions about these lines, and how to show the relationships between jobs.\nThere's the [`needs` visualization](https://docs.gitlab.com/ee/ci/directed_acyclic_graph/#needs-visualization), which does an excellent job of displaying these relationships, but the main pipeline graph was still inaccurate.\nFor the past few months, we've been [refactoring the pipeline graph](https://gitlab.com/gitlab-org/gitlab/-/issues/276949), giving it a new lease of life and fixing some of its issues along the way.\nOne of those issues were the faked lines.\nIn the new version, we can accurately draw lines between jobs.\nLines that actually depict the relationships jobs have with each other.\nNow the lines no-longer lie!\n\n![The newer pipeline graph showing the correct needs links between jobs](https://about.gitlab.com/images/blogimages/placebo-lines_new-graph.png)\n\nThe above image shows an unreleased version of the pipeline graph.\nYou can see the lines drawn between the jobs to show that the `deploy:API` job can start as soon as the `rspec` job is successful.\nSomething the old lines (shown earlier in this post) would have been unable to depict.\n\nOne unfortunate downside of this is that these lines can be quite expensive to calculate.\nThey're actual DOM nodes, drawn deliberately and placed precisely.\nOn smaller graphs this isn't a problem, but some of our initial tests have found pipelines with a potential 8000+ job connections.\nThat kind of calculation would grind the browser to a halt, and nobody wants that.\n\nAt GitLab, we believe in boring solutions.\nWe make the simple change that sets us on the path towards where we want to be.\nShip it, get feedback, and iterate.\nSo that's what we did.\nIn the first phase of this rollout, we shipped the new pipeline graph with no lines connecting the jobs.\nWe don't have to worry about the expensive calculations, and we still get to roll out the refactored pipeline graph.\n\n![The current (v13.11) pipeline graph showing no links between jobs](https://about.gitlab.com/images/blogimages/placebo-lines_current-graph.png)\n\nWe know some of you will miss them, but fear not.\nBoring solutions are just technical debt if you don't iterate on them.\nSo the [improved lines are coming](https://gitlab.com/groups/gitlab-org/-/epics/4509) in a future release, along with several other improvements to the pipeline graph.\nWe're already starting to roll out the new [Job Dependencies](https://gitlab.com/gitlab-org/gitlab/-/issues/298973) view which shows the jobs in a (much closer to) execution order.\nStay tuned for more updates, and watch [Sarah Groff Hennigh Palermo's talk](https://www.youtube.com/watch?v=R2EKqKjB7OQ) for the technical side of this effort and a deeper dive into some of the decisions we made.\n",[9,1814,2360,2361],"agile","design",{"slug":2363,"featured":6,"template":699},"placebo-lines-on-the-pipeline-graph","content:en-us:blog:placebo-lines-on-the-pipeline-graph.yml","Placebo Lines On The Pipeline Graph","en-us/blog/placebo-lines-on-the-pipeline-graph.yml","en-us/blog/placebo-lines-on-the-pipeline-graph",{"_path":2369,"_dir":246,"_draft":6,"_partial":6,"_locale":7,"seo":2370,"content":2376,"config":2381,"_id":2383,"_type":14,"title":2384,"_source":16,"_file":2385,"_stem":2386,"_extension":19},"/en-us/blog/prevent-crypto-mining-abuse",{"title":2371,"description":2372,"ogTitle":2371,"ogDescription":2372,"noIndex":6,"ogImage":2373,"ogUrl":2374,"ogSiteName":686,"ogType":687,"canonicalUrls":2374,"schema":2375},"How to prevent crypto mining abuse on GitLab.com SaaS","GitLab now requires new users to provide a valid credit or debit card in order to use free pipeline minutes on GitLab.com SaaS.","https://res.cloudinary.com/about-gitlab-com/image/upload/v1749663397/Blog/Hero%20Images/logoforblogpost.jpg","https://about.gitlab.com/blog/prevent-crypto-mining-abuse","\n                        {\n        \"@context\": \"https://schema.org\",\n        \"@type\": \"Article\",\n        \"headline\": \"How to prevent crypto mining abuse on GitLab.com SaaS\",\n        \"author\": [{\"@type\":\"Person\",\"name\":\"GitLab\"}],\n        \"datePublished\": \"2021-05-17\",\n      }",{"title":2371,"description":2372,"authors":2377,"heroImage":2373,"date":2378,"body":2379,"category":805,"tags":2380},[1474],"2021-05-17","\n\n**Update: As of 2022-01-13, GitLab no longer requires users created after 2021-05-17 to provide a valid credit or debit card in order to run CI/CD jobs hosted at GitLab, [if those CI/CD jobs are run on namespaces that have purchased CI/CD minutes that have not been used](https://gitlab.com/gitlab-org/gitlab/-/issues/349835).**\n\n**Update: As of 2021-07-17, GitLab has implemented [CI minute quotas](https://docs.gitlab.com/ee/subscriptions/gitlab_com/index.html#ci-pipeline-minutes) for public projects on new namespaces. Existing public projects and namespaces are not impacted.**\n\n**Update: As of 2021-05-24, GitLab will require trial users created on or after 2021-05-17 to provide a valid credit or debit card number in order to use CI jobs hosted at GitLab. Prospective customers that are unable or unwilling to provide a card can reach out to [sales for assistance](https://about.gitlab.com/sales/)**\n\nRecently, there has been a massive uptick in abuse of free pipeline minutes available on GitLab.com and on\n[other CI/CD providers](https://layerci.com/blog/crypto-miners-are-killing-free-ci/) to mine cryptocurrencies.\nIn addition to the cost increases, the abuse creates intermittent performance issues for GitLab.com users\nand requires our teams to work 24/7 to maintain optimal services for our customers and users.\nTo discourage and reduce abuse, starting May 17, 2021, GitLab will require new [free users](/pricing/) to provide a valid credit or debit card number in order to use shared runners on GitLab.com. A user will be able to run pipelines without providing a credit or debit card if they use their own runner and disable shared runners.\nAlthough imperfect, we believe this solution will reduce the abuse.\n\n\nWe plan to rollout this change gradually and increase the scope if needed as follows:\n- Start with adding the new requirement for new free users created on or after May 17, 2021.\n- If we continue to see abuse through existing free accounts, we plan to extend the requirement to additional users.\n\nThis change does not currently impact any of the following users:\n\n* GitLab self-managed customers and users (free or otherwise)\n* Paid or program users (e.g., [education](/solutions/education/), [open source](https://about.gitlab.com/solutions/open-source/)) on GitLab.com\n* Users created before **May 17, 2021**\n\nWhen you provide the card, it will not be charged but instead will be verified with a one-dollar authorization transaction.\nNo charge will be made and no money will transfer.\n\nA credit or debit card is one (of many) controls we have put in place to reduce abuse of our platform.\nWe will never fully solve platform abuse, but the more barriers we put up, the more difficult and expensive it becomes to engage in abuse.\n\nThe GitLab team members have already activated and shipped many improvements. These were helpful in deterring abuse, although are not sufficient.\nA sampling of the fixes we have delivered to mitigate pipeline abuse include:\n\n1. Fail creation of jobs when pipeline minutes quota is exceeded.\n1. Fail pipelines after user exceeds pipeline minutes quota.\n1. Adding restrictions to the creation of namespaces via the API.\n1. Enabling the termination of pipelines when blocking a user.\n1. Ensuring pipelines do not run when pipelines are owned by a blocked user.\n1. Closing gaps in jobs running by user accounts deleted by users.\n1. Utilizing and enhancing the [External Pipeline Validation Service](https://docs.gitlab.com/ee/administration/external_pipeline_validation.html) specifically around authentication, payload, and access restriction.\n1. Ensuring scheduled pipelines don't run by blocked users.\n1. Include public projects in pipeline minutes quota for free users.\n\nAs of July 17, 2021 public projects in namespaces created on July 17th or later will be included in [CI pipeline minute usage quotas](https://docs.gitlab.com/ee/subscriptions/gitlab_com/index.html#ci-pipeline-minutes). Once a free user exceeds the 50,000 minute quota on public projects, a failed pipeline will occur and to resume running the user will need to [purchase additional minutes](https://about.gitlab.com/pricing/faq-compute-minutes/#purchasing-additional-cicd-minutes).\n\nWe expect to make enhancements to harden our pipeline system against abuse.\nWe believe using pipeline minute quotas as the foundation for free minute usage will be the best mechanism for failing jobs and pipelines to stop abuse.\nIncluding this effort, our other pipeline abuse improvements are below:\n\n1. Expand application limits for preventing abuse of webhooks.\n\nA user impacted by this change has the following options:\n\n* Provide a credit or debit card and use the 400 free minutes with shared runners.\n* A user can also run pipelines without providing a credit or debit card if they use their [own runner](https://docs.gitlab.com/runner/install/index.html)\n  and [disable shared runners](https://docs.gitlab.com/ee/ci/runners/#disable-shared-runners) for their project.\n* Decline to provide the card and continue to use many of the GitLab capabilities for free.\n  In this case, any feature within GitLab that relies on our pipelines won't work, such as:\n  A pipeline ([CI/CD generally](https://docs.gitlab.com/ee/ci/quick_start/index.html)),\n  scheduled pipelines including [on-demand DAST scans](https://docs.gitlab.com/ee/user/application_security/dast/),\n  [defining your own pipelines](https://docs.gitlab.com/ee/ci/quick_start/#create-a-gitlab-ciyml-file),\n  utilizing [Auto DevOps](https://docs.gitlab.com/ee/topics/autodevops/).\n* Switch to GitLab self-managed\n\n## Validating an account\n\n\u003C!-- blank line -->\n\u003Cfigure class=\"video_container\">\n    \u003Ciframe src=\"https://www.youtube-nocookie.com/embed/s3G0qxwT11c\" frameborder=\"0\" allowfullscreen=\"true\"> \u003C/iframe>\n\u003C/figure>\n\u003C!-- blank line -->\n\n## Continue the conversation\nPlease share your questions and feedback with us on the [community forum](https://forum.gitlab.com/t/preventing-crypto-mining-abuse-on-gitlab-com-saas/).\n",[9,805,784],{"slug":2382,"featured":6,"template":699},"prevent-crypto-mining-abuse","content:en-us:blog:prevent-crypto-mining-abuse.yml","Prevent Crypto Mining Abuse","en-us/blog/prevent-crypto-mining-abuse.yml","en-us/blog/prevent-crypto-mining-abuse",{"_path":2388,"_dir":246,"_draft":6,"_partial":6,"_locale":7,"seo":2389,"content":2395,"config":2399,"_id":2401,"_type":14,"title":2402,"_source":16,"_file":2403,"_stem":2404,"_extension":19},"/en-us/blog/product-development-management",{"title":2390,"description":2391,"ogTitle":2390,"ogDescription":2391,"noIndex":6,"ogImage":2392,"ogUrl":2393,"ogSiteName":686,"ogType":687,"canonicalUrls":2393,"schema":2394},"Version control and collaborating for product development management","Gitlab provides collaboration functionalities to product teams that work not only with source code but also graphic assets.","https://res.cloudinary.com/about-gitlab-com/image/upload/v1749681632/Blog/Hero%20Images/blog-pdm-image.png","https://about.gitlab.com/blog/product-development-management","\n                        {\n        \"@context\": \"https://schema.org\",\n        \"@type\": \"Article\",\n        \"headline\": \"Version control and collaborating for product development management\",\n        \"author\": [{\"@type\":\"Person\",\"name\":\"William Arias\"}],\n        \"datePublished\": \"2020-10-02\",\n      }",{"title":2390,"description":2391,"authors":2396,"heroImage":2392,"date":1272,"body":2397,"category":1088,"tags":2398},[1128],"\n{::options parse_block_html=\"true\" /}\n\n\n\nFor Designers working with Developers or Product Managers it's frustrating to create a file in one tool, manually export the file, create a prototype in another tool, and use separate software or channels to handoff the designs to developers, the feedback exchange happens not in the context of the design but gets diluted in emails or messaging platforms, making it harder to retake the context after some time has passed.\nTo address those pains mentioned above, Gitlab counts with tools to bridge the communication, collaboration and bring together different roles, using capabilities such as Design Management combined with easy to install plugins like Gitlab Figma Plugin, Developers, Designers, Product Managers and in general any participant role of a project can provide early feedback on any-fidelity designs and open discussions that ultimately lead a team to be more efficient and create awesome products.\n\n\n\nWatch this short video to learn how to leverage Gitlab Design Capabilities along with robust Version Control and collaboration\n\n\u003Ciframe width=\"560\" height=\"315\" src=\"https://www.youtube.com/embed/YtbHkCzxFW4\" frameborder=\"0\" allow=\"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture\" allowfullscreen>\u003C/iframe>\n\n\nCover image by [Pexels](https://pixabay.com/photos/action-figure-art-color-cute-1853285/) on [pixabay](https://pixabay.com/)\n{: .note}\n\n",[2361,9],{"slug":2400,"featured":6,"template":699},"product-development-management","content:en-us:blog:product-development-management.yml","Product Development Management","en-us/blog/product-development-management.yml","en-us/blog/product-development-management",{"_path":2406,"_dir":246,"_draft":6,"_partial":6,"_locale":7,"seo":2407,"content":2413,"config":2421,"_id":2423,"_type":14,"title":2424,"_source":16,"_file":2425,"_stem":2426,"_extension":19},"/en-us/blog/production-grade-infra-devsecops-with-five-minute-production",{"title":2408,"description":2409,"ogTitle":2408,"ogDescription":2409,"noIndex":6,"ogImage":2410,"ogUrl":2411,"ogSiteName":686,"ogType":687,"canonicalUrls":2411,"schema":2412},"GitOps & DevSecOps for production infrastructure in minutes","Unlock production-grade infrastructure and development workflows in under five minutes with Five Minute Production App: a blend of solutions offered by AWS, Hashicorp Terraform, and GitLab.","https://res.cloudinary.com/about-gitlab-com/image/upload/v1749665839/Blog/Hero%20Images/devops.png","https://about.gitlab.com/blog/production-grade-infra-devsecops-with-five-minute-production","\n                        {\n        \"@context\": \"https://schema.org\",\n        \"@type\": \"Article\",\n        \"headline\": \"Production-grade infrastructure, GitOps convergence, and DevSecOps in under 5 minutes\",\n        \"author\": [{\"@type\":\"Person\",\"name\":\"Sri Rangan\"}],\n        \"datePublished\": \"2021-02-24\",\n      }",{"title":2414,"description":2409,"authors":2415,"heroImage":2410,"date":2417,"body":2418,"category":717,"tags":2419},"Production-grade infrastructure, GitOps convergence, and DevSecOps in under 5 minutes",[2416],"Sri Rangan","2021-02-24","This blog post was originally published on the GitLab Unfiltered\nblog. It was reviewed and republished on\n2021-03-10.\n\n{: .note .alert-info .text-center}\n\n\nThis is a story about achieving production-grade infrastructure in under\nfive minutes.\\\\\n\nThis is a story about achieving production-grade DevSecOps in under five\nminutes.\\\\\n\nThis is a story about achieving total convergence of GitOps in under five\nminutes.\n\n\nMy name is Sri and over the last three months and I worked closely with\nGitLab co-founder [DZ](/company/team/#dzaporozhets) in building \"Five Minute\nProduction App.\"\n\n\nThe app blends solutions offered by AWS, Hashicorp Terraform, and GitLab,\nand offers production-grade infrastructure and development workflows in\nunder five minutes.\n\n\n![Five Minute Production App\nDiagram](https://about.gitlab.com/images/blogimages/five-min-prod-01-complete-flow.png){:\n.shadow.medium.center}\n\n\nApart from the efficiencies gained from using Five Minute Production App,\nyou benefit by achieving stateful, production-ready infrastructure on the\nAWS hypercloud.\n\n\nWe started with AWS first, as it is the hypercoud leader today. Support for\nAzure and Google Cloud is on the roadmap.\n\n\nOur vision and design decisions are explained in the\n[README](https://gitlab.com/gitlab-org/5-minute-production-app/deploy-template#quickly).\n\n\n## Quickstart \n\n\nWe start with your GitLab project which has the source code of your web\napplication. Regardless of which language or framework you use, your web\napplication is packaged as a container image and stored within your GitLab\nproject's Container Registry.\n\nThis is the Build stage.\n\n\nThis is followed by the Provision stage where Terraform scripts connect to\nAWS and create a secure environment for your web application.\n\nThe environments provisioned relate to your Git branching workflow.\n\nLong-lived Git branches create long-lived environments, and short-lived Git\nbranches correspond to short-lived environments.\n\n\nResources provisioned include an Ubuntu VM, scalable PostgreSQL database, a\nRedis cluster, and S3 object storage.\n\nWe consider these elements as the building blocks for majority of web\napplications, and many of these fall under AWS free tier.\n\n\nThe infra state and credentials are stored within your GitLab project's\nmanaged Terraform state.\n\n\nFinally, we reach the Deploy stage which:\n\n1. Retrieves the deployable image from the GitLab Container Registry\n\n1. Retrieves the infrastructure credentials from the Gitlab Managed\nTerraform State, and\n\n1. Proceeds to deploy your web application\n\n\nEverything is achieved by including these two lines in your `.gitlab-ci.yml`\nfile.\n\n\n```yaml\n\ninclude:\n  remote: https://gitlab.com/gitlab-org/5-minute-production-app/deploy-template/-/raw/stable/deploy.yml\n```\n\n\nLet's look at the complete process in more detail.\n\n\n![Three stages of Five Minute Production\nApp](https://about.gitlab.com/images/blogimages/five-min-prod-02-pipeline.png){:\n.shadow.medium.center}\n\nThe three stages of Five Minute Production App\n\n{: .note.text-center}\n\n\n## Build and package\n\n\nThe Build stage is where it all begins. Five Minute Production App reuses\nthe [Auto Build\nstage](https://docs.gitlab.com/ee/topics/autodevops/stages.html#auto-build)\nfrom the GitLab Auto DevOps pipeline.\n\n\nAuto Build builds and packages web applications that are:\n\n1. Containerized with a Dockerfile, or\n\n2. Compatible with the Cloud Native buildpack, or\n\n3. Compatible with the Heroku buildpack\n\n\nThus, web applications across multitudes of technologies are supported,\nincluding web frameworks such as Rails, Django, Express, Next.js, Spring,\netc.\n\nand programming languages including Python, Java, Node.js, Ruby, Clojure,\netc.\n\n\nOnce the Auto Build job has finished execution, the newly created container\nimage is stored as an artifact in your GitLab project's Container Registry.\n\n\n## Provision the infrastructure\n\n\nThe next step, Provision, prepares infrastructure resources in AWS.\n\nThe first requirement here is the presence of AWS credentials stored as\nCI/CD variables at the project or group level.\n\nOnce valid AWS credentials are found, a Terraform script is executed to\ngenerate resources in AWS.\n\n\nThese resources include:\n\n1. EC2 VM based on Ubuntu 20.04 LTS\n\n2. PostgreSQL database managed by AWS RDS\n\n3. Redis cluster managed by AWS ElastiCache\n\n4. S3 bucket for file storage\n\n5. Email Service credentials managed by AWS SES\n\n\nThe most critical resource is the PostgreSQL service which has daily backups\nenabled.\n\nPostgreSQL data is snapshotted if the infrastructure resource is \"destroyed\"\nthrough a manual user action via the Five Minute Production App pipeline.\n\n\nThe EC2 VM is the only service accessible publicly. Ports 22, 80 and 443 are\nexposed.\n\nEvery other resource described above is part of a secure, private network,\nhidden from the public web, accessible ony via the EC2 instance and your web\napplicable deployed there.\n\n\nThe stateful services and environments are tied to your Git branches.\\\\\n\nThis means every Git branch creates a new environment with these resource\nsets.\\\\\n\nWe don't have a preference on your Git branching and environments\nlifecycle.\\\\\n\nUse long-lived or short-lived branches as you see fit, just keep in mind\nthat long-lived branches leads to long-lived environments and short-lived\nbranches leads to short-lived environments.\n\n\n![Infrastructure resources provisioned on\nAWS](https://about.gitlab.com/images/blogimages/five-min-prod-03-infra-resources.png){:\n.shadow.medium.center}\n\nInfrastructure resources provisioned on AWS\n\n{: .note.text-center}\n\n\n## Deploy your web application\n\n\nFinally comes the Deploy stage.\n\n\nThis is where the deploy script retrieves your web application package\n(container image) from the GitLab Container Registry, then retrieves the EC2\ninstance\n\ncredentials from the GitLab Managed Terraform State, and proceeds to deploy\nthe relevant version of your web application in its environment.\n\n\nModern web applications might require additional commands being executed\nafter each deployment or after the initial deployment,\n\nand these commands can be defined as variables in your `.gitlab-ci.yml`\nfile.\n\n\nFinally, with the help of Certbot from Letsencrypt, SSL certificates are\ngenerated and configured for your web application.\n\nIf you have defined the `CERT_DOMAIN` CI/CD variable the SSL certificate\nwill be generated for your custom domain name.\n\nOtherwise the generated SSL certificate uses a dynamic URL that Five Minute\nProduction App prepares for you.\n\n\n## Conclusion\n\n\nThere we have it. A simple yet production-ready setup for your web\napplication. If you are looking for an AWS-based setup, this is ready for\nusage.\n\n\nIf you are looking for something similar but not quite Five Minute\nProduction App, this serves as an example of how to converge\ninfrastructure-as-code with software development and provide seamless\ncontinuous deployment workflows.\n\n\nIn my personal experience, this is one of the most complete examples of\nGitOps:\n\n\n1. Your application source code lives in your GitLab project\n\n2. Your infrastructure defined as code lives in your GitLab project\n\n3. Your CI/CD pipeline lives in your GitLab project\n\n4. Your infrastructure state lives in your GitLab project\n\n5. Your infrastructure secrets and credentials live in your GitLab project\n\n6. Your environments configuration lives in your GitLab project\n\n\nThis complete GitOps convergence is not specifically configured for one\nproject. It can be included as a template from multiple projects.\n\nThere is no reason why the GitLab project in your organization cannot be the\nsingle source of truth for everything.\n\n\n### Links\n\n\n- [Five Minute Production\nApp](https://gitlab.com/gitlab-org/5-minute-production-app/deploy-template/-/blob/master/README.md)\n\n- [Reference\nExamples](https://gitlab.com/gitlab-org/5-minute-production-app/examples)\n\n\n### About the author\n\n\n[Sri Rangan](mailto:sri@gitlab.com), an Enterprise Solutions Architect with\nGitLab, is a core-contributor and maintainer\n\nof [Five Minute Production\nApp](https://gitlab.com/gitlab-org/5-minute-production-app/deploy-template/-/blob/master/README.md).\n",[9,696,741,1133,550,2420],"production",{"slug":2422,"featured":6,"template":699},"production-grade-infra-devsecops-with-five-minute-production","content:en-us:blog:production-grade-infra-devsecops-with-five-minute-production.yml","Production Grade Infra Devsecops With Five Minute Production","en-us/blog/production-grade-infra-devsecops-with-five-minute-production.yml","en-us/blog/production-grade-infra-devsecops-with-five-minute-production",{"_path":2428,"_dir":246,"_draft":6,"_partial":6,"_locale":7,"seo":2429,"content":2435,"config":2440,"_id":2442,"_type":14,"title":2443,"_source":16,"_file":2444,"_stem":2445,"_extension":19},"/en-us/blog/progressive-delivery-using-review-apps",{"title":2430,"description":2431,"ogTitle":2430,"ogDescription":2431,"noIndex":6,"ogImage":2432,"ogUrl":2433,"ogSiteName":686,"ogType":687,"canonicalUrls":2433,"schema":2434},"Progressive Delivery: How to get started with Review Apps","Progressive Delivery is the next evolution of continuous delivery, and Review Apps are a key enabler.","https://res.cloudinary.com/about-gitlab-com/image/upload/v1749666841/Blog/Hero%20Images/progressive-delivery-review-apps.jpg","https://about.gitlab.com/blog/progressive-delivery-using-review-apps","\n                        {\n        \"@context\": \"https://schema.org\",\n        \"@type\": \"Article\",\n        \"headline\": \"Progressive Delivery: How to get started with Review Apps\",\n        \"author\": [{\"@type\":\"Person\",\"name\":\"Jason Yavorska\"}],\n        \"datePublished\": \"2019-04-19\",\n      }",{"title":2430,"description":2431,"authors":2436,"heroImage":2432,"date":2437,"body":2438,"category":717,"tags":2439},[1594],"2019-04-19","\nIf you're not familiar with [Progressive Delivery](https://redmonk.com/jgovernor/2018/08/06/towards-progressive-delivery/),\nit's a new set of best practices that is gaining hold for delivering safely and frequently to\nproduction. Although it's not a completely new idea in the same way that continuous\ndelivery originally was, it is a clear evolution of those ideas that brings something\nnew to the table. By taking a step back and considering the corpus of knowledge and experience\ngained over the last 10 years, then applying a bit of systems thinking to\nhow all these different practices interact with emerging technologies, it has set a new\nbaseline for how software delivery can be done effectively.\n\nWe discuss our overall vision for Progressive Delivery on our [CI/CD vision page](/direction/ops/#progressive-delivery),\nwhich also links to a few more resources if you're not up to speed with the concept in general.\n\nIn summary, though, continuous delivery gets you out of the mode of shipping one, big, risky\ndeployment to production, and instead breaks that risk up into many tiny parts – each with a\nfraction of the risk. Progressive Delivery takes this a step further by enabling you to\n[canary test code](https://docs.gitlab.com/ee/user/project/canary_deployments.html) in\nproduction with a small portion of your user base, use [feature flags](https://docs.gitlab.com/ee/operations/feature_flags.html)\nto manage rollout pacing, tie everything together with [tracing](https://docs.gitlab.com/ee/operations/tracing.html),\nand automate the further deployment or rollback of that code depending on how it performs.\n\n## How Review Apps can help enable Progressive Delivery\n\nLet me begin by explaining what GitLab Review Apps are:\n\n[GitLab Review Apps](https://docs.gitlab.com/ee/ci/review_apps/) are\nstaging environments that are automatically created for every branch and/or merge request. They are a collaboration tool\nbuilt into GitLab that helps take the hard work out of providing an environment to\nshowcase or validate product changes. There are a lot of different ways to configure\nthem, but the recommended way is to automatically create review app instances during your\n[merge request pipelines](https://docs.gitlab.com/ee/ci/pipelines/merge_request_pipelines.html). Doing this\nwill ensure that any merge request that is being considered will have an application\nthat developers can connect to to validate their changes.\n\nWith GitLab, we go a step beyond simply creating the review environment: we make it accessible.\n\nOnce configured, on your merge request page you'll now see a \"view app\" button that, as long as your\n[route maps](https://docs.gitlab.com/ee/ci/review_apps/#route-maps) are configured correctly, will allow your\nusers to jump right to the changed content. Review apps do work even without the route maps – in that case\nthey will take you to the home page of your app – but with them they almost feel like magic.\n\n![Review app](https://docs.gitlab.com/ee/ci/review_apps/img/review_apps_preview_in_mr.png \"Review app\"){: .shadow.medium.center}\n\nReview apps are a powerful tool on their own for enabling quick iteration, but if we think about\nthem in the context of Progressive Delivery, a whole new set of possibilities opens up.\n\n## Review apps for progressive validation\n\nAs mentioned above, a typical Progressive Delivery flow involves using targeted feature flags to validate\nchanges as they flow to production environments. Review apps, if configured to point to production\ndata/endpoints instead of ephemeral data, can serve as a merge request-based window into the changes\nthat are being considered for release.\n\nSome of this will of course depend on your code, your testing procedures, and environments. You may\npoint review apps at production endpoints from the moment they are spun up, or perhaps only later\nin your merge request pipeline after some initial validation.\n\nSince anyone can use these environments, you can point anyone with a stake in the success of the\nnew feature to the review app, and they are able to see the live behavior, using their own real\ndata, immediately in their own web browser. This is incredibly powerful for enabling rapid feedback\nand iteration. As a preview, we're also looking to improve this capability by adding an\n[easy-to-use review interface for collecting feedback](https://gitlab.com/gitlab-org/gitlab-ee/issues/10761)\nright into review apps directly.\n\n## Feature flags and tracing\n\nWe can take this idea even one more step further. Using [per-environment feature flag behaviors](https://docs.gitlab.com/ee/operations/feature_flags.html#define-environment-specs), we\ncan control the behavior of the review app environment in any way that the production environment can\nbe controlled. This opens up the possibility of validating any combination.\n\nFinally, since review apps are built and deployed from GitLab CI/CD, all the predefined CI/CD environment\nvariables are available to the deploy script. You could configure your application to use your\nmerge request ID (`CI_MERGE_REQUEST_ID`) as its unique ID for transaction tracing, tying transactions\nin the system automatically to the appropriate GitLab merge request.\n\n## As you can see, there's a ton of potential for Progressive Delivery here\n\nReview apps don't replace\nthe role of feature flags in a Progressive Delivery pipeline, but they provide an incredible\nsupplement that enables segmented validation in a completely new way. All in all, it's such an exciting time for\ncontinuous delivery – there's so much innovation happening on the process and technology fronts, and I'm\ncertain we're only scratching the surface of where we're headed.\n\nReview Apps is just one way [GitLab CI/CD](/solutions/continuous-integration/) enables Progressive Delivery. Join us for our webcast _Mastering continuous software development_ and learn how GitLab’s built-in CI/CD helps teams implement Progressive Delivery workflows, without the complicated integrations and plugin maintenance.\n\n[Watch the GitLab CI/CD webcast](/webcast/mastering-ci-cd/)\n{: .alert .alert-gitlab-purple .text-center}\n\nIf you have more ideas on how to use review apps even more effectively, or where you see the technology\nevolving next, please share in the comments.\n\nPhoto by [Helloquence](https://unsplash.com/photos/5fNmWej4tAA?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText) on [Unsplash](https://unsplash.com/)\n{: .note}\n",[109,983,9],{"slug":2441,"featured":6,"template":699},"progressive-delivery-using-review-apps","content:en-us:blog:progressive-delivery-using-review-apps.yml","Progressive Delivery Using Review Apps","en-us/blog/progressive-delivery-using-review-apps.yml","en-us/blog/progressive-delivery-using-review-apps",{"_path":2447,"_dir":246,"_draft":6,"_partial":6,"_locale":7,"seo":2448,"content":2454,"config":2460,"_id":2462,"_type":14,"title":2463,"_source":16,"_file":2464,"_stem":2465,"_extension":19},"/en-us/blog/provision-group-runners-with-google-cloud-platform-and-gitlab-ci",{"title":2449,"description":2450,"ogTitle":2449,"ogDescription":2450,"noIndex":6,"ogImage":2451,"ogUrl":2452,"ogSiteName":686,"ogType":687,"canonicalUrls":2452,"schema":2453},"Provision group runners with Google Cloud Platform and GitLab CI","This tutorial will teach you how to set up a new group runner on GitLab.com using Google Cloud Platform in less than 10 minutes.","https://res.cloudinary.com/about-gitlab-com/image/upload/v1750098300/Blog/Hero%20Images/Blog/Hero%20Images/AdobeStock_623844718_4E5Fx1Q0DHikigzCsQWhOG_1750098300048.jpg","https://about.gitlab.com/blog/provision-group-runners-with-google-cloud-platform-and-gitlab-ci","\n                        {\n        \"@context\": \"https://schema.org\",\n        \"@type\": \"Article\",\n        \"headline\": \"Provision group runners with Google Cloud Platform and GitLab CI\",\n        \"author\": [{\"@type\":\"Person\",\"name\":\"Sarah Matthies\"},{\"@type\":\"Person\",\"name\":\"Noah Ing\"}],\n        \"datePublished\": \"2024-11-19\",\n      }",{"title":2449,"description":2450,"authors":2455,"heroImage":2451,"date":2457,"body":2458,"category":717,"tags":2459},[2456,781],"Sarah Matthies","2024-11-19","Are you interested in hosting your own servers to run your GitLab CI/CD\npipelines but don’t know where to begin? Setting up a GitLab Runner to run\nyour pipelines on your own infrastructure can seem like a daunting task as\nit requires infrastructure knowledge and the know-how to maintain that\ninfrastructure. Typically this process requires the provision of\ninfrastructure, the installing of dependency, and testing that it works with\nyour GitLab instance.\n\n\nThis article highlights how easy it is to easily spin up a GitLab Runner of\nyour own utilizing GitLab’s Google Cloud Integration. Follow this tutorial\nand it will teach you how to set up a new group runner on GitLab.com using\nGoogle Cloud Platform in less than 10 minutes!\n\n\nYou will learn how to:\n\n\n- Create a new group runner.\n\n- Configure the new group runner’s tags and description.\n\n- Register the new group runner by adding in configurations.\n\n- Provision the GitLab Runner utilizing `gcloud cli` and Terraform.\n\n- Have your GitLab Runner pick up its first GitLab CI job.\n\n\n## Prerequisites\n\n- A terminal with Bash installed\n\n- Owner access on a Google Cloud Platform project\n\n- Terraform (or OpenTofu) [Version\n1.5](https://releases.hashicorp.com/terraform/1.5.7/) or greater \n\n- [gcloud CLI](https://cloud.google.com/sdk/docs/install) \n\n- 10 minutes\n\n\n## Tutorial\n\n1. Create a new group runner under __Build > Runners > New Group Runner__.\n\n\n__Note:__ Navigate to the group level.\n\n\n![GitLab Runner setup\nscreen](https://res.cloudinary.com/about-gitlab-com/image/upload/v1750098317/Blog/Content%20Images/Blog/Content%20Images/image7_aHR0cHM6_1750098317126.png)\n\n\n2. Configure the new group runner's tags, description, and any additional\nconfigurations.\n\n\n![New Group Runner\nsetup](https://res.cloudinary.com/about-gitlab-com/image/upload/v1750098317/Blog/Content%20Images/Blog/Content%20Images/image4_aHR0cHM6_1750098317127.png)\n\n\n3. Select __Google Cloud__.\n\n\n![Select Google Cloud\nscreen](https://res.cloudinary.com/about-gitlab-com/image/upload/v1750098317/Blog/Content%20Images/Blog/Content%20Images/image3_aHR0cHM6_1750098317129.png)\n\n\n4. Copy your project ID from Google Cloud Platform.\n\n\n![Copy project ID from GCP\nscreen](https://res.cloudinary.com/about-gitlab-com/image/upload/v1750098317/Blog/Content%20Images/Blog/Content%20Images/image1_aHR0cHM6_1750098317131.png)\n\n\n5. Fill out your Google Cloud project ID and choose a region, zone, and type\nof machine you want to use.\n\n\n![Screen to fill out Google Cloud\ninformation](https://res.cloudinary.com/about-gitlab-com/image/upload/v1750098317/Blog/Content%20Images/Blog/Content%20Images/image8_aHR0cHM6_1750098317132.png)\n\n\n6\\. Once this information is filled out, click **Setup instructions**.\n\n\nRun the bash script provided in Step 1 above.\n\n\n**Note:** This script was saved to a file called `setup.sh` for ease of use.\nYou may copy this right into your terminal if you are confident in\ndebugging.\n\n\n![Setup instructions\nscreen](https://res.cloudinary.com/about-gitlab-com/image/upload/v1750098317/Blog/Content%20Images/Blog/Content%20Images/image6_aHR0cHM6_1750098317134.png)\n\n\n![Script for GitLab\nRunner](https://res.cloudinary.com/about-gitlab-com/image/upload/v1750098317/Blog/Content%20Images/Blog/Content%20Images/image10_aHR0cHM6_1750098317135.png)\n\n\n7\\. Create a `main.tf` file and follow the instructions in GitLab.\n\n\n**Note:** If you want to use OpenTofu instead of Terraform, you can still\ncopy the code and only have to adjust the Terraform commands for applying\nthe configuration. \n\n\n![Install and register GitLab Runner\nscreen](https://res.cloudinary.com/about-gitlab-com/image/upload/v1750098317/Blog/Content%20Images/Blog/Content%20Images/image9_aHR0cHM6_1750098317136.png)\n\n\nOnce successfully provisioned, you should be see the following:\n\n\n![GitLab Runner\ncode](https://res.cloudinary.com/about-gitlab-com/image/upload/v1750098317/Blog/Content%20Images/Blog/Content%20Images/image5_aHR0cHM6_1750098317137.png)\n\n\n8\\. If you close the instructions and click the **View runners** button, you\nwill now have a newly provisioned runner present with \"Never contacted\" as\nits status.\n\n\n![Newly provisioned runner on\nscreen](https://res.cloudinary.com/about-gitlab-com/image/upload/v1750098317/Blog/Content%20Images/Blog/Content%20Images/image2_aHR0cHM6_1750098317139.png)\n\n\n9\\. In any project, add the following `.gitlab-ci.yml`.\n\n\n```  \n\nstages:  \n  - greet\n\nhello_job:  \n  stage: greet  \n  tags:  \n    - gcp-runner  \n  script:  \n    - echo \"hello\"  \n```\n\n\nVolia! You have set up your first GitLab Runner utilizing Google Cloud\nPlatform.\n\n\n# Next steps\n\n\nNow that you have provisioned your very own GitLab Runner, consider\noptimizing it for your specific use case. Some things to consider with your\nrunner moving forward:\n\n\n- Is the runner I provisioned the right size? Does it need additional\nresources for my use case? \n\n- Does the GitLab Runner contain all the dependency my builds need?  \n\n- How can I store the GitLab Runner as infrastructure as code?\n\n\n> Make sure to bookmark the [Provisioning runners in Google Cloud\ndocumentation](https://docs.gitlab.com/ee/ci/runners/provision_runners_google_cloud.html)\nfor easy reference.\n",[742,496,9,109,720,960,233],{"slug":2461,"featured":6,"template":699},"provision-group-runners-with-google-cloud-platform-and-gitlab-ci","content:en-us:blog:provision-group-runners-with-google-cloud-platform-and-gitlab-ci.yml","Provision Group Runners With Google Cloud Platform And Gitlab Ci","en-us/blog/provision-group-runners-with-google-cloud-platform-and-gitlab-ci.yml","en-us/blog/provision-group-runners-with-google-cloud-platform-and-gitlab-ci",{"_path":2467,"_dir":246,"_draft":6,"_partial":6,"_locale":7,"seo":2468,"content":2474,"config":2480,"_id":2482,"_type":14,"title":2483,"_source":16,"_file":2484,"_stem":2485,"_extension":19},"/en-us/blog/public-project-minute-limits",{"title":2469,"description":2470,"ogTitle":2469,"ogDescription":2470,"noIndex":6,"ogImage":2471,"ogUrl":2472,"ogSiteName":686,"ogType":687,"canonicalUrls":2472,"schema":2473},"Changes to GitLab.com public project CI/CD minute quotas","How cryptomining has shaped our pipeline consumption visibility approach and our forward-looking changes.","https://res.cloudinary.com/about-gitlab-com/image/upload/v1749666275/Blog/Hero%20Images/ci_minutes.jpg","https://about.gitlab.com/blog/public-project-minute-limits","\n                        {\n        \"@context\": \"https://schema.org\",\n        \"@type\": \"Article\",\n        \"headline\": \"Changes to GitLab.com public project CI/CD minute quotas\",\n        \"author\": [{\"@type\":\"Person\",\"name\":\"Jackie Porter\"}],\n        \"datePublished\": \"2021-11-11\",\n      }",{"title":2469,"description":2470,"authors":2475,"heroImage":2471,"date":2476,"body":2477,"category":805,"tags":2478},[1085],"2021-11-11","\nIn the upcoming milestones, we will be extending CI/CD minute usage quotas to GitLab.com public projects not part of GitLab [open source programs](/handbook/marketing/developer-relations/community-programs/opensource-program/).\n\nIn the first half of 2021, GitLab.com and other CI/CD providers faced a large uptick in the abuse of free pipeline minutes to mine for cryptocurrencies. To discourage and reduce abuse, we [annouced](/blog/prevent-crypto-mining-abuse/) several changes to help ensure service continued to be reliable for our customers and users. The most recent change was made on 2021-07-17 to \"include public projects in pipeline minutes quota for free users.\"\n\n### Why add a usage quota to public projects?\n\nPreviously, GitLab provided public projects on GitLab.com with a very high number of shared runner minutes to facilitate community contributions to open source. This means cryptominers are able to use GitLab.com shared runners to process large quantities of pipelines and consume an inordinate amount of minutes. These behaviors negatively impact the performance and the availability of GitLab.com shared runners. By adding a quota to the pipelines, the abusers are no longer able to process and consume minutes because there is a limit in place.\n\n[CI/CD minute quotas](https://docs.gitlab.com/ee/subscriptions/gitlab_com/index.html#ci-pipeline-minutes) enable CI/CD minute accumulation, which also gives you transparency into pipeline minute billing. Accumulation of CI/CD minutes in GitLab empowers you to make informed decisions about how to optimize your pipelines and CI/CD usage.\n\n### How does this usage quota impact you?\n\n* **Self-managed users** are not impacted by these changes as CI/CD minutes are only relevant for GitLab.com users.\n\n* **Members of GitLab's [open source program](/handbook/marketing/developer-relations/community-programs/opensource-program/)** [are not subject to the new quotas](https://gitlab.com/groups/gitlab-org/-/epics/6895) for public project CI/CD minutes. As noted in the [program description](https://about.gitlab.com/solutions/open-source/), `Features of [GitLab Ultimate](/pricing/ultimate/), including 50,000 CI/CD minutes, are free to qualifying open source projects through the GitLab for Open Source Program.`, calculated at a [program-specific cost factor](https://docs.gitlab.com/ee/ci/pipelines/cicd_minutes.html#cost-factor).\n\n* **All other GitLab.com public project users** (who account for 5% of our usage) will receive a notification email when they reach their CI/CD quota. Namespace owners will then have the option of upgrading the account to a higher [plan](https://about.gitlab.com/pricing/) or [purchasing additional CI/CD minutes](https://docs.gitlab.com/ee/subscriptions/gitlab_com/index.html#purchase-additional-ci-minutes). Self-managed runners can still be used even when a project reaches quota limits.\n\nFor more information on CI/CD minutes and billing, please refer to the [customer FAQ](/pricing/faq-compute-minutes/).\n\n### What's Next?\n\nTo further protect GitLab.com from cryptomining abuse, in the next few months you'll notice some changes to GitLab.com [CI/CD minute quotas](https://docs.gitlab.com/ee/subscriptions/gitlab_com/index.html#ci-pipeline-minutes) and the types of projects that accumulate pipeline minutes.\n\nTo address your questions and feedback on these changes going forward, we have created a space in the [GitLab Community Forum](https://forum.gitlab.com/t/pipeline-minute-quotas-on-gitlab-com/58976), which is actively monitored by GitLab team members and product managers involved with this change.\n",[9,696,2479],"contributors",{"slug":2481,"featured":6,"template":699},"public-project-minute-limits","content:en-us:blog:public-project-minute-limits.yml","Public Project Minute Limits","en-us/blog/public-project-minute-limits.yml","en-us/blog/public-project-minute-limits",{"_path":2487,"_dir":246,"_draft":6,"_partial":6,"_locale":7,"seo":2488,"content":2493,"config":2499,"_id":2501,"_type":14,"title":2502,"_source":16,"_file":2503,"_stem":2504,"_extension":19},"/en-us/blog/qpage-on-the-devops-platform",{"title":2489,"description":2490,"ogTitle":2489,"ogDescription":2490,"noIndex":6,"ogImage":1923,"ogUrl":2491,"ogSiteName":686,"ogType":687,"canonicalUrls":2491,"schema":2492},"QPage improves deployment & efficiency using GitLab platform","QPage went from a homegrown CI/CD solution to the GitLab DevOps Platform and found more benefits than expected.","https://about.gitlab.com/blog/qpage-on-the-devops-platform","\n                        {\n        \"@context\": \"https://schema.org\",\n        \"@type\": \"Article\",\n        \"headline\": \"How QPage achieved automatic deployment and efficiency using the GitLab DevOps Platform\",\n        \"author\": [{\"@type\":\"Person\",\"name\":\"GitLab\"}],\n        \"datePublished\": \"2021-09-15\",\n      }",{"title":2494,"description":2490,"authors":2495,"heroImage":1923,"date":2496,"body":2497,"category":1131,"tags":2498},"How QPage achieved automatic deployment and efficiency using the GitLab DevOps Platform",[1474],"2021-09-15","Deployment automation is essential for any company involved in software development to stay competitive. [QPage](https://www.qpage.one/), a company that provides an end-to-end sourcing and recruitment solution for SMEs, realized it quickly and migrated to GitLab’s DevOps Platform to accelerate their deployment process.\n\nWe spoke with Pouya Lotfi, the co-founder of QPage, to see how they use GitLab at QPage and how it has helped the company.\n\n## Why GitLab?\n\nQPage was initially using a local bespoke CI/CD for about the first two months, but they soon realized they needed a more professional DevOps Platform system. Because Pouya and the team at QPage had already used GitLab at a previous employer, they knew it would be the right fit. So, they didn’t consider other options and opted for GitLab straight away.\n\n**Everything you need to know about [a DevOps platform](/solutions/devops-platform/)**\n\n \"We started from the local CI/CD, but soon we realized that would be something we can actually do with GitLab,” said Pouya Lotfi, co-founder QPage. “I had the experience with GitLab back in the other companies I was part of, so we soon actually migrated to GitLab, and we brought everything we could actually have in GitLab’s DevOps Platform to accelerate our deployment and the processes.”\n\nQPage chose GitLab’s paid subscription plan.\n\n## How GitLab’s DevOps Platform works\n\nQPage is using several CI/CD integrations that GitLab offers.\n\n\"We are using it end-to-end, but we did use the benefit of integrating it with other platforms as well,\" Pouya said.\n\nThey are using the GitLab-Kubernetes integration for CI/CD funnels, which allows building, testing, and deploying to cluster, as well as using Auto DevOps to automate the CI/CD process.\n\nAnother key integration for QPage is the JIRA integration - they get notifications and assign a ticket to one of the developers/engineers. However, a part of this process is still done manually as they are not yet using issues, boards, and milestones within GitLab. But, they are considering using GitLab altogether to automate the whole process.\n\n**Get the [most out of your DevOps platform](/topics/devops/seven-tips-to-get-the-most-out-of-your-devops-platform/)**\n\nQPage is also taking advantage of the Docker-GitLab integration. They use containers and images, push them through the GitLab CI and then finally deploy.\n\nThey start with the staging environment, then move to testing and QA, and finally, they push it to the production; their deployment and release part is divided into staging and production. For deployment, QPage is using cloud providers AWS and Digital Ocean.\n\n## The dev team and GitLab\n\nThe developers at QPage find GitLab an easy solution to work with because they already knew how it worked; one of QPage’s basic criteria to hire a developer or an engineer is to have experience with using GitLab or GitHub CI/CD.\n\nAdditionally, they find GiLab’s documentation very helpful. When they come across any problem with using GitLab, they quickly reach for the documentation to solve their problems. This eliminates the bottleneck of depending on one person on the team, who is an expert, to solve a problem.\n\n## Key DevOps Platform benefits\n\nOne of the major benefits QPage has seen from using GitLab is achieving automatic deployment. GitLab has made their CI/CD process more efficient as they have integrated it with tools like Kubernetes, Docker, and JIRA.\n\nThey believe the management within GitLab is also a huge plus where they can now test the codes and push them. Additionally, they like the visibility of work and collaboration among the developers. Their team can now know the status of the deployment in terms of whether it was successful or it failed and where it was deployed, such as the staging environment or the production.\n\n**How [DevOps gets easier](https://learn.gitlab.com/smb-devops-1/simplify-devops) with a DevOps platform**\n\nAnother big benefit of migrating to GitLab is the operational efficiency. Their deployment time has now reduced by 80% - with the local CI/CD, it took around 6-8 hours, but with GitLab, it’s between 15-20 minutes.\n\n \"In the beginning, when we had done it through the local server CI/CD, it would take around 6-8 or 10 hours, and that was a real hassle for us,\" Pouya said. “With our GitLab migration, and we push something to production, it takes like 15 to 20 to 30 minutes.”\n\nAlthough QPage has one main product, they have around 29 sub-products, like API algorithms, and they've seen great optimization in deployment with all of their products after using GitLab.\n\nLast but not least, QPage believes using GitLab is also cost-effective for them.",[741,1007,9],{"slug":2500,"featured":6,"template":699},"qpage-on-the-devops-platform","content:en-us:blog:qpage-on-the-devops-platform.yml","Qpage On The Devops Platform","en-us/blog/qpage-on-the-devops-platform.yml","en-us/blog/qpage-on-the-devops-platform",{"_path":2506,"_dir":246,"_draft":6,"_partial":6,"_locale":7,"seo":2507,"content":2512,"config":2518,"_id":2520,"_type":14,"title":2521,"_source":16,"_file":2522,"_stem":2523,"_extension":19},"/en-us/blog/second-law-of-complexity-dynamics",{"title":2508,"description":2509,"ogTitle":2508,"ogDescription":2509,"noIndex":6,"ogImage":2373,"ogUrl":2510,"ogSiteName":686,"ogType":687,"canonicalUrls":2510,"schema":2511},"How pursuit of simplicity complicates container-based CI","Simplicity always has a certain player in mind - learn how to avoid antipatterns by ensuring simplicity themes do not compromise your productivity by over-focusing on machine efficiencies.","https://about.gitlab.com/blog/second-law-of-complexity-dynamics","\n                        {\n        \"@context\": \"https://schema.org\",\n        \"@type\": \"Article\",\n        \"headline\": \"When the pursuit of simplicity creates complexity in container-based CI pipelines\",\n        \"author\": [{\"@type\":\"Person\",\"name\":\"Darwin Sanoy\"}],\n        \"datePublished\": \"2022-05-24\",\n      }",{"title":2513,"description":2509,"authors":2514,"heroImage":2373,"date":2515,"body":2516,"category":717,"tags":2517},"When the pursuit of simplicity creates complexity in container-based CI pipelines",[691],"2022-05-24","\n\nIn a GitLab book club, I recently read \"[The Laws of Simplicity](http://lawsofsimplicity.com/),\" a great book on a topic that has deeply fascinated me for many years. The book contains an acronym that expresses simplicity generation approaches: SHE, which stands for \"shrink, hide, embody.\" These three approaches for simplicity generation all share a common attribute: They are all creating illusions - not eliminations.\n\nI've seen this illusion repeat across many, many realms of pursuit for many years. Even in human language, vocabulary development, jargon, and acronyms all simply encapsulate worlds of complexity that still exist, but can be more easily referenced in a compact form that performs SHE on the world of concepts.\n\nAny illusion has a boundary or curtain where in front of the curtain the complexity can be dealt with by following simple rules, but, behind the curtain, the complexity must be managed by a stage manager. \n\nFor instance, when the magic show creates the spectre of sawing people in half, what appears to be a simple box is in fact an exceedingly elaborate contraption. Not only that, but the manufacturing process for an actual simple box and the sawing box are markedly different in terms of complexity. The manufacturing of complexity and its result are essentially the tradeoff for what would be the real-world complexity of actually sawing people in half and having them heal and stand up unharmed immediately afterward.\n\nTo bring this into the technical skills realm, consider that when you leverage a third-party component or API to add functionality, you only need to know the parameters to obtain the desired result. The people maintaining that component or API must know the quantum mechanics detail level of how to perform that work in a reliable and complete way.\n\nDocker containers are a mechanism for embodying complexity, and are used in scaled applications and within container-based CI. When a [CI/CD](/topics/ci-cd/) automation engineer uses container-based CI, it is possible to make things more complex and more expensive when attempting to do exactly the opposite.\n\nAt its core, this post is concerned with how it can happen that pursuing a simpler world through containers can turn into an antipattern - a reversal of desired outcomes - many times, without us noticing that the reversal is affecting our productivity. The prison of a paradigm is secure indeed.\n\n### The Second Law of Complexity Dynamics\n\nOver the years I have come to believe that the pursuit of reducing complexity has similar characteristics to [The Second Law of Thermodynamics](https://www.grc.nasa.gov/www/k-12/airplane/thermo2.html). The net result of a change between mass and energy results in the the same net amount of mass and energy, but their ratio and form have changed. In what I will coin \"The Second Law of Complexity Dynamics,\" complexity is similarly \"conserved,\" it is just reformed.\n\nIf complexity is not eliminated by simplifying efforts, we reduce its impact in a given realm by changing the ratio of complexity and simplicity on each side of one or more curtains. But alas, complexity did not die, it just hid and is now someone else's management challenge. It is important not to think of this as cheating. There is no question that hiding complexity carries the potential for massive efficiency gains when the world behind the hiding mechanisms becomes the realm of specialty skills and specialists. When it truly externalizes the complexity management for one party, the world becomes more simple for that party.\n\nHowever, the devil is in the details. If the hypothesis of \"no net elimination of complexity\" is correct, it is then important where the complexity migrates to. If it migrates to another part of the same process that must also be managed by the same people, then it may not result in a net gain of efficiency. If it migrates out of a previously embodied realm, then, in the pusuit of simplicity, we can actually reduce our overall efficiency when the process is considered as a whole.\n\n### Container-based CI pipelines as a useful case in point\n\nI see the potential for efficiency reversals to crop up in my daily work time and again, and an interesting place where I've seen it lately is in the tradeoff of linking together hyper-specialized modules of code in containers for CI versus leveraging more generalized modules.\n\nIn creating container-based pipelines, I experience the potential for an efficiency reversal I have to consciously manage.\n\nContainers make a simplicity tradeoff by design. They create a full runtime environment for a very single purpose but in doing so they strip back the container internals so far that general compute tasks are difficult inside them. If you step behind their \"complexity embodying\" curtain into the container, their simplistic environment can require more complex code to operate within.\n\nIn GitLab CI pipelines that utilize containers, all the scripts of jobs run inside the containers that are specified as their runtime environment. When one selects a specialized container - such as the alpine git container or the skopeo image management container - the code is subject to the limitations of the shell that container employs (if it has one at all).\n\nContainers were devised to be hyper-specialized, purpose-specific runtimes that assure they can always run and run quickly for scaled applications. However, for many containers this means no shell or a very stripped back shell like busybox sh. It frequently also means not including the package manager for the underlying Linux distribution.\n\nTime and again, I've found myself degrading the implementation of my shell code in key ways that make it more complex, so that it can run under these stripped back shells. In these cases, I do not benefit from the complexity hiding of newer versions of advanced shells like Bash v5. One of the areas is advanced Bash shell expansions, which embody a huge world of complex parsing and avoid a bunch of extraneous utilities. And another is advanced if and case statement comparison logic that processes regular expressions without external utilities and performs many other abstracted comparisons. There are many other areas of the language where this comes into play, but these two stand out.\n\n![](https://about.gitlab.com/images/blogimages/second-law-of-complexity-dynamics-container-pipeline-tradeoffs.png)\n\nSo by having a simpler shell like busybox sh, the simplicities of advanced shell features become *unhidden* and join my side of the curtain. Now I have to manage them in my code. But then, guess what? No package manager means the inability to install other Linux utilities and languages extensions that I could also employ to push that same complexity back out of my space. And, of course, it means installing Bash v5 would be difficult as well.\n\nSo the simplicity proposition of a tightly optimized purpose-specific container can reverse the purported efficiency gains in the very important realm of the code I have to write. It also means I frequently have to break up my code into multiple jobs to utilize the specializations of these containers in a sequence or to transport the results of a specialized container into a fuller coding environment. This increases the complexity of the pipeline as I now have to pass artifacts and variable data from one job to another with a host of additional YAML directives, and sometimes deploy infrastructure (e.g., [Runner caching](https://docs.gitlab.com/ee/ci/caching/#:~:text=For%20runners%20to%20work%20with,GitLab.com%20behave%20this%20way)).\n\nIn the case of CI using containers, when the simplicity tradeoffs move complexity to things I do not maintain, such as base containers, operating system packages, and full shell environments, into things I do maintain, such as CI YAML and Shell Script code, then I am also inheriting long-term complexity maintenance. In the cloud, we know this as undifferentiated heavy lifting.\n\nInterestingly, the proliferation of specialized containers can also require more machine resources and can lengthen processing time as containers are retrieved from registries and loaded and artifacts and source code are copied in and out of each job-based container.\n\n### Simplicity target: Efficiency\n\nIt's easy to lose sight of the amount of human effort and ingenuity being applied to knowing and managing the coding structure, rather than being applied to solving the real automation problems of the CI pipeline. The net complexity of the pipeline can also mean it is hard to maintain an understanding of it even if you are working in it every day - and for newcomers onboarding, it can be many weeks before they fully understand how the system works.\n\nOf course, I can create my own containers for CI pipelines, but now I've added the complexity of container development and continuous updates of the same in order for my pipeline code to be operational and stay healthy. I am still behind the curtain for that container. For teams whose software is not itself containerized, the prospect of learning to build containers just for CI can create a lot of understandable friction to adopting a container-based CI development process. This friction may be unnecessary if we make a key heuristic adaptation.\n\n### Walking the tightwire above the curtain\n\nSo how do I manage the tensions of these multiple worlds of complexity when it comes to container-based pipelines to try to avoid efficiency reversals in the net complexity of the pipeline?\n\nIt is simple. I will describe the method and then the key misapplied heuristic and how to adjust it.\n\n1. I hold that the primary benefits of container-based CI are a) dependency isolation by job (so that you don’t have a massive and brittle CI build machine specification to handle all possible build requirements), and b) clean CI build agent state by obtaining a clean container copy for each job. These benefits do not imply having to abide by microservices container resource planning and doing so is what creates an antipattern in my productivity.\n\n2. I frequently use a Bash 5 container (version pegged if need be) where all the complexity that advanced shell capabilities embody for me stay behind the curtain.\n\n3. Instead of running a hyper-minimalized container for a given utility, I do a runtime install of that utility (gasp!) in a container that has my rich shell. I utilize version pegging during the install if I feel version safety is paramount on the utility. Alternatively, if a very desirable runtime of some type is difficult to setup and does not have a package, I look for a container that has a package manager that matches a packaged version of the runtime and also allows me to install my advanced scripting language if needed.\n\n4. If, and only if, the net time of the needed runtime installs exceeds the net pipeline time to load a string of specialized containers (with artifact handling) plus my time to develop and manage a pipeline dependency in the form of a custom container, then do I consider possibly creating a pipeline specific container.\n\n5. Through this process a balancing principle also emerges. Since I have been doing runtime installs as a development practice, I have actually already MVPed what a pipeline specific container would need to have installed. I can literally copy the installation lines into a Docker file if I wish. I can also notice if I have commonality across multiple pipelines where it makes sense to create a multi-pipeline utility container.\n\nIn a recent project, following these principles caused me to avoid the skopeo container and instead install it on the Bash 5 container using a package manager.\n\nIf your team is big into Python or PowerShell as your CI language, it would make sense to start with recent releases of those containers. The point is not advanced Bash -but an advanced version of your general CI scripting language that prevents you from creating work arounds in your code for problems that are well-solved in publicly available runtimes.\n\nKeep in mind that this adjustment is very, very focused on containers **in CI pipelines**, which, by nature, reflect general compute processing requirements where many vastly different operations are required in a pipeline. I am not advocating this approach for true microservices applications where, by design, a given service has very defined purpose and characteristics and, at scale, massively benefits from the machine efficiency of hyper-minimalized, purpose-specific granularity.\n\n### Misapplied heuristics\n\nFrequently when a pattern has an inflection point at which it becomes an antipattern, it is due to misapplying the heuristics of the wrong realm. In this case, I believe, that normal containerization patterns for microservices apps are well founded, but they apply narrowly to \"engineered hyper-specialized compute\" of a granule we call \"a microservice\" (note the word \"micro\" applies to the scope of compute activities). Importantly, they apply because the process itself is designed as hyper-specialized around a very specific task. The container contents (included dependencies), immutability principle (no runtime change), and the runtime compute resources can be managed exceedingly minimally because of the small and highly specific scope of computing activities that occur within the process.\n\nThis is essentially the embodiment of the 12 Factor App principle called “[VIII. Concurrency](https://12factor.net/concurrency),” which asserts that scaling should be horizontal scaling of the same minimalized process, not vertical scaling of compute resources inside a given process. If the system experiences 10x work for a particular activity, we create 10 processes, we do not request 10x memory and 10x CPU within one running process. Microservices architecture tightly controls the amount of work in each request so that it is hyper-predictable in its compute resource requirements and, therefore, scalable by adding identical processes.\n\nCI compute, by nature, is the opposite of hyper-specialized. Across build, test, package, deploy, etc., etc., there are many huge variations in required machine resources of memory, CPU, network I/O and high-speed disk access and, importantly, included dependencies. The generalized compute nature also occurs due to varying inputs so the same defined process might need a lot more resources due to the nature of the raw input data. For example, varying input volume (e.g. a lot versus few data items) or varying input density (e.g. processing binary files versus text files). \n\nIt is the process that is being containerized that holds the attribute of generalized compute (bursty on at least some compute resources) or hyper-specialized (narrow definition of work to be done and therefore well-known compute resources per unit of completed work). Containerizing a process that exhibits generalized compute requirements is useful, but planning the resources of that container as if containerizing it has transformed the compute requirements into hyper-minimalized is the inflection point at which it becomes an antipattern, actually eroding the sought-after benefits we set out to create.\n\nIn the model I employ for leveraging containers in CI, the loosening of the hyper-specialization, immutablility (no-runtime installs), and very narrow compute resources principles of microservices simply reflects the real world in that CI compute as a whole exhibits the nature of generalized, not hyper-specialized, compute characteristics.\n\n> Another realm where this seems true is desired state configuration management technologies - also known as “Configuration as Code”. It is super simple if there are pre-existing components or recipes for all that you need to do but as soon as you have to build some for yourself, you enter a world of creating imperative code against a declarative API boundary (there's the \"embodiment\" curtain - the declarative API boundary). Generally, if you have not had to implement imperative code to process declaratively, this new world takes some significant experience to become proficient.\n\n### Iterating SA: Experimental improvements for your next project\n\n1. In general, favor simplicity boundaries that reduce your work, especially in the realm of undifferentiated heavy lifting. In the realm of container-based CI, this includes having a rich coding language and a package manager to acquire additional complexity embodying utilities quickly and easily.\n\n2. In general, be suspicious of an underlying antipattern if you have to spend an inordinate amount of time coding and maintaining workarounds in the service of simplicity. In the realm of container-based CI, this would be containers that are ultra-minimalized around microservices performance characteristics when they don’t hyper-scale as a standing service within CI.\n\n3. In general, stand back and examine the net complexity of the code and frameworks that will have to be maintained by yourself or your team and check if you’ve made tradeoffs that have a net negative tax on your efficiency. When complexity that can be managed by machines enters your workspace at high frequency, then you have a massive antipattern of human efficiency.\n\n4. It is frequent that when the hueristics being applied create negative human efficiency they also create negative machine efficiency. Watch for this effect in your projects. The diagram in the post shows that over-minimalized containers can easily lead to using a lot more of them - all of which has machine overhead as well.\n\nIf the above resonates, CI pipeline engineers might want to consider loosening the \"microservices\" heuristics of hyper-specialization, ultra-minimalization,  and immutability (no dynamic installs) for CI pipeline containers in order to ensure that the true net complexity level of the code they have to maintain is in balance and their productivity is preserved.\n\n### Appendix: Working examples of this idea\n\n- [AWS CLI Tools in Containers](https://gitlab.com/guided-explorations/aws/aws-cli-tools) has both Bash and PowerShell Core (on Linux OS) available so that one container set can suit the automation shell preference of both Linux and Windows heritage CI automation engineers.\n\n- CI file [installs yq dynamically](https://gitlab.com/guided-explorations/gl-k8s-agent/gitops/envs/world-greetings-env-1/-/blob/main/.gitlab-ci.yml#L47-48) in the Bash container, but then [only installs the heavier jq and skopeo](https://gitlab.com/guided-explorations/gl-k8s-agent/gitops/envs/world-greetings-env-1/-/blob/main/.gitlab-ci.yml#L63) if needed by the work implied, which demonstrates a way to be more efficient even when runtime installs are desired.\n\n- [Bash and PowerShell Script Code Libraries in Pure GitLab CI YAML](https://gitlab.com/guided-explorations/ci-cd-plugin-extensions/script-code-libraries-in-pure-gitlab-ci-yaml) shows how to have libraries of CI script code available to every container in a pipeline without encapsulating the libraries in a container themselves and with minimalized CI YAML complexity compared to YAML anchors, references, or extends. While the method is a little bit challenging to setup, from then on out it pays back by decoupling scripting libraries from any other pipeline artifact.\n\n- [CI/CD Extension Freemarker File Templating](https://gitlab.com/guided-explorations/ci-cd-plugin-extensions/ci-cd-plugin-extension-freemarker-file-templating) shows the install is very quick and only affects one job and still version pegs the installed utility.\n",[9,696,742,1416,720],{"slug":2519,"featured":6,"template":699},"second-law-of-complexity-dynamics","content:en-us:blog:second-law-of-complexity-dynamics.yml","Second Law Of Complexity Dynamics","en-us/blog/second-law-of-complexity-dynamics.yml","en-us/blog/second-law-of-complexity-dynamics",{"_path":2525,"_dir":246,"_draft":6,"_partial":6,"_locale":7,"seo":2526,"content":2532,"config":2538,"_id":2540,"_type":14,"title":2541,"_source":16,"_file":2542,"_stem":2543,"_extension":19},"/en-us/blog/secure-and-publish-python-packages-a-guide-to-ci-integration",{"title":2527,"description":2528,"ogTitle":2527,"ogDescription":2528,"noIndex":6,"ogImage":2529,"ogUrl":2530,"ogSiteName":686,"ogType":687,"canonicalUrls":2530,"schema":2531},"Secure and publish Python packages: A guide to CI integration","Learn how to implement a secure CI/CD pipeline across five stages with the GitLab DevSecOps platform.","https://res.cloudinary.com/about-gitlab-com/image/upload/v1749662080/Blog/Hero%20Images/AdobeStock_1097303277.jpg","https://about.gitlab.com/blog/secure-and-publish-python-packages-a-guide-to-ci-integration","\n                        {\n        \"@context\": \"https://schema.org\",\n        \"@type\": \"Article\",\n        \"headline\": \"Secure and publish Python packages: A guide to CI integration\",\n        \"author\": [{\"@type\":\"Person\",\"name\":\"Tim Rizzi\"}],\n        \"datePublished\": \"2025-01-21\",\n      }",{"title":2527,"description":2528,"authors":2533,"heroImage":2529,"date":2535,"body":2536,"category":784,"tags":2537},[2534],"Tim Rizzi","2025-01-21","Supply chain security is a critical concern in software development.\nOrganizations need to verify the authenticity and integrity of their\nsoftware packages. This guide will show you how to implement a secure CI/CD\npipeline for Python packages using GitLab CI, incorporating package signing\nand attestation using Sigstore's Cosign.\n\n\nYou'll learn:\n\n\n- [Why sign and attest your Python\npackages?](#why-sign-and-attest-your-python-packages%3F)\n\n- [Pipeline overview](#pipeline-overview)\n\n- [Complete pipeline implementation: Setting up the\nenvironment](#complete-pipeline-implementation-setting-up-the-environment)\n   * [Environment configuration](#environment-configuration)\n   * [Configuration breakdown](#configuration-breakdown)\n-  The 6 stages\n\n    1. [Building](#building-crafting-the-package)\n    2. [Signing](#signing-the-digital-notarization)\n    3. [Verification](#verification-the-security-checkpoint)\n    4. [Publishing](#publishing-the-controlled-release)\n    5. [Publishing signatures](#publishing-signatures-making-verification-possible)\n    6. [Consumer verification](#consumer-verification-testing-the-user-experience)\n\n## Why sign and attest your Python packages?\n\n\nHere are four reasons to sign and attest your Python packages:\n\n\n* **Supply chain security:** Package signing ensures that the code hasn't\nbeen tampered with between build and deployment, protecting against supply\nchain attacks.\n\n* **Compliance requirements:** Many organizations, especially in regulated\nindustries, require cryptographic signatures and provenance information for\nall deployed software.\n\n* **Traceability:** Attestations provide a verifiable record of build\nconditions, including who built the package and under what circumstances.\n\n* **Trust verification:** Consumers of your package can cryptographically\nverify its authenticity before installation.\n\n\n## Pipeline overview\n\n\nEnsuring your code's integrity and authenticity is necessary. Imagine a\npipeline that doesn't just compile your code but creates a cryptographically\nverifiable narrative of how, when, and by whom your package was created.\nEach stage acts as a guardian, checking and documenting the package's\nprovenance.\n\n\nHere are six stages of a GitLab pipeline that ensure your package is secure\nand trustworthy:\n\n\n* Build: Creates a clean, standard package that can be easily shared and\ninstalled.\n\n* Signing: Adds a digital signature that proves the package hasn't been\ntampered with since it was created.\n\n* Verification: Double-checks that the signature is valid and the package\nmeets all our security requirements.\n\n* Publishing: Uploads the verified package to GitLab's package registry,\nmaking it available for others to use.\n\n* Publishing Signatures: Makes signatures available for verification.\n\n* Consumer Verification: Simulates how end users can verify package\nauthenticity.\n\n\n## Complete pipeline implementation: Setting up the environment\n\n\nBefore we build our package, we need to set up a consistent and secure build\nenvironment. This configuration ensures every package is created with the\nsame tools, settings, and security checks.\n\n\n### Environment configuration\n\n\nOur pipeline requires specific tools and settings to work correctly.\n\n\nPrimary configurations:\n\n\n* Python 3.10 for consistent builds\n\n* Cosign 2.2.3 for package signing\n\n* GitLab package registry integration\n\n* Hardcoded package version for reproducibility\n\n\n**Note about versioning:** We've chosen to use a hardcoded version\n(`\"1.0.0\"`) in this example rather than deriving it from git tags or\ncommits. This approach ensures complete reproducibility and makes the\npipeline behavior more predictable. In a production environment, you might\nwant to use semantic versioning based on git tags or another versioning\nstrategy that fits your release process.\n\n\nTool requirements:\n\n\n* Basic utilities: `curl`, `wget`\n\n* Cosign for cryptographic signing\n\n* Python packaging tools: `build`, `twine`, `setuptools`, `wheel`\n\n\n### Configuration breakdown\n\n\n```yaml\n\nvariables:\n  PYTHON_VERSION: '3.10'\n  PACKAGE_NAME: ${CI_PROJECT_NAME}\n  PACKAGE_VERSION: \"1.0.0\"\n  FULCIO_URL: 'https://fulcio.sigstore.dev'\n  REKOR_URL: 'https://rekor.sigstore.dev'\n  CERTIFICATE_IDENTITY: 'https://gitlab.com/${CI_PROJECT_PATH}//.gitlab-ci.yml@refs/heads/${CI_DEFAULT_BRANCH}'\n  CERTIFICATE_OIDC_ISSUER: 'https://gitlab.com'\n  PIP_CACHE_DIR: \"$CI_PROJECT_DIR/.pip-cache\"\n  COSIGN_YES: \"true\"\n  GENERIC_PACKAGE_BASE_URL: \"${CI_API_V4_URL}/projects/${CI_PROJECT_ID}/packages/generic/${PACKAGE_NAME}/${PACKAGE_VERSION}\"\n```\n\n\nWe use caching to speed up subsequent builds:\n\n\n```yaml\n\ncache:\n  paths:\n    - ${PIP_CACHE_DIR}\n```\n\n\n## Building: Crafting the package\n\n\nEvery software journey begins with creation. In our pipeline, the build\nstage is where raw code transforms into a distributable package, ready to\ntravel across different Python environments.\n\n\nThe build process creates two standardized formats:\n\n\n* a wheel package (.whl) for quick, efficient installation\n\n* a source distribution (.tar.gz) that carries the complete code\n\n\nHere's the build stage implementation:\n\n\n```yaml\n\nbuild:\n  extends: .python-job\n  stage: build\n  script:\n    - git init\n    - git config --global init.defaultBranch main\n    - git config --global user.email \"ci@example.com\"\n    - git config --global user.name \"CI\"\n    - git add .\n    - git commit -m \"Initial commit\"\n    - export NORMALIZED_NAME=$(echo \"${CI_PROJECT_NAME}\" | tr '-' '_')\n    - sed -i \"s/name = \\\".*\\\"/name = \\\"${NORMALIZED_NAME}\\\"/\" pyproject.toml\n    - sed -i \"s|\\\"Homepage\\\" = \\\".*\\\"|\\\"Homepage\\\" = \\\"https://gitlab.com/${CI_PROJECT_PATH}\\\"|\" pyproject.toml\n    - python -m build\n  artifacts:\n    paths:\n      - dist/\n      - pyproject.toml\n```\n\n\nLet's break down what this build stage does:\n\n\n1. Initializes a Git repository (`git init`) and configures it with basic\nsettings\n\n2. Normalizes the package name by converting hyphens to underscores, which\nis required for Python packaging\n\n3. Updates the package metadata in `pyproject.toml` to match our project\nsettings\n\n4. Builds both wheel and source distribution packages using `python -m\nbuild`\n\n5. Preserves the built packages and configuration as artifacts for\nsubsequent stages\n\n\n## Signing: The digital notarization\n\n\nIf attestation is the package's biography, signing is its cryptographic seal\nof authenticity. This is where we transform our package from a mere\ncollection of files into a verified, tamper-evident artifact.\n\n\nThe signing stage uses Cosign to apply a digital signature as an unbreakable\nseal. This isn't just a stamp — it's a complex cryptographic handshake that\nproves the package's integrity and origin.\n\n\n```yaml\n\nsign:\n  extends: .python+cosign-job\n  stage: sign\n  id_tokens:\n    SIGSTORE_ID_TOKEN:\n      aud: sigstore\n  script:\n    - |\n      for file in dist/*.whl dist/*.tar.gz; do\n        if [ -f \"$file\" ]; then\n          filename=$(basename \"$file\")\n          cosign sign-blob --yes \\\n            --fulcio-url=${FULCIO_URL} \\\n            --rekor-url=${REKOR_URL} \\\n            --oidc-issuer $CI_SERVER_URL \\\n            --identity-token $SIGSTORE_ID_TOKEN \\\n            --output-signature \"dist/${filename}.sig\" \\\n            --output-certificate \"dist/${filename}.crt\" \\\n            \"$file\"\n        fi\n      done\n  artifacts:\n    paths:\n      - dist/\n```\n\n\nThis signing stage performs several crucial operations:\n\n\n1. Obtains an OIDC token from GitLab for authentication with Sigstore\nservices\n\n2. Processes each built package (both wheel and source distribution)\n\n3. Uses Cosign to create a cryptographic signature (`.sig`) for each package\n\n4. Generates a certificate (`.crt`) that proves the signature's authenticity\n\n5. Stores both signatures and certificates alongside the packages as\nartifacts\n\n\n## Verification: The security checkpoint\n\n\nVerification is our final quality control gate. It's not just a check — it's\na security interrogation where every aspect of the package is scrutinized.\n\n\n```yaml\n\nverify:\n  extends: .python+cosign-job\n  stage: verify\n  script:\n    - |\n      failed=0\n      for file in dist/*.whl dist/*.tar.gz; do\n        if [ -f \"$file\" ]; then\n          filename=$(basename \"$file\")\n          if ! cosign verify-blob \\\n            --signature \"dist/${filename}.sig\" \\\n            --certificate \"dist/${filename}.crt\" \\\n            --certificate-identity \"${CERTIFICATE_IDENTITY}\" \\\n            --certificate-oidc-issuer \"${CERTIFICATE_OIDC_ISSUER}\" \\\n            \"$file\"; then\n            failed=1\n          fi\n        fi\n      done\n      if [ $failed -eq 1 ]; then\n        exit 1\n      fi\n```\n\n\nThe verification stage implements several security checks:\n\n\n1. Examines each package file in the `dist` directory\n\n2. Uses Cosign to verify the signature matches the package content\n\n3. Confirms the certificate's identity matches our expected GitLab pipeline\nidentity\n\n4. Validates our trusted OIDC provider issued the certificate\n\n5. Fails the entire pipeline if any verification check fails, ensuring only\nverified packages proceed\n\n\n## Publishing: The controlled release\n\n\nPublishing is where we make our verified packages available through GitLab's\npackage registry. It's a carefully choreographed release that ensures only\nverified, authenticated packages reach their destination.\n\n\n```yaml\n\npublish:\n  extends: .python-job\n  stage: publish\n  script:\n    - |\n      cat \u003C\u003C EOF > ~/.pypirc\n      [distutils]\n      index-servers = gitlab\n      [gitlab]\n      repository = ${CI_API_V4_URL}/projects/${CI_PROJECT_ID}/packages/pypi\n      username = gitlab-ci-token\n      password = ${CI_JOB_TOKEN}\n      EOF\n      TWINE_PASSWORD=${CI_JOB_TOKEN} TWINE_USERNAME=gitlab-ci-token \\\n        twine upload --repository-url ${CI_API_V4_URL}/projects/${CI_PROJECT_ID}/packages/pypi \\\n        dist/*.whl dist/*.tar.gz\n```\n\n\nThe publishing stage handles several important tasks:\n\n\n1. Creates a `.pypirc` configuration file with GitLab package registry\ncredentials\n\n2. Uses the GitLab CI job token for secure authentication\n\n3. Uploads both wheel and source distribution packages to the GitLab PyPI\nregistry\n\n4. Makes the packages available for installation via pip\n\n\n## Publishing signatures: Making verification possible\n\n\nAfter publishing the packages, we must make their signatures and\ncertificates available for verification. We store these in GitLab's generic\npackage registry, making them easily accessible to users who want to verify\npackage authenticity.\n\n\n```yaml\n\npublish_signatures:\n  extends: .python+cosign-job\n  stage: publish_signatures\n  script:\n    - |\n      for file in dist/*.whl dist/*.tar.gz; do\n        if [ -f \"$file\" ]; then\n          filename=$(basename \"$file\")\n          curl --header \"JOB-TOKEN: ${CI_JOB_TOKEN}\" \\\n               --fail \\\n               --upload-file \"dist/${filename}.sig\" \\\n               \"${GENERIC_PACKAGE_BASE_URL}/${filename}.sig\"\n\n          curl --header \"JOB-TOKEN: ${CI_JOB_TOKEN}\" \\\n               --fail \\\n               --upload-file \"dist/${filename}.crt\" \\\n               \"${GENERIC_PACKAGE_BASE_URL}/${filename}.crt\"\n        fi\n      done\n```\n\n\nThe signature publishing stage performs these key operations:\n\n\n1. Processes each built package to find its corresponding signature files\n\n2. Uses the GitLab API to upload the signature (`.sig`) file to the generic\npackage registry\n\n3. Uploads the corresponding certificate (`.crt`) file\n\n4. Makes these verification artifacts available for downstream package\nconsumers\n\n5. Uses the same version and package name to maintain the connection between\npackages and signatures\n\n\n## Consumer verification: Testing the user experience\n\n\nThe final stage simulates how end users will verify your package's\nauthenticity. This stage acts as a final check and a practical example of\nthe verification process.\n\n\n```yaml\n\nconsumer_verification:\n  extends: .python+cosign-job\n  stage: consumer_verification\n  script:\n    - |\n      git init\n      git config --global init.defaultBranch main\n      mkdir -p pkg signatures\n\n      pip download --index-url \"https://gitlab-ci-token:${CI_JOB_TOKEN}@gitlab.com/api/v4/projects/${CI_PROJECT_ID}/packages/pypi/simple\" \\\n          \"${NORMALIZED_NAME}==${PACKAGE_VERSION}\" --no-deps -d ./pkg\n\n      pip download --no-binary :all: \\\n          --index-url \"https://gitlab-ci-token:${CI_JOB_TOKEN}@gitlab.com/api/v4/projects/${CI_PROJECT_ID}/packages/pypi/simple\" \\\n          \"${NORMALIZED_NAME}==${PACKAGE_VERSION}\" --no-deps -d ./pkg\n\n      failed=0\n      for file in pkg/*.whl pkg/*.tar.gz; do\n        if [ -f \"$file\" ]; then\n          filename=$(basename \"$file\")\n          sig_url=\"${GENERIC_PACKAGE_BASE_URL}/${filename}.sig\"\n          cert_url=\"${GENERIC_PACKAGE_BASE_URL}/${filename}.crt\"\n\n          curl --fail --silent --show-error \\\n               --header \"JOB-TOKEN: ${CI_JOB_TOKEN}\" \\\n               --output \"signatures/${filename}.sig\" \\\n               \"$sig_url\"\n\n          curl --fail --silent --show-error \\\n               --header \"JOB-TOKEN: ${CI_JOB_TOKEN}\" \\\n               --output \"signatures/${filename}.crt\" \\\n               \"$cert_url\"\n\n          if ! cosign verify-blob \\\n            --signature \"signatures/${filename}.sig\" \\\n            --certificate \"signatures/${filename}.crt\" \\\n            --certificate-identity \"${CERTIFICATE_IDENTITY}\" \\\n            --certificate-oidc-issuer \"${CERTIFICATE_OIDC_ISSUER}\" \\\n            \"$file\"; then\n            failed=1\n          fi\n        fi\n      done\n\n      if [ $failed -eq 1 ]; then\n        exit 1\n      fi\n```\n\n\nThis consumer verification stage simulates the end-user experience by:\n\n\n1. Creating a clean environment to test package installation\n\n2. Downloading the published packages from the GitLab PyPI registry\n\n3. Retrieving the corresponding signatures and certificates from the generic\npackage registry\n\n4. Performing the same verification steps that end users would perform\n\n5. Ensuring the entire process works from a consumer's perspective\n\n6. Failing the pipeline if any verification step fails, providing an early\nwarning of any issues\n\n\n## Summary\n\n\nThis comprehensive pipeline provides a secure and reliable way to build,\nsign, and publish Python packages to GitLab's package registry. By following\nthese practices and implementing the suggested security measures, you can\nensure your packages are appropriately verified and safely distributed to\nyour users.\n\n\nThe pipeline combines modern security practices with efficient automation to\ncreate a robust software supply chain. Using Sigstore's Cosign for signing\nand attestation, along with GitLab's built-in security features, you can\nprovide users with trustworthy cryptographically verified packages.\n\n\n> #### Get started on your security journey today with a [free trial\nof GitLab\nUltimate](https://gitlab.com/-/trials/new?glm_content=default-saas-trial&glm_source=about.gitlab.com).\n\n\n## Learn more\n\n- [Documentation: Use Sigstore for keyless signing and\nverification](https://docs.gitlab.com/ee/ci/yaml/signing_examples.html)\n\n- [Streamline security with keyless signing and verification in\nGitLab](https://about.gitlab.com/blog/keyless-signing-with-cosign/)\n\n- [Annotate container images with build provenance using Cosign in GitLab\nCI/CD](https://about.gitlab.com/blog/annotate-container-images-with-build-provenance-using-cosign-in-gitlab-ci-cd/)\n",[784,233,284,983,9,109,496,742,720],{"slug":2539,"featured":91,"template":699},"secure-and-publish-python-packages-a-guide-to-ci-integration","content:en-us:blog:secure-and-publish-python-packages-a-guide-to-ci-integration.yml","Secure And Publish Python Packages A Guide To Ci Integration","en-us/blog/secure-and-publish-python-packages-a-guide-to-ci-integration.yml","en-us/blog/secure-and-publish-python-packages-a-guide-to-ci-integration",{"_path":2545,"_dir":246,"_draft":6,"_partial":6,"_locale":7,"seo":2546,"content":2552,"config":2557,"_id":2559,"_type":14,"title":2560,"_source":16,"_file":2561,"_stem":2562,"_extension":19},"/en-us/blog/simple-kubernetes-management-with-gitlab",{"title":2547,"description":2548,"ogTitle":2547,"ogDescription":2548,"noIndex":6,"ogImage":2549,"ogUrl":2550,"ogSiteName":686,"ogType":687,"canonicalUrls":2550,"schema":2551},"Simple Kubernetes management with GitLab","Follow our tutorial to provision a Kubernetes cluster and manage it with IAC using Terraform and Helm in 20 minutes or less.","https://res.cloudinary.com/about-gitlab-com/image/upload/v1749670037/Blog/Hero%20Images/auto-deploy-google-cloud.jpg","https://about.gitlab.com/blog/simple-kubernetes-management-with-gitlab","\n                        {\n        \"@context\": \"https://schema.org\",\n        \"@type\": \"Article\",\n        \"headline\": \"Simple Kubernetes management with GitLab\",\n        \"author\": [{\"@type\":\"Person\",\"name\":\"Noah Ing\"}],\n        \"datePublished\": \"2022-11-15\",\n      }",{"title":2547,"description":2548,"authors":2553,"heroImage":2549,"date":2554,"body":2555,"category":717,"tags":2556},[781],"2022-11-15","Kubernetes can be very complex and has dozens of tutorials out there on how\nto provision and manage a cluster. This tutorial aims to provide a simple,\nlightweight solution to provision a Kubernetes cluster and manage it with\ninfrastructure as code (IaC) using Terraform and Helm in 20 minutes or less.\n\n\n**The final product of this tutorial will be two IaC repositories with fully\nfunctional CI/CD pipelines:**\n\n\n1.\n[gitlab-terraform-k8s](https://gitlab.com/gitlab-org/configure/examples/gitlab-terraform-eks)\n- A single source of truth to provision, configure, and manage your\nKubernetes infrastructure using Terraform\n\n1.\n[cluster-management](https://gitlab.com/gitlab-org/project-templates/cluster-management)\n- A single source of truth to define the desired state of your Kubernetes\ncluster using the GitLab Agent for Kubernetes and Helm\n\n\n![Final\nProduct](https://about.gitlab.com/images/blogimages/2022-11-11-simple-kubernetes-management-with-gitlab/final-product.png){:\n.shadow}\n\n\n\n### Prerequisites\n\n- AWS or GCP account with permissions to provision resources\n\n- GitLab account \n\n- Access to a GitLab Runner\n\n- 20 minutes\n\n\n### An overview of this tutorial is as follows:\n\n\n1. Set up the GitLab Terraform Kubernetes Template 🏗️\n\n2. Register the GitLab Agent 🕵️\n\n3. Add in Cloud Credentials ☁️🔑\n\n4. Set up the Kubernetes Cluster Management Template 🚧\n\n5. Enjoy your Kubernetes Cluster completely managed in code! 👏\n\n\n## Set up the GitLab Terraform Kubernetes Template\n\n\nStart by importing the example project by URL -\n[https://gitlab.com/projects/new#import_project](https://gitlab.com/projects/new#import_project)\n\n\nTo import the project:\n\n\n1. In GitLab, on the top bar, select **Main menu > Projects > View all\nprojects**.\n\n2. On the right of the page, select **New project**.\n\n3. Select **Import project**.\n\n4. Select **Repository by URL**.\n\n5. For the Git repository URL:\n\n- [GCP Google Kubernetes\nEngine](https://cloud.google.com/kubernetes-engine):\nhttps://gitlab.com/gitlab-org/configure/examples/gitlab-terraform-gke.git\n\n- [AWS Elastic Kubernetes Service](https://aws.amazon.com/eks/):\nhttps://gitlab.com/gitlab-org/configure/examples/gitlab-terraform-eks.git\n\n6. Complete the fields and select **Create project**.\n\n\n## Register the GitLab Agent\n\n\nWith your newly created **gitlab-terraform-k8s** repo, create a GitLab Agent\nfor Kubernetes:\n\n\n1. On the left sidebar, select **Infrastructure > Kubernetes clusters**.\nSelect **Connect a cluster (agent).**\n\n2. From the **Select an agent** dropdown list, select **eks-agent/gke-agent\nand select **Register an agent**.\n\n3. GitLab generates a registration token for the agent. **Securely store\nthis secret token, as you will need it later.**\n\n4. GitLab provides an address for the agent server (KAS). Securely store\nthis as you will also need it later.\n\n5. Add this to the\n**gitlab-terraform-eks/.gitlab/agents/eks-agent/config.yaml** in order to\nallow the GitLab Agent to have access to your entire group.\n\n\n```yaml\n\nci_access:\n  groups:\n    - id: your-namespace-here\n```\n\n\n![Register GitLab\nAgent](https://about.gitlab.com/images/blogimages/2022-11-11-simple-kubernetes-management-with-gitlab/register-gitlab-agent.png){:\n.shadow}\n\n\n\n## Add in your Cloud Credentials to CI/CD variables\n\n\n### [AWS EKS](https://aws.amazon.com/eks/)\n\n\nOn the left sidebar, select **Settings > CI/CD. Expand Variables**.\n\n1. Set the variable **AWS_ACCESS_KEY_ID** to your AWS access key ID.\n\n2. Set the variable **AWS_SECRET_ACCESS_KEY** to your AWS secret access key.\n\n3. Set the variable **TF_VAR_agent_token** to the agent token displayed in\nthe previous task.\n\n4. Set the variable **TF_VAR_kas_address** to the agent server address\ndisplayed in the previous task.\n\n\n![Add in CI/CD\nvariables](https://about.gitlab.com/images/blogimages/2022-11-11-simple-kubernetes-management-with-gitlab/cicd-variables.png){:\n.shadow}\n\n\n\n### [GCP GKE](https://cloud.google.com/kubernetes-engine)\n\n\n1. To authenticate GCP with GitLab, create a GCP service account with the\nfollowing roles: **Compute Network Viewer, Kubernetes Engine Admin, Service\nAccount User, and Service Account Admin**. Both User and Admin service\naccounts are necessary. The User role impersonates the default service\naccount when creating the node pool. The Admin role creates a service\naccount in the kube-system namespace.\n\n2. **Download the JSON file** with the service account key you created in\nthe previous step.\n\n3. On your computer, encode the JSON file to base64 (replace\n/path/to/sa-key.json to the path to your key):\n\n\n```\n\nbase64 -i /path/to/sa-key.json | tr -d\n\n```\n\n\n- Use the output of this command as the **BASE64_GOOGLE_CREDENTIALS**\nenvironment variable in the next step.\n\n\nOn the left sidebar, select **Settings > CI/CD. Expand Variables**.\n\n5. Set the variable **BASE64_GOOGLE_CREDENTIALS** to the base64 encoded JSON\nfile you just created.\n\n6. Set the variable **TF_VAR_gcp_project** to your GCP’s project name.\n\n7. Set the variable **TF_VAR_agent_token** to the agent token displayed in\nthe previous task.\n\n8. Set the variable **TF_VAR_kas_address** to the agent server address\ndisplayed in the previous task.\n\n\n## Run GitLab CI to deploy your Kubernetes cluster!\n\n\n![Deploy Kubernetes\ncluster](https://about.gitlab.com/images/blogimages/2022-11-11-simple-kubernetes-management-with-gitlab/pipeline-view.png){:\n.shadow}\n\n\nWhen successfully completed, view the cluster in the AWS/GCP console!\n\n\n![AWS\nEKS](https://about.gitlab.com/images/blogimages/2022-11-11-simple-kubernetes-management-with-gitlab/aws-eks.png){:\n.shadow}\n\n\n### You are halfway done! 👏 Keep it up!\n\n\n## Set up the Kubernetes Cluster Management Project\n\n\nCreate a project from the cluster management project template -\n[https://gitlab.com/projects/new#create_from_template](https://gitlab.com/projects/new#create_from_template)\n\n\n1. In GitLab, on the top bar, select **Main menu > Projects > View all\nprojects**.\n\n2. On the right of the page, select **New project**.\n\n3. Select **Create from template**.\n\n4. From the list of templates, next to **GitLab Cluster Management**, select\n**Use template**.\n\n5. Enter the project details. Ensure this project is created in the same\nnamespace as the gitlab-terraform-k8s project.\n\n6. Select **Create project**.\n\n7. Once the project is created on the left sidebar, select **Settings >\nCI/CD. Expand Variables**.\n\n8. Set the variable KUBE_CONTEXT to point to the GitLab Agent. For example,\n`noah-ing-demos/infrastructure/gitlab-terraform-eks:eks-agent`.\n\n\n![Set Kube\nContext](https://about.gitlab.com/images/blogimages/2022-11-11-simple-kubernetes-management-with-gitlab/kube-config.png){:\n.shadow}\n\n\n\n- **Uncomment the applications you'd like to be installed** into your\nKubernetes cluster in the **helmfile.yaml**. In this instance I chose\ningress, cert-manager, prometheus, and Vault. \n\n\n![Uncomment Applications in\nhelmfile](https://about.gitlab.com/images/blogimages/2022-11-11-simple-kubernetes-management-with-gitlab/helmfile.png){:\n.shadow}\n\n\nThat will trigger your **CI/CD pipeline** and it should look like this.\n\n\n![Cluster Management\nCI/CD](https://about.gitlab.com/images/blogimages/2022-11-11-simple-kubernetes-management-with-gitlab/cluster-management-cicd.png){:\n.shadow}\n\n\nOnce completed, **go to the AWS/GCP console** and check out all the deployed\nresources!\n\n\n![Deployed EKS\napplications](https://about.gitlab.com/images/blogimages/2022-11-11-simple-kubernetes-management-with-gitlab/deployed-eks-applications.png){:\n.shadow}\n\n\n### Voila! 🎉\n\n\n## Enjoy your Kubernetes cluster completely defined in code! 👏👏👏\n\n\nNow with these two repositories you can **manage a Kubernetes cluster\nentirely through code**:\n\n\n- For managing the Kubernetes cluster's infrastructure and configuring its\nresources you can make changes to the\n[gitlab-terraform-eks](https://gitlab.com/gitlab-org/configure/examples/gitlab-terraform-eks)\nrepository you have setup. This project has a **Terraform CI/CD pipeline**\nthat will allow you to **review, provision, configure, and manage your\nKubernetes** infrastructure with ease.\n\n\n- For managing the desired state of the Kubernetes cluster, the\n[cluster-management](https://gitlab.com/gitlab-org/project-templates/cluster-management)\nrepository has a **GitLab Agent** set up and will **deploy any Kubernetes\nobjects defined in the helm files**.\n\n\n➡️ Bonus: If you'd like to deploy your own application to the Kubernetes\ncluster, then add to your **cluster-management** `helmfile` and see the\nGitLab Agent for Kubernetes roll it out with ease!\n\n\n\n## References\n\n- [Create a New GKE\nCluster](https://docs.gitlab.com/ee/user/infrastructure/clusters/connect/new_gke_cluster.html)\n\n- [Create a New EKS\nCluster](https://docs.gitlab.com/ee/user/infrastructure/clusters/connect/new_eks_cluster.html)\n\n- [Cluster Management\nProject](https://docs.gitlab.com/ee/user/clusters/management_project.html)\n\n\n\n## Related posts\n\n- [The ultimate guide to GitOps with\nGitLab](https://about.gitlab.com/blog/the-ultimate-guide-to-gitops-with-gitlab/)\n\n- [GitOps with GitLab: Infrastructure provisioning with GitLab and\nTerraform](https://about.gitlab.com/blog/gitops-with-gitlab-infrastructure-provisioning/)\n\n- [GitOps with GitLab: Connect with a Kubernetes\ncluster](https://about.gitlab.com/blog/gitops-with-gitlab-connecting-the-cluster/)\n",[742,914,550,9,696,741],{"slug":2558,"featured":6,"template":699},"simple-kubernetes-management-with-gitlab","content:en-us:blog:simple-kubernetes-management-with-gitlab.yml","Simple Kubernetes Management With Gitlab","en-us/blog/simple-kubernetes-management-with-gitlab.yml","en-us/blog/simple-kubernetes-management-with-gitlab",{"_path":2564,"_dir":246,"_draft":6,"_partial":6,"_locale":7,"seo":2565,"content":2570,"config":2575,"_id":2577,"_type":14,"title":2578,"_source":16,"_file":2579,"_stem":2580,"_extension":19},"/en-us/blog/stageless-pipelines",{"title":2566,"description":2567,"ogTitle":2566,"ogDescription":2567,"noIndex":6,"ogImage":1687,"ogUrl":2568,"ogSiteName":686,"ogType":687,"canonicalUrls":2568,"schema":2569},"Write a stageless CI/CD pipeline using GitLab 14.2","With GitLab 14.2, you can write a complete CI/CD pipeline without defining any stages.","https://about.gitlab.com/blog/stageless-pipelines","\n                        {\n        \"@context\": \"https://schema.org\",\n        \"@type\": \"Article\",\n        \"headline\": \"Write a stageless CI/CD pipeline using GitLab 14.2\",\n        \"author\": [{\"@type\":\"Person\",\"name\":\"Dov Hershkovitch\"}],\n        \"datePublished\": \"2021-08-24\",\n      }",{"title":2566,"description":2567,"authors":2571,"heroImage":1687,"date":2572,"body":2573,"category":717,"tags":2574},[1150],"2021-08-24","\n\nGitLab CI/CD technology has historically divided a pipeline into stages based on the typical development workflow. Now that [GitLab 14.2 has launched](/releases/2021/08/22/gitlab-14-2-released/), users can speed up cycle times by using the [`needs`](https://docs.gitlab.com/ee/ci/yaml/#needs) command to write a complete CI/CD pipeline with every job in the single stage. In fact, you can omit stages completely and have a [\"stageless\" pipeline](https://about.gitlab.com/releases/2021/08/22/gitlab-14-2-released/#stageless-pipelines) that executes entirely based on the `needs` dependencies.\n\n## Understanding stages\n\nIn GitLab CI/CD, you use [stages](https://docs.gitlab.com/ee/ci/yaml/#stages) to group jobs based on the development workflow and control the order of execution for CI/CD jobs.\n\nPipelines execute each stage in order, where all jobs in a single stage run in parallel. After a stage completes, the pipeline moves on to execute the next stage and runs those jobs, and the process continues like this until the pipeline completes or a job fails. If a job fails, the jobs in later stages don't start at all.\n\n## History of stages in GitLab CI/CD\n\nWhen we first designed GitLab CI/CD, we knew that in a continuous integration workflow you build and test software every time a developer pushes code to the repository. The use of stages in GitLab CI/CD helped establish a mental model of how a pipeline will execute. By default, stages are ordered as: `build`, `test`, and `deploy` - so all stages execute in a logical order that matches a development workflow. The first step is to build the code, and if that works, the next step is to test it. If the tests pass, then you deploy the application.\n\nOf course, you can actually create as many stages as you like and order them as desired. We also introduced the `.pre` and `.post` stages which are predefined stages that let you set certain jobs to always run at the beginning (`.pre`) or end (`.post`) of your pipeline. GitLab CI/CD used stages for the past few years.\n\n## Starting to break out of stage order\n\nLast year we introduced the [`needs`](https://docs.gitlab.com/ee/ci/yaml/#needs) keyword which allows a user to create a Directed Acyclic Graphs (DAG) to speed up the pipeline. A job that uses the `needs` keyword creates a dependency between it and one or more different jobs in earlier stages. The job is allowed to start as soon as the earlier jobs finish, skipping the stage order to speed up the pipeline.\n\nIn a sense, you can think of a pipeline that only uses stages as the same as a pipeline that uses `needs` – except every job \"needs\" every job in the previous stage. On the other hand, if jobs in a pipeline *do* use `needs`, they only \"need\" the exact jobs that will allow them to complete successfully. They shouldn't need all the jobs in the previous stage. For example, there's no need for a ruby test job to wait for a javascript linter to complete.\n\n## Stageless pipelines become reality\n\nThe `needs` keyword quickly became popular among our users and helped optimize and accelerate CI/CD pipelines. However it had one limitation: A `needs` dependency could only exist between the jobs in different stages. This limitation was a pain point for our users because they wanted to configure the pipeline based on the `needs` dependencies only and drop the use of stages completely. The importance of adding this functionality became clear because this was one of the most popular [feature requests](https://gitlab.com/gitlab-org/gitlab/-/issues/30632) for GitLab CI/CD.\n\nNow in GitLab 14.2, [you can finally define a whole pipeline using nothing but `needs` to control the execution order](/releases/2021/08/22/gitlab-14-2-released/#stageless-pipelines). No more need to define any stages if you use `needs`!\n\n## Are we getting rid of stages?\n\nNo, we do not have any plans to remove stages from our GitLab CI/CD, and it still works great for those that prefer this workflow.\n\nIn fact if you build a \"stageless\" pipeline, there will still be at least one stage that holds all the jobs. Removing stages was never the goal. Our goal is still to support you in building better and faster pipelines, while providing you with the high degree of flexibility you want.\n\nAs always, share any thoughts, comments, or questions, by [opening an issue in GitLab](https://gitlab.com/gitlab-org/gitlab/-/issues/new?issue%5Bmilestone_id%5D=) and mentioning me (@dhershkovitch).\n",[9,696],{"slug":2576,"featured":6,"template":699},"stageless-pipelines","content:en-us:blog:stageless-pipelines.yml","Stageless Pipelines","en-us/blog/stageless-pipelines.yml","en-us/blog/stageless-pipelines",{"_path":2582,"_dir":246,"_draft":6,"_partial":6,"_locale":7,"seo":2583,"content":2589,"config":2595,"_id":2597,"_type":14,"title":2598,"_source":16,"_file":2599,"_stem":2600,"_extension":19},"/en-us/blog/the-kubecon-summary-from-a-product-perspective",{"title":2584,"description":2585,"ogTitle":2584,"ogDescription":2585,"noIndex":6,"ogImage":2586,"ogUrl":2587,"ogSiteName":686,"ogType":687,"canonicalUrls":2587,"schema":2588},"How what we learned at KubeCon EU 2022 will impact our product roadmaps","Platform integrations and secrets management are among our product team's primary takeaways. Find out why.","https://res.cloudinary.com/about-gitlab-com/image/upload/v1750097776/Blog/Hero%20Images/Blog/Hero%20Images/2_2.png_1750097776369.png","https://about.gitlab.com/blog/the-kubecon-summary-from-a-product-perspective","\n                        {\n        \"@context\": \"https://schema.org\",\n        \"@type\": \"Article\",\n        \"headline\": \"How what we learned at KubeCon EU 2022 will impact our product roadmaps\",\n        \"author\": [{\"@type\":\"Person\",\"name\":\"Viktor Nagy\"}],\n        \"datePublished\": \"2022-05-31\",\n      }",{"title":2584,"description":2585,"authors":2590,"heroImage":2586,"date":2592,"body":2593,"category":1131,"tags":2594},[2591],"Viktor Nagy","2022-05-31","\nAfter two years of only virtual KubeCon events, the GitLab product team was excited to participate in and meet colleagues, partners, and more from our industry at KubeCon EU 2022, held in Valencia, Spain. We were present with four product leaders, a software developer, and a UX researcher. This post summarizes our primary takeaways from the conference, an experience that will affect our roadmaps.\n\nWe will discuss the following topics:\n\n- Internal platforms and GitOps\n- Secrets management\n- Infrastructure integrations\n- WebAssembly a.k.a. WASM\n\nThere were 32 topic types and several 0-day events at KubeCon. Many talks focused on a few tools. Many Cloud Native Computing Foundation ([CNCF](https://www.cncf.io/)) projects had their community meetings during these days. Some talks were given IRL, and others were broadcast virtually with live Q&A. There were a variety of topics and approaches. There were many talks about the various aspects of cluster management, too. However, we left this topic out on purpose because at GitLab we want to focus on the software developers and provide one DevOps platform to support their work. Cluster management is one step away from this focus. Still, we noticed some remarkable patterns as highlighted by the four elements of our list.\n\n> You’re invited! Join us on June 23rd for the [GitLab 15 launch event](https://page.gitlab.com/fifteen) with DevOps guru Gene Kim and several GitLab leaders. They’ll show you what they see for the future of DevOps and The One DevOps Platform.\n\n## Internal platforms and GitOps\n\nCompanies want their developers to focus on their core business. They create internal platforms to hide the complexity of Day 0-2 operations from their software engineers and still allow the \"shift left\" movement of DevOps. These platforms often involve the welding of several tools.\n\nMany talks presented how the given team or company approached their platform problem and what tools they used, and one could often feel the 18-month sweat of a whole platform team trying to come up with a solution.\n\nThese platforms use either a push- or pull-based model for deployments. No single approach is emerging due to legacy applications and different requirements. While there is a definition of GitOps provided by the [OpenGitOps](https://opengitops.dev/) initiative, several presenters offered their own definitions, including of pull-based deployments.\n\nWe fielded a large-scale survey related to secrets at KubeCon, and learned that users would like help with the [Pipeline Authoring](/direction/verify/pipeline_composition/) workflow.\n\nBesides the wiring of the tools, the industry is still looking for a unified approach to multi-tenancy (there might not be one), and sometimes integrating security processes seems overly challenging.\n\n### How does this affect our roadmap?\n\nThere is a lot of potential in building a platform used as the starting point for internal platforms. Imagine a \"tool\" that shortens the time required to create an internal platform to days or weeks instead of a whole year. This is the GitLab vision of The One DevOps platform.\n\nAs a result, we don't plan any changes in our direction. We will continue investing in the recently started [Deployment direction](/direction/delivery/) to provide all the building blocks for a platform in a single tool and are already actively looking for integrated experiences across our offering.\n\nWe’re working on a CI/CD Component Catalog that includes CI templates. This will [support the Pipeline Authoring workflow](https://gitlab.com/groups/gitlab-org/-/epics/7462).\n\n## Secrets management\n\nOne of the things that often came up in our discussions is secrets management. We fielded a large-scale survey related to secrets at KubeCon, and attendees were glad that we’re thinking about this topic. Security is part of the DevOps discussion, and secrets management is a serious issue, especially in a cloud-native aspect.\n\n- Jenkins, GitHub and GitLab were all mentioned during the secret management discussions.\n- Users would like to offload the secrets management responsibility to another product. In many cases, their security requirements are strict, so they don't want/can't handle secrets by themselves.\n- Hashicorp Vault is a preferred tool (primarily in large enterprise companies working in finance or government) to manage and handle secrets. At the same time, most companies would like to avoid operating one more application in their stack.\n- Open ID Connect [OIDC](https://docs.gitlab.com/ee/integration/openid_connect_provider.html) with the JSON web token (JWT) is an essential direction for us.\n\n### How does this affect our roadmap?\n\nWe should invest more in secrets management since this is a pain our customers would like us to solve, and it's becoming a nonstarter feature for many organizations.\n\nWe want to advance in three main vectors:\n\n- Improve our existing secrets management solution - although we don't have a clear solution, we should improve our current variables capabilities to include additional features that could help users leverage variables for secrets. So it would be a \"good enough\" feature they can use. We are actively working toward this direction by removing some of the limitations we have around [variables and masking](https://gitlab.com/groups/gitlab-org/-/epics/1994).\n- Improve our existing [Hashicorp Vault integration](https://docs.gitlab.com/ee/ci/examples/authenticating-with-hashicorp-vault/) using the JWT token, allowing us to integrate with additional vendors (AWS, AZURE, GCP). Like the previous point, we are moving toward this direction by supporting OIDC and [adding audience claims to our JWT token](https://gitlab.com/groups/gitlab-org/-/epics/7335).\n- We need to develop [a clear strategy for a built-in secrets management solution](/direction/govern/pipeline_security/secrets_management/#next-9-12-monhts). In order to provide our users/customers with choice, GitLab wants to use Hashicorp Vault for secrets management handling. We believe that our approach should be not to build the logic ourselves but to leverage an open source, [cloud native](/topics/cloud-native/) project that we could build into GitLab.\n\n## Infrastructure integrations\n\nInfrastructure integrations came in several flavors during the talks. Some are about cluster management, that is not our focus in this blog. Several presentations show that internal platforms need a strong infrastructure aspect, too. When a new project/microservice is started, it might require a new namespace in the cluster with associated RBAC and policies, optionally storage, a source code management repo with automation, and the appropriate permissions. Deployments might create ephemeral environments or could modify the underlying environment within predefined constraints.\n\nThe top tools mentioned in this area are:\n\n- Terraform\n- Crossplane\n- Pulumi\n\n### How does this affect our roadmap?\n\nGitLab already has [great integrations for Terraform](https://docs.gitlab.com/ee/user/infrastructure/iac/), and the other tools are on our radar, too.\n\nWe are open to integrations but cannot currently prioritize the other integrations on our own. We hope that the community will be interested in contributing to benefit everyone.\n\nBuilding Docker containers might not be necessary to get easy-to-manage container binaries. WASM runtimes become available for Kubernetes, and many programming languages can natively compile to WASM. WASM can provide a secure runtime environment without Docker and might be able to simplify the toolchain developers need to learn.\n\nWe don't plan to add direct WASM support to GitLab yet. The generic package registry can hold WASM modules while their deployment is up to the user.\n\nAt the same time, we see a lot of potential in simple runtime environments built around WASM. While GitLab is not in the business of offering runtime services, we will be actively monitoring the market. We might look into more WASM integrations as we see more demand and tools and services maturing in this space.\n\n## GitLab feedback\n\nIt's great to work on a product where the overall sentiment is positive, both from customers that intensely rely on it and from attendees that have to use other tools but would love to use GitLab or just started to play with it recently.\n\nWe received the following notable mentions as feedback:\n\n- Stability and reliability improved over the last several months.\n- Users love our documentation (primarily around CI) - they mentioned it's easy to use and get started with.\n- Given the size of GitLab and the number of our users, we received feedback about long-outstanding issues. We were happy to respond that we are addressing at least some of them shortly.\n- Several customers had asked if we got some resources for migrating from Jenkins to GitLab.\n- A few customers mentioned that they had to move away from GitLab mainly because of an upper-level decision despite favouring GitLab.\n\n## Conclusions\n\n![The GitLab team](https://about.gitlab.com/images/blogimages/kubecon-gitlab-team.jpg)\n\nWe enjoyed all the talks and were delighted to meet and speak with our users and customers. Thanks to all of you, we could \"feel the pulse\" on how we are doing and validate our direction.\n\nWe hope that this blog will guide those who could not [attend KubeCon](https://about.gitlab.com/events/kubecon/) and serve as a summary for those who did attend. All the recordings will likely be available on YouTube from Jun 6, 2022.\n\nLet us know in the comments if you think we missed some important direction.\n\n_This blog post and linked pages contain information related to upcoming products, features, and functionality.\nIt is important to note that the information presented is for informational purposes only. Please do not rely on this information for purchasing or planning purposes. As with all projects, the items mentioned in this blog and linked pages are subject to change or delay. The development, release, and timing of any products, features, or functionality remain at the sole discretion of GitLab Inc._\n",[914,9,696,550,1133,741],{"slug":2596,"featured":6,"template":699},"the-kubecon-summary-from-a-product-perspective","content:en-us:blog:the-kubecon-summary-from-a-product-perspective.yml","The Kubecon Summary From A Product Perspective","en-us/blog/the-kubecon-summary-from-a-product-perspective.yml","en-us/blog/the-kubecon-summary-from-a-product-perspective",{"_path":2602,"_dir":246,"_draft":6,"_partial":6,"_locale":7,"seo":2603,"content":2609,"config":2615,"_id":2617,"_type":14,"title":2618,"_source":16,"_file":2619,"_stem":2620,"_extension":19},"/en-us/blog/thelastmile-gitlab",{"title":2604,"description":2605,"ogTitle":2604,"ogDescription":2605,"noIndex":6,"ogImage":2606,"ogUrl":2607,"ogSiteName":686,"ogType":687,"canonicalUrls":2607,"schema":2608},"Inside the collaboration between GitLab and The Last Mile","GitLab teamed up with The Last Mile to bring open source DevOps and tech mentorship to incarcerated populations across the United States.","https://res.cloudinary.com/about-gitlab-com/image/upload/v1749681743/Blog/Hero%20Images/tlm-blogpost-banner.png","https://about.gitlab.com/blog/thelastmile-gitlab","\n                        {\n        \"@context\": \"https://schema.org\",\n        \"@type\": \"Article\",\n        \"headline\": \"Inside the collaboration between GitLab and The Last Mile\",\n        \"author\": [{\"@type\":\"Person\",\"name\":\"Christina Hupy, Ph.D.\"}],\n        \"datePublished\": \"2020-11-13\",\n      }",{"title":2604,"description":2605,"authors":2610,"heroImage":2606,"date":2612,"body":2613,"category":694,"tags":2614},[2611],"Christina Hupy, Ph.D.","2020-11-13","\n\n[The Last Mile (TLM)](https://thelastmile.org/), an organization focused on changing lives through technology, is tackling the daunting problem of mass incarceration in the United States by providing education and career training opportunities to incarcerated individuals to help break the generational cycle of incarceration. GitLab team members with similar passions and ideas connected with The Last Mile team and built a partnership to help bring the tech industry and mentorship directly to incarcerated individuals.\n\n## AMA to Coffee Chat to Partnership\n\nThe idea for TLM partnership originated during an AMA (or \"Ask Me Anything\" session) between GitLab CEO, [Sid Sijbrandij](/company/team/#sytses), and GitLab team members. [In one of these AMAs](https://www.youtube.com/watch?v=qi9zrymBO8o), [Tucker Logan](/company/team/#tuckcodes), a federal solutions architect at GitLab, asked Sid about the inspiration behind his [tweet](https://twitter.com/sytses/status/1227319454817804288) about mass incarceration. In a follow-up question, [Morgen Smith](/company/team/#msmith6), a sales development representative (SDR) for the Americas, asked Sid if GitLab would consider creating initiatives to help combat the school-to-prison pipeline.\n\nAs a former educator, Morgen has witnessed first-hand the national trend of disadvantaged youth being agressively disciplined in schools, which can then lead to juvenile offenses and later to formal charges. During the AMA, Morgen asked Sid: \"What do you think GitLab could do to encourage minority youth in this situation to be inspired by opportunities in tech?\" Sid shared his support and passion for the topic, and invited Morgen and Tyler to host an [open coffee chat](/company/culture/all-remote/informal-communication/#coffee-chats) on the topic to brainstorm ideas and next steps.\n\nDuring the coffee chat, Sid decided to take the smallest step, first. He visited San Quentin State Prison in San Rafael, Calif., and organized a call with Chris Redlitz, a co-founder of TLM. It turns out that TLM was using GitLab internally and also using the GitLab Community Edition to train nearly 300 students participating in their programs about how to use DevOps.\n\nTLM is a nonprofit program that started at San Quentin. TLM works with the incarcerated populations at men’s, women’s, and young adult correctional facilities to help them build relevant skills in technology with the goal of preparing individuals for successful reentry and building careers in business and technology. Today, TLM is in 23 classrooms across six states and has served 622 students since its inception.\n\n## TLM students learn DevOps with GitLab\n\nParticipants in TLM use the self-managed, free open core version of GitLab in their courses on Web Development. Each of the twenty individual classrooms have their own self-managed instance which around 20 students use to create and host their own private repositories. The sandbox environments are deployed centrally via Google Cloud. The core curriculum includes HTML/CSS and JavaScript, Node.js, Express.js, React.js, and Mongodb. GitLab is used primarily as a [source code management tool](/solutions/source-code-management/) for the students. Students write and commit code to personal repositories during course assignments. TLM Remote Instruction team also manages student-facing GitLab repositories to demonstrate industry best practices in merging, code collaboration, and version control platforms. Additionally, TLM leverages GitLab by providing students access to their repositories after they are released from prison, preserving commit history and all version control for the aspiring coders.\n\n\"By utilizing GitLab, The Last Mile students become comfortable using a best-in-class open source DevOps tool,\" says Tulio Cardozo, IT Manager, TLM. \"This experience empowers our students as aspiring software engineers, enabling them to enter the workforce with the collaboration and communication framework skills employers demand.\"\n\nThe GitLab team is partnering with the TLM Programs department to organize a series of webinars and workshops for the students. The first webinar kicked off in June of 2020 and was broadcast to 27 students (men, women, and youth programs), across four classrooms in several states. The topic was an introduction to GitLab and DevOps. Sid joined and shared the story of founding GitLab and his journey in tech. [Brendan O’Leary](/company/team/#brendan), a senior developer evangelist at GitLab, provided an overview of DevOps and explained how GitLab is the first single application for the entire DevOps lifecycle.\n\n\"The students appreciated the information on how to get started as new developers. Sid and Brendan helped the students believe they could accomplish anything with enough hard work,\" says a classroom facilitator from the Pendleton Youth Correctional Facility in Indiana.\n\nThe TLM team added that the webinar exposed students to a large company that works remotely and introduced them to an industry-recognized brand that the students use. In addition to the value of the content itself, there was a Q&A portion of the session where the studetns asked questions about the technology itself, such as how to start an open-source project and protecting intellectual property in open source, and about the facilitators' personal journey into tech.\n\nWatch the webinar with GitLab and TLM below.\n\n\u003C!-- blank line -->\n\u003Cfigure class=\"video_container\">\n \u003Ciframe src=\"https://www.youtube-nocookie.com/embed/ejHmvMjXJVU\" frameborder=\"0\" allowfullscreen=\"true\"> \u003C/iframe>\n\u003C/figure>\n\u003C!-- blank line -->\n\nIn addition to the general workshop, the teams also collaborated on more technical content. The students at the Pendleton Juvenile Correctional Facility had a very special guest visit their [Web Development Fundamentals Course](https://thelastmile.org/our-work/), [Natalia Tepluhina](/company/team/#ntepluhina). Natalia, who currently lives in the Ukraine, is a frontend engineer at GitLab and also serves as a [core Vue.js team member](https://vuejs.org/v2/guide/team.html) and [core team member](/community/core-team/) of GitLab itself. Natalia answered a variety of questions about how to approach learning Javascript and provided a few demos related to specific questions from the students.\n\n## Mentorship for a career in DevOps\n\nGitLab and TLM also partnered on a series of Technical recruiting workshops with the classrooms. These have definitely been one of the highlights of the partnership thus far. In these workshops, a GitLab recruiter gave a presentation on the technical recruiting processes at GitLab, best practices during the application process and interview process, as well as an overview of what to expect during an interview. During each of the four sessions, the recruiters directly engaged with the participants, who asked a variety of questions, including:\n\n* How do I address incarceration on my resume?\n* What about background checks?\n* How do I gain professional experience while incarcerated?\n\nThe GitLab recruiting team was very sensitive to the participants' concerns and provided honest, clear answers, and great suggestions. The recruiters shared that during the process candidates should think of their recruiter as a resource, and they can always ask to speak to the People team at GitLab in confidence if it would help reassure them with any concerns they have regarding their criminal records. The recruiters encouraged the students to highlight their work in TLM courses on their resume and think about whether they can use course projects to start to build a portfolio. In addition, the facilitators encouraged participants to think about contributing to open source projects as a way to build technical skills, increase their network and mentorship opportunities.\n\n## How can open source help incarcerated populations gain experience in tech?\n\nThe discussion around contributing to open source projects as a way to build technical skills sparked a few different exciting ideas with the teams. One of these ideas was to hold a first time contributor workshop with alumni from TLM. The workshop was held in September 2020 had 16 alumni participants, four GitLab team members, including Sid, and five TLM team members. The workshop covered the basics on how to contribute to GitLab and demonstrated the step-by-step process. Participants were [provided an issue](https://gitlab.com/gitlab-org/gitlab/-/issues/247284) with a list of simple fixes with the label [\"good-for-new-contributors\"](https://gitlab.com/groups/gitlab-org/-/labels?utf8=%E2%9C%93&subscribed=&search=good+for+new+contributors) in the GitLab docs or handbook with typos or other minor changes. We had a few merge requests after just a few hours of the workshop! Participants were encouraged to tag GitLab team members for recognition and to win a pair of tanuki socks – by the end of the week we had given away six pairs of socks.\n\nParticipants and instructors appreciated the opportunity to learn in a hands-on way during the workshop:\n\n\"Thank you for the opportunity to participate in the GitLab workshop. I am so grateful to the GitLab staff for taking the time to introduce those of us who are new to GitLab to the history and functionality of the company. I learned so much, not just about how I can utilize GitLab to accomplish personal tasks more efficiently, but also how I can contribute and collaborate more with others and contribute to my local and global communities.\" - TLM staff and alumna.\n\nThe GitLab team found the experience equally rewarding. \"Working with The Last Mile was such a rewarding experience! When I think about how our product takes in contributions from all over the world and knowing it is also leveraged by those currently and or previously incarcerated really shows how truly 'inclusive' Git can be. Additionally, the empowerment it offers and the gift of knowledge and skill that can't be taken away is invaluable,\" says [Candace Brydsong Williams](/company/team/#cwilliams3), manage of the Diversity, Inclusion and Belonging program at GitLab.\n\n## How TLM uses GitLab technology\n\nGitLab also provides free licenses of our top-tier hosted application for the TLM team, who use our DevOps technology in nearly every aspect of their operations.\n\nTLM transitioned from GitHub to GitLab in 2019 after we provided the licenses. Initially, GitLab was used primarily in TLM's engineering department to track all internal processes with issues and Wikis. Infrastructure as code data and internal information is stored in repositories. Soon, TLM adopted GitLab technology in their education and programs departments, where it is now being used for project management. TLM now uses sprint planning, milestones, issues, priority levels, burndown charts, and issues boards to streamline project management across their departments.\n\nThe Last Mile has introduced numerous new and distinct use cases for GitLab. These include:\n\n* Issues are used to manage classroom facilities including to keep track of the impacts of COVID-19 on each classroom. For example, status updates are recorded on the issue and in the comments.\n* [The Last Mile’s reentry program](https://thelastmile.org/our-work/#reentry) uses GitLab to track returned citizen onboarding and service delivery process as well as tracking internal workloads, task efforts, and collaboration across teams. To-do lists are used to manage actions and labels are used to view the status of various efforts.\n\n\"The GitLab platform provides The Last Mile with a remarkable range of solutions -- from our application of GitOps workflows for managing our hybrid infrastructure, to our org-wide application of issues across teams,\" says Mike Bowie, Director of Engineering, The Last Mile. \"By solving such a broad range of our needs, GitLab enables us to focus on delivering value into our programs, instead of administering and maintaining a plethora of disparate tools.\"\n",[916,1005,1004,915,9],{"slug":2616,"featured":6,"template":699},"thelastmile-gitlab","content:en-us:blog:thelastmile-gitlab.yml","Thelastmile Gitlab","en-us/blog/thelastmile-gitlab.yml","en-us/blog/thelastmile-gitlab",{"_path":2622,"_dir":246,"_draft":6,"_partial":6,"_locale":7,"seo":2623,"content":2629,"config":2635,"_id":2637,"_type":14,"title":2638,"_source":16,"_file":2639,"_stem":2640,"_extension":19},"/en-us/blog/tracking-down-missing-tcp-keepalives",{"title":2624,"description":2625,"ogTitle":2624,"ogDescription":2625,"noIndex":6,"ogImage":2626,"ogUrl":2627,"ogSiteName":686,"ogType":687,"canonicalUrls":2627,"schema":2628},"Tracking TCP Keepalives: Lessons in Docker, Golang & GitLab","An in-depth recap of debugging a bug in the Docker client library.","https://res.cloudinary.com/about-gitlab-com/image/upload/v1749680874/Blog/Hero%20Images/network.jpg","https://about.gitlab.com/blog/tracking-down-missing-tcp-keepalives","\n                        {\n        \"@context\": \"https://schema.org\",\n        \"@type\": \"Article\",\n        \"headline\": \"What tracking down missing TCP Keepalives taught me about Docker, Golang, and GitLab\",\n        \"author\": [{\"@type\":\"Person\",\"name\":\"Stan Hu\"}],\n        \"datePublished\": \"2019-11-15\",\n      }",{"title":2630,"description":2625,"authors":2631,"heroImage":2626,"date":2632,"body":2633,"category":717,"tags":2634},"What tracking down missing TCP Keepalives taught me about Docker, Golang, and GitLab",[1614],"2019-11-15","This blog post was originally published on the GitLab Unfiltered\nblog. It was reviewed and republished on\n2019-12-03.\n\n{: .alert .alert-info .note}\n\n\nWhat began as failure in a GitLab static analysis check led to a\n\ndizzying investigation that uncovered a subtle [bug in the Docker client\n\nlibrary code](https://github.com/docker/for-linux/issues/853) used by\n\nthe GitLab Runner. We ultimately worked around the problem by upgrading\n\nthe Go compiler, but in the process we uncovered an unexpected change in\n\nthe Go TCP keepalive defaults that fixed an issue with Docker and GitLab\n\nCI.\n\n\nThis investigation started on October 23, when backend engineer [Luke\n\nDuncalfe](/company/team/#.luke) mentioned, \"I'm seeing\n\n[`static-analysis` failures with no\noutput](https://gitlab.com/gitlab-org/gitlab/-/jobs/331174397).\n\nIs there something wrong with this job?\" He opened [a GitLab\n\nissue](https://gitlab.com/gitlab-org/gitlab/issues/34951) to discuss.\n\n\nWhen Luke ran the static analysis check locally on his laptop, he saw\n\nuseful debugging output when the test failed. For example, an extraneous\n\nnewline would accurately be reported by Rubocop. However, when the same\n\ntest ran in GitLab's automated test infrastructure, the test failed\n\nquietly:\n\n\n![Failed\njob](https://about.gitlab.com/images/blogimages/docker-tcp-keepalive-debug/job-failure.png){:\n.shadow.center}\n\n\nNotice how the job log did not include any clues after the `bin/rake\n\nlint:all` step. This made it difficult to determine whether a real\n\nproblem existed, or whether this was just a flaky test.\n\n\nIn the ensuing days, numerous team members reported the same problem.\n\nNothing kills productivity like silent test failures.\n\n\n## Was something wrong with the test itself?\n\n\nIn the past, we had seen that if that specific test generated enough\n\nerrors, [the output buffer would fill up, and the continuous integration\n\n(CI) job would lock\n\nindefinitely](https://gitlab.com/gitlab-org/gitlab-foss/issues/61432). We\n\nthought we had [fixed that issue months\n\nago](https://gitlab.com/gitlab-org/gitlab-foss/merge_requests/28402). Upon\n\nfurther review, that fix seemed to eliminate any chance of a thread\n\ndeadlock.\n\n\nDid we have to flush the buffer? No, because the Linux kernel will do\n\nthat for an exiting process already.\n\n\n## Was there a change in how CI logs were handled?\n\n\nWhen a test runs in GitLab CI, the [GitLab\n\nRunner](https://gitlab.com/gitlab-org/gitlab-runner/) launches a Docker\n\ncontainer that runs commands specified by a `.gitlab-ci.yml` inside the\n\nproject repository. As the job runs, the runner streams the output to\n\nthe GitLab API via PATCH requests. The GitLab backend saves this data\n\ninto a file. The following sequence diagram shows how this works:\n\n\n```plantuml\n\n== Get a job! ==\n\nRunner -> GitLab: POST /api/v4/jobs/request\n\nGitLab -> Runner: 201 Job was scheduled\n\n\n== Job sends logs (1 of 2) ==\n\nRunner -> GitLab: PATCH /api/v4/job/:id/trace\n\nGitLab -> File: Save to disk\n\nGitLab -> Runner: 202 Accepted\n\n\n== Job sends logs (2 of 2) ==\n\nRunner -> GitLab: PATCH /api/v4/job/:id/trace\n\nGitLab -> File: Save to disk\n\nGitLab -> Runner: 202 Accepted\n\n```\n\n\n[Henrich Lee Yu](/company/team/#engwan) mentioned\n\nthat we had recently [disabled a feature flag that changed how GitLab\n\nhandled CI job\n\nlogs](https://docs.gitlab.com/ee/administration/job_logs.html#new-incremental-logging-architecture).\n[The\n\ntiming seemed to line\n\nup](https://gitlab.com/gitlab-org/gitlab/issues/34951#note_236723888).\n\n\nThis feature, called live CI traces, eliminates the need for a shared\n\nPOSIX filesystem (e.g., NFS) when saving job logs to disk by:\n\n\n1. Streaming data into memory via Redis\n\n2. Persisting the data in the database (PostgreSQL)\n\n3. Archiving the final data into object storage\n\n\nWhen this flag is enabled, the flow of CI job logs looks something like\n\nthe following:\n\n\n```plantuml\n\n== Get a job! ==\n\nRunner -> GitLab: POST /api/v4/jobs/request\n\nGitLab -> Runner: 201 Job was scheduled\n\n\n== Job sends logs ==\n\nRunner -> GitLab: PATCH /api/v4/job/:id/trace\n\nGitLab -> Redis: Save chunk\n\nGitLab -> Runner: 202 Accepted\n\n...\n\n== Copy 128 KB chunks from Redis to database ==\n\nGitLab -> Redis: GET gitlab:ci:trace:id:chunks:0\n\nGitLab -> PostgreSQL: INSERT INTO ci_build_trace_chunks\n\n...\n\n== Job finishes ==\n\n\nRunner -> GitLab: PUT /api/v4/job/:id\n\nGitLab -> Runner: 200 Job was updated\n\n\n== Archive trace to object storage ==\n\n```\n\n\nLooking at the flow diagram above, we see that this approach has more\n\nsteps. After receiving data from the runner, something could have gone\n\nwrong with handling a chunk of data. However, we still had many\n\nquestions:\n\n\n1. Did the runners send the right data in the first place?\n\n1. Did GitLab drop a chunk of data somewhere?\n\n1. Did this new feature actually have anything to do with the problem?\n\n1. Are they really making another Gremlins movie?\n\n\n## Reproducing the bug: Simplify the `.gitlab-ci.yml`\n\n\nTo help answer those questions, we simplified the `.gitlab-ci.yml` to\n\nrun only the `static-analysis` step. We inserted a known Rubocop error,\n\nreplacing a `eq` with `eql`. We first ran this test on a separate GitLab\n\ninstance with a private runner. No luck there – the job showed the right\n\noutput:\n\n\n```\n\nOffenses:\n\n\nee/spec/models/project_spec.rb:55:42: C: RSpec/BeEql: Prefer be over eql.\n        expect(described_class.count).to eql(2)\n                                         ^^^\n\n12669 files inspected, 1 offense detected\n\n```\n\n\nHowever, we repeated the test on our staging server and found that we\n\nreproduced the original problem. In addition, the live CI trace feature\n\nflag had been activated on staging. Since the problem occurred with and\n\nwithout the feature, we could eliminate that feature as a possible\n\ncause.\n\n\nPerhaps something with the GitLab server environment caused a\n\nproblem. For example, could the load balancers be rate-limiting the\n\nrunners? As an experiment, we pointed a private runner at the staging\n\nserver and re-ran the test. This time, it succeeded: the output was\n\nshown. That seemed to suggest that the problem had more to do with the\n\nrunner than with the server.\n\n\n## Docker Machine vs. Docker\n\n\nOne key difference between the two tests: One runner used a shared,\n\nautoscaled runner using a [Docker\n\nMachine](https://docs.docker.com/machine/overview/) executor, and the\n\nprivate runner used a [Docker\n\nexecutor](https://docs.gitlab.com/runner/executors/docker.html).\n\n\nWhat does Docker Machine do exactly? The following diagram may help\n\nillustrate:\n\n\n![Docker Machine](https://docs.docker.com/machine/img/machine.png){:\n.medium.center}\n\n\nThe top-left shows a local Docker instance. When you run Docker from the\n\ncommand-line interface (e.g., `docker attach my-container`), the program\n\njust makes [REST calls to the Docker Engine\n\nAPI](https://docs.docker.com/engine/api/v1.40/).\n\n\nThe rest of the diagram shows how Docker Machine fits into the\n\npicture. Docker Machine is an entirely separate program. The GitLab\n\nRunner shells out to `docker-machine` to create and destroy virtual\n\nmachines using cloud-specific (e.g. Amazon, Google, etc.) drivers. Once\n\na machine is running, the runner then uses the Docker Engine API to run,\n\nwatch, and stop containers.\n\n\nNote that this API is used securely over an HTTPS connection. This is an\n\nimportant difference between the Docker Machine executor and Docker\n\nexecutor: The former needs to communicate across the network, while the\n\nlatter can either use a local TCP socket or UNIX domain socket.\n\n\n## Google Cloud Platform timeouts\n\n\nWe've known for a while that Google Cloud [has a 10-minute idle\n\ntimeout](https://cloud.google.com/compute/docs/troubleshooting/general-tips),\n\nwhich has caused issues in the past:\n\n\n> Note that idle connections are tracked for a maximum of 10 minutes,\n\n> after which their traffic is subject to firewall rules, including the\n\n> implied deny ingress rule. If your instance initiates or accepts\n\n> long-lived connections with an external host, you should adjust TCP\n\n> keep-alive settings on your Compute Engine instances to less than 600\n\n> seconds to ensure that connections are refreshed before the timeout\n\n> occurs.\n\n\nWas the problem caused by this timeout? With the Docker Machine\n\nexecutor, we found that we could reproduce the problem with a simple\n\n`.gitlab-ci.yml`:\n\n\n```yaml\n\nimage: \"busybox:latest\"\n\n\ntest:\n  script:\n    - date\n    - sleep 601\n    - echo \"Hello world!\"\n    - date\n    - exit 1\n```\n\n\nThis would reproduce the failure, where we would never see the `Hello\n\nworld!` output. Changing the `sleep 601` to `sleep 599` would make the\n\nproblem go away. Hurrah! All we have to do is tweak the system TCP\n\nkeepalives, right? Google provided these sensible settings:\n\n\n```sh\n\nsudo /sbin/sysctl -w net.ipv4.tcp_keepalive_time=60\nnet.ipv4.tcp_keepalive_intvl=60 net.ipv4.tcp_keepalive_probes=5\n\n```\n\n\nHowever, enabling these kernel-level settings didn't solve the\n\nproblem. Were keepalives even being sent? Or was there some other issue?\n\nWe turned our attention to network traces.\n\n\n## Eavesdropping on Docker traffic\n\n\nIn order to understand what was happening, we needed to be able to\n\nmonitor the network communication between the runner and the Docker\n\ncontainer. But how exactly does the GitLab Runner stream data from a\n\nDocker container to the GitLab server?  The following diagram\n\nillustrates the flow:\n\n\n```plantuml\n\nRunner -> Docker: POST /containers/name/attach\n\nDocker -> Runner: \u003Ccontainer output>\n\nDocker -> Runner: \u003Ccontainer output>\n\nRunner -> GitLab: PATCH /api/v4/job/:id/trace\n\nGitLab -> File: Save to disk\n\nGitLab -> Runner: 202 Accepted\n\n```\n\n\nFirst, the runner makes a [POST request to attach to the container\n\noutput](https://docs.docker.com/engine/api/v1.40/#operation/ContainerAttach).\n\nAs soon as a process running in the container outputs some data, Docker\n\nwill transmit the data over this HTTPS stream. The runner then copies\n\nthis data to GitLab via the PATCH request.\n\n\nHowever, as mentioned earlier, traffic between a GitLab Runner and the\n\nremote Docker machine is encrypted over HTTPS on port 2376. Was there an\n\neasy way to disable HTTPS? Searching through the code of Docker Machine,\n\nwe found that it did not appear to be supported out of the box.\n\n\nSince we couldn't disable HTTPS, we had two ways to eavesdrop:\n\n\n1. Use a man-in-the-middle proxy (e.g. [mitmproxy](https://mitmproxy.org/))\n\n1. Record the traffic and decrypt the traffic later using the private keys\n\n\n## Ok, let's be the man-in-the-middle!\n\n\nThe first seemed more straightforward, since [we already had experience\n\ndoing this with the Docker\n\nclient](https://docs.gitlab.com/ee/administration/packages/container_registry.html#running-the-docker-daemon-with-a-proxy).\n\n\nHowever, after [defining the proxy variables for GitLab\n\nRunner](https://docs.gitlab.com/runner/configuration/proxy.html#adding-proxy-variables-to-the-runner-config),\n\nwe found we were only able to intercept the GitLab API calls with\n\n`mitmproxy`. The Docker API calls still went directly to the remote\n\nhost. Something wasn't obeying the proxy configuration, but we didn't\n\ninvestigate further. We tried the second approach.\n\n\n## Decrypting TLS data\n\n\nTo decrypt TLS data, we would need to obtain the encryption keys. Where\n\nwere these located for a newly-created system with `docker-machine`? It\n\nturns out `docker-machine` worked in the following way:\n\n\n1. Call the Google Cloud API to create a new machine\n\n1. Create a `/root/.docker/machine/machines/:machine_name` directory\n\n1. Generate a new SSH keypair\n\n1. Install the SSH key on the server\n\n1. Generate a new TLS certificate and key\n\n1. Install and configure Docker on the newly-created machine with TLS\ncertificates\n\n\nAs long as the machine runs, the directory will contain the information\n\nneeded to decode this traffic. We ran `tcpdump` and saved the private keys.\n\n\nOur first attempt at decoding the traffic failed. Wireshark could not\n\ndecode the encrypted traffic, although general TCP traffic could still\n\nbe seen. Researching more, we found out why: If the encrypted traffic\n\nused a [Diffie-Hellman key\n\nexchange](https://en.wikipedia.org/wiki/Diffie%E2%80%93Hellman_key_exchange),\n\nhaving the private keys would not suffice! This is by design, a property\n\ncalled [perfect forward\n\nsecrecy](https://en.m.wikipedia.org/wiki/Forward_secrecy).\n\n\nTo get around that limitation, we modified the GitLab Runner to disable\n\ncipher suites that used the Diffie-Hellman key exchange:\n\n\n```diff\n\ndiff --git\na/vendor/github.com/docker/go-connections/tlsconfig/config_client_ciphers.go\nb/vendor/github.com/docker/go-connections/tlsconfig/config_client_ciphers.go\n\nindex 6b4c6a7c0..a3f86d756 100644\n",[269,961,550,9,960,9,808,851,983],{"slug":2636,"featured":6,"template":699},"tracking-down-missing-tcp-keepalives","content:en-us:blog:tracking-down-missing-tcp-keepalives.yml","Tracking Down Missing Tcp Keepalives","en-us/blog/tracking-down-missing-tcp-keepalives.yml","en-us/blog/tracking-down-missing-tcp-keepalives",{"_path":2642,"_dir":246,"_draft":6,"_partial":6,"_locale":7,"seo":2643,"content":2649,"config":2655,"_id":2657,"_type":14,"title":2658,"_source":16,"_file":2659,"_stem":2660,"_extension":19},"/en-us/blog/transform-code-quality-and-compliance-with-automated-processes",{"title":2644,"description":2645,"ogTitle":2644,"ogDescription":2645,"noIndex":6,"ogImage":2646,"ogUrl":2647,"ogSiteName":686,"ogType":687,"canonicalUrls":2647,"schema":2648},"Transform code quality and compliance with automated processes","Learn how GitLab Premium features address the technical debt and security vulnerability challenges that plague traditional approaches.","https://res.cloudinary.com/about-gitlab-com/image/upload/v1749660151/Blog/Hero%20Images/blog-image-template-1800x945__26_.png","https://about.gitlab.com/blog/transform-code-quality-and-compliance-with-automated-processes","\n                        {\n        \"@context\": \"https://schema.org\",\n        \"@type\": \"Article\",\n        \"headline\": \"Transform code quality and compliance with automated processes\",\n        \"author\": [{\"@type\":\"Person\",\"name\":\"Jessica Hurwitz\"}],\n        \"datePublished\": \"2024-12-13\",\n      }",{"title":2644,"description":2645,"authors":2650,"heroImage":2646,"date":2652,"body":2653,"category":1477,"tags":2654},[2651],"Jessica Hurwitz","2024-12-13","While manual code review processes may suffice for a small team, as DevSecOps teams scale, the processes create significant bottlenecks that impede software development velocity and quality. Often slow, inconsistent, and frequently failing to catch critical vulnerabilities, the manual approach leads to technical debt and increased security risks.\n\nTo mitigate risks and drive innovation, organizations must prioritize automated code quality and compliance systems. The financial implications of poor code management are substantial, with technical debt consuming up to 40% of IT budgets ([McKinsey Digital: Tech Debt Report](https://www.mckinsey.com/capabilities/mckinsey-digital/our-insights/tech-debt-reclaiming-tech-equity)) and software vulnerabilities costing an average of $4.88 million per security breach ([IBM Cost of a Data Breach Report](https://www.ibm.com/reports/data-breach)). \n\nModern software development requires a strategic approach to code management and compliance that goes beyond traditional review processes. With more robust review systems and compliance controls, organizations can innovate and secure software faster than their competitors.\n\n## The power of code review and approval processes\n\nAccording to the [GitLab 2024 Global DevSecOps Report](https://about.gitlab.com/developer-survey/), C-level executives rank code quality as one of the top benefits of DevSecOps. With executives recognizing code quality as a strategic priority, systematic review processes have emerged as a cornerstone of modern development practices. \n\n[Code review](https://about.gitlab.com/topics/version-control/what-is-code-review/) processes benefit developers through knowledge sharing, the discovery of bugs earlier in the process, and improved security. However, developers say the top changes that could be made to improve job satisfaction are increasing automation and collaboration, according to our survey.\n\nAs code quality and code review processes are embedded into the software development lifecycle, focusing on systems that remove manual code review and enhance collaboration across teams will help keep developer workflows running smoothly. \n\n### Code review processes increase collaboration and development speed\n\nThe improvement in organizational efficiency can be seen in this example with [Airbus Intelligence](https://about.gitlab.com/customers/airbus/), a leader in the geospatial industry. The development teams at Airbus struggled with inefficient processes and needed tools that could help their team collaborate efficiently across the globe. After adopting GitLab Premium, Airbus quickly noticed the improvement in code quality. \n\nGitLab CI’s built-in security testing meant developers could identify bugs and vulnerabilities before they reached production. Instead of spending a full day setting up for production and doing manual tests, those simple tasks are now automated. \n\nAirbus’ release time dramatically decreased from 24 hours to just 10 minutes. \n\n“What used to happen is we would touch one part of the code and it would break another part. Now, each time a developer pushes code, we can immediately identify problems,” said Logan Weber, Software Automation Engineer at Airbus Defense and Space, Intelligence.\n\n### Features that enable higher code quality\n\nPowerful GitLab Premium features like [Multiple Approvers for Merge Requests](https://docs.gitlab.com/ee/user/project/merge_requests/approvals/rules.html), [Code Quality](https://docs.gitlab.com/ee/ci/testing/code_quality.html) checks integration with third-party code quality solutions, and [Protected Branches](https://docs.gitlab.com/ee/user/project/repository/branches/protected.html), enable companies to innovate faster than their competitors. \n\nBy reducing review cycle times while strengthening code integrity and compliance, DevSecOps teams address both the technical debt and security vulnerability challenges that plague traditional approaches. These security benefits help teams like AirBus Intelligence develop faster, more secure solutions.  \n\n## Why enhanced compliance controls matter\n\nThe implementation of effective code compliance strategies is constantly evolving due to [changing regulations](https://about.gitlab.com/blog/meet-regulatory-standards-with-gitlab/), and keeping up with these regulations is a challenge for most companies. \n\nBy developing code compliance strategies and automated control mechanisms, companies ensure that quality and compliance policies are met. \n\nFor Airbus Intelligence, security and vulnerability scans built into integration testing enabled teams to catch security and compliance issues earlier in the process.\n\n[Continuous integration](https://about.gitlab.com/topics/ci-cd/#what-is-continuous-integration-ci) gives teams visibility into more projects and allows all team members to manage deployments. Expanded access controls improve cross-team collaboration and accountability. \n\n### Features that increase accountability \n\nGitLab Premium's [advanced compliance controls](https://about.gitlab.com/solutions/security-compliance/) create an unbroken chain of accountability throughout the development process, enabling organizations to systematically track and validate every code change.\n\nUsers have greater auditability of any change and can track commits. This is in addition to strict [access controls](https://docs.gitlab.com/ee/administration/settings/visibility_and_access_controls.html) that provide specific people with the ability to push and merge changes. With [audit logs](https://docs.gitlab.com/ee/user/compliance/audit_event_types.html), users can track and review changes and activities within the repository.\n\n## Ship software faster with GitLab Premium\n\n“It’s simple. All teams operate around this one tool. Instantly, that made communication easier. We wouldn’t be where we are today if we didn’t have GitLab in our stack,” according to Airbus' Weber.\n\nGitLab Premium represents more than just a tool — it's a comprehensive approach to software engineering that empowers development teams to deliver high-quality, secure, and efficient software solutions. \n\n> #### Discover why [customers are upgrading to GitLab Premium](https://about.gitlab.com/pricing/premium/why-upgrade/).",[9,1416,496,1477,784,983],{"slug":2656,"featured":6,"template":699},"transform-code-quality-and-compliance-with-automated-processes","content:en-us:blog:transform-code-quality-and-compliance-with-automated-processes.yml","Transform Code Quality And Compliance With Automated Processes","en-us/blog/transform-code-quality-and-compliance-with-automated-processes.yml","en-us/blog/transform-code-quality-and-compliance-with-automated-processes",{"_path":2662,"_dir":246,"_draft":6,"_partial":6,"_locale":7,"seo":2663,"content":2669,"config":2676,"_id":2678,"_type":14,"title":2679,"_source":16,"_file":2680,"_stem":2681,"_extension":19},"/en-us/blog/tutorial-automated-release-and-release-notes-with-gitlab",{"title":2664,"description":2665,"ogTitle":2664,"ogDescription":2665,"noIndex":6,"ogImage":2666,"ogUrl":2667,"ogSiteName":686,"ogType":687,"canonicalUrls":2667,"schema":2668},"Tutorial: Automate releases and release notes with GitLab","With the GitLab Changelog API, you can automate the generation of release artifacts, release notes, and a comprehensive changelog detailing all user-centric software modifications.","https://res.cloudinary.com/about-gitlab-com/image/upload/v1749659978/Blog/Hero%20Images/automation.png","https://about.gitlab.com/blog/tutorial-automated-release-and-release-notes-with-gitlab","\n                        {\n        \"@context\": \"https://schema.org\",\n        \"@type\": \"Article\",\n        \"headline\": \"Tutorial: Automate releases and release notes with GitLab\",\n        \"author\": [{\"@type\":\"Person\",\"name\":\"Ben Ridley\"}],\n        \"datePublished\": \"2023-11-01\",\n      }",{"title":2664,"description":2665,"authors":2670,"heroImage":2666,"date":2672,"body":2673,"category":1477,"tags":2674,"updatedDate":2675},[2671],"Ben Ridley","2023-11-01","***2025 update** - The Changelog API has continued to evolve and now has some great new capabilities we don’t cover in this blog, such as the ability to provide custom changelogs with templated values from your commit history. [Discover more in the official Changelogs docs.](https://docs.gitlab.com/user/project/changelogs/)*\n\nWhen you develop software that users rely on, effective communication about changes with each release is essential. By keeping users informed about new features and any modifications or removals, you ensure they maximize the software's benefits and avoid encountering unpleasant surprises during upgrades.\n\nHistorically, creating release notes and maintaining a changelog has been a laborious task, requiring developers to monitor changes externally or release managers to sift through merge histories. With the GitLab Changelog API, you can use the rich history provided in our git repository to easily create release notes and maintain a changelog.\n\nIn this tutorial, we'll delve into automating releases with GitLab, covering the generation of release artifacts, release notes, and a comprehensive changelog detailing all user-centric software modifications.\n\n## Releases in GitLab\nFirst, let's explore how releases work in GitLab.\n\nIn GitLab, a release is a specific version of your code, identified by a git tag, that includes details about changes since the last release (and release notes) and any related artifacts built from that version of the code, such as Docker images, installation packages, and documentation.\n\nYou can create and track releases in GitLab using the UI by calling our Release API or by defining a special `release` job inside a CI pipeline. In this tutorial, we'll use the `release` job in a CI/CD pipeline, which allows us to extend the automation we're using in our pipelines for testing, code scanning, etc. to also perform automated releases.\n\nTo automate our releases, we first need to answer this question: Where are we going to get the information on changes made for our release notes and our changelog? The answer: Our git repository, which provides us with a rich history of development activity through commit messages and merge commit history. Let's see if we can leverage this rich history to automatically create our notes and changelogs.\n\n## Introducing commit trailers\n[Commit trailers](https://git-scm.com/docs/git-interpret-trailers) are structured entries in your git commits, created by adding simple `\u003CHEADER>:\u003CBODY>` format messages to the end of your commit. The `git` CLI tool can then parse and extract these for use in other systems. An example you might have already used is `git commit --sign-off` to sign off on a commit. This is implemented by adding a `Signed-off-by: \u003CYour Name>` trailer to the commit. We can add any arbitrary structured data here, which makes it a great place to store information that could be useful for our changelog.\n\nIn fact, if we use a `Changelog: \u003Cadded/changed/removed>` trailer in our commits, the GitLab Changelog API will parse these and use them to create a changelog for us automatically!\n\nLet's see this in action by making some changes to a real codebase and performing a release, and generating release notes and changelog entries.\n\n## Our example project\nFor the purposes of this blog, I'm using a simple Python web app repository. Let's pretend Version 1.0.0 of the application was just released and is the current version of the code. I've also created a 1.0.0 release in GitLab, which I did manually because we haven't created our automated release pipeline yet:\n\n![A screenshot of the GitLab UI showing a release for Version 1.0.0](https://about.gitlab.com/images/blogimages/2023-08-22-automated-release-and-release-notes-with-gitlab/1-0-release.png)\n\n## Making our changes\nWe're in rapid development mode, so we're going to be working on releasing Version 2.0.0 of our application today. As part of our 2.0.0 release, we're going to be adding a new feature to our app: A chatbot! And we're also going to be removing the quantum blockchain feature, because we only needed that for our first venture capital funding round. Also, we're going to be adding an automated release job to our CI/CD pipeline for our 2.0.0 release.\n\nFirst, let's remove unneeded features. I've created a merge request that contains the necessary removals. Importantly, we need to ensure we have a commit message that includes the `Changelog: removed` trailer. There's a few ways to do this, such as including it directly in a commit, or performing an interactive rebase and adding it using the CLI. But I think the easiest way in our situation is to leave it until the end and then use the `Edit commit message` button in GitLab to add the trailer to the merge commit like so:\n\n![A screenshot the GitLab UI showing a merge request removing unused features](https://about.gitlab.com/images/blogimages/2023-08-22-automated-release-and-release-notes-with-gitlab/remove-unused-features-mr.png)\n\nIf you use this method, you can also change the merge commit title to something more succinct. I've changed the title of my merge commit to 'Remove Unused Features', as this is what will appear in the changelog entry.\n\nNext, let's add some new functionality for the 2.0.0 release. Again, all we need to do is open another merge request that includes our new features and then edit the merge commit to include the `Changelog: added` trailer and edit the commit title to be more succinct:\n\n![A screenshot of the GitLab UI showing a merge request to add new functionality](https://about.gitlab.com/images/blogimages/2023-08-22-automated-release-and-release-notes-with-gitlab/add-chatbot-mr.png)\n\nNow we're pretty much ready to release 2.0.0. But we don't want to create our release manually this time. So before our release we're going to add some jobs to our `.gitlab-ci.yml` file that will perform the release for us automatically, and generate the respective release notes and changelog entries, when we tag our code with a new version like `2.0.0`.\n\n**Note:** If you want to enforce changelog trailers, consider using something like [Danger to perform automated checks for MR conventions](https://docs.gitlab.com/ee/development/dangerbot.html).\n\n## Building an automated release pipeline\nFor our pipeline to work, we need to create a project access token that will allow us to call GitLab's API to generate changelog entries. [Create a project access token with the API scope](https://docs.gitlab.com/ee/user/project/settings/project_access_tokens.html#create-a-project-access-token), and then [store the token as a CI/CD variable](https://docs.gitlab.com/ee/ci/variables/#define-a-cicd-variable-in-the-ui) called `CI_API_TOKEN`. We'll reference this variable to authenticate to the API.\n\nNext, we're going to add two new jobs to our `gitlab-ci.yml` file:\n```yaml\nprepare_job:\n  stage: prepare\n  image: alpine:latest\n  rules:\n  - if: '$CI_COMMIT_TAG =~ /^v?\\d+\\.\\d+\\.\\d+$/'\n  script:\n    - apk add curl jq\n    - 'curl -H \"PRIVATE-TOKEN: $CI_API_TOKEN\" \"$CI_API_V4_URL/projects/$CI_PROJECT_ID/repository/changelog?version=$CI_COMMIT_TAG\" | jq -r .notes > release_notes.md'\n  artifacts:\n    paths:\n    - release_notes.md\n\nrelease_job:\n  stage: release\n  image: registry.gitlab.com/gitlab-org/release-cli:latest\n  needs:\n    - job: prepare_job\n      artifacts: true\n  rules:\n  - if: '$CI_COMMIT_TAG =~ /^v?\\d+\\.\\d+\\.\\d+$/'\n  script:\n    - echo \"Creating release\"\n  release:\n    name: 'Release $CI_COMMIT_TAG'\n    description: release_notes.md\n    tag_name: '$CI_COMMIT_TAG'\n    ref: '$CI_COMMIT_SHA'\n    assets:\n      links:\n        - name: 'Container Image $CI_COMMIT_TAG'\n          url: \"https://$CI_REGISTRY_IMAGE/$CI_COMMIT_REF_SLUG:$CI_COMMIT_SHA\"\n```\n\nIn the above configuration, the `prepare_job` uses `curl` and `jq` to call the GitLab Changelog API endpoint and then passes this to our `release_job` to actually create the release. To break it down further:\n- We use the project access token created earlier to call the GitLab Changelog API, which performs the generation of the release notes and we store this as an artifact.\n- We're using the `$CI_COMMIT_TAG` variable as the version. For this to work, we need to be using semantic versioning for our tags (something like `2.0.0` for example), so you'll notice I've also restricted the release job using a `rules` section that checks for a semantic version tag.\n\t- Semantic versioning is required for the GitLab Changelog API to work. It uses this format to find the most recent release to compare to our current release.\n- We use the official `release-cli` image from GitLab. The release-cli is required to use the `release` keyword in a job.\n- We use the `release` keyword to create a release in GitLab. This is a special job keyword reserved for creating a release and populating the required fields.\n- We can pass a file as an argument to the `description` of the release. In our case, it's the file we generated in the `prepare_job`, which was passed to this job as an artifact.\n- We've also included our container image that is being built earlier in the pipeline as a release asset. You can attach any assets you'd like from your build process, such as binaries or documentation by providing a URL to wherever you've uploaded them earlier in the pipeline.\n\n## Performing an automated release\nWith this setup, all we need to do to perform a release is push a tag to our repository that follows our versioning scheme. You can simply push a tag using the CLI, this example uses GitLab's UI to create a tag on the main branch. Create a tag by selecting Code -> Tags -> New Tag on the sidebar:\n![A screenshot of the GitLab UI illustrating how to create a tag](https://about.gitlab.com/images/blogimages/2023-08-22-automated-release-and-release-notes-with-gitlab/create-2-tag.png)\n\nOn creation, our pipelines will start to execute. The GitLab Changelog API will automatically generate release notes for us as markdown, which contains all the changes between this release and the previous release. Here's the resulting markdown generated in our example:\n\n```md\n## 2.0.0 (2023-08-25)\n\n### added (1 change)\n\n- [Add ChatBot](gl-demo-ultimate-bridley/super-devsecops-incorporated/simply-notes-release-demo@0c3601a45af617c5481322bfce4d71db1f911b02) ([merge request](gl-demo-ultimate-bridley/super-devsecops-incorporated/simply-notes-release-demo!4))\n\n### removed (1 change)\n\n- [Remove Unused Features](gl-demo-ultimate-bridley/super-devsecops-incorporated/simply-notes-release-demo@463d453c5ae0f4fc611ea969e5442e3298bf0d8a) ([merge request](gl-demo-ultimate-bridley/super-devsecops-incorporated/simply-notes-release-demo!3))\n```\n\nAs you can see, GitLab has extracted the entries for our release notes automatically using our git commit trailers. In addition, it's helpfully provided links back to the merge request so readers can see more details and discussion around the changes.\n\nAnd now, our final release:\n![The GitLab release UI showing a release for version 2.0.0](https://about.gitlab.com/images/blogimages/2023-08-22-automated-release-and-release-notes-with-gitlab/2-0-release.png)\n\n## Creating the changelog\nNext, we want to update our changelog (which is basically a collated history of all your release notes). You can use a `POST` request to the changelog API endpoint we used earlier to do this.\n\nYou can do this as part of your release pipeline if you like, for example by adding this to the `script` section of your prepare job:\n```sh\n'curl -H \"PRIVATE-TOKEN: $CI_API_TOKEN\" -X POST \"$CI_API_V4_URL/projects/$CI_PROJECT_ID/repository/changelog?version=$CI_COMMIT_TAG\"\n```\n\n**Note that this will actually modify the repository.** It will create a commit to add the latest notes to a `CHANGELOG.md` file:\n![A screenshot of the repository which shows a commit updating the changelog file](https://about.gitlab.com/images/blogimages/2023-08-22-automated-release-and-release-notes-with-gitlab/changelog-api-commit.png)\n\nAnd we are done! By utilizing the rich history provided by `git` with some handy commit trailers, we can leverage GitLab's powerful API and CI/CD pipelines to automate our release process and generate release notes for us.\n\n> If you’d like to explore the project we used for this article, [you can find the project at this link](https://gitlab.com/gitlab-learn-labs/sample-projects/release-automation-demo).\n",[742,9,109,741,786,961],"2025-06-05",{"slug":2677,"featured":6,"template":699},"tutorial-automated-release-and-release-notes-with-gitlab","content:en-us:blog:tutorial-automated-release-and-release-notes-with-gitlab.yml","Tutorial Automated Release And Release Notes With Gitlab","en-us/blog/tutorial-automated-release-and-release-notes-with-gitlab.yml","en-us/blog/tutorial-automated-release-and-release-notes-with-gitlab",{"_path":2683,"_dir":246,"_draft":6,"_partial":6,"_locale":7,"seo":2684,"content":2689,"config":2694,"_id":2696,"_type":14,"title":2697,"_source":16,"_file":2698,"_stem":2699,"_extension":19},"/en-us/blog/ubs-gitlab-devops-platform",{"title":2685,"description":2686,"ogTitle":2685,"ogDescription":2686,"noIndex":6,"ogImage":2410,"ogUrl":2687,"ogSiteName":686,"ogType":687,"canonicalUrls":2687,"schema":2688},"How UBS created their own DevOps platform using GitLab","How GitLab helped power more than a million builds in six months on UBS DevCloud.","https://about.gitlab.com/blog/ubs-gitlab-devops-platform","\n                        {\n        \"@context\": \"https://schema.org\",\n        \"@type\": \"Article\",\n        \"headline\": \"How UBS created their own DevOps platform using GitLab\",\n        \"author\": [{\"@type\":\"Person\",\"name\":\"Sara Kassabian\"}],\n        \"datePublished\": \"2021-08-04\",\n      }",{"title":2685,"description":2686,"authors":2690,"heroImage":2410,"date":2691,"body":2692,"category":717,"tags":2693},[1634],"2021-08-04","\n\nUBS, the largest truly global wealth manager, uses GitLab to power DevCloud, a single [DevOps platform](/solutions/devops-platform/) that allows for a cloud-based, service-oriented, software development lifecycle.\n\n\"GitLab is a fundamental part of DevCloud,\" said [Rick Carey](https://www.bloomberg.com/profile/person/20946258), Group Chief Technology Officer at UBS. \"We wouldn't be able to have that seamless experience without GitLab. It allowed us to pull ahead of many of our competitors, and break down the barriers between coding, testing, and deployment.\"\n\nDuring GitLab Virtual Commit 2021, Rick and [Eric Johnson](/company/team/#edjdev), Chief Technology Officer at GitLab, talked about how building DevCloud on GitLab's DevOps Platform allowed UBS to increase their development velocity, lower their infrastructure costs, and increase collaboration between engineers and non-engineering teams worldwide.\n\n## How engineers used DevCloud to collaborate during UBS Hackathon\n\nThe annual [UBS Hackathon](https://www.ubs.com/global/en/our-firm/what-we-do/technology/2020/hackathon-2020.html), which typically brings together engineers from around the world in one room, went virtual in 2020 due to the COVID-19 pandemic. UBS did a soft launch of the DevCloud platform during the 2020 Hackathon to have a truly global development and seamless team experience among the more than 500 participants dispersed worldwide.\n\n\"It was hard to pick a winner, because nearly every program and team built something absolutely incredible in such a short amount of time,\" said Rick. \"They got so much done that even while chatting with each other, they said, 'I can't believe how easy it is to get this done.'\n\nOnce this Hackathon was successful, we knew that we were going to be able to migrate the rest of our engineers to DevCloud.\"\n\n## Open source collaboration benefitted UBS and GitLab\n\n\"I must say it's uncommon in my experience to see such a large organization let alone one in such a compliance-driven industry as finance take on such a large project and deliver it on time,\" Eric said.\n\nRick attributes part of that success to GitLab's commitment to open source collaboration, which allowed UBS to turn to GitLab team members with questions.\n\n\"In an open source model, every time there was a gap, or an issue, or something we just needed your help with, we could reach out to GitLab and say, 'Can we work on this together? Is there a way to improve this?'\", said Rick. \"That's the value, and that's one of the reasons we went with GitLab.\"\n\nIt wasn't a one-way relationship. Eric said that GitLab learned a lot about compliance and risk processes that are unique to the financial sector by collaborating on open source projects with UBS.\n\n\"Collaboration is one of the GitLab's core values – which was key to this project. We set common goals. We're in constant communication, and we're always working together to remove roadblocks. Working with UBS's engineers is a truly agile experience,\" said Eric.\n\nGitLab forums have a lot of contributions from UBS team members, and both UBS and GitLab are members of open source communities such as the Fintech Open Source Foundation (FINOS) and Cloud Native Computing Foundation (CNCF).\n\n## How adopting DevCloud paid off for UBS\n\nOne of the key messages for why adopting a single DevOps platform such as GitLab or DevCloud benefits engineering teams is the productivity pay-off – for engineers and non-engineers alike.\n\nSimilar to GitLab, which enables simple asynchronous collaboration between team members, DevCloud was built with engineers in mind but so everyone can contribute. Rick said that one of the best pieces of feedback he got on DevCloud was from someone on the business side of UBS, who wanted to do some development projects but struggled with other tools.\n\n\"He said, 'Oh, that's DevCloud? I love DevCloud,'\" said Rick.\n\nIn the roughly six months since UBS launched DevCloud, there have been more than 12,000 users and more than one million successful builds.\n\n## What's next?\n\nIn June 2021, [GitLab acquired machine learning company UnReview](/press/releases/2021-06-02-gitlab-acquires-unreview-machine-learning-capabilities.html) which has allowed us to improve our machine learning capabilities as part of our DevOps Platform. Eric said that by practicing applied machine learning, specifically for code review, GitLab should be able to balance review workloads across teams to increase efficiency.\n\nKeeping all the DevOps activities in a single application makes it easier to extract insights throughout the software development lifecycle. By adding machine learning to a DevOps Platform such as GitLab or DevCloud, teams can not only derive data from past activities, but start to predict the future.\n\n \"We were very impressed by UBS's development culture,\" said Eric. \"It is very complimentary to our own, and we look forward to our continued partnership.\"\n\n## More of a video person?\n\nThis conversation was part of GitLab Virtual Commit 2021. Watch the video below to see the full conversation between Eric and Rick.\n\n\u003C!-- blank line -->\n\u003Cfigure class=\"video_container\">\n  \u003Ciframe src=\"https://www.youtube-nocookie.com/embed/Tof-7fDultw\" frameborder=\"0\" allowfullscreen=\"true\"> \u003C/iframe>\n\u003C/figure>\n\u003C!-- blank line -->\n",[741,916,9,1004],{"slug":2695,"featured":6,"template":699},"ubs-gitlab-devops-platform","content:en-us:blog:ubs-gitlab-devops-platform.yml","Ubs Gitlab Devops Platform","en-us/blog/ubs-gitlab-devops-platform.yml","en-us/blog/ubs-gitlab-devops-platform",{"_path":2701,"_dir":246,"_draft":6,"_partial":6,"_locale":7,"seo":2702,"content":2708,"config":2715,"_id":2717,"_type":14,"title":2718,"_source":16,"_file":2719,"_stem":2720,"_extension":19},"/en-us/blog/unreview-a-year-later-how-gitlab-is-being-transformed-by-ml-powered-code-review",{"title":2703,"description":2704,"ogTitle":2703,"ogDescription":2704,"noIndex":6,"ogImage":2705,"ogUrl":2706,"ogSiteName":686,"ogType":687,"canonicalUrls":2706,"schema":2707},"GitLab transforms code review with machine learning tools","Learn how last year's acquisition has resulted in impactful features for the One DevOps Platform.","https://res.cloudinary.com/about-gitlab-com/image/upload/v1749668002/Blog/Hero%20Images/pg-gear.jpg","https://about.gitlab.com/blog/unreview-a-year-later-how-gitlab-is-being-transformed-by-ml-powered-code-review","\n                        {\n        \"@context\": \"https://schema.org\",\n        \"@type\": \"Article\",\n        \"headline\": \"UnReview a year later: How GitLab is transforming DevOps code review with ML-powered functionality\",\n        \"author\": [{\"@type\":\"Person\",\"name\":\"Taylor McCaslin\"}],\n        \"datePublished\": \"2022-06-02\",\n      }",{"title":2709,"description":2704,"authors":2710,"heroImage":2705,"date":2712,"body":2713,"category":1131,"tags":2714},"UnReview a year later: How GitLab is transforming DevOps code review with ML-powered functionality",[2711],"Taylor McCaslin","2022-06-02","\n\nA little over a year ago, [GitLab acquired UnReview](/press/releases/2021-06-02-gitlab-acquires-unreview-machine-learning-capabilities.html), a machine learning-based solution for automatically identifying [relevant code reviewers](/stages-devops-lifecycle/create/) and distributing review workloads and knowledge. Our goal is to integrate UnReview’s ML-powered code review features throughout GitLab, the One DevOps Platform. We checked in with Taylor McCaslin, principal product manager, ModelOps, at GitLab, to find out the impact UnReview has had so far and what comes next.\n\n**The idea of applying machine learning to code review was already underway at GitLab before the UnReview acquisition. What was it about ML/AI and automation that seemed a good fit for the code review process? How did the UnReview acquisition affect that strategy?**\n\nThe acquisition of UnReview gave GitLab a practical way to get started with a really focused value proposition that was obvious to the platform. ML/AI is a lot more than just having a useful algorithm. UnReview and its team gave GitLab talent with experience building MLOps pipelines and working with production DataOps workflows. As a source code management ([SCM](/solutions/source-code-management/)) and continuous integration ([CI](/topics/ci-cd/)) platform, MLOps and DataOps are key ambitions for our ModelOps stage. UnReview is the foundational anchor of our AI Assisted group, and we anticipate developing more ML-powered features with the base that we’ve built integrating UnReview into our One DevOps platform. If it’s something you manually set today within GitLab, we’ll consider suggestions and automations: suggested labels, assignees, issue relationships, etc. You can learn more about our plans on our [AI Assisted direction page](/direction/modelops/ai_assisted/).\n\n> You’re invited! Join us on June 23rd for the [GitLab 15 launch event](https://page.gitlab.com/fifteen) with DevOps guru Gene Kim and several GitLab leaders. They’ll show you what they see for the future of DevOps and The One DevOps Platform.\n\n**There were [three specific objectives with the UnReview project](/handbook/engineering/development/data-science/ai-assisted/projects/unreview/#overview) when you first started:**\n- **Eliminate the time wasted manually searching for an appropriate code reviewer to review code changes.**\n- **Make optimum recommendations that consider the reviewers’ experience and optimize the review load across the team, which additionally facilitates knowledge sharing.**\n- **Provide analytics on the state of code review in the project, explaining why a particular code reviewer is recommended.**\n\n**Have you had to change or add to these in any way?**\n\nWe now have Suggested Reviewers running for external beta customers as well as dogfooding it internally. We’ve learned a lot about what makes a good code reviewer. Some of the obvious things like context with the changed files and history of committing to that area of code are obvious. But there are less obvious things like what type of code someone has experience with (front-end or back-end).\n\nWe’re finding the concept of recency interesting: the idea that people who more recently interacted with files and functions may be better suited to review the code. Also, people leave companies, and that’s usually not something that can be inferred by the source graph, so we’re working on merging additional GitLab activity data with the recommendation engine.\n\nIn addition, we’re thinking a lot about bias in our recommendations. For example, a senior engineer likely has the most commits across a project, but we don’t always want to recommend a senior engineer. The more we work with the algorithm and recommendations, the more nuanced we find it.\n\nNot every organization does code review the same way, so we’re considering building different models for those that have no process versus organizations that have very rigid and hierarchical reviewer requirements. We also have to consider how recommendations interact with other features of the platform like code owners, maintainer roles, and commit access.\n\nWe’ve never been more excited about the potential of machine learning within GitLab. Some of the feedback we’ve had from beta customers are “this feels like magic” and that honestly encapsulates what we’re going for. Sometimes the right code reviewer is just a feeling that you can’t quite put your finger on. Through data and a little bit of magic, we may see Suggested Reviewers help speed up workflows, and cut down on back and forth and wasted time trying to find someone to do a great review of your code.\n\n**Introducing ML-powered features can come with challenges, especially being GitLab’s first data science feature. Can you speak to some of those challenges and how the team overcame them?**\n\nIt has been about a year since we closed the transaction. During that time period we’ve introduced a lot of new concepts to GitLab. Access to real-time data within the feature with DataOps extraction and cleaning of platform activity data. We have an end-to-end MLOps pipeline running 100% within GitLab CI that extracts, builds, trains, and deploys the UnReview model, and new observability metrics to know if the whole system is working. These are all foundational concepts that we’ve had to build from the ground up.\n\nAlso, we’ve introduced Python to the GitLab tech stack and have to develop new engineering standards and hiring interview practices to find the right talent for this team. We’re now turning the corner of this foundational work and I anticipate that relatively soon we’ll release Suggested Reviewers fully integrated with the platform and UI.\n\nMilestones have been part of the way we’ve sliced up the integration work. We have a variety of internal milestones we’ve been tracking against, including porting the model into GitLab SCM and CI, building the Dataops and MLOps pipelines, and internal and external customer betas. It’s helpful to have these milestones to know what’s most important at any given time and not to get overwhelmed with all the moving pieces. We’re paving a new path with ML-powered features at GitLab, and once we’re done we’ll have a repeatable process and template to replicate over and over with new data science-powered features.\n\n**What has been the most surprising thing you’ve encountered or learned since UnReview first debuted?**\n\nCode Reviewers are foundational to the software development lifecycle. We thought this would be a really straightforward feature, but it turns out people REALLY care about recommendations. People hate bad suggestions so when the recommendations are wrong, the feedback is fast and furious. But when it’s right, it feels like magic. That really surprised me how positively people respond to a great suggestion.\n\nA lot of GitLab users have asked me what our success metric is for Suggested Reviewers. It should just feel like magic. Maybe you don’t know why someone was chosen, but you just feel they were the right person to review the change. And hopefully that leads to a more thoughtful code review, reduces the back and forth of trying to find someone to review your code, and ultimately creates a better experience end-to-end. A lot of engineers dread code reviews; we want to change that. I hope Suggested Reviewers can take the pain out of the experience and make it something engineers look forward to. That’s the feeling we’re trying to create with our recommendations. Obvious but magic.\n\n**What’s next for UnReview specifically and DevOps code review more generally? Where do you see the next big advances happening?**\n\nWe’re just scratching the surface. There are so many opportunities for recommendations and automations across the platform. We have a lot of data at GitLab, from the source graph, contribution history, CI builds, test logs, security scans, and deployment data. We believe all of this can be integrated together. I’m particularly excited about what we’re calling [Intelligent Code Security](/direction/modelops/ai_assisted/#categories). The idea is that we will be able to look at your source code as you’re writing it, analyze it for security vulnerabilities, and not only suggest fixes to common security flaws, but also apply that change, run your CI, confirm the build succeeds, confirm the vulnerability was resolved, and possibly even deploy that change, all automatically.\n\nImagine the future where your code gets more secure automatically while you sleep. That sounds wild, but we have the data to power [a feature like this in the future](/direction/modelops/ai_assisted/#categories). Suggested Reviewers is just the beginning. We haven’t seen many DevOps platforms fully embrace the data, code, and activity data that they have in a material way. I think we’ll see a lot more in this space moving forward as development platforms identify the massive opportunities to drive efficiencies and remove the frustrating parts of software development from the process.\n",[741,1416,9,233,962],{"slug":2716,"featured":6,"template":699},"unreview-a-year-later-how-gitlab-is-being-transformed-by-ml-powered-code-review","content:en-us:blog:unreview-a-year-later-how-gitlab-is-being-transformed-by-ml-powered-code-review.yml","Unreview A Year Later How Gitlab Is Being Transformed By Ml Powered Code Review","en-us/blog/unreview-a-year-later-how-gitlab-is-being-transformed-by-ml-powered-code-review.yml","en-us/blog/unreview-a-year-later-how-gitlab-is-being-transformed-by-ml-powered-code-review",{"_path":2722,"_dir":246,"_draft":6,"_partial":6,"_locale":7,"seo":2723,"content":2729,"config":2735,"_id":2737,"_type":14,"title":2738,"_source":16,"_file":2739,"_stem":2740,"_extension":19},"/en-us/blog/using-child-pipelines-to-continuously-deploy-to-five-environments",{"title":2724,"description":2725,"ogTitle":2724,"ogDescription":2725,"noIndex":6,"ogImage":2726,"ogUrl":2727,"ogSiteName":686,"ogType":687,"canonicalUrls":2727,"schema":2728},"Using child pipelines to continuously deploy to five environments","Learn how to manage continuous deployment to multiple environments, including temporary, on-the-fly sandboxes, with a minimalist GitLab workflow.","https://res.cloudinary.com/about-gitlab-com/image/upload/v1750097012/Blog/Hero%20Images/Blog/Hero%20Images/AdobeStock_397632156_3Ldy1urjMStQCl4qnOBvE0_1750097011626.jpg","https://about.gitlab.com/blog/using-child-pipelines-to-continuously-deploy-to-five-environments","\n                        {\n        \"@context\": \"https://schema.org\",\n        \"@type\": \"Article\",\n        \"headline\": \"Using child pipelines to continuously deploy to five environments\",\n        \"author\": [{\"@type\":\"Person\",\"name\":\"Olivier Dupré\"}],\n        \"datePublished\": \"2024-09-26\",\n      }",{"title":2724,"description":2725,"authors":2730,"heroImage":2726,"date":2732,"body":2733,"category":717,"tags":2734},[2731],"Olivier Dupré","2024-09-26","DevSecOps teams sometimes require the ability to manage continuous\ndeployment across multiple environments — and they need to do so without\nchanging their workflows. The [GitLab DevSecOps\nplatform](https://about.gitlab.com/) supports this need, including\ntemporary, on-the-fly sandboxes, with a minimalist approach. In this\narticle, you'll learn how to run continuous deployment of infrastructure\nusing Terraform, over multiple environments.\n\n\nThis strategy can easily be applied to any project, whether it is\ninfrastructure as code (IaC) relying on another technology, such as\n[Pulumi](https://www.pulumi.com/) or [Ansible](https://www.ansible.com/),\nsource code in any language, or a monorepo that mixes many languages.\n\n\nThe final pipeline that you will have at the end of this tutorial will\ndeploy:\n\n\n* A temporary **review** environment for each feature branch.\n\n* An **integration** environment, easy to wipe out and deployed from the\nmain branch.\n\n* A **QA** environment, also deployed from the main branch, to run quality\nassurance steps.\n\n* A **staging** environment, deployed for every tag. This is the last round\nbefore production.\n\n* A **production** environment, just after the staging environment. This one\nis triggered manually for demonstration, but can also be continuously\ndeployed.\n\n\n>Here is the legend for the flow charts in this article:\n\n> * Round boxes are the GitLab branches.\n\n> * Square boxes are the environments.\n\n> * Text on the arrows are the actions to flow from one box to the next.\n\n> * Angled squares are decision steps.\n\n\n\u003Cpre class=\"mermaid\">\n\nflowchart LR\n    A(main) -->|new feature| B(feature_X)\n\n    B -->|auto deploy| C[review/feature_X]\n    B -->|merge| D(main)\n    C -->|destroy| D\n\n    D -->|auto deploy| E[integration]\n    E -->|manual| F[qa]\n\n    D -->|tag| G(X.Y.Z)\n    F -->|validate| G\n\n    G -->|auto deploy| H[staging]\n    H -->|manual| I{plan}\n    I -->|manual| J[production]\n\u003C/pre>\n\n\nOn each step, you'll learn the [why](#why) and the [what](#what) before\nmoving to the [how](#how). This will help you fully understand and replicate\nthis tutorial.\n\n\n## Why\n\n\n* [Continuous\nintegration](https://about.gitlab.com/topics/ci-cd/#what-is-continuous-integration-ci)\nis almost a de facto standard. Most companies have implemented CI pipelines\nor are willing to standardize their practice.\n\n\n* [Continuous\ndelivery](https://about.gitlab.com/topics/ci-cd/#what-is-continuous-delivery-cd),\nwhich pushes artifacts to a repository or registry at the end of the CI\npipeline, is also popular.\n\n\n* Continuous deployment, which goes further and deploys these artifacts\nautomatically, is less widespread. When it has been implemented, we see it\nessentially in the application field. When discussing continuously\ndeploying  infrastructure, the picture seems less obvious, and is more about\nmanaging several environments. In contrast, testing, securing, and verifying\nthe infrastructure's code seems more challenging. And this is one of the\nfields where DevOps has not yet reached its maturity. One of the other\nfields is to shift security left, integrating security teams and, more\nimportantly, security concerns, earlier in the delivery lifecycle, to\nupgrade from DevOps to ***DevSecOps***.\n\n\nGiven this high-level picture, in this tutorial, you will work toward a\nsimple, yet efficient way to implement DevSecOps for your infrastructure\nthrough the example of deploying resources to five environments, gradually\nprogressing from development to production.\n\n\n__Note:__ Even if I advocate embracing a FinOps approach and reducing the\nnumber of environments, sometimes there are excellent reasons to maintain\nmore than just dev, staging, and production. So, please, adapt the examples\nbelow to match your needs.\n\n\n## What\n\n\nThe rise of cloud technology has driven the usage of IaC. Ansible and\nTerraform were among the first to pave the road here. OpenTofu, Pulumi, AWS\nCDK, Google Deploy Manager, and many others joined the party.\n\n\nDefining IaC is a perfect solution to feel safe when deploying\ninfrastructure. You can test it, deploy it, and replay it again and again\nuntil you reach your goal.\n\n\nUnfortunately, we often see companies maintain several branches, or even\nrepositories, for each of their target environments. And this is where the\nproblems start. They are no longer enforcing a process. They are no longer\nensuring that any change in the production code base has been accurately\ntested in previous environments. And they start seeing drifts from one\nenvironment to the other.\n\n\nI realized this tutorial was necessary when, at a conference I attended,\nevery participant said they do not have a workflow that enforces the\ninfrastructure to be tested thoroughly before being deployed to production.\nAnd they all agreed that sometimes they patch the code directly in\nproduction. Sure, this is fast, but is it safe? How do you report back to\nprevious environments? How do you ensure there are no side effects? How do\nyou control whether you are putting your company at risk with new\nvulnerabilities being pushed too quickly in production?\n\n\nThe question of *why* DevOps teams deploy directly to production is critical\nhere. Is it because the pipeline could be more efficient or faster? Is there\nno automation? Or, even worse, because there is *no way to test accurately\noutside of production*?\n\n\nIn the next section, you will learn how to implement automation for your\ninfrastructure and ensure that your DevOps team can effectively test what\nyou are doing before pushing to any environment impacting others. You will\nsee how your code is secured and its deployment is controlled, end-to-end.\n\n\n## How\n\n\nAs mentioned earlier, there are many IaC languages out there nowadays and we\nobjectively cannot cover *all* of them in a single article. So, I will rely\non a basic Terraform code running on Version 1.4. Please do not focus on the\nIaC language itself but instead on the process that you could apply to your\nown ecosystem.\n\n\n### The Terraform code\n\n\nLet's start with a fundamental Terraform code.\n\n\nWe are going to deploy to AWS, a virtual private cloud (VPC), which is a\nvirtual network. In that VPC, we will deploy a public and a private subnet.\nAs their name implies, they are subnets of the main VPC. Finally, we will\nadd an Elastic Cloud Compute (EC2) instance (a virtual machine) in the\npublic subnet.\n\n\nThis demonstrates the deployment of four resources without adding too much\ncomplexity. The idea is to focus on the pipeline, not the code.\n\n\nHere is the target we want to reach for your repository.\n\n\n![target for\nrepository](https://res.cloudinary.com/about-gitlab-com/image/upload/v1750097033/Blog/Content%20Images/Blog/Content%20Images/image5_aHR0cHM6_1750097033415.png)\n\n\nLet’s do it step by step.\n\n\nFirst, we declare all resources in a `terraform/main.tf` file:\n\n\n```terraform\n\nprovider \"aws\" {\n  region = var.aws_default_region\n}\n\n\nresource \"aws_vpc\" \"main\" {\n  cidr_block = var.aws_vpc_cidr\n\n  tags = {\n    Name     = var.aws_resources_name\n  }\n}\n\n\nresource \"aws_subnet\" \"public_subnet\" {\n  vpc_id     = aws_vpc.main.id\n  cidr_block = var.aws_public_subnet_cidr\n\n  tags = {\n    Name = \"Public Subnet\"\n  }\n}\n\nresource \"aws_subnet\" \"private_subnet\" {\n  vpc_id     = aws_vpc.main.id\n  cidr_block = var.aws_private_subnet_cidr\n\n  tags = {\n    Name = \"Private Subnet\"\n  }\n}\n\n\nresource \"aws_instance\" \"sandbox\" {\n  ami           = var.aws_ami_id\n  instance_type = var.aws_instance_type\n\n  subnet_id = aws_subnet.public_subnet.id\n\n  tags = {\n    Name     = var.aws_resources_name\n  }\n}\n\n```\n\n\nAs you can see, there are a couple of variables that are needed for this\ncode, so let's declare them in a `terraform/variables.tf` file:\n\n\n```terraform\n\nvariable \"aws_ami_id\" {\n  description = \"The AMI ID of the image being deployed.\"\n  type        = string\n}\n\n\nvariable \"aws_instance_type\" {\n  description = \"The instance type of the VM being deployed.\"\n  type        = string\n  default     = \"t2.micro\"\n}\n\n\nvariable \"aws_vpc_cidr\" {\n  description = \"The CIDR of the VPC.\"\n  type        = string\n  default     = \"10.0.0.0/16\"\n}\n\n\nvariable \"aws_public_subnet_cidr\" {\n  description = \"The CIDR of the public subnet.\"\n  type        = string\n  default     = \"10.0.1.0/24\"\n}\n\n\nvariable \"aws_private_subnet_cidr\" {\n  description = \"The CIDR of the private subnet.\"\n  type        = string\n  default     = \"10.0.2.0/24\"\n}\n\n\nvariable \"aws_default_region\" {\n  description = \"Default region where resources are deployed.\"\n  type        = string\n  default     = \"eu-west-3\"\n}\n\n\nvariable \"aws_resources_name\" {\n  description = \"Default name for the resources.\"\n  type        = string\n  default     = \"demo\"\n}\n\n```\n\n\nAlready, we are almost good to go on the IaC side. What's missing is a way\nto share the Terraform states. For those who don't know, Terraform works\nschematically doing the following:\n\n\n* `plan` checks the differences between the current state of the\ninfrastructure and what is defined in the code. Then, it outputs the\ndifferences.\n\n* `apply` applies the differences in the `plan` and updates the state.\n\n\nFirst round, the state is empty, then it is filled with the details (ID,\netc.) of the resources applied by Terraform.\n\n\nThe problem is: Where is that state stored? How do we share it so several\ndevelopers can collaborate on code?\n\n\nThe solution is fairly simple: Leverage GitLab to store and share the state\nfor you through a [Terraform HTTP\nbackend](https://docs.gitlab.com/ee/user/infrastructure/iac/terraform_state.html).\n\n\nThe first step in using this backend is to create the most simple\n`terraform/backend.tf` file. The second step will be handled in the\npipeline.\n\n\n```terraform\n\nterraform {\n  backend \"http\" {\n  }\n}\n\n```\n\n\nEt voilà! We have a bare minimum Terraform code to deploy these four\nresources. We will provide the variable values at the runtime, so let's do\nthat later.\n\n\n### The workflow\n\n\nThe workflow that we are going to implement now is the following:\n\n\n\u003Cpre class=\"mermaid\">\n\nflowchart LR\n    A(main) -->|new feature| B(feature_X)\n\n    B -->|auto deploy| C[review/feature_X]\n    B -->|merge| D(main)\n    C -->|destroy| D\n\n    D -->|auto deploy| E[integration]\n    E -->|manual| F[qa]\n\n    D -->|tag| G(X.Y.Z)\n    F -->|validate| G\n\n    G -->|auto deploy| H[staging]\n    H -->|manual| I{plan}\n    I -->|manual| J[production]\n\u003C/pre>\n\n\n1. Create a **feature** branch. This will continuously run all scanners on\nthe code to ensure that it is still compliant and secured. This code will be\ncontinuously deployed to a temporary environment `review/feature_branch`\nwith the name of the current branch. This is a safe environment where the\ndevelopers and operations teams can test their code without impacting\nanybody. This is also where we will enforce the process, like enforcing code\nreviews and running scanners, to ensure that the quality and security of the\ncode are acceptable and do not put your assets at risk. The infrastructure\ndeployed by this branch is automatically destroyed when the branch is\nclosed. This helps you keep your budget under control.\n\n\n\u003Cpre class=\"mermaid\">\n\nflowchart LR\n    A(main) -->|new feature| B(feature_X)\n\n    B -->|auto deploy| C[review/feature_X]\n    B -->|merge| D(main)\n    C -->|destroy| D\n\u003C/pre>\n\n\n2. Once approved, the feature branch will be **merged** into the main\nbranch. This is a [protected\nbranch](https://docs.gitlab.com/ee/user/project/protected_branches.html)\nwhere no one can push. This is mandatory to ensure that every change request\nto production is thoroughly tested. That branch is also continuously\ndeployed. The target here is the `integration` environment. To keep this\nenvironment slightly more stable, its deletion is not automated but can be\ntriggered manually.\n\n\n\u003Cpre class=\"mermaid\">\n\nflowchart LR\n    D(main) -->|auto deploy| E[integration]\n\u003C/pre>\n\n\n3. From there, manual approval is required to trigger the next deployment.\nThis will deploy the main branch to the `qa` environment. Here, I have set a\nrule to prevent deletion from the pipeline. The idea is that this\nenvironment should be quite stable (after all, it's already the third\nenvironment), and I would like to prevent deletion by mistake. Feel free to\nadapt the rules to match your processes.\n\n\n\u003Cpre class=\"mermaid\">\n\nflowchart LR\n    D(main)-->|auto deploy| E[integration]\n    E -->|manual| F[qa]\n\u003C/pre>\n\n\n4. To proceed, we will need to **tag** the code. We are relying on\n[protected\ntags](https://docs.gitlab.com/ee/user/project/protected_tags.html) here to\nensure that only a specific set of users are allowed to deploy to these last\ntwo environments. This will immediately trigger a deployment to the\n`staging` environment.\n\n\n\u003Cpre class=\"mermaid\">\n\nflowchart LR\n    D(main) -->|tag| G(X.Y.Z)\n    F[qa] -->|validate| G\n\n    G -->|auto deploy| H[staging]\n\u003C/pre>\n\n\n5. Finally, we are landing to `production`. When discussing infrastructure,\nit is often challenging to deploy progressively (10%, 25%, etc.), so we will\ndeploy the whole infrastructure. Still, we control that deployment with a\nmanual trigger of this last step. And to enforce maximum control on this\nhighly critical environment, we will control it as a [protected\nenvironment](https://docs.gitlab.com/ee/ci/environments/protected_environments.html).\n\n\n\u003Cpre class=\"mermaid\">\n\nflowchart LR\n    H[staging] -->|manual| I{plan}\n    I -->|manual| J[production]\n\u003C/pre>\n\n\n### The pipeline\n\n\nTo implement the above [workflow](#the-workflow), we are now going to\nimplement a pipeline with two [downstream\npipelines](https://docs.gitlab.com/ee/ci/pipelines/downstream_pipelines.html).\n\n\n#### The main pipeline\n\n\nLet's start with the main pipeline. This is the one that will be triggered\nautomatically on any **push to a feature branch**, any **merge to the\ndefault branch**, or any **tag**. *The one* that will do true **continuous\ndeployment** to the following environments: `dev`, `integration`, and\n`staging`. And it is declared in the `.gitlab-ci.yml` file at the root of\nyour project.\n\n\n![the repository\ntarget](https://res.cloudinary.com/about-gitlab-com/image/upload/v1750097033/Blog/Content%20Images/Blog/Content%20Images/image1_aHR0cHM6_1750097033417.png)\n\n\n```yml\n\nStages:\n  - test\n  - environments\n\n.environment:\n  stage: environments\n  variables:\n    TF_ROOT: terraform\n    TF_CLI_ARGS_plan: \"-var-file=../vars/$variables_file.tfvars\"\n  trigger:\n    include: .gitlab-ci/.first-layer.gitlab-ci.yml\n    strategy: depend            # Wait for the triggered pipeline to successfully complete\n    forward:\n      yaml_variables: true      # Forward variables defined in the trigger job\n      pipeline_variables: true  # Forward manual pipeline variables and scheduled pipeline variables\n\nreview:\n  extends: .environment\n  variables:\n    environment: review/$CI_COMMIT_REF_SLUG\n    TF_STATE_NAME: $CI_COMMIT_REF_SLUG\n    variables_file: review\n    TF_VAR_aws_resources_name: $CI_COMMIT_REF_SLUG  # Used in the tag Name of the resources deployed, to easily differenciate them\n  rules:\n    - if: $CI_COMMIT_BRANCH && $CI_COMMIT_BRANCH != $CI_DEFAULT_BRANCH\n\nintegration:\n  extends: .environment\n  variables:\n    environment: integration\n    TF_STATE_NAME: $environment\n    variables_file: $environment\n  rules:\n    - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH\n\nstaging:\n  extends: .environment\n  variables:\n    environment: staging\n    TF_STATE_NAME: $environment\n    variables_file: $environment\n  rules:\n    - if: $CI_COMMIT_TAG\n\n#### TWEAK\n\n# This tweak is needed to display vulnerability results in the merge\nwidgets.\n\n# As soon as this issue https://gitlab.com/gitlab-org/gitlab/-/issues/439700\nis resolved, the `include` instruction below can be removed.\n\n# Until then, the SAST IaC scanners will run in the downstream pipelines,\nbut their results will not be available directly in the merge request\nwidget, making it harder to track them.\n\n# Note: This workaround is perfectly safe and will not slow down your\npipeline.\n\ninclude:\n  - template: Security/SAST-IaC.gitlab-ci.yml\n#### END TWEAK\n\n\n```\n\n\nThis pipeline runs only two stages: `test` and  `environments`. The former\nis needed for the *TWEAK* to run scanners. The later triggers a child\npipeline with a different set of variables for each case defined above (push\nto the branch, merge to the default branch, or tag).\n\n\nWe are adding here a dependency with the keyword\n[strategy:depend](https://docs.gitlab.com/ee/ci/yaml/index.html#triggerstrategy)\non our child pipeline so the pipeline view in GitLab will be updated only\nonce the deployment is finished.\n\n\nAs you can see here, we are defining a base job,\n[hidden](https://docs.gitlab.com/ee/ci/jobs/#hide-jobs), and we are\nextending it with specific variables and rules to trigger only one\ndeployment for each target environment.\n\n\nBesides the [predefined\nvariables](https://docs.gitlab.com/ee/ci/variables/predefined_variables.html),\nwe are using two new entries that we need to define:\n\n1. [The variables specific](#the-variable-definitions) to each environment:\n`../vars/$variables_file.tfvars`\n\n2. [The child pipeline](#the-child-pipeline), defined in\n`.gitlab-ci/.first-layer.gitlab-ci.yml`\n\n\nLet's start with the smallest part, the variable definitions.\n\n\n### The variable definitions\n\n\nWe are going here to mix two solutions to provide variables to Terraform:\n\n\n* The first one using [.tfvars\nfiles](https://developer.hashicorp.com/terraform/language/values/variables#variable-definitions-tfvars-files)\nfor all non-sensitive input, which should be stored within GitLab.\n\n\n![solution one to provide variables to\nTerraform](https://res.cloudinary.com/about-gitlab-com/image/upload/v1750097034/Blog/Content%20Images/Blog/Content%20Images/image2_aHR0cHM6_1750097033419.png)\n\n\n* The second using [environment\nvariables](https://developer.hashicorp.com/terraform/language/values/variables#environment-variables)\nwith the prefix `TF_VAR`. That second way to inject variables, associated\nwith the GitLab capacity to [mask\nvariables](https://docs.gitlab.com/ee/ci/variables/#mask-a-cicd-variable),\n[protect\nthem](https://docs.gitlab.com/ee/ci/variables/#protect-a-cicd-variable), and\n[scope them to\nenvironments](https://docs.gitlab.com/ee/ci/environments/index.html#limit-the-environment-scope-of-a-cicd-variable)\nis a powerful solution to **prevent sensitive information leakages**. (If\nyou consider your production’s private CIDR very sensitive, you could\nprotect it like this, ensuring it is only available for the `production`\nenvironment, for pipelines running against protected branches and tags, and\nthat its value is masked in the job’s logs.)\n\n\n![solution two to provide variables to\nTerraform](https://res.cloudinary.com/about-gitlab-com/image/upload/v1750097034/Blog/Content%20Images/Blog/Content%20Images/image4_aHR0cHM6_1750097033422.png)\n\n\nAdditionally, each variable file should be controlled through a\n[`CODEOWNERS` file](https://docs.gitlab.com/ee/user/project/codeowners/) to\nset who can modify each of them.\n\n\n```\n\n[Production owners] \n\nvars/production.tfvars @operations-group\n\n\n[Staging owners]\n\nvars/staging.tfvars @odupre @operations-group\n\n\n[CodeOwners owners]\n\nCODEOWNERS @odupre\n\n```\n\n\nThis article is not a Terraform training, so we will go very fast and simply\nshow here the `vars/review.tfvars` file. Subsequent environment files are,\nof course, very similar. Just set the non-sensitive variables and their\nvalues here.\n\n\n```shell\n\naws_vpc_cidr = \"10.1.0.0/16\"\n\naws_public_subnet_cidr = \"10.1.1.0/24\"\n\naws_private_subnet_cidr = \"10.1.2.0/24\"\n\n```\n\n\n#### The child pipeline\n\n\nThis one is where the actual work is done. So, it is slightly more complex\nthan the first one. But there is no difficulty here that we cannot overcome\ntogether!\n\n\nAs we have seen in the definition of the [main\npipeline](#the-main-pipeline), that downstream pipeline is declared in the\nfile `.gitlab-ci/.first-layer.gitlab-ci.yml`.\n\n\n![Downstream pipeline declared in\nfile](https://res.cloudinary.com/about-gitlab-com/image/upload/v1750097033/Blog/Content%20Images/Blog/Content%20Images/image3_aHR0cHM6_1750097033424.png)\n\n\nLet's break it down into small chunks. We'll see the big picture at the end.\n\n\n##### Run Terraform commands and secure the code\n\n\nFirst, we want to run a pipeline for Terraform. We, at GitLab, are open\nsource. So, our Terraform template is open source. And you simply need to\ninclude it. This can be achieved with the following snippet:\n\n\n```yml\n\ninclude:\n  - template: Terraform.gitlab-ci.yml\n```\n\n\nThis template runs for you the Terraform checks on the formatting and\nvalidates your code, before planning and applying it. It also allows you to\ndestroy what you have deployed.\n\n\nAnd, because GitLab is the a single, unified DevSecOps platform, we are also\nautomatically including two security scanners within that template to find\npotential threats in your code and warn you before you deploy it to the next\nenvironments.\n\n\nNow that we have checked, secured, built, and deployed our code, let's do\nsome tricks.\n\n\n##### Share cache between jobs\n\n\nWe will cache the job results to reuse them in subsequent pipeline jobs.\nThis is as simple as adding the following piece of code:\n\n\n```yml\n\ndefault:\n  cache:  # Use a shared cache or tagged runners to ensure terraform can run on apply and destroy\n    - key: cache-$CI_COMMIT_REF_SLUG\n      fallback_keys:\n        - cache-$CI_DEFAULT_BRANCH\n      paths:\n        - .\n```\n\n\nHere, we are defining a different cache for each commit, falling back to the\nmain branch name if needed.\n\n\nIf we look carefully at the templates that we are using, we can see that it\nhas some rules to control when jobs are run. We want to run all controls\n(both QA and security) on all branches. So, we are going to override these\nsettings.\n\n\n##### Run controls on all branches\n\n\nGitLab templates are a powerful feature where one can override only a piece\nof the template. Here, we are interested only in overwriting the rules of\nsome jobs to always run quality and security checks. Everything else defined\nfor these jobs will stay as defined in the template.\n\n\n```yml\n\nfmt:\n  rules:\n    - when: always\n\nvalidate:\n  rules:\n    - when: always\n\nkics-iac-sast:\n  rules:\n    - when: always\n\niac-sast:\n  rules:\n    - when: always\n```\n\n\nNow that we have enforced the quality and security controls, we want to\ndifferentiate how the main environments (integration and staging) in the\n[workflow](#the-workflow) and review environments behave. Let's start by\ndefining the main environment’s behavior, and we will tweak this\nconfiguration for the review environments.\n\n\n##### CD to integration and staging\n\n\nAs defined earlier, we want to deploy the main branch and the tags to these\ntwo environments. We are adding rules to control that on both the `build`\nand `deploy` jobs. Then, we want to enable `destroy` only for the\n`integration` as we have defined `staging` to be too critical to be deleted\nwith a single click. This is error-prone and we don't want to do that.\n\n\nFinally, we are linking the `deploy` job to the `destroy` one, so we can\n`stop` the environment directly from GitLab GUI.\n\n\nThe `GIT_STRATEGY` is here to prevent retrieving the code from the source\nbranch in the runner when destroying. This would fail if the branch has been\ndeleted manually, so we are relying on the cache to get everything we need\nto run the Terraform instructions.\n\n\n```yml\n\nbuild:  # terraform plan\n  environment:\n    name: $TF_STATE_NAME\n    action: prepare\n  rules:\n    - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH\n    - if: $CI_COMMIT_TAG\n\ndeploy: # terraform apply --> automatically deploy on corresponding env\n(integration or staging) when merging to default branch or tagging. Second\nlayer environments (qa and production) will be controlled manually\n  environment: \n    name: $TF_STATE_NAME\n    action: start\n    on_stop: destroy\n  rules:\n    - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH\n    - if: $CI_COMMIT_TAG\n\ndestroy:\n  extends: .terraform:destroy\n  variables:\n    GIT_STRATEGY: none\n  dependencies:\n    - build\n  environment:\n    name: $TF_STATE_NAME\n    action: stop\n  rules:\n    - if: $CI_COMMIT_TAG  # Do not destroy production\n      when: never\n    - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH && $TF_DESTROY == \"true\" # Manually destroy integration env.\n      when: manual\n```\n\n\nAs said, this matches the need to deploy to `integration` and `staging`. But\nwe are still missing a temporary environment where the developers can\nexperience and validate their code without impacts on others. This is where\nthe deployment to the `review` environment takes place.\n\n\n##### CD to review environments\n\n\nDeploying to review environment is not too different than deploying to\n`integration` and `staging`. So we will once again leverage GitLab's\ncapacity to overwrite only pieces of job definition here.\n\n\nFirst, we set rules to run these jobs only on feature branches.\n\n\nThen, we link the `deploy_review` job to `destroy_review`. This will allow\nus to stop the environment **manually** from the GitLab user interface, but\nmore importantly, it will **automatically trigger the environment\ndestruction** when the feature branch is closed. This is a good FinOps\npractice to help you control your operational expenditures.\n\n\nSince Terraform needs a plan file to destroy an infrastructure, exactly like\nit needs one to build an infrastructure, then we are adding a dependency\nfrom `destroy_review` to `build_review`, to retrieve its artifacts.\n\n\nFinally, we see here that the environment's name is set to `$environment`.\nIt has been set in the [main pipeline](#the-main-pipeline) to\n`review/$CI_COMMIT_REF_SLUG`, and forwarded to this child pipeline with the\ninstruction `trigger:forward:yaml_variables:true`.\n\n\n```yml\n\nbuild_review:\n  extends: build\n  rules:\n    - if: $CI_COMMIT_TAG\n      when: never\n    - if: $CI_COMMIT_BRANCH != $CI_DEFAULT_BRANCH\n      when: on_success\n\ndeploy_review:\n  extends: deploy\n  dependencies:\n    - build_review\n  environment:\n    name: $environment\n    action: start\n    on_stop: destroy_review\n    # url: https://$CI_ENVIRONMENT_SLUG.example.com\n  rules:\n    - if: $CI_COMMIT_TAG\n      when: never\n    - if: $CI_COMMIT_BRANCH != $CI_DEFAULT_BRANCH\n      when: on_success\n\ndestroy_review:\n  extends: destroy\n  dependencies:\n    - build_review\n  environment:\n    name: $environment\n    action: stop\n  rules:\n    - if: $CI_COMMIT_TAG  # Do not destroy production\n      when: never\n    - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH   # Do not destroy staging\n      when: never\n    - when: manual\n```\n\n\nSo, to recap, we now have a pipeline that can:\n\n\n* Deploy temporary review environments, which are automatically cleaned up\nwhen the feature branch is closed\n\n* Continuously deploy the **default branch** to `integration`\n\n* Continuously deploy the **tags** to `staging`\n\n\nLet's now add an extra layer, where we will deploy, based on a manual\ntrigger this time, to `qa` and `production` environments.\n\n\n##### Continously deploy to QA and production\n\n\nBecause not everybody is willing to deploy continuously to production, we\nwill add a manual validation to the next two deployments. From a purely\n**CD** perspective, we would not add this trigger, but take this as an\nopportunity to learn how to run jobs from other triggers.\n\n\nSo far, we have started a [child pipeline](#the-child-pipeline) from the\n[main pipeline](#the-main-pipeline) to run all deployments.\n\n\nSince we want to run other deployments from the default branch and the tags,\nwe will add another layer dedicated to these additional steps. Nothing new\nhere. We will just repeat exactly the same process as the one we only did\nfor the [main pipeline](#the-main-pipeline). Going this way allows you to\nmanipulate as many layers as you need. I have already seen up to nine\nenvironments in some places.\n\n\nWithout arguing once again on the benefits to have fewer environments, the\nprocess that we are using here makes it very easy to implement the same\npipeline all the way from early stages to final delivery, while keeping your\npipeline definition simple and split in small chunks that you can maintain\nat no cost.\n\n\nTo prevent variable conflicts here, we are just using new var names to\nidentify the Terraform state and input file.\n\n\n```yml\n\n.2nd_layer:\n  stage: 2nd_layer\n  variables:\n    TF_ROOT: terraform\n  trigger:\n    include: .gitlab-ci/.second-layer.gitlab-ci.yml\n    # strategy: depend            # Do NOT wait for the downstream pipeline to finish to mark upstream pipeline as successful. Otherwise, all pipelines will fail when reaching the pipeline timeout before deployment to 2nd layer.\n    forward:\n      yaml_variables: true      # Forward variables defined in the trigger job\n      pipeline_variables: true  # Forward manual pipeline variables and scheduled pipeline variables\n\nqa:\n  extends: .2nd_layer\n  variables:\n    TF_STATE_NAME_2: qa\n    environment: $TF_STATE_NAME_2\n    TF_CLI_ARGS_plan_2: \"-var-file=../vars/$TF_STATE_NAME_2.tfvars\"\n  rules:\n    - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH\n\nproduction:\n  extends: .2nd_layer\n  variables:\n    TF_STATE_NAME_2: production\n    environment: $TF_STATE_NAME_2\n    TF_CLI_ARGS_plan_2: \"-var-file=../vars/$TF_STATE_NAME_2.tfvars\"\n  rules:\n    - if: $CI_COMMIT_TAG\n```\n\n\n**One important trick here is the strategy used for the new downstream\npipeline.** We leave that `trigger:strategy` to its default value;\notherwise, the [main pipeline](#the-main-pipeline) would wait for your\n[grand-child pipeline](#the-grand-child-pipeline) to finish. With a manual\ntrigger, this could last for a very long time and make your pipeline\ndashboard harder to read and understand.\n\n\nYou have probably already wondered what is the content of that\n`.gitlab-ci/.second-layer.gitlab-ci.yml` file we are including here.  We\nwill cover that in the next section.\n\n\n##### The first layer complete pipeline definition\n\n\nIf you are looking for a complete view of this first layer (stored in\n`.gitlab-ci/.first-layer.gitlab-ci.yml`), just expand the section below.\n\n\n```yml\n\nvariables:\n  TF_VAR_aws_ami_id: $AWS_AMI_ID\n  TF_VAR_aws_instance_type: $AWS_INSTANCE_TYPE\n  TF_VAR_aws_default_region: $AWS_DEFAULT_REGION\n\ninclude:\n  - template: Terraform.gitlab-ci.yml\n\ndefault:\n  cache:  # Use a shared cache or tagged runners to ensure terraform can run on apply and destroy\n    - key: cache-$CI_COMMIT_REF_SLUG\n      fallback_keys:\n        - cache-$CI_DEFAULT_BRANCH\n      paths:\n        - .\n\nstages:\n  - validate\n  - test\n  - build\n  - deploy\n  - cleanup\n  - 2nd_layer       # Use to deploy a 2nd environment on both the main branch and on the tags\n\nfmt:\n  rules:\n    - when: always\n\nvalidate:\n  rules:\n    - when: always\n\nkics-iac-sast:\n  rules:\n    - if: $SAST_DISABLED == 'true' || $SAST_DISABLED == '1'\n      when: never\n    - if: $SAST_EXCLUDED_ANALYZERS =~ /kics/\n      when: never\n    - when: on_success\n\niac-sast:\n  rules:\n    - if: $SAST_DISABLED == 'true' || $SAST_DISABLED == '1'\n      when: never\n    - if: $SAST_EXCLUDED_ANALYZERS =~ /kics/\n      when: never\n    - when: on_success\n\n###########################################################################################################\n\n## Integration env. and Staging. env\n\n##  * Auto-deploy to Integration on merge to main.\n\n##  * Auto-deploy to Staging on tag.\n\n##  * Integration can be manually destroyed if TF_DESTROY is set to true.\n\n##  * Destroy of next env. is not automated to prevent errors.\n\n###########################################################################################################\n\nbuild:  # terraform plan\n  environment:\n    name: $TF_STATE_NAME\n    action: prepare\n  rules:\n    - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH\n    - if: $CI_COMMIT_TAG\n\ndeploy: # terraform apply --> automatically deploy on corresponding env\n(integration or staging) when merging to default branch or tagging. Second\nlayer environments (qa and production) will be controlled manually\n  environment: \n    name: $TF_STATE_NAME\n    action: start\n    on_stop: destroy\n  rules:\n    - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH\n    - if: $CI_COMMIT_TAG\n\ndestroy:\n  extends: .terraform:destroy\n  variables:\n    GIT_STRATEGY: none\n  dependencies:\n    - build\n  environment:\n    name: $TF_STATE_NAME\n    action: stop\n  rules:\n    - if: $CI_COMMIT_TAG  # Do not destroy production\n      when: never\n    - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH && $TF_DESTROY == \"true\" # Manually destroy integration env.\n      when: manual\n###########################################################################################################\n\n\n###########################################################################################################\n\n## Dev env.\n\n##  * Temporary environment. Lives and dies with the Merge Request.\n\n##  * Auto-deploy on push to feature branch.\n\n##  * Auto-destroy on when Merge Request is closed.\n\n###########################################################################################################\n\nbuild_review:\n  extends: build\n  rules:\n    - if: $CI_COMMIT_TAG\n      when: never\n    - if: $CI_COMMIT_BRANCH != $CI_DEFAULT_BRANCH\n      when: on_success\n\ndeploy_review:\n  extends: deploy\n  dependencies:\n    - build_review\n  environment:\n    name: $environment\n    action: start\n    on_stop: destroy_review\n    # url: https://$CI_ENVIRONMENT_SLUG.example.com\n  rules:\n    - if: $CI_COMMIT_TAG\n      when: never\n    - if: $CI_COMMIT_BRANCH != $CI_DEFAULT_BRANCH\n      when: on_success\n\ndestroy_review:\n  extends: destroy\n  dependencies:\n    - build_review\n  environment:\n    name: $environment\n    action: stop\n  rules:\n    - if: $CI_COMMIT_TAG  # Do not destroy production\n      when: never\n    - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH   # Do not destroy staging\n      when: never\n    - when: manual\n###########################################################################################################\n\n\n###########################################################################################################\n\n## Second layer\n\n##  * Deploys from main branch to qa env.\n\n##  * Deploys from tag to production.\n\n###########################################################################################################\n\n.2nd_layer:\n  stage: 2nd_layer\n  variables:\n    TF_ROOT: terraform\n  trigger:\n    include: .gitlab-ci/.second-layer.gitlab-ci.yml\n    # strategy: depend            # Do NOT wait for the downstream pipeline to finish to mark upstream pipeline as successful. Otherwise, all pipelines will fail when reaching the pipeline timeout before deployment to 2nd layer.\n    forward:\n      yaml_variables: true      # Forward variables defined in the trigger job\n      pipeline_variables: true  # Forward manual pipeline variables and scheduled pipeline variables\n\nqa:\n  extends: .2nd_layer\n  variables:\n    TF_STATE_NAME_2: qa\n    environment: $TF_STATE_NAME_2\n    TF_CLI_ARGS_plan_2: \"-var-file=../vars/$TF_STATE_NAME_2.tfvars\"\n  rules:\n    - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH\n\nproduction:\n  extends: .2nd_layer\n  variables:\n    TF_STATE_NAME_2: production\n    environment: $TF_STATE_NAME_2\n    TF_CLI_ARGS_plan_2: \"-var-file=../vars/$TF_STATE_NAME_2.tfvars\"\n  rules:\n    - if: $CI_COMMIT_TAG\n###########################################################################################################\n\n```\n\n\nAt this stage, we are already deploying safely to three environments. That\nis my personal ideal recommendation. However, if you need more environments,\nadd them to your CD pipeline.\n\n\nYou have certainly already noted that we include a downstream pipeline with\nthe keyword `trigger:include`. This includes the file\n`.gitlab-ci/.second-layer.gitlab-ci.yml`. We want to run almost the same\npipeline so obviously, its content is very similar to the one we have\ndetailed above. The main advantage here to define this [grand-child\npipeline](#the-grand-child-pipeline) is that it lives on its own, making\nboth variables and rules way easier to define.\n\n\n### The grand-child pipeline\n\n\nThis second layer pipeline is a brand new pipeline. Hence, it needs to mimic\nthe first layer definition with:\n\n\n* [Inclusion of the Terraform\ntemplate](#run-terraform-commands-and-secure-the-code).\n\n* [Enforcement of security checks](#run-controls-on-all-branches). Terraform\nvalidation would be duplicates of the first layer, but security scanners may\nfind threats that did not yet exist when scanners previously ran (for\nexample, if you deploy to production a couple of days after your deployment\nto staging).\n\n* [Overwrite build and deploy jobs to set specific\nrules](#cd-to-review-environments). Note that the `destroy` stage is no\nlonger automated to prevent too fast deletions.\n\n\nAs explained above, the `TF_STATE_NAME` and `TF_CLI_ARGS_plan` have been\nprovided from the [main pipeline](#the-main-pipeline) to the [child\npipeline](#the-child-pipeline). We needed another variable name to pass\nthese values from the [child pipeline](#the-child-pipeline) to here, the\n[grand-child pipeline](#the-grand-child-pipeline). This is why they are\npostfixed with `_2` in the child pipeline and the value is copied back to\nthe appropriate variable during the `before_script` here.\n\n\nSince we have already broken down each step above, we can zoom out here\ndirectly to the broad view of the global second layer definition (stored in\n`.gitlab-ci/.second-layer.gitlab-ci.yml`).\n\n\n```yml\n\n# Use to deploy a second environment on both the default branch and the\ntags.\n\n\ninclude:\n  template: Terraform.gitlab-ci.yml\n\nstages:\n  - validate\n  - test\n  - build\n  - deploy\n\nfmt:\n  rules:\n    - when: never\n\nvalidate:\n  rules:\n    - when: never\n\nkics-iac-sast:\n  rules:\n    - if: $SAST_DISABLED == 'true' || $SAST_DISABLED == '1'\n      when: never\n    - if: $SAST_EXCLUDED_ANALYZERS =~ /kics/\n      when: never\n    - when: always\n\n###########################################################################################################\n\n## QA env. and Prod. env\n\n##  * Manually trigger build and auto-deploy in QA\n\n##  * Manually trigger both build and deploy in Production\n\n##  * Destroy of these env. is not automated to prevent errors.\n\n###########################################################################################################\n\nbuild:  # terraform plan\n  cache:  # Use a shared cache or tagged runners to ensure terraform can run on apply and destroy\n    - key: $TF_STATE_NAME_2\n      fallback_keys:\n        - cache-$CI_DEFAULT_BRANCH\n      paths:\n        - .\n  environment:\n    name: $TF_STATE_NAME_2\n    action: prepare\n  before_script:  # Hack to set new variable values on the second layer, while still using the same variable names. Otherwise, due to variable precedence order, setting new value in the trigger job, does not cascade these new values to the downstream pipeline\n    - TF_STATE_NAME=$TF_STATE_NAME_2\n    - TF_CLI_ARGS_plan=$TF_CLI_ARGS_plan_2\n  rules:\n    - when: manual\n\ndeploy: # terraform apply\n  cache:  # Use a shared cache or tagged runners to ensure terraform can run on apply and destroy\n    - key: $TF_STATE_NAME_2\n      fallback_keys:\n        - cache-$CI_DEFAULT_BRANCH\n      paths:\n        - .\n  environment: \n    name: $TF_STATE_NAME_2\n    action: start\n  before_script:  # Hack to set new variable values on the second layer, while still using the same variable names. Otherwise, due to variable precedence order, setting new value in the trigger job, does not cascade these new values to the downstream pipeline\n    - TF_STATE_NAME=$TF_STATE_NAME_2\n    - TF_CLI_ARGS_plan=$TF_CLI_ARGS_plan_2\n  rules:\n    - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH\n    - if: $CI_COMMIT_TAG && $TF_AUTO_DEPLOY == \"true\"\n    - if: $CI_COMMIT_TAG\n      when: manual\n###########################################################################################################\n\n```\n\n\nEt voilà. **We are ready to go.** Feel free to change the way you control\nyour job executions, leveraging for example GitLab's capacity to [delay a\njob](https://docs.gitlab.com/ee/ci/jobs/job_control.html#run-a-job-after-a-delay)\nbefore deploying to production.\n\n\n## Try it yourself\n\n\nWe finally reached our destination. We are now able to control **deployments\nto five different environments**, with only the **feature branches**, the\n**main branch**, and **tags**.\n\n* We are intensively reusing GitLab open source templates to ensure\nefficiency and security in our pipelines.\n\n* We are leveraging GitLab template capacities to overwrite only the blocks\nthat need custom control.\n\n* We have split the pipeline in small chunks, controlling the downstream\npipelines to match exactly what we need.\n\n\nFrom there, the floor is yours. You could, for example, easily update the\nmain pipeline to trigger downstream pipelines for your software source code,\nwith the\n[trigger:rules:changes](https://docs.gitlab.com/ee/ci/yaml/#ruleschanges)\nkeyword. And use another\n[template](https://gitlab.com/gitlab-org/gitlab/-/tree/master/lib/gitlab/ci/templates/)\ndepending on the changes that happened. But that is another story.\n",[109,9,696,496,742],{"slug":2736,"featured":6,"template":699},"using-child-pipelines-to-continuously-deploy-to-five-environments","content:en-us:blog:using-child-pipelines-to-continuously-deploy-to-five-environments.yml","Using Child Pipelines To Continuously Deploy To Five Environments","en-us/blog/using-child-pipelines-to-continuously-deploy-to-five-environments.yml","en-us/blog/using-child-pipelines-to-continuously-deploy-to-five-environments",{"_path":2742,"_dir":246,"_draft":6,"_partial":6,"_locale":7,"seo":2743,"content":2749,"config":2755,"_id":2757,"_type":14,"title":2758,"_source":16,"_file":2759,"_stem":2760,"_extension":19},"/en-us/blog/using-gitlab-ci-to-build-gitlab-faster",{"title":2744,"description":2745,"ogTitle":2744,"ogDescription":2745,"noIndex":6,"ogImage":2746,"ogUrl":2747,"ogSiteName":686,"ogType":687,"canonicalUrls":2747,"schema":2748},"How we used GitLab CI to build GitLab faster","Here's how we went from a daily manual merge of GitLab Core into GitLab Enterprise to automated merges every three hours.","https://res.cloudinary.com/about-gitlab-com/image/upload/v1749665440/Blog/Hero%20Images/automate-ce-ee-merges.jpg","https://about.gitlab.com/blog/using-gitlab-ci-to-build-gitlab-faster","\n                        {\n        \"@context\": \"https://schema.org\",\n        \"@type\": \"Article\",\n        \"headline\": \"How we used GitLab CI to build GitLab faster\",\n        \"author\": [{\"@type\":\"Person\",\"name\":\"Rémy Coutable\"}],\n        \"datePublished\": \"2018-05-02\",\n      }",{"title":2744,"description":2745,"authors":2750,"heroImage":2746,"date":2752,"body":2753,"category":717,"tags":2754},[2751],"Rémy Coutable","2018-05-02","GitLab is an [open source project], but also a [commercial project]. For\nhistoric\n\nreasons, we have two Git repositories: [`gitlab-ce`] for GitLab Core and\n\n[`gitlab-ee`] for GitLab Enterprise packages (you can read [our recent blog\npost explaining GitLab self-managed tiers](/blog/gitlab-tiers/)).\n\nWhile we're working on having a [single codebase], we still need to\nregularly\n\nmerge [`gitlab-ce`] into [`gitlab-ee`] since most of the development happens\non\n\nGitLab Core, but we also develop features on top of it for GitLab Starter,\nPremium, and Ultimate.\n\n\n## How we used to merge GitLab CE into GitLab EE\n\n\nUntil December 2017, the merge of [`gitlab-ce`] into [`gitlab-ee`] was\nmanual\n\non a daily basis with basically the following commands ([see the full\ndocumentation]):\n\n\n```shell\n\n# the `origin` remote refers to https://gitlab.com/gitlab-org/gitlab-ee.git\n\n# the `ce` remote refers to https://gitlab.com/gitlab-org/gitlab-ce.git\n\ngit fetch origin master\n\ngit checkout -b ce-to-ee origin/master\n\ngit fetch ce master\n\ngit merge --no-ff ce/master\n\n```\n\n\nAt this point, since we'd merge a day's worth of GitLab Core's new commits,\n\nchances were good we'd see conflicts.\n\nMost of the time, the person responsible for this process would handle the\n\nconflict resolutions, commit them and push the `ce-to-ee` branch to\nGitLab.com.\n\n\nThere were a few problems with this approach:\n\n\n- GitLab's development pace is fast, which means the longer we go without a\n  merge, the more changes there are and thus more opportunities for conflicts\n- If we had many conflicts, it could take a significant amount of time for\nthe\n  developer responsible for the merge\n- The developer performing the merge wasn't always the best person to\nresolve the\n  conflicts\n- Significant time was spent identifying and notifying developers to help\nresolve conflicts\n\n\n## The solution\n\n\nOur plan was to have a single script that would automate the merge, and in\nthe\n\ncase of conflicts, identify the person best suited to resolve each of them.\n\nIt would then create the merge request using the [GitLab API] and a\n\n[GitLab API Ruby wrapper], and post a message in Slack when a new merge\nrequest\n\nwas created or an existing one was still pending.\n\n\nFinally, we'd use GitLab's [pipeline schedules] to run the script every\nthree hours.\n\n\n### Step 1: Write the script\n\n\nWe chose to write the script in our [`release-tools`] project, since it\nalready\n\nhad a strong foundation for working with the relevant Git repositories.\n\n\nThis script was written iteratively as a set of classes over the course of a\nfew\n\nmonths:\n\n\n1. [Add the ability to find/create a merge request][!139]\n\n1. [Move remotes to the `Project` classes and get rid of the `Remotes`\nclass][!168]\n\n1. [Add `head`, `status`, `log`, `fetch`, `checkout_new_branch`, `pull`,\n`push`, and `merge` to `RemoteRepository`][!177]\n\n1. [Introduce a new `CommitAuthor` class][!197]\n\n\nThe last piece of the puzzle was the new [`upstream_merge` Rake task][!219].\n\n\n### Step 2: Create a pair of SSH keys and add the public key to the\n`gitlab-ee` project\n\n\nUnder **Repository Settings > Deploy Keys** of the [`gitlab-ee`] project:\n\n\n![Deploy key in\n`gitlab-ee`](https://about.gitlab.com/images/blogimages/using-gitlab-ci-to-build-gitlab-faster/step2.png){:\n.shadow.center.medium}\n\n\n### Step 3: Create secret variables in the `release-tools` project\n\n\nUnder **CI / CD Settings** of the [`release-tools`] project, create three\nsecret\n\nvariables:\n\n\n- `AUTO_UPSTREAM_MERGE_BOT_SSH_PRIVATE_KEY` for the SSH private key\n\n- `GITLAB_API_PRIVATE_TOKEN` is a personal access token for our\n[`@gitlab-bot`]\n  user\n- `SLACK_UPSTREAM_MERGE_URL` which is the Slack webhook URL we created\n  specifically for this job and used in our [`Slack::UpstreamMergeNotification` class]\n\n![Secret\nvariable](https://about.gitlab.com/images/blogimages/using-gitlab-ci-to-build-gitlab-faster/step3.png){:\n.shadow.center.medium}\n\n\n### Step 4: Add a new CI job that runs the `upstream_merge` Rake task for\npipeline schedules only\n\n\n*This was heavily inspired by [GitBot – automating boring Git operations\nwith CI].*\n\n\nCreate a new `upstream-merge` CI job that:\n\n\n- Adds the SSH private key to the `~/.ssh` folder\n\n- Add `gitlab.com` to the `~/.ssh/known_hosts` file\n\n- Runs `bundle exec rake upstream_merge`\n\n\n![`upstream-merge`\njob](https://about.gitlab.com/images/blogimages/using-gitlab-ci-to-build-gitlab-faster/step4.png){:\n.shadow.center.medium}\n\n\nYou can [check out the task for\nyourself](https://gitlab.com/gitlab-org/release-tools/blob/1cd437823113d4529919c29b177bb2037c19fc3c/.gitlab-ci.yml#L50-64).\n\n\n### Step 5: Create a pipeline schedule that runs every three hours\n\n\nUnder **Schedules** of the [`release-tools`] project:\n\n\n![Pipeline\nschedule](https://about.gitlab.com/images/blogimages/using-gitlab-ci-to-build-gitlab-faster/step5.png){:\n.shadow.center.medium}\n\n\n### Step 6: Let the bot work for us!\n\n\n**The CI job:**\n\n\n![CI\njob](https://about.gitlab.com/images/blogimages/using-gitlab-ci-to-build-gitlab-faster/step6-1.png){:\n.shadow.center.medium}\n\n\n**The Slack messages:**\n\n\n![Slack\nmessages](https://about.gitlab.com/images/blogimages/using-gitlab-ci-to-build-gitlab-faster/step6-2.png){:\n.shadow.center.medium}\n\n\n**The merge request:**\n\n\n![Merge\nrequest](https://about.gitlab.com/images/blogimages/using-gitlab-ci-to-build-gitlab-faster/step6-3.png){:\n.shadow.center.medium}\n\n\n## What are the benefits?\n\n\nSince we started automating this process in December 2017, our dear\n\n[`@gitlab-bot`] created no fewer than [229 automatic merges], and we started\n\nnoticing the benefits immediately:\n\n\n- Automating the merge request creation saved developers time and removed a\nmanual\n\nchore.\n\n- Automatically identifying the developer who introduced a conflict and\nassigning\n\nthem to resolve it spread out the workload and reduced bugs caused by\nimproper\n\nconflict resolution.\n\n- Performing the merge automatically every three hours instead of manually\nonce a\n\nday led to fewer changes at a time and a reduced number of conflicts.\n\n\nThe last, perhaps least visible, but most important benefit, is that we\nreduced\n\ndeveloper frustration and increased happiness by removing a tedious chore.\n\n\n[Photo](https://unsplash.com/photos/w6OniVDCfn0?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText)\nby Max Ostrozhinskiy on\n[Unsplash](https://unsplash.com/search/photos/build?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText)\n\n{: .note}\n\n\n[open source project]: /community/contribute/\n\n[commercial project]: /pricing/\n\n[`gitlab-ce`]: https://gitlab.com/gitlab-org/gitlab-ce\n\n[`gitlab-ee`]: https://gitlab.com/gitlab-org/gitlab-ee\n\n[single codebase]: https://gitlab.com/gitlab-org/gitlab-ee/issues/2952\n\n[see the full documentation]:\nhttps://gitlab.com/gitlab-org/release/docs/blob/master/general/merge-ce-into-ee.md\n\n[pipeline schedules]: https://docs.gitlab.com/ee/ci/pipelines/schedules.html\n\n[GitLab API]: https://docs.gitlab.com/ee/api/merge_requests.html\n\n[GitLab API Ruby wrapper]: https://rubygems.org/gems/gitlab\n\n[`release-tools`]: https://gitlab.com/gitlab-org/release-tools/\n\n[!139]: https://gitlab.com/gitlab-org/release-tools/merge_requests/139\n\n[!168]: https://gitlab.com/gitlab-org/release-tools/merge_requests/168\n\n[!177]: https://gitlab.com/gitlab-org/release-tools/merge_requests/177\n\n[!197]: https://gitlab.com/gitlab-org/release-tools/merge_requests/197\n\n[!219]: https://gitlab.com/gitlab-org/release-tools/merge_requests/219\n\n[`Slack::UpstreamMergeNotification` class]:\nhttps://gitlab.com/gitlab-org/release-tools/blob/1cd437823113d4529919c29b177bb2037c19fc3c/lib/slack/upstream_merge_notification.rb#L7\n\n[GitBot – automating boring Git operations with CI]:\n/2017/11/02/automating-boring-git-operations-gitlab-ci/\n\n[229 automatic merges]:\nhttps://gitlab.com/gitlab-org/gitlab-ee/merge_requests?scope=all&utf8=%E2%9C%93&state=merged&label_name[]=CE%20upstream&author_username=gitlab-bot\n\n[`@gitlab-bot`]: https://gitlab.com/gitlab-bot\n",[915,9],{"slug":2756,"featured":6,"template":699},"using-gitlab-ci-to-build-gitlab-faster","content:en-us:blog:using-gitlab-ci-to-build-gitlab-faster.yml","Using Gitlab Ci To Build Gitlab Faster","en-us/blog/using-gitlab-ci-to-build-gitlab-faster.yml","en-us/blog/using-gitlab-ci-to-build-gitlab-faster",{"_path":2762,"_dir":246,"_draft":6,"_partial":6,"_locale":7,"seo":2763,"content":2769,"config":2775,"_id":2777,"_type":14,"title":2778,"_source":16,"_file":2779,"_stem":2780,"_extension":19},"/en-us/blog/using-run-parallel-jobs",{"title":2764,"description":2765,"ogTitle":2764,"ogDescription":2765,"noIndex":6,"ogImage":2766,"ogUrl":2767,"ogSiteName":686,"ogType":687,"canonicalUrls":2767,"schema":2768},"How we used parallel CI/CD jobs to increase our productivity","GitLab uses parallel jobs to help long-running jobs run faster.","https://res.cloudinary.com/about-gitlab-com/image/upload/v1749666717/Blog/Hero%20Images/cover-image.jpg","https://about.gitlab.com/blog/using-run-parallel-jobs","\n                        {\n        \"@context\": \"https://schema.org\",\n        \"@type\": \"Article\",\n        \"headline\": \"How we used parallel CI/CD jobs to increase our productivity\",\n        \"author\": [{\"@type\":\"Person\",\"name\":\"Miguel Rincon\"}],\n        \"datePublished\": \"2021-01-20\",\n      }",{"title":2764,"description":2765,"authors":2770,"heroImage":2766,"date":2772,"body":2773,"category":717,"tags":2774},[2771],"Miguel Rincon","2021-01-20","At GitLab, we must verify simultaneous changes from the hundreds of people\nthat contribute to GitLab each day. How can we help them contribute\nefficiently using our pipelines?\n\n\nThe pipelines that we use to build and verify GitLab have more than 90 jobs.\nNot all of those jobs are equal. Some are simple tasks that take a few\nseconds to finish, while others are long-running processes that must be\noptimized carefully.\n\n\nAt the time of this writing, we have more than 700 [pipelines\nrunning](https://gitlab.com/gitlab-org/gitlab/-/pipelines?page=1&scope=all&status=running).\nEach of these pipelines represent changes from team members and contributors\nfrom the wider community. All GitLab contributors must wait for the\npipelines to finish to make sure the change works and integrates with the\nrest of the product. We want our pipelines to finish as fast as possible to\nmaintain the productivity of our teams.\n\n\nThis is why we constantly monitor the duration of our pipelines. For\nexample, in December 2020, successful merge request pipelines had a duration\nof [53.8\nminutes](/handbook/engineering/quality/performance-indicators/#average-merge-request-pipeline-duration-for-gitlab):\n\n\n![Average pipeline duration was 53.8 minutes in\nDecember](https://about.gitlab.com/images/blogimages/using-run-parallel-jobs/historical-pipeline-duration.png){:\n.shadow.medium.center}\n\nThe average pipeline took 53.8 minutes to finish in December.\n\n{: .note.text-center}\n\n\nGiven that we run [around 500 merge request\npipelines](https://gitlab.com/gitlab-org/gitlab/-/pipelines/charts) per day,\nwe want to know: Can we optimize our process to change how long-running jobs\n_run_?\n\n\n## How we fixed our bottleneck jobs by making them run in parallel\n\n\nThe `frontend-fixtures` job uses `rspec` to generate mock data files, which\nare then saved as files called \"fixtures\". These files are loaded by our\nfrontend tests, so the `frontend-fixtures` must finish before any of our\nfrontend tests can start.\n\n\n> As not all of our tests need these frontend fixtures, many jobs use the\n[`needs` keyword](https://docs.gitlab.com/ee/ci/yaml/#needs) to start before\nthe `frontend-fixtures` job is done.\n\n\nIn our pipelines, this job looked like this:\n\n\n![The `frontend-fixtures`\njob](https://about.gitlab.com/images/blogimages/using-run-parallel-jobs/fixtures-job.png){:\n.shadow.medium.center}\n\nInside the frontend fixtures job.\n\n{: .note.text-center}\n\n\n\nThis job had a normal duration of 20 minutes, and each individual fixture\ncould be generated independently, so we knew there was an opportunity to run\nthis process in parallel.\n\n\nThe next step was to configure our pipeline to split the job into multiple\nbatches that could be run in parallel.\n\n\n## How to make frontend-fixtures a parallel job\n\n\nFortunately, GitLab CI provides an easy way to run a job in parallel using\nthe [`parallel` keyword](https://docs.gitlab.com/ee/ci/yaml/#parallel). In\nthe background, this creates \"clones\" of the same job, so that multiple\ncopies of it can run simultaneously.\n\n\n**Before:**\n\n\n```yml\n\nfrontend-fixtures:\n  extends:\n    - .frontend-fixtures-base\n    - .frontend:rules:default-frontend-jobs\n```\n\n\n**After:**\n\n\n```yml\n\nrspec-ee frontend_fixture:\n  extends:\n    - .frontend-fixtures-base\n    - .frontend:rules:default-frontend-jobs\n  parallel: 2\n```\n\n\nYou will notice two changes. First, we changed the name of the job, so our\njob is picked up by [Knapsack](https://docs.knapsackpro.com/ruby/knapsack)\n(more on that later), and then we add the keyword `parallel`, so the job\ngets duplicated and runs in parallel.\n\n\nThe new jobs that are generated look like this:\n\n\n![Our fixtures job running in\nparallel](https://about.gitlab.com/images/blogimages/using-run-parallel-jobs/fixtures-job-parallel.png){:\n.shadow.medium.center}\n\nThe new jobs that are picked up by Knapsack and run in parallel.\n\n{: .note.text-center}\n\n\nAs we used a value of `parallel: 2`, actually two jobs are generated with\nthe names:\n\n\n- `rspec-ee frontend_fixture 1/2`\n\n- `rspec-ee frontend_fixture 2/2`\n\n\nOur two \"generated\" jobs, now take three and 17 minutes respectively, giving\nus an overall decrease of about three minutes.\n\n\n![Two parallel jobs in the\npipeline](https://about.gitlab.com/images/blogimages/using-run-parallel-jobs/fixtures-job-detail.png){:\n.shadow.medium.center}\n\nThe parallel jobs that are running in the pipeline.\n\n{: .note.text-center}\n\n\n## Another way we optimized the process\n\n\nAs we use Knapsack to distribute the test files among the parallel jobs, we\nwere able to make more improvements by reducing the time it takes our\nlongest-running fixtures-generator file to run.\n\n\nWe did this by splitting the file into smaller batches and optimizing it, so\nwe have more tests running in parallel, which shaved off an additional [~3.5\nminutes](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/47158#note_460372560).\n\n\n## Tips for running parallel jobs\n\n\nIf you want to ramp up your productivity you can leverage `parallel` on your\npipelines by following these tips:\n\n\n1. Measure the time your pipelines take to run and identify possible\nbottlenecks to your jobs. You can do this by checking which jobs are slower\nthan others.\n\n1. Once your slow jobs are identified, try to figure out if they can be run\nindependently from each other or in batches.\n   - Automated tests are usually good candidates, as they tend to be self-contained and run in parallel anyway.\n1. Add the `parallel` keyword, while measuring the outcome over the next few\nrunning pipelines.\n\n\n## Learn more about our solution\n\n\nWe discuss how running jobs in parallel improved the speed of pipelines on\nGitLab Unfiltered.\n\n\n\u003C!-- blank line -->\n\n\u003Cfigure class=\"video_container\">\n  \u003Ciframe src=\"https://www.youtube-nocookie.com/embed/hKsVH_ZhSAk\" frameborder=\"0\" allowfullscreen=\"true\"> \u003C/iframe>\n\u003C/figure>\n\n\u003C!-- blank line -->\n\n\nAnd here are links to some of the resources we used to run pipelines in\nparallel:\n\n\n- The [merge request that introduced `parallel` to\nfixtures](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/46959).\n\n- An important [optimization\nfollow-up](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/47158) to\nmake one of the slow tests faster.\n\n- The [Knapsack gem](https://docs.knapsackpro.com/ruby/knapsack), which we\nleverage to split the tests more evenly in multiple CI nodes.\n\n\nAnd many thanks to [Rémy Coutable](/company/team/#rymai), who helped me\nimplement this improvement.\n\n\nCover image by [@dustt](https://unsplash.com/@dustt) on\n[Unsplash](https://unsplash.com/photos/ZqBNb7xK5s8)\n\n{: .note}\n",[915,9,696,719,1734],{"slug":2776,"featured":6,"template":699},"using-run-parallel-jobs","content:en-us:blog:using-run-parallel-jobs.yml","Using Run Parallel Jobs","en-us/blog/using-run-parallel-jobs.yml","en-us/blog/using-run-parallel-jobs",{"_path":2782,"_dir":246,"_draft":6,"_partial":6,"_locale":7,"seo":2783,"content":2789,"config":2795,"_id":2797,"_type":14,"title":2798,"_source":16,"_file":2799,"_stem":2800,"_extension":19},"/en-us/blog/using-the-gitlab-ci-slash-cd-for-smart-home-configuration-management",{"title":2784,"description":2785,"ogTitle":2784,"ogDescription":2785,"noIndex":6,"ogImage":2786,"ogUrl":2787,"ogSiteName":686,"ogType":687,"canonicalUrls":2787,"schema":2788},"How to simplify your smart home configuration with GitLab CI/CD","How to use GitLab pipelines to automatically test and deploy new home-assistant configurations, wherever you are.","https://res.cloudinary.com/about-gitlab-com/image/upload/v1749678717/Blog/Hero%20Images/ci-smart-home-configuration.jpg","https://about.gitlab.com/blog/using-the-gitlab-ci-slash-cd-for-smart-home-configuration-management","\n                        {\n        \"@context\": \"https://schema.org\",\n        \"@type\": \"Article\",\n        \"headline\": \"How to simplify your smart home configuration with GitLab CI/CD\",\n        \"author\": [{\"@type\":\"Person\",\"name\":\"Mario de la Ossa\"}],\n        \"datePublished\": \"2018-08-02\",\n      }",{"title":2784,"description":2785,"authors":2790,"heroImage":2786,"date":2792,"body":2793,"category":717,"tags":2794},[2791],"Mario de la Ossa","2018-08-02","So you've read all about the [Internet of\nThings](https://en.wikipedia.org/wiki/Internet_of_things) and all the cool\nstuff you can do with it – from setting up timers for your lights to [making\nyour breakfast](/blog/introducing-auto-breakfast-from-gitlab/) – and now\nyou're itching to get started? Great!\n\n\nIf you're a power user, you've probably settled on using [Home\nAssistant](https://www.home-assistant.io/) as your smart home hub, but this\nchoice has a few pitfalls:\n\n\n- It's annoying to SSH into the server itself to change configuration.\nWouldn't you like to use your favorite local editor instead?\n\n- How do you keep your configuration backed up?\n\n- How do you protect yourself from accidentally messing up the\nconfiguration?\n\n\nIn this guide we'll show you how to fix these annoyances yourself, thanks to\nGit and the power of [GitLab\nPipelines](https://docs.gitlab.com/ee/ci/pipelines/index.html)! We will set\nup a pipeline that will check your home-assistant configuration and deploy\nit to your home-assistant install, giving you the power to deploy changes\nfrom anywhere in the world with a simple `git push`!\n\nDid you go on vacation and forget you wanted your lights to [turn on and off\nrandomly to make it seem like someone's\nhome](https://community.home-assistant.io/t/set-random-time-for-random-automatic-turning-off-lights-mimic-someone-is-home/3524)?\nNo worries! Just open GitLab's [Web\nIDE](https://docs.gitlab.com/ee/user/project/web_ide/) and make your changes\nfrom your hotel room.\n\n\nBy the end of this tutorial you'll have:\n\n\n- Automatic configuration backups thanks to `git`. You'll be able to see the\nhistory of every change you've made and revert changes easily.\n\n- Automatic configuration testing via GitLab pipelines. Never again will a\nsimple typo have you scratching your head, wondering why things don't work!\n\n- An easy way to push changes to your Home Assistant configuration without\nhaving to SSH into the server.\n\n\n## Requirements\n\n\nIn this guide we'll be assuming a few things:\n\n\n- You installed Home Assistant using the Docker image\n\n- The server Home Assistant runs in is accessible from the internet via SSH\n(or you're using a self-managed GitLab installation in the same network)\n\n\n## Set up your server\n\n\n1.   Navigate to your Home Assistant configuration folder.\n\n1.   Create a new file called `.gitignore` with the following content:\n\n     ```\n     *.db\n     *.log\n     ```\n\n1.   Initialize the Git repo\n\n     ```bash\n     git init\n     git add .\n     git commit -m 'Initial commit'\n     ```\n1.   [Create a new GitLab project](https://gitlab.com/projects/new) and push\nto it\n\n     ```bash\n     git remote add origin YOUR_PROJECT_HERE\n     git push -u origin master\n     ```\n\nWith this you now have a backup of your Home Assistant configuration. Let's\nnow set up the GitLab pipeline!\n\n\n## Setting up the pipeline\n\n\nWe have a few goals for the [CI/CD pipeline](/topics/ci-cd/):\n\n- Test the new configuration to ensure it's valid\n\n- Deploy the new configuration to the Home Assistant server\n\n- Bonus: Notify us of a successful deployment, since the default is to only\nnotify for failures\n\n\n[The complete `.gitlab-ci.yml` can be found\nhere.](https://gitlab.com/mdelaossa/hass-via-cicd/blob/master/.gitlab-ci.yml)\n\n{: .note}\n\n[General documentation for how to configure jobs can be found\nhere.](https://docs.gitlab.com/ee/ci/yaml/)\n\n{: .note}\n\n\nWe will be using the following stages in our pipeline:\n\n- test: Will test the Home Assistant configuration to ensure it is valid\n\n- deploy: Will update the Home Assistant configuration in the server and\nrestart Home Assistant\n\n- notify: Will send a push notification with success/failure state\n\n\nSince these aren't default pipeline stages we need to declare them in our\n`.gitlab-ci.yml` like so:\n\n\n```yaml\n\nstages:\n  - test\n  - deploy\n  - notify\n```\n\n\n### Automating configuration testing\n\n\nSince GitLab CI/CD [supports Docker\nimages](https://docs.gitlab.com/ee/ci/docker/using_docker_images.html) and\nHome Assistant is available as a Docker image, this is a fairly\nstraightforward stage to add.\n\n\nAdd this to your `.gitlab-ci.yml` file:\n\n\n```yaml\n\ntest:\n  stage: test\n  image: homeassistant/amd64-homeassistant\n  script:\n    - hass --script check_config -c .\n```\n\n\nWith this we are creating a job called `test` which will run in the `test`\nstage. We're using the `homeassistant/amd64-homeassistant` image because it\nexposes the `hass` command globally so we can use the built-in configuration\nchecking command on our committed files. That's it!\n\n\nFeel free to commit and push this change to test it out!\n\n\n```bash\n\ngit add .\n\ngit commit -m 'Added testing stage to GitLab pipeline'\n\ngit push\n\n```\n\n\nYou'll now see that a pipeline gets created whenever you push:\n\n\n![HASS Test pipeline\nsuccess](https://about.gitlab.com/images/blogimages/hass-cicd/pipeline-pass-1.png){:\n.shadow.center.large}\n\n\nIf your configuration contains any errors, they'll be shown in the `Failed\nJobs` view of the pipeline and you'll get an email notifying you of the\nfailure:\n\n\n![HASS Test pipeline\nfailure](https://about.gitlab.com/images/blogimages/hass-cicd/pipeline-fail-1.png){:\n.shadow.center.large}\n\n\n### Automating deployments\n\n\nNow that we have automated testing, let's add another stage that will deploy\nour new configuration if the tests pass!\n\n\n\"Deploying\" in this case will consist of:\n\n- SSHing into the server\n\n- Doing a `git pull` to pull down changes from the repo\n\n- Restart the Home Assistant Docker image\n\n\n#### Preparing the server (and GitLab) for SSH access\n\n\nSince we will be using SSH we need to prepare our server first. We'll follow\n[these instructions from the GitLab\ndocumentation](https://docs.gitlab.com/ee/ci/ssh_keys/).\n\nWe will also set some [CI/CD\nVariables](https://gitlab.com/help/ci/variables/README#variables).\n\n\n1.   Generate a new SSH key pair. It's OK to save them to the current folder\nas you'll delete them later anyway.\n\n     ```bash\n     ssh-keygen -t rsa -C \"hass-deploy\" -b 4096\n     ```\n\n1.   On the server that runs Home Assistant, save the contents of the public\nkey (the file ending in `.pub`) to\n`/home/user_running_hass/.ssh/authorized_keys`\n\n1.   Go to your GitLab project's CI/CD variables (inside Settings). Add the\ncontents of the private key file to a variable named `SSH_PRIVATE_KEY`. You\ncan now delete the SSH key pair files if you'd like, or store them somewhere\nsafe.\n\n\nWe also need to add our server's host keys to the GitLab runner so the\nrunner will be able to SSH successfully. Alternatively we could disable host\nkey checking, but this is not recommended.\n\n\n1.   On your server, run `ssh-keyscan example.com` where example.com is the\ndomain or IP of your server.\n\n1.   Create a new CI/CD variable called `SSH_KNOWN_HOSTS` and add the output\nof `ssh-keyscan` to it.\n\n\nYou should also create two other CI/CD variables (optional):\n\n- `DEPLOY_USER`: the user running HASS that the runner with SSH into the\nserver as to perform the deploy\n\n- `DEPLOY_HOST`: the domain or IP of the server\n\n\n#### The deploy stage\n\n\nNow that we have prepared our server and GitLab CI/CD variables, we can add\nour deploy stage to `.gitlab-ci.yml`. Please note that we are using the\n`only: ` keyword so that only new commits in the `master` branch will\nattempt a deploy.\n\n\n```yaml\n\ndeploy:\n  stage: deploy\n  only:\n    - master\n  before_script:\n    - 'which ssh-agent || ( apt-get update -y && apt-get install openssh-client -y )'\n    - eval $(ssh-agent -s)\n    - echo \"$SSH_PRIVATE_KEY\" | tr -d '\\r' | ssh-add - > /dev/null\n    - mkdir -p ~/.ssh\n    - chmod 700 ~/.ssh\n    - echo \"$SSH_KNOWN_HOSTS\" > ~/.ssh/known_hosts\n    - chmod 644 ~/.ssh/known_hosts\n  script:\n    - ssh $DEPLOY_USER@$DEPLOY_HOST \"cd '$DEPLOY_PATH'; git pull; docker restart home-assistant\"\n```\n\n\nThe `before_script` above is in charge of:\n\n- Making sure `ssh-agent` is installed and installing it otherwise\n\n- Making sure `ssh-agent` is running\n\n- Adding the `SSH_PRIVATE_KEY` to the keys to use when logging into a server\n\n- Creating the `.ssh` folder with required permissions\n\n- Adding the values we added to the `SSH_KNOWN_HOSTS` variable to the proper\nlocation\n\n\nThe `script` portion is what actually deploys our new configuration:\n\n- We `cd` into the proper location (where the Home Assistant configuration\nfiles are kept)\n\n- We update the configuration with a `git pull`, since this directory is a\nGit repo\n\n- We restart Home Assistant (in this case the Docker image was created with\nthe name `home-assistant`. Please use the name of your container)\n\n\nNote: If you did not create `DEPLOY_USER` and `DEPLOY_HOST` variables on\nGitLab, please replace the proper values in the script\n\n{: .note}\n\n\nNow let's commit and push this new stage to GitLab!\n\n```bash\n\ngit add .\n\ngit commit -m 'Added deploy stage to GitLab pipeline'\n\ngit push\n\n```\n\n\nWith this new stage added, you can now edit your configuration from anywhere\n(including the GitLab Web IDE!) and be confident that these changes will be\npushed to your Home Assistant server if there are no issues with the\nconfiguration.\n\nThere's no longer a need to figure out how to connect directly to your Home\nAssistant server to make the edits you need.\n\n\n### Bonus: Successful deployment notifications\n\n\nYou'll notice that if the configuration is wrong or an error occurs during\nthe deployment, you will get an email notification, but what about when\neverything runs successfully?\n\n\nWe have two options:\n\n\n1. Enable the `Pipeline Emails` integration and set it to notify on every\npipeline\n\n2. Add a new stage called `notify` and use it to send push notifications to\nyour phone\n\n\nWhile email is really nice, there's something really satisfying about\ngetting push notification for your services, so let's set things up using\n[Pushover](https://pushover.net/).\n\nYou'll need to create an 'Application' and add the token you get to a GitLab\nvariable called `PUSHOVER_API_TOKEN`. You'll also need to add your user key\nto a variable called `PUSHOVER_USER_TOKEN`.\n\n\nSince we'd like a different notification depending on whether our pipeline\npassed or failed, we will be adding two jobs to the `notify` stage:\n\n\n```yaml\n\nnotify_success:\n  stage: notify\n  allow_failure: true\n  only:\n    - master\n  script:\n    - curl -s --form-string \"token=$PUSHOVER_API_TOKEN\" --form-string \"user=$PUSHOVER_USER_TOKEN\" --form-string \"message=New Hass config deployed successfully!\" https://api.pushover.net/1/messages.json\n\nnotify_fail:\n  stage: notify\n  allow_failure: true\n  only:\n    - master\n  when: on_failure\n  script:\n    - curl -s --form-string \"token=$PUSHOVER_API_TOKEN\" --form-string \"user=$PUSHOVER_USER_TOKEN\" --form-string \"message=New Hass config failed. Please check for errors\" https://api.pushover.net/1/messages.json\n```\n\n\nOur first job, `notify_success`, runs when the stage before it (`deploy`)\ncompletes successfully. This is the default for GitLab. Our `notify_fail`\njob on the other hand has `when: on_failure` set, which means it will _only_\nrun when the stage before it fails. We also set `allow_failure: true` on\nboth these jobs so that we aren't notified of a failed pipeline if for some\nreason the notification commands fail. We also set the `only: - master`\noption since deploys only happen on the master branch.\n\n\nWe are using Pushover's API to send the message we want in the `script`\narea.\n\n\nWith this final stage in place, your pipeline should now look like this:\n\n\n![HASS pipeline\noverview](https://about.gitlab.com/images/blogimages/hass-cicd/pipeline-final-1.png){:\n.shadow.center.large}\n\n\n### Enjoy!\n\n\nThere you have it! Now you can edit your Home Assistant configuration from\nanywhere you'd like, using your favorite editor, by following three simple\nsteps:\n\n\n1. `git clone PATH_TO_REPO` (if you have not cloned it before)\n\n2. Edit the configuration\n\n3. `git push -u remote master`\n\n\n[Photo](https://unsplash.com/photos/9TF54VdG0ws?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText)\nby Kevin Bhagat on\n[Unsplash](https://unsplash.com/search/photos/smart-home?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText)\n\n{: .note}\n",[9,961],{"slug":2796,"featured":6,"template":699},"using-the-gitlab-ci-slash-cd-for-smart-home-configuration-management","content:en-us:blog:using-the-gitlab-ci-slash-cd-for-smart-home-configuration-management.yml","Using The Gitlab Ci Slash Cd For Smart Home Configuration Management","en-us/blog/using-the-gitlab-ci-slash-cd-for-smart-home-configuration-management.yml","en-us/blog/using-the-gitlab-ci-slash-cd-for-smart-home-configuration-management",{"_path":2802,"_dir":246,"_draft":6,"_partial":6,"_locale":7,"seo":2803,"content":2809,"config":2814,"_id":2816,"_type":14,"title":2817,"_source":16,"_file":2818,"_stem":2819,"_extension":19},"/en-us/blog/velocity-with-confidence",{"title":2804,"description":2805,"ogTitle":2804,"ogDescription":2805,"noIndex":6,"ogImage":2806,"ogUrl":2807,"ogSiteName":686,"ogType":687,"canonicalUrls":2807,"schema":2808},"How GitLab 14 satisfies the need for speed with modern DevOps","GitLab 14: Ship with velocity, ship with confidence","https://res.cloudinary.com/about-gitlab-com/image/upload/v1749682089/Blog/Hero%20Images/racecar_devops.jpg","https://about.gitlab.com/blog/velocity-with-confidence","\n                        {\n        \"@context\": \"https://schema.org\",\n        \"@type\": \"Article\",\n        \"headline\": \"How GitLab 14 satisfies the need for speed with modern DevOps\",\n        \"author\": [{\"@type\":\"Person\",\"name\":\"Parker Ennis\"}],\n        \"datePublished\": \"2021-07-29\",\n      }",{"title":2804,"description":2805,"authors":2810,"heroImage":2806,"date":1852,"body":2812,"category":717,"tags":2813},[2811],"Parker Ennis","\n\n## How DevOps and NFS changed the game\n\nWhat if I told you that one of the best-selling racing video game franchises of all time, the \"Need For Speed\" (NFS), and DevOps have more in common with each other than you think? Yes, you read that correctly, probably not the NFS (Network File System) you were expecting.\n\n### An appetite for change\n\nFor context, the NFS series originally set out to redefine a saturated, yet unsophisticated, racing video game market. Motivated by an appetite for change, the NFS user experience reflected the human connection to real cars and how they behaved, which was a big challenge for developers in the 1990s. Nearly 30 years ago, \"The Need for Speed\" forever changed the landscape of racing games, selling 150 million copies since its debut.\n\n![The original Need For Speed game from 1994](https://about.gitlab.com/images/blogimages/need_for_speed.png){: .shadow.center}\nThe original Need For Speed video game set a new standard with an appetite for industry change.\n{: .note.text-center}\n\nCoincidentally, it was in 1994 that Grady Booch coined the term \"continuous integration\" (CI). Booch, like NFS, paved the way for immense industry growth in the realm of software development. CI aimed to redefine the manual, time-consuming development processes that paid little mind to how real humans and developers behaved and collaborated around application development by [leveraging automation to increase development speed without sacrificing quality](/topics/ci-cd/benefits-continuous-integration/).\n\nSimilar to how NFS took the racing scene by storm and laid the groundwork for the racing game genre, CI evolved into what is arguably the most important piece of DevOps best practices today: Continuous integration and continuous delivery (CI/CD).\n\nDevOps continues to evolve, but without CI/CD, DevOps isn't the collaborative practice that helps teams work faster and more efficiently. CI/CD is a super power within DevOps – unlocking the potential to ship apps with increased velocity and confidence in their quality, without having to choose one or the other.\n\n### DIY DevOps vs Modern DevOps\n\nToday, it doesn't matter what your business does, it's going to involve some amount of using and building software. DevOps gained traction in the age of digital transformation, where the rate of technical innovation acted as a forcing function for companies to fail or survive. Over the past 10 years or so, organizations had a choice to either embrace this \"need for speed\" and adopt DevOps practices, or be displaced by their competition.\n\nThis scramble led to a \"DIY\" style of DevOps that couldn't deliver on its promises much of the time. For many organizations, the biggest problem wasn't just the brittle toolchains composed of disparate pieces of software but also trying to make these complicated toolchains and processes benefit from DevOps. Since uprooting everything wasn't an option, the root of the problem was still there, and DevOps was hard to adopt.\n\nFor all the teams DevOps has helped, the DevOps marketplace must continuously improve and evolve as we learn more about the challenges of modernizing workflows. DevOps must modernize alongside businesses to ensure it's an accessible and realistic framework for as many companies as possible to leverage.\n\n### GitLab 14 fuels the modern DevOps need for speed\n\nWith a platform-driven approach, [GitLab 14](/releases/2021/06/22/gitlab-14-0-released/) delivers a consistent and efficient developer and operator experience that leads to a simplified and more predictable SDLC. A single user interface, embedded security, and a unified data store are just some of the features of a platform any company can use without the tradeoffs of the DIY DevOps past. By using one tool for source code management, CI, and CD, teams are more efficient and productive with streamlined collaboration. Engineers are happier when focused on value-add than when maintaining integrations – and happy developers help attract and retain talent.\n\n[GitLab 14](/gitlab-14/) ushers in a new era of modern DevOps as a global movement, and I'm excited to talk a little bit about some of its capabilities that help you ship software faster, with a higher degree of confidence, and improve your ability to respond to market changes.\n\n### Ship with velocity and confidence\n\n**1. [GitLab pipeline editor](/releases/2021/01/22/gitlab-13-8-released/#pipeline-editor)**\n\nCrafting pipelines can be complicated and verbose without an understanding of advanced pipeline syntax and how it fits within the workflow using the '.gitlab-ci.yml' configuration file. Needing to craft pipelines from scratch presents a steeper learning curve for organizations and teams with a less mature DevOps culture. The GitLab pipeline editor lowers the barrier to entry for CI/CD novices and accelerates power users with visual authoring and versioning, continuous validation, and pipeline visualization. Whether you're a more advanced user or novice, the pipeline editor unlocks additional power and usability.\n\n![Pipeline editor linting capability makes pipeline authoring easier](https://about.gitlab.com/images/blogimages/lint_ci.png){: .shadow.center}\nPipeline editor linting capability makes pipeline authoring easier and more efficient.\n{: .note.text-center}\n\nHere's what some of our wider community is saying about the pipeline editor:\n\n> \"I really like the direction of making CI/CD more accessible to first-time users and how GitLab rolls out this feature piece by piece.\" - Bernhard Knasmüller, computer scientist\n\n> \"This is going to improve the CI/CD configuration experience greatly!\" - Olivier Jourdan, developer\n\n**2. [GitLab Agent for Kubernetes](https://youtu.be/17O_ARVaRGo)**\n\nThe GitLab Agent for Kubernetes enables secure, cloud-native [GitOps](/solutions/gitops/). GitLab also meets customers where they are by supporting GitOps with agent-based and agentless approaches, and for deployments anywhere, regardless of whether infrastructure is cloud-native. It also enables alerts based on network policies for pull-based deployments.\n\nHere's piece of feedback from the wider GitLab community on the Kubernetes Agent:\n\n> \"GitLab is leading the evolution of DevOps by optimising work efficiency and cloud-native integration capabilities. This enables the rapid delivery of digital value.\" - Vasanth Kandaswamy, Head of Data and Applications Portfolio, Fujitsu Australia\n\nWe look forward to iterating and improving these capabilities and always [welcome your feedback](/submit-feedback/#product-feedback) on our product.\n\n### What's next?\n\nOne thing is for sure: **people want to go fast,** but not when it requires sacrificing peace of mind and quality. We're committed to helping you ship with velocity and confidence by [investing in specific product areas](/direction/#fy22-product-investment-themes) to bring the benefits of modern DevOps to anyone using GitLab to deliver their applications.\n\n![Go fast with confidence](https://about.gitlab.com/images/blogimages/gofast.gif){: .shadow.center}\nEven Ricky Bobby from Talledega Nights agrees. People just want to go fast!\n{: .note.text-center}\n\nWe'll continue executing on our [vision for CI/CD](https://gitlab.com/groups/gitlab-org/-/epics/4534) to create a visual pipeline authoring experience built right into GitLab that simplifies the complexity, letting you quickly create and edit pipelines while still exposing advanced options when you need them.\n\nWe're also committed to making sure you can deploy anytime and anywhere to take advantage of the benefits of Kubernetes, no matter where you are at on your cloud native development journey. If you have feedback or suggestions on what we can do better, please [let us know in our product epic.](https://gitlab.com/groups/gitlab-org/-/epics/3329)\n\nWe look forward to delivering you more value as we iterate upon this new era of GitLab 14 going foward and can't wait to see the great things you're creating with Gitlab.\n\n_This blog is part three in a three-part series on the top capabilities of GitLab 14. Learn more about [how GitLab 14 prepares you for DevSecOps 2.0 in part one](/blog/are-you-ready-for-the-newest-era-of-devsecops/), and about [how to optimize DevOps with GitLab 14's enhanced visibility tools in part two](/blog/optimizing-devops-visibility-in-gitlab-14/)._\n\nCover image by [CHUTTERSNAP](https://unsplash.com/@chuttersnapk) on [Unsplash](https://unsplash.com/photos/5Yo1P9ErikM)\n{: .note}\n",[741,915,9,696,550],{"slug":2815,"featured":6,"template":699},"velocity-with-confidence","content:en-us:blog:velocity-with-confidence.yml","Velocity With Confidence","en-us/blog/velocity-with-confidence.yml","en-us/blog/velocity-with-confidence",{"_path":2821,"_dir":246,"_draft":6,"_partial":6,"_locale":7,"seo":2822,"content":2828,"config":2834,"_id":2836,"_type":14,"title":2837,"_source":16,"_file":2838,"_stem":2839,"_extension":19},"/en-us/blog/verify-week-hackathon",{"title":2823,"description":2824,"ogTitle":2823,"ogDescription":2824,"noIndex":6,"ogImage":2825,"ogUrl":2826,"ogSiteName":686,"ogType":687,"canonicalUrls":2826,"schema":2827},"What we learned during an internal Hackathon Week","The Verify team spent a week on Hackathon projects building new features, Proof of Concepts and cleaning up “dead code”","https://res.cloudinary.com/about-gitlab-com/image/upload/v1749682399/Blog/Hero%20Images/marvin-meyer-SYTO3xs06fU-unsplash.jpg","https://about.gitlab.com/blog/verify-week-hackathon","\n                        {\n        \"@context\": \"https://schema.org\",\n        \"@type\": \"Article\",\n        \"headline\": \"What we learned during an internal Hackathon Week\",\n        \"author\": [{\"@type\":\"Person\",\"name\":\"James Heimbuck\"}],\n        \"datePublished\": \"2022-07-28\",\n      }",{"title":2823,"description":2824,"authors":2829,"heroImage":2825,"date":2831,"body":2832,"category":301,"tags":2833},[2830],"James Heimbuck","2022-07-28","\n\nTo inspire the Verify Stage product and engineering teams to solve new problems, we conducted a \"Hackathon Week\" in July 2022. \n\nPrior to the Hackathon, the team spent time brainstorming ideas to solve and enhancements to make. We then collaborated to make those ideas better. For projects with a critical mass of people who wanted to work on them, an engineer took the lead in organizing the work, making sure there was a design (where needed) and doing other unblocking to deliver an outcome. The team also focused on deleting some dead code in the code base to make future development easier. We even offered a prize for the most code/files deleted. Several themes emerged as the team brainstormed, which resulted in some greatly improved Proof of Concepts (POCs) or new features shipped in the product.\n\n## Searching job logs is hard\n\nAt GitLab we use the product to build new features, and we have a very rich build pipeline but not a perfect one. Because of this the team knows how hard it can be to parse through the job logs shown on the job detail page.\n\nOne engineer delivered a POC to make scrolling through failures easier, delivering an MVC for an [open issue](https://gitlab.com/gitlab-org/gitlab/-/issues/343658). Another team worked on adding a simple search and highlight to the job logs page. After seeing the demo we quickly decided that [this](https://gitlab.com/gitlab-org/gitlab/-/issues/367574) was something we could ship and it will roll out during the 15.3 milestone.\n\n## Could this job be faster?\n\nSpeedy and reliable pipelines is something we think a lot about across the stage so it was no wonder we saw several groups thinking about ways to get more job performance data to users.\n\nOn demo day, a POC re-envisioning how the job performance page shows job executions in other pipelines was shown. Before delivering the feature to customers, the team wants to make some improvements and opened [gitlab#367322](https://gitlab.com/gitlab-org/gitlab/-/issues/367322) for a future milestone.\n\nAnother group delivered a POC that took a similar approach on the pipeline editor, which delivers the insights in a context where users can act on data to improve its performance. \n\nWe know failed tests are often the culprit of failed pipelines but debugging the failures can be slowed down simply by the process of finding them. A POC to create a one-click copy of the path of all the failing tests from the Merge Request Test Report widget was developed during our Hackathon Week. This has since been merged and is available on gitlab.com\n\n## Fault Tolerant Runners\n\nThe Runner team recently introduced the GitLab Runner Pod Cleanup as a way to clean up orphaned pods the GitLab Runner Manager has not had a chance to do (like the Runner Manager pod getting evicted). The team delivered a [POC MR](https://gitlab.com/gitlab-org/gitlab-runner/-/merge_requests/3512) that lays the foundation for the next iteration toward a Fault Tolerant Runner.\n\n## CI Workflows POC\n\nGitLab has a very flexible way to start a pipeline on any number of events from other pipelines, by schedule and by a commit among other things. Teams may want to run a pipeline on other events, like issue creation, merge request approval, etc. A team headed up by \n[Grzegorz](https://gitlab.com/grzesiek) created an initial concept for GitLab CI workflows. This is a great first step towards the larger goal of creating a full-fledged [GitLab Workflow](https://gitlab.com/groups/gitlab-org/-/epics/8349) solution. Watch the demo:\n\n\u003Cfigure class=\"video_container\">\n  \u003Ciframe src=\"https://www.youtube.com/embed/cwfRI9m3rRs\" frameborder=\"0\" allowfullscreen=\"true\"> \u003C/iframe>\n\u003C/figure>\n\n## Code cleanup\n\nOne of the highest upvoted items from our brainstorming was code cleanup. The team does a great job while working on new features and enhancements in doing what they can to reduce any technical debt and to keep things lean, but \"cruft\" builds up that needs attention from time to time. Over the course of the week, over 1300 lines of code and 4421 MB of database data were deleted by the team.\n\n## Closing thoughts\n\nThis hackathon was an incredible example of iteration, collaboration, and efficiency as individuals worked to deliver new features to solve problems and streamline the codebase. \n\nThese are some of the highlights of the work completed over the course of the week. We would love to hear your thoughts on the pending issues, proof of concept merge requests, and in new issues about features we should build. You can also contribute code to these projects or many others in the next [GitLab Hackathon](https://about.gitlab.com/community/hackathon/) that runs August 2 through August 9 2022.\n\nCover image by [Marvin Meyer](https://unsplash.com/@marvelous) on [Unsplash](https://unsplash.com/photos/SYTO3xs06fU-unspalsh.jpg)\n{: .note}\n\n\n",[9,1004,1477],{"slug":2835,"featured":6,"template":699},"verify-week-hackathon","content:en-us:blog:verify-week-hackathon.yml","Verify Week Hackathon","en-us/blog/verify-week-hackathon.yml","en-us/blog/verify-week-hackathon",{"_path":2841,"_dir":246,"_draft":6,"_partial":6,"_locale":7,"seo":2842,"content":2848,"config":2855,"_id":2857,"_type":14,"title":2858,"_source":16,"_file":2859,"_stem":2860,"_extension":19},"/en-us/blog/vestiaire-collective-on-moving-to-a-devsecops-platform",{"title":2843,"description":2844,"ogTitle":2843,"ogDescription":2844,"noIndex":6,"ogImage":2845,"ogUrl":2846,"ogSiteName":686,"ogType":687,"canonicalUrls":2846,"schema":2847},"Vestiaire Collective's DevSecOps migration: Wins and insights","Support for container registries and integrations with existing tools were the top reasons for the ecommerce company's migration to GitLab.","https://res.cloudinary.com/about-gitlab-com/image/upload/v1749670278/Blog/Hero%20Images/fasttrack.jpg","https://about.gitlab.com/blog/vestiaire-collective-on-moving-to-a-devsecops-platform","\n                        {\n        \"@context\": \"https://schema.org\",\n        \"@type\": \"Article\",\n        \"headline\": \"Vestiaire Collective VP shares wins, insights, and what's next with DevSecOps migration\",\n        \"author\": [{\"@type\":\"Person\",\"name\":\"Chandler Gibbons\"}],\n        \"datePublished\": \"2023-01-05\",\n      }",{"title":2849,"description":2844,"authors":2850,"heroImage":2845,"date":2852,"body":2853,"category":1002,"tags":2854},"Vestiaire Collective VP shares wins, insights, and what's next with DevSecOps migration",[2851],"Chandler Gibbons","2023-01-05","\n[Vestiaire Collective](https://us.vestiairecollective.com/), an online marketplace for second-hand clothing and luxury items, needed a faster and easier-to-use solution for code reviews and running pipelines. In 2018, the company migrated its codebase to GitLab for its speed and flexibility in setting up custom workflows and pipelines for releases. Since making the move, Vestiaire Collective has taken advantage of GitLab’s integrations with other tools — including [Jenkins for CI/CD](https://docs.gitlab.com/ee/integration/jenkins.html), [Jira](https://docs.gitlab.com/ee/integration/jira/) for issue management, and Nexus artifact storage — to improve productivity and simplify complex toolchains. We talked to Sardorbek Pulatov, vice president of engineering at Vestiaire Collective, about what his team has been able to achieve with the GitLab DevSecOps Platform and the lessons learned along the way.\n\n**What were the challenges that led Vestiaire Collective to explore GitLab?**\n\nWhen Vestiaire Collective started with GitLab back in 2018, we wanted to have a fast and in-house version control system with features such as running pipelines. One of the biggest chunks of our code base, the monolith, was on Subversion. We migrated to GitLab for speed and also the better maintainability, and code reviews being much easier. GitLab has also enabled us to set up workflows and pipelines for our releases. And recently we also created our own tool for releases because we have a custom workflow in Jira.\n\nNow we have not just engineers in GitLab, but also data engineers and data scientists. So, for example, data scientists manage their releases through their repositories in GitLab. They’re actually quite advanced in using GitLab, the data scientist teams. So they use everything new released by GitLab.\n\n**Since moving to a single platform for DevSecOps, what are the biggest benefits you’ve noticed? How has GitLab helped Vestiaire Collective simplify complicated toolchains?**\n\nWhen GitLab released support for container registries and npm, it was such a relief for us because we were using Amazon Elastic Container Registry (ECR) and it was slow because it was in a different location — we deploy in Ireland but our team is spread across Europe and the United States. We also tried to use our own setup with Nexus and support it ourselves, meaning if there was a vulnerability we would need to update it and maintain it separately. Even if that’s only required once every six months, it still takes time. You still need to plan the upgrade. But with GitLab, our problem was solved. Now developers have [a registry for containers inside GitLab](https://docs.gitlab.com/ee/user/packages/container_registry/) so they can easily push new releases of their services.\n\nThe fact that GitLab integrates with the other tools we are using has also been a huge benefit. We use Jira for project management, and thanks to GitLab’s Jira integration, whenever a developer pushes a commit in GitLab it’s fully visible in Jira. And now, with our custom integration, the releases are also synced, so when you create a release in GitLab, it creates a release with the same ticket in Jira.\n\nAs a next step, personally, I would love us to be able to migrate entirely into GitLab for project management, using GitLab [issues](https://docs.gitlab.com/ee/user/project/issues/index.html) and [epics](https://docs.gitlab.com/ee/user/group/epics/). We’re not there yet, but GitLab provides almost all the functionality needed for developers. Tracking everything in GitLab would make it much easier to reference the issues in code reviews. Now, when you create a ticket in Jira, you need to create a branch in GitLab with the Jira ticket number, and then, when you push a commit, you also need to remember the ticket number. But once everything is in GitLab, we’ll be able to just push a commit to a merge request. GitLab already gives us so much transparency into what we are doing. That would be even greater if everyone was using GitLab issues and epics.\n\n**What has the response from your team been like?**\n\nThere have been no complaints about stability or performance, and the performance is improving release by release! GitLab became very fast with [version 15](/releases/2022/05/22/gitlab-15-0-released/) — I can feel and see the performance boost. People are happy. People have been quiet, and when engineers are not complaining, that means that the tool is quite good. \n\n**For companies that are just getting started with GitLab, what advice would you give them on where to start?**\n\nI’d recommend starting with smaller projects, setting up all the steps needed for your pipeline, and trying to use features of GitLab such as issues and epics. In our case, we started with a larger project from our Product Information Management service team — the project’s repository had three services and we needed to run different pipelines for different changes. And even in our case, GitLab was quite flexible. We could say, “Okay, if a commit message has this specific word, then run these steps. If it has this word, run these other steps.”\n\nWhat we learned from that experience was that first it’s valuable to understand what you need to run as a pipeline. What comes to mind first is tests and probably deployment into an environment. Then we need to monitor the performance and see if we need to pass our caches in between the pipelines to speed up the deployment, or in the case of Node.js, do not download [npm packages](https://docs.gitlab.com/ee/user/packages/npm_registry/) in every change or merge request or branch. Just cache it once in the first run. Then you can optimize step by step. So that’s what I mean by starting small.\n\n**What are you most looking forward to doing with GitLab in the future?**\n\nI love this question. First, I would like to point out that GitLab surprises me with each release. Personally, I am looking forward to using more automation tools for QA engineers, as well as auto pipelines and integrations with the latest automation frameworks.\n\nWe recently moved away from Sentry for error tracking, so I’m also interested in exploring doing [error tracking in GitLab](https://docs.gitlab.com/ee/operations/error_tracking.html). And, I’m interested in seeing how we might be able to use [feature flags in GitLab](https://docs.gitlab.com/ee/operations/feature_flags.html). We’re currently using LaunchDarkly for A/B testing, but if GitLab can even match half of that functionality, it would be great to bring everything together into one platform.\n\nFinally, we’re also looking into how we can make our GitLab implementation even better and more stable, so we want to deploy it into [a Kubernetes cluster](https://docs.gitlab.com/ee/user/clusters/agent/). Currently, it’s just deployed into EC2s, so that would be our next big step for GitLab.\n\nPhoto by [Mathew Schwartz](https://unsplash.com/@cadop?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText) on [Unsplash](https://unsplash.com)\n",[786,1007,719,9,696],{"slug":2856,"featured":6,"template":699},"vestiaire-collective-on-moving-to-a-devsecops-platform","content:en-us:blog:vestiaire-collective-on-moving-to-a-devsecops-platform.yml","Vestiaire Collective On Moving To A Devsecops Platform","en-us/blog/vestiaire-collective-on-moving-to-a-devsecops-platform.yml","en-us/blog/vestiaire-collective-on-moving-to-a-devsecops-platform",{"_path":2862,"_dir":246,"_draft":6,"_partial":6,"_locale":7,"seo":2863,"content":2869,"config":2874,"_id":2876,"_type":14,"title":2877,"_source":16,"_file":2878,"_stem":2879,"_extension":19},"/en-us/blog/wag-labs-blog-post",{"title":2864,"description":2865,"ogTitle":2864,"ogDescription":2865,"noIndex":6,"ogImage":2866,"ogUrl":2867,"ogSiteName":686,"ogType":687,"canonicalUrls":2867,"schema":2868},"How Wag! cut their release process from 40 minutes to just 6","The popular dog-walking app is rolling out new features faster and with more confidence as they adopt GitLab for more of their DevOps workflows.","https://res.cloudinary.com/about-gitlab-com/image/upload/v1749678923/Blog/Hero%20Images/dog-walking.jpg","https://about.gitlab.com/blog/wag-labs-blog-post","\n                        {\n        \"@context\": \"https://schema.org\",\n        \"@type\": \"Article\",\n        \"headline\": \"How Wag! cut their release process from 40 minutes to just 6\",\n        \"author\": [{\"@type\":\"Person\",\"name\":\"Aricka Flowers\"}],\n        \"datePublished\": \"2019-01-16\",\n      }",{"title":2864,"description":2865,"authors":2870,"heroImage":2866,"date":2871,"body":2872,"category":694,"tags":2873},[2221],"2019-01-16","\nDo you own a dog and work outside of the home? If you do, or even just know someone who does, you know that finding a trustworthy caretaker is of the utmost importance. With dog walkers in cities and towns across the U.S., the folks at [Wag!](https://wagwalking.com/about) have proven to be a source of reliable caretakers for countless fur parents. In three years, the company has powered more than one billion walks via its app for on-demand dog walking, sitting, and boarding, that boasts of millions of users.\n\nWag! recently signed on with GitLab to make the most of their engineering hours and bring their customers new features and updates at a faster clip.\n\n### From version control, to CI, to the full pipeline\n\nHaving previously used GitLab as their main source of truth for repositories, Wag! initially planned to return to the app solely for [continuous integration (CI)](/solutions/continuous-integration/). But after giving it a whirl, they quickly expanded their strategy to include the use of other features.\n\n\"We started our GitLab project about seven or eight months ago,\" explains [Dave Bullock](https://www.linkedin.com/in/eecue), director of engineering at Wag! \"The original idea was to just use it as our CI platform. But as we built that out, we started using it for more and more tasks, and ended up using it for our full [CI/CD pipeline](/topics/ci-cd/). That includes both our application, so the CI/CD that powers the API, along with our infrastructure. We use GitLab with Terraform to test, review, save, and deploy all of our infrastructure as well as the application on two separate pipelines. Every team uses it in their application, whether it's the Android application, the web application, the API, or our infrastructure; it's all being tested, built, and deployed through GitLab.\"\n\n### Streamlining to a single application\n\nPart of GitLab's appeal stemmed from the [ability to do everything in one place](/topics/single-application/). Wag! was searching for an [integrated solution](/solutions/continuous-integration/) that would streamline their development process, and they found it in GitLab.\n\n\"We were previously using a combination of Travis and other random technologies, and we just wanted something with a little bit better interface, a little more control, and something that we owned as far as the hosting and the management,\" says Bullock. \"We really wanted to move towards a single, full-service application.\"\n\n>\"We just wanted something with a better interface, a little more control, and something that we owned as far as the hosting and the management. We really wanted to move towards a single, full-service application.\"\n\nThe impact of that choice is also being felt on the infrastructure side. Wag!'s infrastructure engineers no longer have to manually stage and test their work. They are now following the same basic workflow that is used for their app, while integrating Terraform to manage their infrastructure.\n\n\"Basically, one of our DevOps team members will make a change, cut a pull request, and it'll be reviewed by the team. If it looks good, we'll say, 'Okay, cool. Merge it into master,'\" Bullock explains. \"If it's one of the modules, we'll tag that module, update the reference to it, and then the CI pipeline will kick off. It'll test the syntax, look for any security issues, and alert a Slack channel if there are any. It'll then stage a full version of the environment and test it. So, it stages all the pieces: the database, cache, and everything else, and tests it all to make sure that it works, just like we would be testing our production website.\n\n\"If that passes, then it allows you to see what your changes are going to do before you apply them,\" he continues. \"We call it Terraform plan. So, it runs Terraform plan on each piece of our infrastructure, and it'll tell us something like, 'Hey, we see 34 changes and 2 destructions and 1 creation in this environment. Click here to review.' Then the group will review it and if it looks good, we'll apply it in production. Having that as a full pipeline is really great.\"\n\n>“Now it's so easy to deploy something and roll it back if there's an issue. It's taken the stress and the fear out of deploying into production.” – Dave Bullock, Director of Engineering\n\n### Easy learning curve\n\nSome of the Wag! engineers had working experience with GitLab, while others had not. Nonetheless, Bullock found the onboarding of his teams to be a fairly easy process due to the intuitive nature of the interface.\n\n\"I think once you kind of understand how CI works, it's basically about following things step by step,\" he says. \"Pipelines were a new concept to a lot of the team, but once you see it happening visually, it's really easy to understand what's going on, expand and add to it. It's a really useful interface. Seeing all those green dots or red dots makes it really clear what's going on.\"\n\n### Built-in security, shaving down test times and faster releases\n\nAs part of their ramp up in GitLab, the dog-walking service recently furled [automated security scanning and license management](/solutions/security-compliance/) into their workflow, with Bullock noting how \"great\" it is to have those features baked into the pipeline so that immediate action can be taken when needed.\n\nWag! currently issues three releases a day, with plans to bump that number up to eight or more. Since adopting GitLab, they have seen a massive improvement in the amount of time spent on the release process. **What previously took 40 minutes to an hour to accomplish, now takes just six minutes.**\n\n\"Traditionally, the release process was slow, fragile, and limited to only a few key release engineers who had access to 10 different systems to monitor, make changes, and log into to make updates and pull in the latest code. It was not optimal. Now it's literally a single pane of glass. A lot of it just happens automatically when you merge `develop` into `master` and tag it.\"\n\nThe release process time should improve even more once Wag! engineers switch from manually pushing parts of the release through to automating the process.\n\n\"Right now, we're still clicking through the interface and saying, 'Okay, do this, now let's monitor,'\" says Bullock. \"But I think as we become more comfortable with it, we'll go to fully automated deployments. Literally, just let it go and deploy. If we see an uptick in errors, we'll let it roll back on its own. But as it is now, it's so easy to deploy something and roll it back ourselves if there's an issue. It's taken the stress and the fear out of deploying into production.\"\n\n### Adopting DevOps\n\nWag!'s engineering team has big plans for 2019. They are currently in the process of moving their repositories from GitHub to GitLab and are planning to switch from Amazon ECS to [Kubernetes](/solutions/kubernetes/). This is all part of their roadmap to implementing DevOps.\n\n\"I think we're going to start working on the project in Q1 and it will be really awesome to have all the bells and functionality,\" Bullock says. \"We're excited about Auto DevOps and a lot of new things GitLab has coming down the pipeline. We're going to push pretty hard on that this year.\n\n\"I'm a big fan of DevOps in general, so I think the closer that you can bring the development engineers to the ops side, the better things work,\" he adds. \"I would love for every software engineer or backend engineer to take ownership of the environment that their code runs in, or at least be able to experiment with it and kind of instantly just spin up a full working environment that is the same as our production environment, which we do now, but not with Kubernetes. I think removing that friction is great.\"\n\n### Growing with GitLab\n\nGitLab's releases are a treat the folks at Wag! look forward to checking out each month. The rollout of new features, which are partly determined by user feedback, tend to correlate with the engineering needs of the growing dog-walking and boarding service.\n\n\"I think it's exciting that as we're growing and adding interesting pieces to our infrastructure and application, we're seeing GitLab grow with your monthly release cycles,\" says Bullock. \"Every month there's some new stuff that we're like, 'Oh cool, we could use that, that's perfect.' It's nice to have GitLab as a partner that's growing with us, and it's exciting to see the parallels of new features that you're launching and how it's solving our problems and optimizing things. There's all kinds of cool stuff, and every time we start using a new piece of GitLab, I feel like, 'Okay, that's great, we're really getting our money’s worth.'\"\n\nPhoto by [Andrii Podilnyk](https://unsplash.com/photos/dWSl8REfpoQ?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText) on [Unsplash](https://unsplash.com/search/photos/dog-walk?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText)\n{: .note}\n",[1007,9,1004,741,784,851,1005,1734],{"slug":2875,"featured":6,"template":699},"wag-labs-blog-post","content:en-us:blog:wag-labs-blog-post.yml","Wag Labs Blog Post","en-us/blog/wag-labs-blog-post.yml","en-us/blog/wag-labs-blog-post",{"_path":2881,"_dir":246,"_draft":6,"_partial":6,"_locale":7,"seo":2882,"content":2888,"config":2895,"_id":2897,"_type":14,"title":2898,"_source":16,"_file":2899,"_stem":2900,"_extension":19},"/en-us/blog/100-runners-in-less-than-10mins-and-less-than-10-clicks",{"title":2883,"description":2884,"ogTitle":2883,"ogDescription":2884,"noIndex":6,"ogImage":2885,"ogUrl":2886,"ogSiteName":686,"ogType":687,"canonicalUrls":2886,"schema":2887},"Setting up 100 AWS Graviton Spot Runners for GitLab","Utilizing the GitLab HA Scaling Runner Vending Machine for AWS Automation to setup 100 GitLab runners on AWS Spot.","https://res.cloudinary.com/about-gitlab-com/image/upload/v1749669882/Blog/Hero%20Images/hundredgitlabspotrunner.png","https://about.gitlab.com/blog/100-runners-in-less-than-10mins-and-less-than-10-clicks","\n                        {\n        \"@context\": \"https://schema.org\",\n        \"@type\": \"Article\",\n        \"headline\": \"How to provision 100 AWS Graviton GitLab Spot Runners in 10 Minutes for $2/hour\",\n        \"author\": [{\"@type\":\"Person\",\"name\":\"Darwin Sanoy\"},{\"@type\":\"Person\",\"name\":\"Nupur Sharma\"}],\n        \"datePublished\": \"2021-08-17\",\n      }",{"title":2889,"description":2884,"authors":2890,"heroImage":2885,"date":2892,"body":2893,"category":717,"tags":2894},"How to provision 100 AWS Graviton GitLab Spot Runners in 10 Minutes for $2/hour",[691,2891],"Nupur Sharma","2021-08-17","Managing elastically scaled or highly available compute infrastructures is\none of the key challenges the cloud was built for. Application scaling\nconcerns can be handled by cloud services that are purpose designed,\nrigorously tested, and continually improved. This article dives into some\nspecific enablement automation that brings the benefits of AWS Autoscaling\nGroups (ASG) to runner management. There are benefits to both the largest\nfleets and single instance runners.\n\n\nEmbedded in this article is a YouTube video that demonstrates the deployment\nof 100 GitLab runners on Amazon EC2 Spot compute in less than 10 minutes\nusing less than 10 clicks. The video also shows updating this entire fleet\nin under 10 minutes to emphasize the time savings of built-in maintenace.\n\n\nThe information and automation in this article applies to GitLab Private\nRunners which are deployed on your own compute resources. Self-managed\nGitLab instances require private runners, but they can also be configured\nand used with GitLab.com SaaS accounts.\n\n\n## Well-architected runner management\n\n\nThere are many different reasons that a customer might need to deploy\nmultiple runners with various characteristics. Some of the more popular ones\nare:\n\n\n- Workloads that require large-scale runner fleets.\n\n- To gain cost savings through Spot compute, uptime scheduling, and ARM\narchitecture.\n\n- Projects with high demand of CI activity to make sure that the runner is\nnot being held up by jobs on another project.\n\n- Jobs that have special security requirements, e.g., security credentials,\nrole-based access or managed identities for Continuous Delivery (CD). These\nsecurity requirements can enable instance-level (AWS IAM Instance Profile)\nsecurity by allowing runners with sufficient rights to deploy in specific\ntarget environments. For example, a CD runner for non-production\nenvironments and a different runner for production.\n\n- Implementing role-based access control rather than user-based. This means\nusers don't have to use secrets to manage security requirements for CI jobs\nto accomplish their tasks.\n\n- Development teams can be confident the runner has the same capabilities\nfor CI and CD automation they test through their interactive logins by\nleveraging a common IAM role.\n\n\n### The challenges of building production-grade elastic GitLab Runners\n\n\n[The GitLab Runner](https://docs.gitlab.com/runner/) is the workhorse of\nGitLab CI and CD capabilities. The runner can handle numerous operating\nenvironments and automation functions for a GitLab instance. The GitLab\nRunner has become very sophisticated due to the broad range of supported\nenvironments. In order to successfully configure the GitLab Runner as a\nset-it-and-forget-it service, the user has to work through many different\ndecisions and considerations. We summarize some of the GitLab\nRunner-specific considerations that can be challenging:\n\n\n- There are a lot of configuration options and scenarios to sort through. It\ncan be an iterative process to discover what needs to be done to set up\nGitLab Runners.\n\n- Ensuring runners are a production-grade capability requires Infrastructure\nas Code (IaC) development so that high availability and scaling can be\nachieved by automatically spawning new instances.\n\n- Ensuring that runner deregistration happens correctly when GitLab Runners\nare automatically scaled in.\n\n- Additional cost-saving configurations, such as Spot compute and scheduled\nrunner uptime, can complicate the automation requirements for AWS\nAutoscaling Groups (ASGs).\n\n- Large organizations often want developers to be able to easily\nself-service deploy runners with various configurations. Service Management\nAutomation (SMA) has been made popular with products like Service Now, AWS\nService Catalog, and AWS Control Tower. This automation is compatible with\nSMA.\n\n- It can be difficult to map runners to AWS and map AWS to runners in large\norganizations with numerous runners and AWS accounts.\n\n\n### Introducing the GitLab HA Scaling Runner Vending Machine for AWS\n\n\nAn effective way to handle multiple design considerations is to make a\nreusable tool. To help you with best practice runner deployments on AWS, we\ncreated the [GitLab HA Scaling Runner Vending Machine for\nAWS](https://gitlab.com/guided-explorations/aws/gitlab-runner-autoscaling-aws-asg/)\n(\"The GitLab Runner Vending Machine\"). It is created in AWS’ Infrastructure\nas Code, known as CloudFormation.\n\n\n> **Designed with AWS Well Architected:** This automation has many features\nbeyond the scope of this blog post. The primary focus of this blog post is\non managing costs. See the [full list of features\nhere](https://gitlab.com/guided-explorations/aws/gitlab-runner-autoscaling-aws-asg/-/blob/main/FEATURES.md).\n\n\nThe GitLab Runner Vending Machine has the following cost management and\nscaling management benefits, exposed as a variety of parameters:\n\n\n- The ability to leverage Spot compute instances. This is important because\nit leaves CI/CD pipeline developers in charge of whether specific Gitlab\nCI/CD jobs run on Spot compute or not.\n\n- ASG-scheduled scaling so that a runner or runner fleet can be completely\nshutdown when not in use.\n\n- The GitLab Runner Vending Machine can leverage ARM compute for Linux -\nwhich runs faster and costs less.\n\n- It can also use ASG to update all runners in a fleet with the latest\nmachine images and GitLab Runner version (or a specific version). When\nmaintenance is not built-in, the labor cost of keeping things up-to-date can\nbe significant.\n\n- Runner naming and tagging in AWS and GitLab, which eases the burden of\nlocating runner instances and managing orphaned runners registrations,\nwhether it is manual or automated.\n\n\n### How to save money with The GitLab Runner Vending Machine\n\n\nSignificant savings are possible with this IaC, whether your team wants to\nsave on a single runner or a fleet of them.\n\n\nThe savings calculations below are for a single runner and should be linear\nfor a given workload. To calculate your savings for more runners, simply\nmultiply the final result by the number of runner instances. The available\n\"Runner Minutes\" per hour is calculated as the runner's job concurrency\nsetting multiplied by the minutes in an hour. For this exercise, we'll use\njob concurrency of \"10\". This number should be changed depending on the\ninstance types you are using and the load testing of your typical CI/CD\nworkloads.\n\n\nJust like most performance analysis, we are assuming that hardware resource\nutilization is optimal and consistent. If a runner cluster can sustain\nrespectable performance with 80% CPU loading, this calculation assumes that\nwould be maintained regardless of the size of the cluster.\n\n\n#### AWS Graviton ARM and Spot savings\n\n\nThe GitLab Runner engineering team has completed performance testing that\ndemonstrates performance gains of more than 30% on some AWS Graviton\n(ARM-based) instance types. Assuming that runners are performance-managed\nfor optimized utilization, this gain is a direct cost savings. Just\nrecently, we shared [how deploying GitLab on Arm-based AWS Graviton2\nresulted in cost savings of 23% and 36% performance\ngains](/blog/achieving-23-cost-savings-and-36-performance-gain-using-gitlab-and-gitlab-runner-on-arm-neoverse-based-aws-graviton2-processor/).\n\n\n![ARM Efficiency Test Results For GitLab\nRunner](https://about.gitlab.com/images/blogimages/hundred-runners/hundredrunners-image1.png)\n\nGitLab Runner testing results for ARM-efficiency gains.\n\n{: .note.text-center}\n\n\n#### Scheduling savings\n\n\nThe savings can be dramatic when teams are able to turn off runners when not\nin use. For instance: Scheduling a runner to operate for 40-hours per week\nsaves 76% when compared to the cost of running it for 168 hours. Runners\nthat are just in use for 10 hours per week saves 94%.\n\n\n#### Combining scheduling, Spot, and ARM to save 97%\n\n\nJust for fun, let's see what savings are possible by comparing a standard\nrunner scenario with deploying runners in customized, stand-alone instances\nto the maximum savings automation can deliver.\n\n\nImagine I am a developer who set up a custom GitLab Runner on an m5.xlarge\ninstance, which is x86 the architecture, for a development team that works\nfor 40 hours on the same time zone. Since there is no automation, the GitLab\nRunner runs 24/7. We will assume a job concurrency of 10, which gives 600\n\"runner minutes\" per hour of run time. Scheduling uptime, running on Spot,\nand leveraging ARM can all be achieved quickly by redeploying the runner\nwith The GitLab Runner Vending Machine.\n\n\nHere is the calculation to run the configuration described above, for one\nweek: On Demand, x86, Always On: 1 x m5.xlarge = .192/hr x 168 hrs/week =\n**$32/week or $1664/year**\n\n\nHere are the savings that come from running Spot, ARM, and scheduling the\nRunner to be up just 40hrs/week: 1 x m6g.large Spot = .0419 x 40hrs/week x\n64% (36% better performance) = **$1/week**\n\n\n$1/$32 x 100 = 3.125% of the original cost for the same work. In other\nwords, **we just saved 97%** without ever impacting the ability to get the\njob done.\n\n\nIn short, The GitLab Runner Vending Machine intends to bring the many cost\nsaving mechanisms of AWS Cloud computing to your GitLab Runner fleets.\n\n\nYou can save costs by using ARM/Graviton instances, Spot compute, or by\nscheduling uptime. In many cases, you can combine all three savings\nmechanisms for maximum impact.\n\n\n### Special pipeline building concerns for Spot Runners\n\n\nSpot instances can disappear with as little as two minutes of warning. This\ninevitably means some runners will be terminated while jobs are still in\nprogress. CI/CD pipeline developers must take into account whether a job\nought to run on compute resources that can disappear with short notice (so\nshort as to be considered \"no notice\"). This comes down to deciding what\njobs are OK to run on Spot and what jobs should instead run on AWS'\npersistent compute known as \"On-Demand\".\n\n\nThe GitLab Runner Vending Machine accounts for these constraints by tagging\nrunner instances in GitLab with `computetype-spot` or `computetype-ondemand`\n– indicating in the \"tags\" segment of GitLab CI/CD jobs if a job should run\non Spot compute.\n\n\nSome types of CI workloads, e.g., mass performance testing or large unit\ntesting suites, may already have work queues and work tracking that make it\nideal for Spot compute. Other activities, e.g., polling another system for a\ndeployment status, could suffer a material discrepancy if terminated\npermaturely. Others, such as building the application, are sort of in the\nmiddle. Usually, restarting the build is sufficient.\n\n\n### Job configuration for Spot\n\n\nIf you need to reschedule terminated work, it is helpful to configure\nGitLab’s job `retry:` keyword. When working with a dispatching engine or\nwork queue that automatically accounts for incompleted work by processing\nagents, the retry configuration is unnecessary.\n\n\nHere is an example that implements both of these concepts:\n\n\n```\n\nmy-scaled-test-suite:\n  parallel: 100\n  tags:\n  - computetype-Spot\n  retry:\n    max: 2\n    when:\n      - runner_system_failure\n      - unknown_failure\n```\n\n\nThe usage and limitations of `retry:` are discussed in greater detail in the\n[GitLab CI documentation on\nretry](https://docs.gitlab.com/ee/ci/yaml/#retry).\n\n\n### How to get started\n\n\nThe CloudFormation templates for the [GitLab Runner Vending Machine are\nmanaged in a public project on\nGitLab.com](https://gitlab.com/guided-explorations/aws/gitlab-runner-autoscaling-aws-asg/).\nThere is a lot of information in the project about how the solution works\nand what problems it aims to solve, and will be useful for very experienced\nAWS builders.\n\n\nBut to keep it simple for users who want the quickest path to creating\nrunners of all sizes, it also has an \"easy button\" page that has a table\nthat looks like this:\n\n\n![Easy Button Page\nSample](https://about.gitlab.com/images/blogimages/hundred-runners/hundredrunners-image2.png)\n\nThe easy buttons launch a CloudFormation Quick Create that only requires\nfilling in a few fields.\n\n{: .note.text-center}\n\n\nKeep in mind that easy buttons intentionally hide the high degree of\ncustomization that is possible with this automation by setting the\nparameters for the most common scenarios in advance. Advanced AWS users\nshould read more of the documentation in the repository to understand that\nthe GitLab Runner Vending Machine is also capable of creating sophisticated\nrunner fleets.\n\n\nFirst, click the CloudFormation icons to launch the Easy Button template\ndirectly into the CloudFormation Quick Create console. The Quick Create\nconsole is designed for simplicity to enable you to complete the prompts and\nthen click one button to launch the stack.\n\n\n![CloudFormation Quick Create\nExample](https://about.gitlab.com/images/blogimages/hundred-runners/hundredrunners-image3.png){:\n.shadow.medium.center}\n\nThis is a typical Quick Create form for the GitLab Vending Machine easy\nbuttons.\n\n{: .note.text-center}\n\n\nNext, select the deploy region by using the drop down menu in the upper\nright of the console (where the screenshot says \"Oregon\").\n\n\nIn most cases, you will only need to add your GitLab instance URL\n(GitLab.com is fine if that is where your repositories are), and the runner\ntoken, which you retrieve from the group level or project you wish to attach\nthe runners to. If you are registering against a self-managed instance, you\ncan use the instance-level tokens from the administrator console to register\nthe runner for use across the entire instance. Read on for [instructions for\nfinding Runner Registration\nTokens](https://docs.gitlab.com/runner/register/#requirements).\n\n\nA few other customization parameters are available for your convenience.\n\n\nNote that the automation attempts to use the default VPC of the region in\nwhich you deploy and the default security group for the VPC. In some\norganizations, default VPCs and/or their security groups are locked. You can\ndeploy to custom VPCs by using the full template instead of an easy button.\nOn the easy button page look for the footnote \"Not any easy button person?\"\"\nto find a link to the full template.\n\n\nWatch the video below to see the deployment of provisioning 100 GitLab Spot\nRunners on AWS in less than 10 minutes and in less than 10 clicks for just\n$5 per hour.\n\n\n\u003C!-- blank line -->\n\n\u003Cfigure class=\"video_container\">\n  \u003Ciframe src=\"https://www.youtube-nocookie.com/embed/EW4RJv5zW4U\" frameborder=\"0\" allowfullscreen=\"true\"> \u003C/iframe>\n\u003C/figure>\n\n\u003C!-- blank line -->\n\n\nCheck out the YouTube playlist for more relevant videos about [GitLab and\nAWS](https://youtube.com/playlist?list=PL05JrBw4t0Ko30Bkf8bAvR-8E441Fy2G9)\n\n\n### This automation does much, much more\n\n\nWhile this article focused how much you can saving while using Spot for\nscaled runners, the underlying automation is capable of many other\nscenarios. Below is a summary of the additional features and benefits\ncovered in the documentation.\n\n\n- Scaled runners that are persistent (not Spot) ([see more easy buttons\nhere](https://gitlab.com/guided-explorations/aws/gitlab-runner-autoscaling-aws-asg/-/blob/main/easybuttons.md)).\n\n- Supports small, single runner setups and scaled ones.\n\n- Supports GitLab.com SaaS or self-managed instances.\n\n- Automates OS patching and Runner version upgrading.\n\n- Supports Windows and Linux.\n\n- Can be reused with Amazon provisioning services such as Service Catalog\nand Control Tower.\n\n- Implements least privilege security throughout.\n\n- Supports deregistering runners on scale-in or Spot termination.\n\n\nA full feature list is in the document [Features of GitLab HA Scaling Runner\nVending Machine for\nAWS](https://gitlab.com/guided-explorations/aws/gitlab-runner-autoscaling-aws-asg/-/blob/main/FEATURES.md)\n\n\n### Easy running\n\n\nWe hope that this automation will make deployment of runners of all sizes\nsimple for you. We are open to your feedback, suggestions and contributions\nin the GitLab project.\n",[9,696,741,808],{"slug":2896,"featured":6,"template":699},"100-runners-in-less-than-10mins-and-less-than-10-clicks","content:en-us:blog:100-runners-in-less-than-10mins-and-less-than-10-clicks.yml","100 Runners In Less Than 10mins And Less Than 10 Clicks","en-us/blog/100-runners-in-less-than-10mins-and-less-than-10-clicks.yml","en-us/blog/100-runners-in-less-than-10mins-and-less-than-10-clicks",13,[679,704,727,749,770,793,815,835,859],1759347855099]