[{"data":1,"prerenderedAt":2889},["ShallowReactive",2],{"/en-us/blog/tags/ci/":3,"navigation-en-us":20,"banner-en-us":438,"footer-en-us":453,"CI-tag-page-en-us":664},{"_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":434,"_type":14,"title":435,"_source":16,"_file":436,"_stem":437,"_extension":19},"/shared/en-us/main-navigation","en-us",{"logo":24,"freeTrial":29,"sales":34,"login":39,"items":44,"search":375,"minimal":406,"duo":425},{"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,185,190,296,356],{"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":167},"Product",true,{"dataNavLevelOne":93},"solutions",{"text":95,"config":96},"View all Solutions",{"href":97,"dataGaName":93,"dataGaLocation":28},"/solutions/",[99,124,146],{"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,136,141],{"text":134,"config":135},"Security & Compliance",{"href":129,"dataGaLocation":28,"dataGaName":134},{"text":137,"config":138},"Software Supply Chain Security",{"href":139,"dataGaLocation":28,"dataGaName":140},"/solutions/supply-chain/","Software supply chain security",{"text":142,"config":143},"Compliance & Governance",{"href":144,"dataGaLocation":28,"dataGaName":145},"/solutions/continuous-software-compliance/","Compliance and governance",{"title":147,"link":148,"items":153},"Measurement",{"config":149},{"icon":150,"href":151,"dataGaName":152,"dataGaLocation":28},"DigitalTransformation","/solutions/visibility-measurement/","visibility and measurement",[154,158,162],{"text":155,"config":156},"Visibility & Measurement",{"href":151,"dataGaLocation":28,"dataGaName":157},"Visibility and Measurement",{"text":159,"config":160},"Value Stream Management",{"href":161,"dataGaLocation":28,"dataGaName":159},"/solutions/value-stream-management/",{"text":163,"config":164},"Analytics & Insights",{"href":165,"dataGaLocation":28,"dataGaName":166},"/solutions/analytics-and-insights/","Analytics and insights",{"title":168,"items":169},"GitLab for",[170,175,180],{"text":171,"config":172},"Enterprise",{"href":173,"dataGaLocation":28,"dataGaName":174},"/enterprise/","enterprise",{"text":176,"config":177},"Small Business",{"href":178,"dataGaLocation":28,"dataGaName":179},"/small-business/","small business",{"text":181,"config":182},"Public Sector",{"href":183,"dataGaLocation":28,"dataGaName":184},"/solutions/public-sector/","public sector",{"text":186,"config":187},"Pricing",{"href":188,"dataGaName":189,"dataGaLocation":28,"dataNavLevelOne":189},"/pricing/","pricing",{"text":191,"config":192,"link":194,"lists":198,"feature":283},"Resources",{"dataNavLevelOne":193},"resources",{"text":195,"config":196},"View all resources",{"href":197,"dataGaName":193,"dataGaLocation":28},"/resources/",[199,232,255],{"title":200,"items":201},"Getting started",[202,207,212,217,222,227],{"text":203,"config":204},"Install",{"href":205,"dataGaName":206,"dataGaLocation":28},"/install/","install",{"text":208,"config":209},"Quick start guides",{"href":210,"dataGaName":211,"dataGaLocation":28},"/get-started/","quick setup checklists",{"text":213,"config":214},"Learn",{"href":215,"dataGaLocation":28,"dataGaName":216},"https://university.gitlab.com/","learn",{"text":218,"config":219},"Product documentation",{"href":220,"dataGaName":221,"dataGaLocation":28},"https://docs.gitlab.com/","product documentation",{"text":223,"config":224},"Best practice videos",{"href":225,"dataGaName":226,"dataGaLocation":28},"/getting-started-videos/","best practice videos",{"text":228,"config":229},"Integrations",{"href":230,"dataGaName":231,"dataGaLocation":28},"/integrations/","integrations",{"title":233,"items":234},"Discover",[235,240,245,250],{"text":236,"config":237},"Customer success stories",{"href":238,"dataGaName":239,"dataGaLocation":28},"/customers/","customer success stories",{"text":241,"config":242},"Blog",{"href":243,"dataGaName":244,"dataGaLocation":28},"/blog/","blog",{"text":246,"config":247},"Remote",{"href":248,"dataGaName":249,"dataGaLocation":28},"https://handbook.gitlab.com/handbook/company/culture/all-remote/","remote",{"text":251,"config":252},"TeamOps",{"href":253,"dataGaName":254,"dataGaLocation":28},"/teamops/","teamops",{"title":256,"items":257},"Connect",[258,263,268,273,278],{"text":259,"config":260},"GitLab Services",{"href":261,"dataGaName":262,"dataGaLocation":28},"/services/","services",{"text":264,"config":265},"Community",{"href":266,"dataGaName":267,"dataGaLocation":28},"/community/","community",{"text":269,"config":270},"Forum",{"href":271,"dataGaName":272,"dataGaLocation":28},"https://forum.gitlab.com/","forum",{"text":274,"config":275},"Events",{"href":276,"dataGaName":277,"dataGaLocation":28},"/events/","events",{"text":279,"config":280},"Partners",{"href":281,"dataGaName":282,"dataGaLocation":28},"/partners/","partners",{"backgroundColor":284,"textColor":285,"text":286,"image":287,"link":291},"#2f2a6b","#fff","Insights for the future of software development",{"altText":288,"config":289},"the source promo card",{"src":290},"/images/navigation/the-source-promo-card.svg",{"text":292,"config":293},"Read the latest",{"href":294,"dataGaName":295,"dataGaLocation":28},"/the-source/","the source",{"text":297,"config":298,"lists":300},"Company",{"dataNavLevelOne":299},"company",[301],{"items":302},[303,308,314,316,321,326,331,336,341,346,351],{"text":304,"config":305},"About",{"href":306,"dataGaName":307,"dataGaLocation":28},"/company/","about",{"text":309,"config":310,"footerGa":313},"Jobs",{"href":311,"dataGaName":312,"dataGaLocation":28},"/jobs/","jobs",{"dataGaName":312},{"text":274,"config":315},{"href":276,"dataGaName":277,"dataGaLocation":28},{"text":317,"config":318},"Leadership",{"href":319,"dataGaName":320,"dataGaLocation":28},"/company/team/e-group/","leadership",{"text":322,"config":323},"Team",{"href":324,"dataGaName":325,"dataGaLocation":28},"/company/team/","team",{"text":327,"config":328},"Handbook",{"href":329,"dataGaName":330,"dataGaLocation":28},"https://handbook.gitlab.com/","handbook",{"text":332,"config":333},"Investor relations",{"href":334,"dataGaName":335,"dataGaLocation":28},"https://ir.gitlab.com/","investor relations",{"text":337,"config":338},"Trust Center",{"href":339,"dataGaName":340,"dataGaLocation":28},"/security/","trust center",{"text":342,"config":343},"AI Transparency Center",{"href":344,"dataGaName":345,"dataGaLocation":28},"/ai-transparency-center/","ai transparency center",{"text":347,"config":348},"Newsletter",{"href":349,"dataGaName":350,"dataGaLocation":28},"/company/contact/","newsletter",{"text":352,"config":353},"Press",{"href":354,"dataGaName":355,"dataGaLocation":28},"/press/","press",{"text":357,"config":358,"lists":359},"Contact us",{"dataNavLevelOne":299},[360],{"items":361},[362,365,370],{"text":35,"config":363},{"href":37,"dataGaName":364,"dataGaLocation":28},"talk to sales",{"text":366,"config":367},"Get help",{"href":368,"dataGaName":369,"dataGaLocation":28},"/support/","get help",{"text":371,"config":372},"Customer portal",{"href":373,"dataGaName":374,"dataGaLocation":28},"https://customers.gitlab.com/customers/sign_in/","customer portal",{"close":376,"login":377,"suggestions":384},"Close",{"text":378,"link":379},"To search repositories and projects, login to",{"text":380,"config":381},"gitlab.com",{"href":42,"dataGaName":382,"dataGaLocation":383},"search login","search",{"text":385,"default":386},"Suggestions",[387,389,393,395,399,403],{"text":57,"config":388},{"href":62,"dataGaName":57,"dataGaLocation":383},{"text":390,"config":391},"Code Suggestions (AI)",{"href":392,"dataGaName":390,"dataGaLocation":383},"/solutions/code-suggestions/",{"text":109,"config":394},{"href":111,"dataGaName":109,"dataGaLocation":383},{"text":396,"config":397},"GitLab on AWS",{"href":398,"dataGaName":396,"dataGaLocation":383},"/partners/technology-partners/aws/",{"text":400,"config":401},"GitLab on Google Cloud",{"href":402,"dataGaName":400,"dataGaLocation":383},"/partners/technology-partners/google-cloud-platform/",{"text":404,"config":405},"Why GitLab?",{"href":70,"dataGaName":404,"dataGaLocation":383},{"freeTrial":407,"mobileIcon":412,"desktopIcon":417,"secondaryButton":420},{"text":408,"config":409},"Start free trial",{"href":410,"dataGaName":33,"dataGaLocation":411},"https://gitlab.com/-/trials/new/","nav",{"altText":413,"config":414},"Gitlab Icon",{"src":415,"dataGaName":416,"dataGaLocation":411},"/images/brand/gitlab-logo-tanuki.svg","gitlab icon",{"altText":413,"config":418},{"src":419,"dataGaName":416,"dataGaLocation":411},"/images/brand/gitlab-logo-type.svg",{"text":421,"config":422},"Get Started",{"href":423,"dataGaName":424,"dataGaLocation":411},"https://gitlab.com/-/trial_registrations/new?glm_source=about.gitlab.com/compare/gitlab-vs-github/","get started",{"freeTrial":426,"mobileIcon":430,"desktopIcon":432},{"text":427,"config":428},"Learn more about GitLab Duo",{"href":62,"dataGaName":429,"dataGaLocation":411},"gitlab duo",{"altText":413,"config":431},{"src":415,"dataGaName":416,"dataGaLocation":411},{"altText":413,"config":433},{"src":419,"dataGaName":416,"dataGaLocation":411},"content:shared:en-us:main-navigation.yml","Main Navigation","shared/en-us/main-navigation.yml","shared/en-us/main-navigation",{"_path":439,"_dir":22,"_draft":6,"_partial":6,"_locale":7,"title":440,"button":441,"image":445,"config":448,"_id":450,"_type":14,"_source":16,"_file":451,"_stem":452,"_extension":19},"/shared/en-us/banner","is now in public beta!",{"text":68,"config":442},{"href":443,"dataGaName":444,"dataGaLocation":28},"/gitlab-duo/agent-platform/","duo banner",{"config":446},{"src":447},"https://res.cloudinary.com/about-gitlab-com/image/upload/v1753720689/somrf9zaunk0xlt7ne4x.svg",{"layout":449},"release","content:shared:en-us:banner.yml","shared/en-us/banner.yml","shared/en-us/banner",{"_path":454,"_dir":22,"_draft":6,"_partial":6,"_locale":7,"data":455,"_id":660,"_type":14,"title":661,"_source":16,"_file":662,"_stem":663,"_extension":19},"/shared/en-us/main-footer",{"text":456,"source":457,"edit":463,"contribute":468,"config":473,"items":478,"minimal":652},"Git is a trademark of Software Freedom Conservancy and our use of 'GitLab' is under license",{"text":458,"config":459},"View page source",{"href":460,"dataGaName":461,"dataGaLocation":462},"https://gitlab.com/gitlab-com/marketing/digital-experience/about-gitlab-com/","page source","footer",{"text":464,"config":465},"Edit this page",{"href":466,"dataGaName":467,"dataGaLocation":462},"https://gitlab.com/gitlab-com/marketing/digital-experience/about-gitlab-com/-/blob/main/content/","web ide",{"text":469,"config":470},"Please contribute",{"href":471,"dataGaName":472,"dataGaLocation":462},"https://gitlab.com/gitlab-com/marketing/digital-experience/about-gitlab-com/-/blob/main/CONTRIBUTING.md/","please contribute",{"twitter":474,"facebook":475,"youtube":476,"linkedin":477},"https://twitter.com/gitlab","https://www.facebook.com/gitlab","https://www.youtube.com/channel/UCnMGQ8QHMAnVIsI3xJrihhg","https://www.linkedin.com/company/gitlab-com",[479,502,559,588,622],{"title":46,"links":480,"subMenu":485},[481],{"text":482,"config":483},"DevSecOps platform",{"href":55,"dataGaName":484,"dataGaLocation":462},"devsecops platform",[486],{"title":186,"links":487},[488,492,497],{"text":489,"config":490},"View plans",{"href":188,"dataGaName":491,"dataGaLocation":462},"view plans",{"text":493,"config":494},"Why Premium?",{"href":495,"dataGaName":496,"dataGaLocation":462},"/pricing/premium/","why premium",{"text":498,"config":499},"Why Ultimate?",{"href":500,"dataGaName":501,"dataGaLocation":462},"/pricing/ultimate/","why ultimate",{"title":503,"links":504},"Solutions",[505,510,513,515,520,525,529,532,536,541,543,546,549,554],{"text":506,"config":507},"Digital transformation",{"href":508,"dataGaName":509,"dataGaLocation":462},"/topics/digital-transformation/","digital transformation",{"text":134,"config":511},{"href":129,"dataGaName":512,"dataGaLocation":462},"security & compliance",{"text":123,"config":514},{"href":105,"dataGaName":106,"dataGaLocation":462},{"text":516,"config":517},"Agile development",{"href":518,"dataGaName":519,"dataGaLocation":462},"/solutions/agile-delivery/","agile delivery",{"text":521,"config":522},"Cloud transformation",{"href":523,"dataGaName":524,"dataGaLocation":462},"/topics/cloud-native/","cloud transformation",{"text":526,"config":527},"SCM",{"href":119,"dataGaName":528,"dataGaLocation":462},"source code management",{"text":109,"config":530},{"href":111,"dataGaName":531,"dataGaLocation":462},"continuous integration & delivery",{"text":533,"config":534},"Value stream management",{"href":161,"dataGaName":535,"dataGaLocation":462},"value stream management",{"text":537,"config":538},"GitOps",{"href":539,"dataGaName":540,"dataGaLocation":462},"/solutions/gitops/","gitops",{"text":171,"config":542},{"href":173,"dataGaName":174,"dataGaLocation":462},{"text":544,"config":545},"Small business",{"href":178,"dataGaName":179,"dataGaLocation":462},{"text":547,"config":548},"Public sector",{"href":183,"dataGaName":184,"dataGaLocation":462},{"text":550,"config":551},"Education",{"href":552,"dataGaName":553,"dataGaLocation":462},"/solutions/education/","education",{"text":555,"config":556},"Financial services",{"href":557,"dataGaName":558,"dataGaLocation":462},"/solutions/finance/","financial services",{"title":191,"links":560},[561,563,565,567,570,572,574,576,578,580,582,584,586],{"text":203,"config":562},{"href":205,"dataGaName":206,"dataGaLocation":462},{"text":208,"config":564},{"href":210,"dataGaName":211,"dataGaLocation":462},{"text":213,"config":566},{"href":215,"dataGaName":216,"dataGaLocation":462},{"text":218,"config":568},{"href":220,"dataGaName":569,"dataGaLocation":462},"docs",{"text":241,"config":571},{"href":243,"dataGaName":244,"dataGaLocation":462},{"text":236,"config":573},{"href":238,"dataGaName":239,"dataGaLocation":462},{"text":246,"config":575},{"href":248,"dataGaName":249,"dataGaLocation":462},{"text":259,"config":577},{"href":261,"dataGaName":262,"dataGaLocation":462},{"text":251,"config":579},{"href":253,"dataGaName":254,"dataGaLocation":462},{"text":264,"config":581},{"href":266,"dataGaName":267,"dataGaLocation":462},{"text":269,"config":583},{"href":271,"dataGaName":272,"dataGaLocation":462},{"text":274,"config":585},{"href":276,"dataGaName":277,"dataGaLocation":462},{"text":279,"config":587},{"href":281,"dataGaName":282,"dataGaLocation":462},{"title":297,"links":589},[590,592,594,596,598,600,602,606,611,613,615,617],{"text":304,"config":591},{"href":306,"dataGaName":299,"dataGaLocation":462},{"text":309,"config":593},{"href":311,"dataGaName":312,"dataGaLocation":462},{"text":317,"config":595},{"href":319,"dataGaName":320,"dataGaLocation":462},{"text":322,"config":597},{"href":324,"dataGaName":325,"dataGaLocation":462},{"text":327,"config":599},{"href":329,"dataGaName":330,"dataGaLocation":462},{"text":332,"config":601},{"href":334,"dataGaName":335,"dataGaLocation":462},{"text":603,"config":604},"Sustainability",{"href":605,"dataGaName":603,"dataGaLocation":462},"/sustainability/",{"text":607,"config":608},"Diversity, inclusion and belonging (DIB)",{"href":609,"dataGaName":610,"dataGaLocation":462},"/diversity-inclusion-belonging/","Diversity, inclusion and belonging",{"text":337,"config":612},{"href":339,"dataGaName":340,"dataGaLocation":462},{"text":347,"config":614},{"href":349,"dataGaName":350,"dataGaLocation":462},{"text":352,"config":616},{"href":354,"dataGaName":355,"dataGaLocation":462},{"text":618,"config":619},"Modern Slavery Transparency Statement",{"href":620,"dataGaName":621,"dataGaLocation":462},"https://handbook.gitlab.com/handbook/legal/modern-slavery-act-transparency-statement/","modern slavery transparency statement",{"title":623,"links":624},"Contact Us",[625,628,630,632,637,642,647],{"text":626,"config":627},"Contact an expert",{"href":37,"dataGaName":38,"dataGaLocation":462},{"text":366,"config":629},{"href":368,"dataGaName":369,"dataGaLocation":462},{"text":371,"config":631},{"href":373,"dataGaName":374,"dataGaLocation":462},{"text":633,"config":634},"Status",{"href":635,"dataGaName":636,"dataGaLocation":462},"https://status.gitlab.com/","status",{"text":638,"config":639},"Terms of use",{"href":640,"dataGaName":641,"dataGaLocation":462},"/terms/","terms of use",{"text":643,"config":644},"Privacy statement",{"href":645,"dataGaName":646,"dataGaLocation":462},"/privacy/","privacy statement",{"text":648,"config":649},"Cookie preferences",{"dataGaName":650,"dataGaLocation":462,"id":651,"isOneTrustButton":91},"cookie preferences","ot-sdk-btn",{"items":653},[654,656,658],{"text":638,"config":655},{"href":640,"dataGaName":641,"dataGaLocation":462},{"text":643,"config":657},{"href":645,"dataGaName":646,"dataGaLocation":462},{"text":648,"config":659},{"dataGaName":650,"dataGaLocation":462,"id":651,"isOneTrustButton":91},"content:shared:en-us:main-footer.yml","Main Footer","shared/en-us/main-footer.yml","shared/en-us/main-footer",{"allPosts":665,"featuredPost":2866,"totalPagesCount":2887,"initialPosts":2888},[666,691,714,736,757,780,802,822,846,866,886,910,932,956,977,1001,1023,1043,1062,1084,1104,1127,1147,1168,1188,1208,1228,1249,1268,1288,1308,1328,1350,1372,1390,1410,1430,1451,1472,1491,1511,1532,1552,1571,1591,1611,1631,1650,1669,1688,1707,1728,1748,1768,1787,1808,1828,1848,1867,1887,1905,1926,1945,1964,1983,2003,2023,2041,2061,2081,2100,2120,2139,2159,2177,2196,2217,2236,2256,2275,2294,2315,2333,2354,2373,2391,2413,2432,2452,2472,2491,2510,2530,2549,2567,2587,2607,2627,2647,2668,2686,2707,2727,2747,2767,2787,2806,2826,2847],{"_path":667,"_dir":244,"_draft":6,"_partial":6,"_locale":7,"seo":668,"content":676,"config":684,"_id":687,"_type":14,"title":688,"_source":16,"_file":689,"_stem":690,"_extension":19},"/en-us/blog/a-ci-component-builders-journey",{"title":669,"description":670,"ogTitle":669,"ogDescription":670,"noIndex":6,"ogImage":671,"ogUrl":672,"ogSiteName":673,"ogType":674,"canonicalUrls":672,"schema":675},"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":669,"description":670,"authors":677,"heroImage":671,"date":679,"body":680,"category":681,"tags":682},[678],"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,683],"CD",{"slug":685,"featured":6,"template":686},"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":692,"_dir":244,"_draft":6,"_partial":6,"_locale":7,"seo":693,"content":699,"config":708,"_id":710,"_type":14,"title":711,"_source":16,"_file":712,"_stem":713,"_extension":19},"/en-us/blog/a-story-of-runner-scaling",{"title":694,"description":695,"ogTitle":694,"ogDescription":695,"noIndex":6,"ogImage":696,"ogUrl":697,"ogSiteName":673,"ogType":674,"canonicalUrls":697,"schema":698},"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":694,"description":695,"authors":700,"heroImage":696,"date":702,"body":703,"category":704,"tags":705},[678,701],"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://about.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,683,706,707],"performance","solutions architecture",{"slug":709,"featured":6,"template":686},"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":715,"_dir":244,"_draft":6,"_partial":6,"_locale":7,"seo":716,"content":722,"config":730,"_id":732,"_type":14,"title":733,"_source":16,"_file":734,"_stem":735,"_extension":19},"/en-us/blog/a-visual-guide-to-gitlab-ci-caching",{"title":717,"description":718,"ogTitle":717,"ogDescription":718,"noIndex":6,"ogImage":719,"ogUrl":720,"ogSiteName":673,"ogType":674,"canonicalUrls":720,"schema":721},"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":717,"description":718,"authors":723,"heroImage":719,"date":725,"body":726,"category":704,"tags":727},[724],"Matthieu Fronton","2022-09-12","\n\nIf you've ever worked with GitLab CI/CD you may have needed, at some point, to use a cache to share content between jobs. The decentralized nature of GitLab CI/CD is a strength that can confuse the understanding of even the best of us when we want to connect wires all together. For instance, we need to know critical information such as the difference between artifacts and cache and where/how to place setups.\n\nThis visual guide will help with both challenges.\n\n## Cache vs. artifacts\n\nThe concepts _may_ seem to overlap because they are about sharing content between jobs, but they actually are fundamentally different:\n\n- If your job does not rely on the the previous one (i.e. can produce it by itself but if content already exists the job will run faster), then use cache.\n- If your job does rely on the output of the previous one (i.e. cannot produce it by itself), then use artifacts and dependencies.\n\nHere is a simple sentence to remember if you struggle between choosing cache or artifact:\n> Cache is here to speed up your job but it may not exist, so don't rely on it.\n\nThis article will focus on **cache**.\n\n## Initial setup\n\nWe'll go with a simple representation of the GitLab CI/CD pipelining model and ignore (for now) that the jobs can be executed on any runners and hosts. It will help get the basics.\n\nLet's say you have:\n- 1 project with 3 branches\n- 1 host running 2 docker runners\n\n![Initial setup](https://about.gitlab.com/images/blogimages/visual-guide-caching/vgc-1.png){: .shadow.center}\n\n## Local cache: Docker volume\n\nIf you want a [local cache](https://docs.gitlab.com/ee/ci/caching/index.html#where-the-caches-are-stored) between all your jobs running on the same runner, use the [cache statement](https://docs.gitlab.com/ee/ci/yaml/#cache) in your `.gitlab-ci.yml`:\n\n```yaml\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![local / container / all branches / all jobs](https://about.gitlab.com/images/blogimages/visual-guide-caching/vgc-2.png){: .shadow.center}\n\nUsing the [predefined variable](https://docs.gitlab.com/ee/ci/variables/predefined_variables.html) `CI_COMMIT_REF_NAME` as the [cache key](https://docs.gitlab.com/ee/ci/yaml/index.html#cachekey), you can ensure the cache is tied to a specific branch:\n\n```yaml\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![local / container / one branch / all jobs](https://about.gitlab.com/images/blogimages/visual-guide-caching/vgc-3.png){: .shadow.center}\n\nUsing the [predefined variable](https://docs.gitlab.com/ee/ci/variables/predefined_variables.html) `CI_JOB_NAME` as the [cache key](https://docs.gitlab.com/ee/ci/yaml/index.html#cachekey), you can ensure the cache is tied to a specific job:\n\n![local / container / all branch / one jobs](https://about.gitlab.com/images/blogimages/visual-guide-caching/vgc-4.png){: .shadow.center}\n\n## Local cache: Bind mount\n\nIf you don't want to use a volume for caching purposes (debugging purpose, cleanup disk space more easily, etc.), you can configure a [bind mount for Docker volumes](https://docs.docker.com/storage/bind-mounts/) while registering the runner. With this setup, you do not need to set up the [cache statement](https://docs.gitlab.com/ee/ci/yaml/#cache) in your `.gitlab-ci.yml`:\n\n```yaml\n#!/bin/bash\n\ngitlab-runner register                             \\\n  --name=\"Bind-Mount Runner\"                       \\\n  --docker-volumes=\"/host/path:/container/path:rw\" \\\n...\n```\n\n![local / one runners / one host / all branch / all jobs](https://about.gitlab.com/images/blogimages/visual-guide-caching/vgc-5.png){: .shadow.center}\n\nIn fact, this setup even allows you to share a cache between jobs running on the same host without requiring you to set up a distributed cache (which we'll talk about later):\n\n```yaml\n#!/bin/bash\n\ngitlab-runner register                             \\\n  --name=\"Bind-Mount Runner X\"                     \\\n  --docker-volumes=\"/host/path:/container/path:rw\" \\\n...\n\ngitlab-runner register                                 \\\n  --name=\"Bind-Mount Runner Y\"                         \\\n  --docker-volumes=\"/host/path:/container/alt/path:rw\" \\\n...\n```\n\n![local / multiple runners / one host / all branch / all jobs](https://about.gitlab.com/images/blogimages/visual-guide-caching/vgc-6.png){: .shadow.center}\n\n## Distributed cache\n\nIf you want to have a [shared cache](https://docs.gitlab.com/runner/configuration/autoscale.html#distributed-runners-caching) between all your jobs running on multiple runners and hosts, use the \u003Ca href=\"https://docs.gitlab.com/runner/configuration/advanced-configuration.html#the-runnerscache-section\">[runner.cache]\u003Ca> section in your `config.toml`:\n\n```yaml\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![remote / multiple runners / multiple hosts / all branch / all jobs](https://about.gitlab.com/images/blogimages/visual-guide-caching/vgc-7.png){: .shadow.center}\n\nUsing the predefined variable `CI_COMMIT_REF_NAME` as the cache key you can ensure the cache is tied to a specific branch between multiple runners and hosts:\n\n![remote / multiple runners / multiple hosts / one branch / all jobs](https://about.gitlab.com/images/blogimages/visual-guide-caching/vgc-8.png){: .shadow.center}\n\n## Real-life setup\n\nThe above assumptions allowed you to harness your understanding of the concepts and possibilities.\n\nIn real life, you'll face more complex wiring and we hope this article will help you as a visual cheatsheet along with the reference documentation.\n\nJust to give you a sneak peek, here is an exercise for you:\n\n- Set up a cache between all the jobs of a specific stage, running on any runner and any hosts, but only between pipeline of the same branches:\n\n![Real-life test assignment](https://about.gitlab.com/images/blogimages/visual-guide-caching/vgc-9.png){: .shadow.center}\n\nHappy caching, folks!\n\n\n\nCover image by [Alina Grubnyak](https://unsplash.com/@alinnnaaaa) on [Unsplash](https://unsplash.com)\n{: .note}\n",[9,683,728,729],"DevOps","tutorial",{"slug":731,"featured":6,"template":686},"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":737,"_dir":244,"_draft":6,"_partial":6,"_locale":7,"seo":738,"content":744,"config":751,"_id":753,"_type":14,"title":754,"_source":16,"_file":755,"_stem":756,"_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":739,"description":740,"ogTitle":739,"ogDescription":740,"noIndex":6,"ogImage":741,"ogUrl":742,"ogSiteName":673,"ogType":674,"canonicalUrls":742,"schema":743},"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":745,"description":740,"authors":746,"heroImage":741,"date":748,"body":749,"category":704,"tags":750},"23% Cost savings and 36% performance gain by deploying GitLab on Arm-based AWS Graviton2",[747],"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":752,"featured":6,"template":686},"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":758,"_dir":244,"_draft":6,"_partial":6,"_locale":7,"seo":759,"content":765,"config":774,"_id":776,"_type":14,"title":777,"_source":16,"_file":778,"_stem":779,"_extension":19},"/en-us/blog/actioning-security-vulnerabilities-in-gitlab-premium",{"title":760,"description":761,"ogTitle":760,"ogDescription":761,"noIndex":6,"ogImage":762,"ogUrl":763,"ogSiteName":673,"ogType":674,"canonicalUrls":763,"schema":764},"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":760,"description":761,"authors":766,"heroImage":762,"date":769,"body":770,"category":771,"tags":772},[767,768],"Sam Morris","Noah Ing","2023-03-13","\n\nGitLab Premium features several security scanners you can leverage to detect vulnerabilities. However, when you incorporate the scanners into your project pipelines and the scanning job succeeds, you'll want feedback on whether you are introducing vulnerabilities into the codebase. This tutorial provides a mechanism to require a merge request approval if a scanner available on GitLab Premium finds a critical vulnerability.\n\n*While this tutorial shows how to add some process around actioning vulnerabilities, we have more robust, governed, and user-friendly functionality available in GitLab Ultimate called a [Scan Result Policy](https://docs.gitlab.com/ee/user/application_security/policies/scan-result-policies.html). The solution outlined here does not seek to replace that functionality, but rather augment the scan results available in GitLab Premium. If you are an Ultimate user or if you want to compare the two experiences, then you should check out [this video introduction](https://www.youtube.com/watch?v=w5I9gcUgr9U&ab_channel=GitLabUnfiltered) instead.*\n\nLearn how to do the following:\n\n1. Set up a .gitlab-ci.yml\n2. Add in a vulnerability processing script\n3. Require approval if vulnerabilities are found \n\n### Prerequisites\n\n- A project with GitLab Premium\n- A gitlab-ci.yml\n- A project access token\n- Basic knowledge of Python\n- 5 minutes (or less)\n\n## Setup the gitlab-ci.yml \n\nThis is how the GitLab CI pipeline of our test project looks visually. Below we will break down the individual stages.\n\nAdd the following to your .gitlab-ci.yml:\n\n```yaml\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\nA breakdown of what is going on above:\n- gl-secret-detection-report.json needs to be overriden so it’s being stored as an artifact in the secret_detection job.\n- The process_secret_detection job is dependent on secret_detection's artifact so we have added a needs keyword requiring successful completion of the secret_detection job.\n- pip installs the python-gitlab dependency so that the process_vulns.py can leverage GitLab API calls.\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\nTo create a project access token:\n1. On the top bar, select Main menu > Projects and find your project.\n2. On the left sidebar, select Settings > Access Tokens.\n3. Enter a name. The token name is visible to any user with permissions to view the project.\n4. Optional. Enter an expiry date for the token. The token expires on that date at midnight UTC. An instance-wide maximum lifetime setting can limit the maximum allowable lifetime in self-managed instances.\n5. Select a role for the token.\n6. Select the desired scopes.\n7. Select Create project access token.\n8. Add this newly created project access token to your CI/CD variables in your project settings!\n\n## Add in the vulnerability processing script\n\n[The process_vulns.py script can be found here.]((https://gitlab.com/gl-demo-premium-smorris/secure-premium-app/-/blob/main/process_vulns.py) Copy that file into your project.\n\nThe goal of this script is to require approval from an author (or group of authors) if a critical vulnerability is found.\n\n**Note:** You will need to [change the user ID in the process_vulns.py](https://gitlab.com/gl-demo-premium-smorris/secure-premium-app/-/blame/main/process_vulns.py#L40) to match the user ID of your designated Approver at your organization.\n\nThe following is a breakdown of what the script is doing:\n\n- JSON security reports are loaded in, if there any vulnerabilities they are parsed.\n- An authentication with GitLab is run using the project access token to interact with the project.\n- If vulnerabilities are not found, then it will print to the GitLab CI Logs: “No vulnerabilities are found.”\n- If a critical vulnerability is found, then it will require an approval.\n\nRun the pipeline and voila! Your pipeline now requires approvers if a critical vulnerability is found!\n\n### Demo\n\nWatch a video demonstration of how to action security vulnerabilities in GitLab Premium, presented by Sam Morris:\n\n\u003Ciframe width=\"560\" height=\"315\" src=\"https://www.youtube.com/embed/Cld36OZrLFo\" title=\"YouTube video player\" frameborder=\"0\" allow=\"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share\" allowfullscreen>\u003C/iframe>\n\n#### Caveats\n- This is mimicking a Scan Result Policy; it is not a replacement.\n- This currently only requires approval for a critical vulnerability, and each new rule would have to be added to the script.\n- This script lives within the same location as your project, so there is no restriction on who can modify the script, breaking separation of duties at scale.\n- Approval rules are not removed once the vulnerability is fixed.\n- Approvers' IDs need to be hardcoded and maintained in the script file.\n- Since there is no vulnerability record generated, you cannot track the vulnerabilities over time in your application.\n- Vulnerabilities are not fed into a report or security dashboard, so this only reports merge request vulnerabilities.\n\n## References\n- [Create a project access token](https://docs.gitlab.com/ee/user/project/settings/project_access_tokens.html#create-a-project-access-token)\n- [Setting up CI/CD variables](https://docs.gitlab.com/ee/ci/variables/#define-a-cicd-variable-in-the-ui)\n- [Secure Premium app project](https://gitlab.com/gl-demo-premium-smorris/secure-premium-app/-/blob/main/process_vulns.py)\n\n## Related posts\n- [GitLab's commitment to enhanced application security in the modern DevOps world](/blog/security-gitlab-15/)\n- [How to become more productive with Gitlab CI](/blog/how-to-become-more-productive-with-gitlab-ci/)\n- [GitLab CI DRY Development](/blog/keeping-your-development-dry/)\n\n_Cover image by [Christopher Burns](https://unsplash.com/@christopher__burns?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText) on [Unsplash](https://www.unsplash.com)._","security",[771,773,9,683,729],"DevSecOps",{"slug":775,"featured":6,"template":686},"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":781,"_dir":244,"_draft":6,"_partial":6,"_locale":7,"seo":782,"content":788,"config":796,"_id":798,"_type":14,"title":799,"_source":16,"_file":800,"_stem":801,"_extension":19},"/en-us/blog/amazon-linux-2-support-and-distro-specific-packages",{"title":783,"description":784,"ogTitle":783,"ogDescription":784,"noIndex":6,"ogImage":785,"ogUrl":786,"ogSiteName":673,"ogType":674,"canonicalUrls":786,"schema":787},"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":783,"description":784,"authors":789,"heroImage":785,"date":790,"body":791,"category":792,"tags":793},[678],"2022-05-02","\n\nGitLab’s Distribution Engineering team has been hard at work getting Amazon Linux 2 distro-specific packages ready in preparation for GitLab’s official support of Amazon Linux 2. Starting with Version 15.0 of GitLab, Amazon Linux 2 is a supported distro and packages are available for both x86 and Graviton ARM architectures.\n\n## What is Amazon Linux 2?\n\nAmazon Linux 2 is the next-generation Amazon Linux operating system that provides a modern application environment with the most recent enhancements from the Linux community alongside long-term support. Amazon Linux 2 is accessible as a virtual machine image for on-premises development and testing. This lets you easily develop, test, and certify your applications right from your local development environment. \n\nAccording to the AWS FAQ page for Amazon Linux 2, the primary elements of this latest version of the operating system include:\n\n1. A Linux kernel tuned for performance on Amazon EC2.\n\n2. A set of core packages including systemd, GCC 7.3, Glibc 2.26, Binutils 2.29.1 that receive Long Term Support (LTS) from [AWS](/blog/deploy-aws/).\n\n3. An extras channel for rapidly evolving technologies that are likely to be updated frequently and outside the Long Term Support (LTS) model.\n\nAmazon Linux 2 has a support lifespan through June 20, 2024, to allow enough time for users to migrate to Amazon Linux 2022.\n\n\n## Safely moving forward to Amazon Linux 2 packages from EL7\n\nWhile Amazon Linux 2 has not been officially supported before 15.0, as a convenience to customers who wanted to use yum and RPM packages to install the EL7 packages, GitLab configured a workaround in our packaging services to direct Amazon Linux 2 yum requests to the EL7 packages. If you’ve been using GitLab’s yum repo registration script, you many not know that you were using EL7 packages and not distro-specific packages.\n\nThis workaround will be deprecated and requests from Amazon Linux 2 will get the distro-specific packages with the release of GitLab 15.3.0 on August 22, 2022.\n\nAs a convenience for those of you who have automation that depends directly on this workaround, we wanted to provide you with information on 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.\n\nGitLab documentation demonstrates how to call our managed yum repository setup scripts by downloading the latest copy and running it directly in [the instructions for installing instances](https://about.gitlab.com/install/#centos-7) and [the instructions for installing runners](https://docs.gitlab.com/runner/install/linux-repository.html).\n\nAny organization using GitLab’s EL 7 packages for Amazon Linux 2 will want to test with - and update to - the distro-specific packages as soon as possible as GitLab will only be testing Amazon Linux 2 with the Amazon Linux 2 specific packages going forward.\n\nWe also understand that the timing of the testing and migration to these packages must be done in a coordinated cutover so that the package type does not change in your existing stacks without you having made any changes. This can be more important if a GitLab stack has undergone platform qualification for compliance purposes.\n\nAmazon Linux 2 specific packages are only available for GitLab 14.9.0 and later. If your automation depends directly on GitLab’s repo configuration script and it is still pegged to a GitLab version prior to 14.9.0 when this change becomes GA, then action must be taken to prevent breaking that automation. We have devised an idempotent two-line script solution that you can put in place now to prevent disruption if you are still on a pre-14.9.0 version at the time the new behavior of `script.rpm.sh` becomes GA on August 22, 2022 with the release of GitLab 15.3.0.\n\nGitLab rake-based backup and restore will continue to work seamlessly across the distro-specific package changes if you have to restore to your Amazon Linux 2 built stack from an EL7 backup. If you are using third-party backup, you may wish to trigger a new backup immediately after transitioning to the new distro packages to avoid the scenario altogether.\n\n## Amazon Linux 2 packages for building GitLab instances before 15.3.0\n\nThe following code inserts two lines of code between those originally outlined in [the instructions for installing using RPM packages](/install/#centos-7). The first one (starts with `sed`) splices in the Amazon Linux 2 yum repo endpoint edits into the repository configuration file created by script.rpm.sh. The second one (starts with `if yum`) cleans the yum cache if the package was already installed so that the new location will be used.\n\n> Sudo note: If you are using these commands interactively under the default SSH or SSM session manager user, then using `sudo su` before running this code is necessary. If you are using these commands in Infrastructure as Code (e.g. CloudFormation userdata scripts), then sudo may cause ‘command not found’ errors when the user running automation is already root equivalent. Be mindful about using interactively tested commands directly in your automation.\n\n```bash\n#Existing packaging script from https://about.gitlab.com/install/#centos-7\ncurl https://packages.gitlab.com/install/repositories/gitlab/gitlab-ee/script.rpm.sh | sudo bash\n\n#Patch to preview and/or peg Amazon Linux 2 specific packages\nsed -i \"s/\\/el\\/7/\\/amazon\\/2/g\" /etc/yum.repos.d/gitlab_gitlab*.repo\n\n#Reset the cache if the package was previously installed (not needed for installs onto a clean machine)\nif yum list installed gitlab-ee; then yum clean all ; yum makecache; fi\n\n#Existing install command (remove \"-y\" to validate package and arch mapping before install)\nyum install gitlab-ee -y\n```\n\n> Notice in this output that the **Version** ends in `.amazon2`. In this case the **Arch** is `aarch64` - indicating 64-bit Graviton ARM.\n\n![Resolved GitLab Dependencies](https://about.gitlab.com/images/blogimages/2022-04-amazon-linux-2/gl-instance-dependencies-resolved.png)\n\n### Moving to Amazon Linux 2 packages early for a seamless post-GA transition\n\nWhen the script.rpm.sh script is cut over to always point Amazon Linux 2 to the new distro-specific packages, the sed command will no longer be necessary. However, sed is also idempotent and will not make edits if the search text is not found. This means you can use the sed command to switch over early, but not have to worry about a breaking change when the `script.rpm.sh` is updated.\n\n### Pegging EL 7 and/or a GitLab version prior to 14.9.0 for a seamless post-GA transition\n\nIf your automation is pegged to an earlier version of GitLab, you will need to keep using EL7 packages, and, in fact, after the cutover you would need to implement the opposite command (which is also idempotent to be implemented now).\n\n```bash\n#Patch to peg GitLab Version to EL 7 Packages (only does something after GA of gitlab repo script)\nsed -i \"s/\\/amazon\\/2/\\/el\\/7/g\" /etc/yum.repos.d/gitlab_gitlab*.repo\n\n#Reset the cache if the package was previously installed (not needed for installs onto a clean machine)\nif yum list installed gitlab-ee; then yum clean all ; yum makecache; fi\n```\n\nJust like the sed command for taking distro-specific packages early, this command can be implemented immediately with no bad effects - which will seamlessly keeping your automation pegged to the EL 7 packages when `script.rpm.sh` is updated.\n\n## Amazon Linux 2 package for building GitLab Runners before 15.3.0\n\nThe following code inserts two lines of code between those originally [outlined in the instructions](https://docs.gitlab.com/runner/install/linux-repository.html). The first one (starts with `sed`) splices in the Amazon Linux 2 yum repo endpoint edits into the repository configuration file created by script.rpm.sh. The second one (starts with `if yum`) cleans the yum cache if the package was already installed so that the new location will be used.\n\n> Sudo note: If you are using these commands interactively under the default SSH or SSM session manager user, then using `sudo su` before running this code is necessary. If you are using these commands in Infrastructure as Code (e.g. CloudFormation userdata scripts), then sudo may cause ‘command not found’ errors when the user running automation is already root equivalent. Be mindful about using interactively tested commands directly in your automation.\n\n```bash\n#Existing packaging script from https://docs.gitlab.com/runner/install/linux-repository.html\ncurl -L \"https://packages.gitlab.com/install/repositories/runner/gitlab-runner/script.rpm.sh\" | sudo bash\n\n#Patch to test or peg Amazon Linux 2 specific packages\nsed -i \"s/\\/el\\/7/\\/amazon\\/2/g\" /etc/yum.repos.d/runner_gitlab*.repo\n\n#Reset the cache if the package was previously installed (not needed for installs onto a clean machine)\nif yum list installed gitlab-runner; then yum clean all ; yum makecache; fi\n\n#Existing install command (remove \"-y\" to validate package and arch mapping before install)\nyum install gitlab-runner -y\n```\n\n> Notice in this output that **Version** is not distro-specific. In this case the **Arch** is `aarch64` - indicating 64-bit Graviton ARM.\n\n![Resolved GitLab Runner Dependencies](https://about.gitlab.com/images/blogimages/2022-04-amazon-linux-2/gl-runner-dependencies-resolved.png)\n\n## Pegging to EL 7 and/or a GitLab Runner version prior to 14.9.1 for a seamless post-GA transition\n\nThe underlying package for EL 7 and Amazon Linux 2 is literally a copy of the same package. However, the Amazon Linux 2 endpoint for Runner RPM packages have only been uploaded from GitLab Runner 14.9.1 and later, so if you have runners that need to be on an earlier version, you would need to stay pointed at EL 7 for those packages to continue to resolve as available. The following code shows how to do that for GitLab Runner.\n\n```bash\n#Patch to peg GitLab Version to EL 7 Packages (only does something after GA of gitlab repo script)\nsed -i \"s/\\/amazon\\/2/\\/el\\/7/g\" /etc/yum.repos.d/runner_gitlab*.repo\n\n#Reset the cache if the package was previously installed (not needed for installs onto a clean machine)\nif yum list installed gitlab-runner; then yum clean all ; yum makecache; fi\n```\n\n## Need-to-know takeaways\n\n- Amazon Linux 2 is a supported distro for GitLab instances and runner as of the release of version 15.0 on May 22, 2022.\n- Amazon Linux 2 packages are available for x86 and ARM for GitLab Version 14.9.0 and higher. (Prior to 14.9.0 the EL7 packages must be used as they have a long version history).\n- This is the first availability of ARM RPM packages of GitLab for Amazon Linux 2.\n- In 15.3 (August 22, 2022), the script.rpm.sh will automatically start directing to the Amazon Linux 2 packages where it had previously directed Amazon Linux 2 yum requests to the EL7 packages.\n- It is common to have taken a dependency directly on the latest version of this GitLab script in other automation.\n- Before the GA cutover date of August 22, 2022 (15.3.0 GitLab Release), for these scripts, you have the opportunity to pre-test these packages and determine whether they create any issues with your automation or GitLab configuration.\n- You can also peg to the Amazon Linux 2 packages early or peg to the EL7 packages in advance if you find problems that you need more time to resolve. Both of these pegging types are idempotent, meaning the code changes do not do anything that causes problems after the change over happens.\n- Existing Amazon Linux 2 installations that were installed using the EL7 packages can use a regular yum upgrade command to start using the new Amazon Linux 2 packages. This operation may also be an upgrade of the product version at the same time. For existing installations you will need to patch the yum repo files as explained in this article in order to upgrade directly to Amazon Linux 2 from EL7 using packages. \n\n> **Note**\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![AWS Partner Logo](https://about.gitlab.com/images/blogimages/2022-04-amazon-linux-2/awsgravitonready.png){: .right}\n","news",[794,9,683,729,795],"releases","AWS",{"slug":797,"featured":6,"template":686},"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":803,"_dir":244,"_draft":6,"_partial":6,"_locale":7,"seo":804,"content":810,"config":816,"_id":818,"_type":14,"title":819,"_source":16,"_file":820,"_stem":821,"_extension":19},"/en-us/blog/application-modernization-best-practices",{"title":805,"description":806,"ogTitle":805,"ogDescription":806,"noIndex":6,"ogImage":807,"ogUrl":808,"ogSiteName":673,"ogType":674,"canonicalUrls":808,"schema":809},"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":805,"description":806,"authors":811,"heroImage":807,"date":813,"body":814,"category":704,"tags":815},[812],"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,728],{"slug":817,"featured":6,"template":686},"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":823,"_dir":244,"_draft":6,"_partial":6,"_locale":7,"seo":824,"content":830,"config":840,"_id":842,"_type":14,"title":843,"_source":16,"_file":844,"_stem":845,"_extension":19},"/en-us/blog/automate-to-accelerate-webcast-recap",{"title":825,"description":826,"ogTitle":825,"ogDescription":826,"noIndex":6,"ogImage":827,"ogUrl":828,"ogSiteName":673,"ogType":674,"canonicalUrls":828,"schema":829},"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":831,"description":826,"authors":832,"heroImage":827,"date":834,"body":835,"category":836,"tags":837},"Automate to accelerate: What you need to know about test and release automation",[833],"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",[728,838,9,839],"testing","webcast",{"slug":841,"featured":6,"template":686},"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":847,"_dir":244,"_draft":6,"_partial":6,"_locale":7,"seo":848,"content":854,"config":860,"_id":862,"_type":14,"title":863,"_source":16,"_file":864,"_stem":865,"_extension":19},"/en-us/blog/automating-a-twitter-bot-using-gitlab-cicd",{"title":849,"description":850,"ogTitle":849,"ogDescription":850,"noIndex":6,"ogImage":851,"ogUrl":852,"ogSiteName":673,"ogType":674,"canonicalUrls":852,"schema":853},"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":849,"description":850,"authors":855,"heroImage":851,"date":857,"body":858,"category":704,"tags":859},[856],"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",[773,9,683,729],{"slug":861,"featured":6,"template":686},"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":867,"_dir":244,"_draft":6,"_partial":6,"_locale":7,"seo":868,"content":874,"config":880,"_id":882,"_type":14,"title":883,"_source":16,"_file":884,"_stem":885,"_extension":19},"/en-us/blog/autoscale-continuous-deployment-gitlab-runner-digital-ocean",{"title":869,"description":870,"ogTitle":869,"ogDescription":870,"noIndex":6,"ogImage":871,"ogUrl":872,"ogSiteName":673,"ogType":674,"canonicalUrls":872,"schema":873},"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":869,"description":870,"authors":875,"heroImage":871,"date":877,"body":878,"category":704,"tags":879},[876],"Owen Williams","2018-06-19","\n\n[GitLab CI/CD](/solutions/continuous-integration/) is an effective way to build the habit of testing all code before it’s deployed. GitLab CI/CD is also highly scalable thanks to an additional tool, GitLab Runner, which automates scaling your build queue in order to avoid long wait times for development teams trying to release code.\n\nIn this guide, we will demonstrate how to configure a highly scalable GitLab infrastructure that manages its own costs, and automatically responds to load by increasing and decreasing available server capacity.\n\n## Goals\n\nWe’re going to build a scalable CI/CD process on DigitalOcean that automatically responds to demand by creating new servers on the platform and destroys them when the queue is empty.\n\nThese reusable servers are spawned by the GitLab Runner process and are automatically deleted when no jobs are running, reducing costs and administration overhead for your team.\n\nAs we’ll explain in this tutorial, you are in control of how many machines are created at any given time, as well as the length of time they’re retained before being destroyed.\n\nWe’ll be using three separate servers to build this project, so let’s go over terminology first:\n\n* **GitLab**: Your hosted GitLab instance or self-managed instance where your code repositories are stored.\n\n* **GitLab Bastion**: The *bastion* server or Droplet is the core of what we’ll be configuring. It is the control instance that is used to interact with the DigitalOcean API to create Droplets and destroy them when necessary. No jobs are executed on this server.\n\n* **GitLab Runner**: Your *runners* are transient servers or Droplets that are created on the fly by the *bastion* server when needed to execute a CI/CD job in your build queue. These servers are disposable, and are where your code is executed or tested before your build is marked as passing or failing.\n\n![GitLab Runners Diagram](https://assets.digitalocean.com/articles/gitlab-runner/Autoscaling-GitLab-Runners.png){: .medium.center}\n\nBy leveraging each of the GitLab components, the CI/CD process will enable you to scale responsively based on demands. With these goals in mind, we are ready to begin setting up our [continuous deployment](/topics/ci-cd/) with GitLab and DigitalOcean.\n\n## Prerequisites\n\nThis tutorial will assume you have already configured GitLab on your own server or through the hosted service, and that you have an existing DigitalOcean account.\n\nTo set this up on an Ubuntu 16.04 Droplet, you can use the DigitalOcean one-click image, or follow our guide: “[How To Install and Configure GitLab on Ubuntu 16.04](https://www.digitalocean.com/community/tutorials/how-to-install-and-configure-gitlab-on-ubuntu-16-04).”\n\nFor the purposes of this tutorial, we assume you have private networking enabled on this Droplet, which you can achieve by following our guide on “[How To Enable DigitalOcean Private Networking on Existing Droplets](https://www.digitalocean.com/community/tutorials/how-to-enable-digitalocean-private-networking-on-existing-droplets),” but it is not compulsory.\n\nThroughout this tutorial, we’ll be using non-root users with admin privileges on our Droplets.\n\n## Step 1: Import JavaScript project\nTo begin, we will create a new example project in your existing GitLab instance containing a sample Node.js application.\n\n![GitLab interface](https://assets.digitalocean.com/articles/gitlab-runner/gitlab.jpg){: .shadow.large.center}\n\nLog into your GitLab instance and click the **plus icon**, then select **New project** from the dropdown menu.\n\nOn the new project screen, select the **Import project** tag, then click **Repo by URL** to import our example project directly from GitHub.\n\nPaste the below clone URL into the Git repository URL:\n\n```bash\nhttps://github.com/do-community/hello_hapi.git\n```\n\nThis repository is a basic JavaScript application for the purposes of demonstration, which we won’t be running in production. To complete the import, click the **New Project** button.\n\nYour new project will now be in GitLab and we can get started setting up our CI pipeline.\n\n## Step 2: Set up infrastructure\n\nOur GitLab Code Runner requires specific configuration as we’re planning to programmatically create Droplets to handle CI load as it grows and shrinks.\n\nWe will create two types of machines in this tutorial: a **bastion** instance, which controls and spawns new machines, and our **runner** instances, which are temporary servers spawned by the bastion Droplet to build code when required. The bastion instance uses Docker to create your runners.\n\nHere are the DigitalOcean products we’ll use, and what each component is used for:\n\n* **Flexible Droplets** — We will create memory-optimized Droplets for our GitLab Runners as it’s a memory-intensive process which will run using Docker for containerization. You can shrink or grow this Droplet in the future as needed, however we recommend the flexible Droplet option as a starting point to understand how your pipeline will perform under load.\n\n* **DigitalOcean Spaces (Object Storage)** — We will use [DigitalOcean Spaces](https://www.digitalocean.com/products/spaces/) to persist cached build components across your runners as they’re created and destroyed. This reduces the time required to set up a new runner when the CI pipeline is busy, and allows new runners to pick up where others left off immediately.\n\n* **Private Networking** — We will create a private network for your bastion Droplet and GitLab runners to ensure secure code compilation and to reduce firewall configuration required.\n\nTo start, we’ll create the bastion Droplet. Create a [new Droplet](https://cloud.digitalocean.com/droplets/new), then under **choose an image**, select the **One-click apps** tab. From there, select **Docker 17.12.0-ce on 16.04** (note that this version is current at the time of writing), then choose the smallest Droplet size available, as our bastion Droplet will manage the creation of other Droplets rather than actually perform tests.\n\nIt is recommended that you create your server in a data center that includes  [DigitalOcean Spaces](https://www.digitalocean.com/community/tutorials/an-introduction-to-digitalocean-spaces) in order to use the object storage caching features mentioned earlier.\n\nSelect both the **Private networking** and **Monitoring** options, then click **Create Droplet**.\n\nWe also need to set up our storage space which will be used for caching. Follow the steps in “[How To Create a DigitalOcean Space and API Key](https://www.digitalocean.com/community/tutorials/how-to-create-a-digitalocean-space-and-api-key)” to create a new Space in the same or nearest data center as your hosted GitLab instance, along with an API Key.\n\nNote this key down, as we’ll need it later in the tutorial.\n\nNow it’s time to get our CI started!\n\n## Step 3: Configure the GitLab Runner Bastion Server\n\nWith the fresh Droplet ready, we can now configure GitLab Runner. We’ll be installing scripts from GitLab and GitHub repositories.\n\nAs a best practice, be sure to inspect scripts to confirm what you will be installing prior to running the full commands below.\n\nConnect to the Droplet using SSH, move into the `/tmp` directory, then add the [official GitLab Runner repository](https://docs.gitlab.com/runner/install/linux-repository.html) to Ubuntu’s package manager:\n\n```bash\ncd /tmp\ncurl -L https://packages.gitlab.com/install/repositories/runner/gitlab-runner/script.deb.sh | sudo bash\n```\n\nOnce added, install the GitLab Runner application:\n\n```bash\nsudo apt-get install gitlab-runner\n```\n\nWe also need to install **[Docker Machine](https://docs.docker.com/machine/install-machine/#install-machine-directly)**, which is an additional Docker tool that assists with automating the deployment of containers on cloud providers:\n\n```bash\ncurl -L https://github.com/docker/machine/releases/download/v0.14.0/docker-machine-`uname -s`-`uname -m` >/tmp/docker-machine && \\\nsudo install /tmp/docker-machine /usr/local/bin/docker-machine\n```\n\nWith these installations complete, we can move on to connecting our GitLab Runner to our GitLab install.\n\n## Step 4: Obtain Runner registration token\n\nTo link GitLab Runner to your existing GitLab install, we need to link the two instances together by obtaining a token that authenticates your runner to your code repositories.\n\nLogin to your existing GitLab instance as the admin user, then click the wrench icon to enter the admin settings area.\n\nOn the left of your screen, hover over **Overview** and select **Runners** from the list that appears.\n\nOn the Runners page under the **How to set up a shared Runner for a new project** section, copy the token shown in Step 3, and make a note of it along with the publicly accessible URL of your GitLab instance from Step 2. If you are using HTTPS for Gitlab, make sure it is not a self-signed certificate, or GitLab Runner will fail to start.\n\n## Step 5: Configure GitLab on the Bastion Droplet\n\nBack in your SSH connection with your bastion Droplet, run the following command:\n\n```bash\nsudo gitlab-runner register\n```\n\nThis will initiate the linking process, and you will be asked a series of questions.\n\nOn the next step, enter the **GitLab instance URL** from the previous step:\n\n```bash\nPlease enter the gitlab-ci coordinator URL (e.g. https://gitlab.com)\nhttps://example.digitalocean.com\n```\n\nEnter the token you obtained from your GitLab instance:\n\n```bash\nPlease enter the gitlab-ci token for this runner\nsample-gitlab-ci-token\n```\n\nEnter a description that will help you recognize it in the GitLab web interface. We recommend naming this instance something unique, like `runner-bastion` for clarity.\n\n```bash\nPlease enter the gitlab-ci description for this runner\n[yourhostname] runner-bastion\n```\n\nIf relevant, you may enter the tags for code you will build with your runner. However, we recommend this is left blank at this stage. This can easily be changed from the GitLab interface later.\n\n```bash\nPlease enter the gitlab-ci tags for this runner (comma separated):\ncode-tag\n```\n\nChoose whether or not your runner should be able to run untagged jobs. This setting allows you to choose whether your runner should build repositories with no tags at all, or require specific tags. Select true in this case, so your runner can execute all repositories.\n\n```bash\nWhether to run untagged jobs [true/false]: true\n```\n\nChoose if this runner should be shared among your projects, or locked to the current one, which blocks it from building any code other than those specified. Select false for now, as this can be changed later in GitLab’s interface:\n\n```bash\nWhether to lock Runner to current project [true/false]: false\n```\n\nChoose the executor which will build your machines. Because we’ll be creating new Droplets using Docker, we’ll choose `docker+machine` here, but you can read more about the advantages of each approach in this [compatibility chart](https://docs.gitlab.com/runner/executors/README.html#compatibility-chart):\n\n```bash\nPlease enter the executor: ssh, docker+machine, docker-ssh+machine, kubernetes, docker, parallels, virtualbox, docker-ssh, shell:\ndocker+machine\n```\n\nYou’ll be asked which image to use for projects that don’t explicitly define one. We’ll choose a basic, secure default:\n\n```bash\nPlease enter the Docker image (e.g. ruby:2.1):\nalpine:latest\n```\n\nNow you’re done configuring the core bastion runner! At this point it should appear within the GitLab Runner page of your GitLab admin settings, which we accessed to obtain the token.\n\nIf you encounter any issues with these steps, the [GitLab Runner documentation](https://docs.gitlab.com/runner/register/index.html) includes options for troubleshooting.\n\n## Step 6: Configure Docker caching and Docker Machine\nTo speed up Droplet creation when the build queue is busy, we’ll leverage Docker’s caching tools on the Bastion Droplet to store the images for your commonly used containers on DigitalOcean Spaces.\n\nTo do so, upgrade Docker Machine on your SSH shell using the following command:\n\n```bash\ncurl -L https://github.com/docker/machine/releases/download/v0.14.0/docker-machine-`uname -s`-`uname -m` >/tmp/docker-machine && sudo install /tmp/docker-machine /usr/local/bin/docker-machine\n```\n\nWith Docker Machine upgraded, we can move on to setting up our access tokens for GitLab Runner to use.\n\n## Step 7: Gather DigitalOcean credentials\n\nNow we need to create the credentials that GitLab Runner will use to create new Droplets using your DigitalOcean account.\n\nVisit your DigitalOcean [dashboard](https://cloud.digitalocean.com) and click **API**. On the next screen, look for **Personal access tokens** and click **Generate New Token**.\n\nGive the new token a name you will recognize such as `GitLab Runner Access` and ensure that both the read and write scopes are enabled, as we need the Droplet to create new machines without human intervention.\n\nCopy the token somewhere safe as we’ll use it in the next step. You can’t retrieve this token again without regenerating it, so be sure it’s stored securely.\n\n## Step 8: Edit GitLab Runner configuration files\nTo bring all of these components together, we need to finish configuring our bastion Droplet to communicate with your DigitalOcean account.\n\nIn your SSH connection to your bastion Droplet, use your favorite text editor, such as nano, to open the GitLab Runner configuration file for editing:\n\n```bash\nnano /etc/gitlab-runner/config.toml\n```\n\nThis configuration file is responsible for the rules your CI setup uses to scale up and down on demand. To configure the bastion to autoscale on demand, you need to add the following lines:\n\n```bash\nconcurrent = 50   # All registered Runners can run up to 50 concurrent builds\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\nOnce you’ve added the new lines, customize the access token, region and Droplet size based on  your setup. For the purposes of this tutorial, we’ve used the smallest Droplet size of 1GB and created our Droplets in NYC3. Be sure to use the information that is relevant in your case.\n\nYou also need to customize the cache component, and enter your Space’s server address from the infrastructure configuration step, access key, secret key and the name of the Space that you created.\n\nWhen completed, restart GitLab Runner to make sure the configuration is being used:\n\n```bash\ngitlab-runner restart\n```\n\nIf you would like to learn about more all available options, including off-peak hours, you can read [GitLab’s advanced documentation](https://docs.gitlab.com/runner/configuration/autoscale.html).\n\n## Step 9 — Test Your GitLab Runner\n\nAt this point, our GitLab Runner bastion Droplet is configured and is able to create DigitalOcean Droplets on demand, as the CI queue fills up. We’ll need to test it to be sure it works by heading to your GitLab instance and the project we imported in Step 1.\n\nTo trigger a build, edit the `readme.md` file by clicking on it, then clicking **edit**, and add any relevant testing text to the file, then click **Commit changes**.\n\nNow a build will be automatically triggered, which can be found under the project’s **CI/CD** option in the left navigation.\n\nOn this page you should see a pipeline entry with the status of **running**. In your DigitalOcean account, you’ll see a number of Droplets automatically created by GitLab Runner in order to build this change.\n\nCongratulations! Your CI pipeline is cloud scalable and now manages its own resource usage. After the specified idle time, the machines should be automatically destroyed, but we recommend verifying this manually to ensure you aren’t unexpectedly billed.\n\n## Troubleshooting\n\nIn some cases, GitLab may report that the runner is unreachable and as a result perform no actions, including deploying new runners. You can troubleshoot this by stopping GitLab Runner, then starting it again in debug mode:\n\n```bash\ngitlab-runner stop\ngitlab-runner --debug start\n```\n\nThe output should throw an error, which will be helpful in determining which configuration is causing the issue.\n\nIf your configuration creates too many machines, and you wish to remove them all at the same time, you can run this command to destroy them all:\n\n```bash\ndocker-machine rm $(docker-machine ls -q)\n```\nFor more troubleshooting steps and additional configuration options, you can refer to [GitLab’s documentation](https://docs.gitlab.com/runner/).\n\n## Conclusion\n\nYou've successfully set up an automated CI/CD pipeline using GitLab Runner and Docker. From here, you could configure higher levels of caching with Docker Registry to optimize performance or explore the use of tagging code builds to specific GitLab code runners.\n\nFor more on GitLab Runner, [see the detailed documentation](https://docs.gitlab.com/runner/), or to learn more, you can read [GitLab’s series of blog posts](https://docs.gitlab.com/ee/ci/) on how to make the most of your continuous integration pipeline.\n\n[This post was originally published by DigitalOcean](https://www.digitalocean.com/community/tutorials/how-to-autoscale-gitlab-continuous-deployment-with-gitlab-runner-on-digitalocean) and is licensed under [CC BY-NC-SA 4.0](https://creativecommons.org/licenses/by-nc-sa/4.0/).\n{: .note}\n",[9,231],{"slug":881,"featured":6,"template":686},"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":887,"_dir":244,"_draft":6,"_partial":6,"_locale":7,"seo":888,"content":894,"config":904,"_id":906,"_type":14,"title":907,"_source":16,"_file":908,"_stem":909,"_extension":19},"/en-us/blog/aws-reinvent-recap",{"title":889,"description":890,"ogTitle":889,"ogDescription":890,"noIndex":6,"ogImage":891,"ogUrl":892,"ogSiteName":673,"ogType":674,"canonicalUrls":892,"schema":893},"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":889,"description":890,"authors":895,"heroImage":891,"date":897,"body":898,"category":299,"tags":899},[896],"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,267,900,277,792,901,902,903],"demo","kubernetes","inside GitLab","open source",{"slug":905,"featured":6,"template":686},"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":911,"_dir":244,"_draft":6,"_partial":6,"_locale":7,"seo":912,"content":918,"config":926,"_id":928,"_type":14,"title":929,"_source":16,"_file":930,"_stem":931,"_extension":19},"/en-us/blog/basics-of-gitlab-ci-updated",{"title":913,"description":914,"ogTitle":913,"ogDescription":914,"noIndex":6,"ogImage":915,"ogUrl":916,"ogSiteName":673,"ogType":674,"canonicalUrls":916,"schema":917},"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":919,"description":914,"authors":920,"heroImage":915,"date":922,"body":923,"category":704,"tags":924,"updatedDate":925},"The basics of CI: How to run jobs sequentially, in parallel, or out of order",[921],"Itzik Gan Baruch","2020-12-10","Let's assume that you don't know anything about [continuous integration (CI)](/topics/ci-cd/) and [why it's needed](/blog/how-to-keep-up-with-ci-cd-best-practices/) in the software development lifecycle.\n\nImagine that you work on a project, where all the code consists of two text files. Moreover, it is super critical that the concatenation of these two files contains the phrase \"Hello, world.\"\n\nIf it's not there, the whole development team won't get paid that month. Yeah, it is that serious!\n\nThe most responsible software developer wrote a small script to run every time we are about to send our code to customers.\n\nThe code is pretty sophisticated:\n\n```bash\ncat file1.txt file2.txt | grep -q \"Hello world\"\n```\n\nThe problem is that there are 10 developers on the team, and, you know, human factors can hit hard.\n\nA week ago, a new guy forgot to run the script and three clients got broken builds. So you decided to solve the problem once and for all. Luckily, your code is already on GitLab, and you remember that there is [built-in CI](/solutions/continuous-integration/). Moreover, you heard at a conference that people use CI to run tests...\n\n## Let's run our first test inside CI\n\nAfter taking a couple of minutes to find and read the docs, it seems like all we need is these two lines of code in a file called `.gitlab-ci.yml`:\n\n```yaml\ntest:\n  script: cat file1.txt file2.txt | grep -q 'Hello world'\n```\n\nWe commit it, and hooray! Our build is successful:\n\n![build succeeded](https://res.cloudinary.com/about-gitlab-com/image/upload/v1749674096/Blog/Content%20Images/build_succeeded.png)\n\nLet's change \"world\" to \"Africa\" in the second file and check what happens:\n\n![build failed](https://res.cloudinary.com/about-gitlab-com/image/upload/v1749674096/Blog/Content%20Images/build_failed.png)\n\nThe build fails as expected!\n\nOK, we now have automated tests here! GitLab CI will run our test script every time we push new code to the source code repository in the DevOps environment.\n\n**Note:** In the above example, we assume that file1.txt and file2.txt exist in the runner host.\n\nTo run this example in GitLab, use the below code that first will create the files and then run the script.\n\n```yaml\ntest:\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\nFor the sake of compactness, we will assume that these files exist in the host, and will not create them in the following examples.\n\n## Make results of builds downloadable\n\nThe next business requirement is to package the code before sending it to our customers. Let's automate that part of the software development process as well!\n\nAll we need to do is define another job for CI. Let's name the job \"package\":\n\n```yaml\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\nWe have two tabs now:\n\n![Two tabs - generated from two jobs](https://res.cloudinary.com/about-gitlab-com/image/upload/v1749674096/Blog/Content%20Images/two_tabs.png)\n\nHowever, we forgot to specify that the new file is a build _artifact_, so that it could be downloaded. We can fix it by adding an `artifacts` section:\n\n```yaml\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\nChecking... it is there:\n\n![Checking the download button](https://res.cloudinary.com/about-gitlab-com/image/upload/v1749674096/Blog/Content%20Images/artifacts.png)\n\nPerfect, it is! However, we have a problem to fix: The jobs are running in parallel, but we do not want to package our application if our tests fail.\n\n## Run jobs sequentially\n\nWe only want to run the 'package' job if the tests are successful. Let's define the order by specifying `stages`:\n\n```yaml\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\nThat should be good!\n\nAlso, we forgot to mention, that compilation (which is represented by concatenation in our case) takes a while, so we don't want to run it twice. Let's define a separate step for it:\n\n```yaml\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\nLet's take a look at our artifacts:\n\n![Unnecessary artifact](https://about.gitlab.com/images/blogimages/the-basics-of-gitlab-ci/clean-artifacts.png)\n\nHmm, we do not need that \"compile\" file to be downloadable. Let's make our temporary artifacts expire by setting `expire_in` to '20 minutes':\n\n```yaml\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\nNow our config looks pretty impressive:\n\n- We have three sequential stages to compile, test, and package our application.\n- We pass the compiled app to the next stages so that there's no need to run compilation twice (so it will run faster).\n- We store a packaged version of our app in build artifacts for further usage.\n\n## Learning which Docker image to use\n\nSo far, so good. However, it appears our builds are still slow. Let's take a look at the logs.\n\n![ruby3.1](https://res.cloudinary.com/about-gitlab-com/image/upload/v1749674096/Blog/Content%20Images/ruby-31.png)\n\nWait, what is this? Ruby 3.1?\n\nWhy do we need Ruby at all? Oh, GitLab.com uses Docker images to [run our builds](/blog/shared-runners/), and [by default](https://docs.gitlab.com/ee/user/gitlab_com/#shared-runners) it uses the [`ruby:3.1`](https://hub.docker.com/_/ruby/) image. For sure, this image contains many packages we don't need. After a minute of Googling, we figure out that there's an image called [`alpine`](https://hub.docker.com/_/alpine/), which is an almost blank Linux image.\n\nOK, let's explicitly specify that we want to use this image by adding `image: alpine` to `.gitlab-ci.yml`.\n\nNow we're talking! We shaved nearly three minutes off:\n\n![Build speed improved](https://about.gitlab.com/images/blogimages/the-basics-of-gitlab-ci/speed.png)\n\nIt looks like there are a lot of public images around:\n- [mysql](https://hub.docker.com/_/mysql/)\n- [Python](https://hub.docker.com/_/python/)\n- [Java](https://hub.docker.com/_/java/)\n- [php](https://hub.docker.com/_/php/)\n\nSo we can just grab one for our technology stack. It makes sense to specify an image that contains no extra software because it minimizes download time.\n\n## Dealing with complex scenarios\n\nSo far, so good. However, let's suppose we have a new client who wants us to package our app into `.iso` image instead of `.gz`. Since CI does all the work, we can just add one more job to it. ISO images can be created using the [mkisofs](http://www.w3big.com/linux/linux-comm-mkisofs.html) command. Here's how our config should look:\n\n```yaml\nimage: alpine\n\nstages:\n  - compile\n  - test\n  - package\n\n# ... \"compile\" and \"test\" jobs are skipped here for the sake of compactness\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\nNote that job names shouldn't necessarily be the same. In fact, if they were the same, it wouldn't be possible to make the jobs run in parallel inside the same stage of the software development process. Hence, think of same names of jobs and stages as coincidence.\n\nAnyhow, the build is failing:\n\n![Failed build because of missing mkisofs](https://about.gitlab.com/images/blogimages/the-basics-of-gitlab-ci/mkisofs.png)\n\nThe problem is that `mkisofs` is not included in the `alpine` image, so we need to install it first.\n\n## Dealing with missing software/packages\n\nAccording to the [Alpine Linux website](https://pkgs.alpinelinux.org/contents?file=mkisofs&path=&name=&branch=edge&repo=&arch=) `mkisofs` is a part of the `xorriso` and `cdrkit` packages. These are the magic commands that we need to run to install a package:\n\n```bash\necho \"ipv6\" >> /etc/modules  # enable networking\napk update                   # update packages list\napk add xorriso              # install package\n```\n\nFor CI, these are just like any other commands. The full list of commands we need to pass to `script` section should look like this:\n\n```yml\nscript:\n- echo \"ipv6\" >> /etc/modules\n- apk update\n- apk add xorriso\n- mkisofs -o ./packaged.iso ./compiled.txt\n```\n\nHowever, to make it semantically correct, let's put commands related to package installation in `before_script`. Note that if you use `before_script` at the top level of a configuration, then the commands will run before all jobs. In our case, we just want it to run before one specific job.\n\n## Directed Acyclic Graphs: Get faster and more flexible pipelines\n\nWe defined stages so that the package jobs will run only if the tests passed. What if we want to break the stage sequencing a bit, and run a few jobs earlier, even if they are defined in a later stage? In some cases, the traditional stage sequencing might slow down the overall pipeline execution time.\n\nImagine that our test stage includes a few more heavy tests that take a lot of time to execute, and that those tests are not necessarily related to the package jobs. In this case, it would be more efficient if the package jobs don't have to wait for those tests to complete before they can start. This is where Directed Acyclic Graphs (DAG) come in: To break the stage order for specific jobs, you can define job dependencies which will skip the regular stage order.\n\nGitLab has a special keyword `needs`, which creates dependencies between jobs, and allows jobs to run earlier, as soon as their dependent jobs complete.\n\nIn the below example, the pack jobs will start running as soon as the test job completes, so if, in future, someone adds more tests in the test stage, the package jobs will start to run before the new test jobs complete:\n\n```yaml\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\nOur final version of `.gitlab-ci.yml`:\n\n```yaml\nimage: alpine\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\nWow, it looks like we have just created a pipeline! We have three sequential stages, the jobs `pack-gz` and `pack-iso`, inside the `package` stage, are running in parallel:\n\n![Pipelines illustration](https://about.gitlab.com/images/blogimages/the-basics-of-gitlab-ci/pipeline.png)\n\n## Elevating your pipeline\n\nHere is how to elevate your pipeline.\n\n### Incorporating automated testing into CI pipelines\n\nIn DevOps, a key software development strategy rule is making really great apps with amazing user experience. So, let's add some tests in our CI pipeline to catch bugs early in the entire process. This way, we fix issues before they get big and before we move on to work on a new project.\n\nGitLab makes our lives easier by offering out-of-the-box templates for various [tests](https://docs.gitlab.com/ee/ci/testing/). All we need to do is include these templates in our CI configuration.\n\nIn this example, we will include [accessibility testing](https://docs.gitlab.com/ee/ci/testing/accessibility_testing.html):\n\n```yaml\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\nCustomize the `a11y_urls` variable to list the URLs of the web pages to test with [Pa11y](https://pa11y.org/) and [code quality](https://docs.gitlab.com/ee/ci/testing/code_quality.html).\n\n```yaml\n   include:\n   - template: Jobs/Code-Quality.gitlab-ci.yml\n```\n\nGitLab makes it easy to see the test report right in the merge request widget area. Having the code review, pipeline status, and test results in one spot makes everything smoother and more efficient.\n\n![Accessibility report](https://res.cloudinary.com/about-gitlab-com/image/upload/v1749674096/Blog/Content%20Images/Screenshot_2024-04-02_at_10.56.41.png)\n\u003Ccenter>\u003Ci>Accessibility merge request widget\u003C/i>\u003C/center>\u003Cp>\u003C/p>\n\n![Code quality widget in MR](https://res.cloudinary.com/about-gitlab-com/image/upload/v1749674096/Blog/Content%20Images/Screenshot_2024-04-02_at_11.00.25.png)\n\u003Ccenter>\u003Ci>Code quality merge request widget\u003C/i>\u003C/center>\n\n### Matrix builds\n\nIn some cases, we will need to test our app in different configurations, OS versions, programming language versions, etc. For those cases, we'll use the [parallel:matrix](https://docs.gitlab.com/ee/ci/yaml/#parallelmatrix) build to test our application across various combinations in parallel using one job configuration. In this blog, we'll test our code with different Python versions using the matrix keyword.\n\n```yaml\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\nDuring pipeline execution, this job will run in parallel four times, each time using different Python image as shown below:\n\n![Matrix job running](https://res.cloudinary.com/about-gitlab-com/image/upload/v1749674096/Blog/Content%20Images/Screenshot_2024-04-02_at_11.12.48.png)\n\n### Unit testing\n\n#### What are unit tests?\n\nUnit tests are small, targeted tests that check individual components or functions of software to ensure they work as expected. They are essential for catching bugs early in the software development process and verifying that each part of the code performs correctly in isolation.\n\nExample: Imagine you're developing a calculator app. A unit test for the addition function would check if 2 + 2 equals 4. If this test passes, it confirms that the addition function is working correctly.\n\n#### Unit testing best practices\n\nIf the tests fail, the pipeline fails and users get notified. The developer needs to check the job logs, which usually contain thousands of lines, and see where the tests failed so that they can fix them. This check is time-consuming and inefficient.\n\nYou can configure your job to use [unit test reports](https://docs.gitlab.com/ee/ci/testing/unit_test_reports.html). GitLab displays reports on the merge request and on the pipelines details page, making it easier and faster to identify the failure without having to check the entire log.\n\n##### JUnit test report\n\nThis is a sample JUnit test report:\n\n![pipelines JUnit test report v13 10](https://res.cloudinary.com/about-gitlab-com/image/upload/v1749674097/Blog/Content%20Images/pipelines_junit_test_report_v13_10.png){: .shadow.center}\n\n### Integration and end-to-end testing strategies\n\nIn addition to our regular development routine, it's super important to set up a special pipeline just for integration and end-to-end testing. This checks that all the different parts of our code work together smoothly, including those [microservices](https://about.gitlab.com/topics/microservices/), UI testing, and any other components.\n\nWe run these tests [nightly](https://docs.gitlab.com/ee/ci/pipelines/schedules.html). We can set it up so that the [results automatically get sent to a special Slack channel](https://docs.gitlab.com/ee/user/project/integrations/gitlab_slack_application.html#notification-events). This way, when developers come in the next day, they can quickly spot any issues. It's all about catching and fixing problems early on!\n\n### Test environment\n\nFor some of the tests, we may need a test environment to properly test our apps. With GitLab CI/CD, we can automate the deployment of testing environments and save a ton of time. Since this blog mostly focuses on CI, I won't elaborate on this, but you can refer to this section in the [GitLab documentation](https://docs.gitlab.com/ee/topics/release_your_application.html).\n\n## Implementing security scans in CI pipelines\n\nHere are the ways to implement security scans in CI pipelines.\n\n### SAST and DAST integration\n\nWe're all about keeping our code safe. If there are any vulnerabilities in our latest changes, we want to know ASAP. That's why it's a good idea to add security scans to your pipeline. They'll check the code with every commit and give you a heads up about any risks. We've put together a product tour to walk you through adding scans, including static application security testing ([SAST](https://docs.gitlab.com/ee/user/application_security/sast/)) and dynamic application security testing ([DAST](https://docs.gitlab.com/ee/user/application_security/dast/)), to your CI pipeline.\n\n__Click__ the image below to start the tour.\n\n[![Scans product tour](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\nPlus, with AI, we can dig even deeper into vulnerabilities and get suggestions on how to fix them. Check out this demo for more info.\n\n__Click__ the image below to start the tour.\n\n[![product tour explain vulnerability ](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## Recap\n\nThere's much more to cover but let's stop here for now. All examples were made intentionally trivial so that you could learn the concepts of GitLab CI without being distracted by an unfamiliar technology stack. Let's wrap up what we have learned:\n\n1. To delegate some work to GitLab CI you should define one or more [jobs](https://docs.gitlab.com/ee/ci/jobs/) in `.gitlab-ci.yml`.\n2. Jobs should have names and it's your responsibility to come up with good ones.\n3. Every job contains a set of rules and instructions for GitLab CI, defined by [special keywords](#keywords).\n4. Jobs can run sequentially, in parallel, or out of order using [DAG](https://docs.gitlab.com/ee/ci/directed_acyclic_graph/index.html).\n5. You can pass files between jobs and store them in build artifacts so that they can be downloaded from the interface.\n6. Add [tests and security scans](https://docs.gitlab.com/ee/development/integrations/secure.html) to the CI pipeline to ensure the quality and security of your app.\n\nBelow are more formal descriptions of the terms and keywords we used, as well as links to the relevant documentation.\n\n### Keyword descriptions and documentation\n\n{: #keywords}\n\n| Keyword/term       | Description |\n|---------------|--------------------|\n| [.gitlab-ci.yml](https://docs.gitlab.com/ee/ci/yaml/) | File containing all definitions of how your project should be built |\n| [script](https://docs.gitlab.com/ee/ci/yaml/#script)        | Defines a shell script to be executed |\n| [before_script](https://docs.gitlab.com/ee/ci/yaml/#before_script) | Used to define the command that should be run before (all) jobs |\n| [image](https://docs.gitlab.com/ee/ci/docker/using_docker_images.html#what-is-image) | Defines what Docker image to use |\n| [stages](https://docs.gitlab.com/ee/ci/yaml/#stages)         | Defines a pipeline stage (default: `test`) |\n| [artifacts](https://docs.gitlab.com/ee/ci/yaml/#artifacts)     | Defines a list of build artifacts |\n| [artifacts:expire_in](https://docs.gitlab.com/ee/ci/yaml/#artifactsexpire_in) | Used to delete uploaded artifacts after the specified time |\n| [needs](https://docs.gitlab.com/ee/ci/yaml/#needs) | Used to define dependencies between jobs and allows to run jobs out of order |\n| [pipelines](https://about.gitlab.com/topics/ci-cd/cicd-pipeline/) | A pipeline is a group of builds that get executed in stages (batches) |\n\n## More on CI/CD\n\n- [GitLab’s guide to CI/CD for beginners](/blog/beginner-guide-ci-cd/)\n- [Get faster and more flexible pipelines with a Directed Acyclic Graph](/blog/directed-acyclic-graph/)\n- [Decrease build time with custom Docker image](http://beenje.github.io/blog/posts/gitlab-ci-and-conda/)\n- [Introducing the GitLab CI/CD Catalog Beta](https://about.gitlab.com/blog/introducing-the-gitlab-ci-cd-catalog-beta/)\n\n## FAQ\n\n### How do you choose between running CI jobs sequentially vs. in parallel?\n\nConsiderations for choosing between running CI jobs sequentially or in parallel include job dependencies, resource availability, execution times, potential interference, test suite structure, and cost considerations. For example, if you have a build job that must finish before a deployment job can start, you would run these jobs sequentially to ensure the correct order of execution. On the other hand, tasks such as unit testing and integration testing can typically run in parallel since they are independent and don't rely on each other's completion.\n\n### What are directed Acyclic Graphs in GitLab CI, and how do they improve pipeline flexibility?\n\nA Directed Acyclic Graph (DAG) in GitLab CI breaks the linear order of pipeline stages. It lets you set dependencies between jobs, so jobs in later stages start as soon as earlier stage jobs finish. This reduces overall pipeline execution time, improves efficiency, and lets some jobs complete earlier than in a regular order.\n\n### What is the importance of choosing the right Docker image for CI jobs in GitLab?\n\nGitLab utilizes Docker images to execute jobs. The default image is ruby:3.1. Depending on your job's requirements, it's crucial to choose the appropriate image. Note that jobs first download the specified Docker image, and if the image contains additional packages beyond what's necessary, it will increase download and execution times. Therefore, it's important to ensure that the chosen image contains only the packages essential for your job to avoid unnecessary delays in execution.\n\n## Next steps\n\nAs a next step and to further modernize your software development practice, check out the [GitLab CI/CD Catalog](https://docs.gitlab.com/ee/architecture/blueprints/ci_pipeline_components/) to learn how to standardize and reuse CI/CD components.",[9,729],"2024-04-24",{"slug":927,"featured":6,"template":686},"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":933,"_dir":244,"_draft":6,"_partial":6,"_locale":7,"seo":934,"content":940,"config":950,"_id":952,"_type":14,"title":953,"_source":16,"_file":954,"_stem":955,"_extension":19},"/en-us/blog/building-gitlab-with-gitlab-a-multi-region-service-to-deliver-ai-features",{"title":935,"description":936,"ogTitle":935,"ogDescription":936,"noIndex":6,"ogImage":937,"ogUrl":938,"ogSiteName":673,"ogType":674,"canonicalUrls":938,"schema":939},"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":935,"description":936,"authors":941,"heroImage":937,"date":944,"body":945,"category":704,"tags":946},[942,943],"Chance Feick","Sam Wiskow","2024-09-12","For GitLab Duo, real-time AI-powered capabilities like [Code Suggestions](https://about.gitlab.com/solutions/code-suggestions/) need low-latency response times for a frictionless developer experience. Users don’t want to interrupt their flow and wait for a code suggestion to show up. To ensure GitLab Duo can provide the right suggestion at the right time and meet high performance standards for critical AI infrastructure, GitLab recently launched our first multi-region service to deliver AI features.\n\nIn this article, we will cover the benefits of multi-region services, how we built an internal platform codenamed ‘Runway’ for provisioning and deploying multi-region services using GitLab features, and the lessons learned migrating to multi-region in production.\n\n## Background on the project\n\nRunway is GitLab’s internal platform as a service (PaaS) for provisioning, deploying, and operating containerized services. Runway's purpose is to enable GitLab service owners to self-serve infrastructure needs with production readiness out of the box, so application developers can focus on providing value to customers. As part of [our corporate value of dogfooding](https://handbook.gitlab.com/handbook/values/#results), the first iteration was built in 2023 by the Infrastructure department on top of core GitLab capabilities, such as continuous integration/continuous delivery ([CI/CD](https://about.gitlab.com/topics/ci-cd/)), environments, and deployments.\n\nBy establishing automated GitOps best practices, Runway services use infrastructure as code (IaC), merge requests (MRs), and CI/CD by default.\n\nGitLab Duo is primarily powered by [AI Gateway](https://gitlab.com/gitlab-org/modelops/applied-ml/code-suggestions/ai-assist), a satellite service written in Python outside of GitLab’s modular monolith written in Ruby. In cloud computing, a region is a geographical location of data centers operated by cloud providers.\n\n## Defining a multi-region strategy\n\nDeploying in a single region is a good starting point for most services, but can come with downsides when you are trying to reach a global audience. Users who are geographically far from where your service is deployed may experience different levels of service and responsiveness than those who are closer. This can lead to a poor user experience, even if your service is well built in all other respects.\n\nFor AI Gateway, it was important to meet global customers wherever they are located, whether on GitLab.com or self-managed instances using Cloud Connector. When a developer is deciding to accept or reject a code suggestion, milliseconds matter and can define the user experience.\n\n### Goals\n\nMulti-region deployments require more infrastructure complexity, but for use cases where latency is a core component of the user experience, the benefits often outweigh the downsides. First, multi-region deployments offer increased responsiveness to the user. By serving requests from locations closest to end users, latency can be significantly reduced. Second, multi-region deployments provide greater availability. With fault tolerance, services can fail over during a regional outage. There is a much lower chance of a service failing completely, meaning users should not be interrupted even in partial failures.\n\nBased on our goals for performance and availability, we used this opportunity to create a scalable multi-region strategy in Runway, which is built leveraging GitLab features.\n\n### Architecture\n\nIn SaaS platforms, GitLab.com’s infrastructure is hosted on Google Cloud Platform (GCP). As a result, Runway’s first supported platform runtime is Cloud Run. The initial workloads deployed on Runway are stateless satellite services (e.g., AI Gateway), so Cloud Run services are a good fit that provide a clear migration path to more complex and flexible platform runtimes, e.g. Kubernetes.\n\nBuilding Runway on top of GCP Cloud Run using GitLab has allowed us to iterate and tease out the right level of abstractions for service owners as part of a platform play in the Infrastructure department.\n\nTo serve traffic from multiple regions in Cloud Run, the multi-region deployment strategy must support global load balancing, and the provisioning and configuration of regional resources. Here’s a simplified diagram of the proposed architecture in GCP:\n\n![simplified diagram of the proposed architecture in GCP](https://res.cloudinary.com/about-gitlab-com/image/upload/v1750098671/Blog/Content%20Images/Blog/Content%20Images/image7_aHR0cHM6_1750098671612.png)\n\nBy replicating Cloud Run services across multiple regions and configuring the existing global load balancing with serverless network endpoint group (NEG) backends, we’re able to serve traffic from multiple regions. For the remainder of the article, we’ll focus less on specifics of Cloud Run and more on how we’re building with GitLab.\n\n## Building a multi-region platform with GitLab\n\nNow that you have context about Runway, let's walk through how to build a multi-region platform using GitLab features.\n\n### Provision\n\nWhen building an internal platform, the first challenge is provisioning infrastructure for a service. In Runway, Provisioner is the component that is responsible for maintaining a service inventory and managing IaC for GCP resources using Terraform.\n\nTo provision a service, an application developer will open an MR to add a service project to the inventory using git, and Provisioner will create required resources, such as service accounts and identity and access management policies. When building this functionality with GitLab, Runway leverages [OpenID Connect (OIDC) with GPC Workload Identity Federation](https://docs.gitlab.com/ee/ci/cloud\\_services/google\\_cloud/) for managing IaC.\n\nAdditionally, Provisioner will create a deployment project for each service project. The purpose of creating separate projects for deployments is to ensure the [principle of least privilege](https://about.gitlab.com/blog/the-ultimate-guide-to-least-privilege-access-with-gitlab/) by authenticating as a GCP service account with restricted permissions. Runway leverages the [Projects API](https://docs.gitlab.com/ee/api/projects.html) for creating projects with [Terraform provider](https://registry.terraform.io/providers/gitlabhq/gitlab/latest/docs).\n\nFinally, Provisioner defines variables in the deployment project for the service account, so that deployment CI jobs can authenticate to GCP. Runway leverages [CI/CD variables](https://docs.gitlab.com/ee/ci/variables/) and [Job Token allowlist](https://docs.gitlab.com/ee/ci/jobs/ci\\_job\\_token.html\\#add-a-group-or-project-to-the-job-token-allowlist) to handle authentication and authorization.\n\nHere’s a simplified example of provisioning a multi-region service in the service inventory:\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\nOnce provisioned, a deployment project and necessary infrastructure will be created for a service.\n\n### Configure\n\nAfter a service is provisioned, the next challenge is the configuration for a service. In Runway, [Reconciler](https://gitlab.com/gitlab-com/gl-infra/platform/runway/runwayctl) is a component that is responsible for configuring and deploying services by aligning the actual state with the desired state using Golang and Terraform.\n\nHere’s a simplified example of an application developer configuring GitLab CI/CD in their service project:\n\n```\n# .gitlab-ci.yml\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\nRunway provides sane default values for configuration that are based on our experience in delivering stable and reliable features to customers. Additionally, service owners can configure infrastructure using a service manifest file hosted in a service project. The service manifest uses JSON Schema for validation. When building this functionality with GitLab, Runway leverages [Pages](https://docs.gitlab.com/ee/user/project/pages/) for schema documentation.\n\nTo deliver this part of the platform, Runway leverages [CI/CD templates](https://docs.gitlab.com/ee/development/cicd/templates.html), [Releases](https://docs.gitlab.com/ee/user/project/releases/), and [Container Registry](https://docs.gitlab.com/ee/user/packages/container\\_registry/) for integrating with service projects.\n\nHere’s a simplified example of a service manifest:\n\n```\n# .runway/runway-production.yml\napiVersion: runway/v1\nkind: RunwayService\nspec:\n container_port: 8181\n regions:\n   - us-east1\n   - us-west1\n   - europe-west1\n\n# omitted for brevity\n```\n\nFor multi-region services, Runway injects an environment variable into the container instance runtime, e.g. RUNWAY\\_REGION, so application developers have the context to make any downstream dependencies regionally-aware, e.g. Vertex AI API.\n\nOnce configured, a service project will be integrated with a deployment project.\n\n### Deploy\n\nAfter a service project is configured, the next challenge is deploying a service. In Runway, Reconciler handles this by triggering a deployment job in the deployment project when an MR is merged to the main branch. When building this functionality with GitLab, Runway leverages [Trigger Pipelines](https://docs.gitlab.com/ee/ci/triggers/) and [Multi-Project Pipelines](https://docs.gitlab.com/ee/ci/pipelines/downstream\\_pipelines.html\\#multi-project-pipelines) to trigger jobs from service project to deployment project.\n\n![trigger jobs from service project to deployment project](https://res.cloudinary.com/about-gitlab-com/image/upload/v1750098672/Blog/Content%20Images/Blog/Content%20Images/image5_aHR0cHM6_1750098671612.png)\n\nOnce a pipeline is running in a deployment project, it will be deployed to an environment. By default, Runway will provision staging and production environments for all services. At this point, Reconciler will apply any Terraform resource changes for infrastructure. When building this functionality with GitLab, Runway leverages [Environments/Deployments](https://docs.gitlab.com/ee/ci/environments/) and [GitLab-managed Terraform state](https://docs.gitlab.com/ee/user/infrastructure/iac/terraform\\_state.html) for each service.\n\n![Reconciler applies any Terraform resource changes for infrastructure](https://res.cloudinary.com/about-gitlab-com/image/upload/v1750098672/Blog/Content%20Images/Blog/Content%20Images/image1_aHR0cHM6_1750098671614.png)\n\nRunway provides default application metrics for services. Additionally, custom metrics can be used by enabling a sidecar container with OpenTelemetry Collector configured to scrape Prometheus and remote write to Mimir. By providing observability out of the box, Runway is able to bake monitoring into CI/CD pipelines.\n\nExample scenarios include gradual rollouts for blue/green deployments, preventing promotions to production when staging is broken, or automatically rolling back to previous revision when elevated error rates occur in production.\n\n![Runway bakes monitoring into CI/CD pipelines](https://res.cloudinary.com/about-gitlab-com/image/upload/v1750098672/Blog/Content%20Images/Blog/Content%20Images/image2_aHR0cHM6_1750098671615.png)\n\nOnce deployed, environments will serve the latest revision of a service. At this point, you should have a good understanding of some of the challenges that will be encountered, and how to solve them with GitLab features.\n\n## Migrating to multi-region in production\n\nAfter extending Runway components to support multi-region in Cloud Run, the final challenge was migrating from AI Gateway’s single-region deployment in production with zero downtime. Today, teams using Runway to deploy their services can self-serve on regions making a multi-region deployment just as simple as a single-region deployment. \n\nWe were able to iterate on building multi-region functionality without impacting existing infrastructure by using semantic versioning for Runway. Next, we’ll share some learnings from the migration that may inform how to operate services for an internal multi-region platform.\n\n### Dry run deployments\n\nIn Runway, Reconciler will apply Terraform changes in CI/CD. The trade-off is that plans cannot be verified in advance, which could risk inadvertently destroying or misconfiguring production infrastructure. To solve this problem, Runway will perform a “dry run” deployment for MRs.\n\n![\"Dry run\" deployment](https://res.cloudinary.com/about-gitlab-com/image/upload/v1750098672/Blog/Content%20Images/Blog/Content%20Images/image6_aHR0cHM6_1750098671616.png)\n\nFor migrating AI Gateway, dry run deployments increased confidence and helped mitigate risk of downtime during rollout. When building an internal platform with GitLab, we recommend supporting dry run deployments from the start.\n\n### Regional observability\n\nIn Runway, existing observability was aggregated by assuming a single-region deployment. To solve this problem, Runway observability was retrofitted to include a new region label for Prometheus metrics.\n\nOnce metrics were retrofitted, we were able to introduce service level indicators (SLIs) for both regional Cloud Run services and global load balancing. Here’s an example dashboard screenshot for a general Runway service:\n\n![dashboard screenshot for a general Runway service](https://res.cloudinary.com/about-gitlab-com/image/upload/v1750098672/Blog/Content%20Images/Blog/Content%20Images/image3_aHR0cHM6_1750098671617.png)\n\n***Note:** Data is not actual production data and is only for illustration purposes.*\n\nAdditionally, we were able to update our service level objectives (SLOs) to support regions. As a result, service owners could be alerted when a specific region experiences an elevated error rate, or increase in response times.\n\n![screenshot of alerts](https://res.cloudinary.com/about-gitlab-com/image/upload/v1750098672/Blog/Content%20Images/Blog/Content%20Images/image4_aHR0cHM6_1750098671617.png)\n\n***Note:** Data is not actual production data and is only for illustration purposes.*\n\nFor migrating AI Gateway, regional observability increased confidence and helped provide more visibility into new infrastructure. When building an internal platform with GitLab, we recommend supporting regional observability from the start.\n\n### Self-service regions\n\nThe Infrastructure department successfully performed the initial migration of multi-region support for AI Gateway in production with zero downtime. Given the risk associated with rolling out a large infrastructure migration, it was important to ensure the service continued working as expected.\n\nShortly afterwards, service owners began self-serving additional regions to meet the growth of customers. At the time of writing, [GitLab Duo](https://about.gitlab.com/gitlab-duo/) is available in six regions around the globe and counting. Service owners are able to configure the desired regions, and Runway will provide guardrails along the way in a scalable solution.\n\nAdditionally, three other internal services have already started using multi-region functionality on Runway. Application developers have entirely self-served functionality, which validates that we’ve provided a good platform experience for service owners. For a platform play, a scalable solution like Runway is considered a good outcome since the Infrastructure department is no longer a blocker.\n\n## What’s next for Runway\n\nBased on how quickly we could iterate to provide results for customers, the SaaS Platforms department has continued to invest in Runway. We’ve grown the Runway team with additional contributors, started evolving the platform runtime (e.g. Google Kubernetes Engine), and continue dogfooding with tighter integration in the product.\n\nIf you’re interested in learning more, feel free to check out [https://gitlab.com/gitlab-com/gl-infra/platform/runway](https://gitlab.com/gitlab-com/gl-infra/platform/runway).\n\n## More Building GitLab with GitLab\n- [Why there is no MLOps without DevSecOps](https://about.gitlab.com/blog/there-is-no-mlops-without-devsecops/)\n- [Stress-testing Product Analytics](https://about.gitlab.com/blog/building-gitlab-with-gitlab-stress-testing-product-analytics/)\n- [Web API Fuzz Testing](https://about.gitlab.com/blog/building-gitlab-with-gitlab-api-fuzzing-workflow/)\n- [How GitLab.com inspired Dedicated](https://about.gitlab.com/blog/building-gitlab-with-gitlabcom-how-gitlab-inspired-dedicated/)\n- [Expanding our security certification portfolio](https://about.gitlab.com/blog/building-gitlab-with-gitlab-expanding-our-security-certification-portfolio/)\n",[109,683,9,902,729,706,947,948,773,949],"google","git","AI/ML",{"slug":951,"featured":91,"template":686},"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":957,"_dir":244,"_draft":6,"_partial":6,"_locale":7,"seo":958,"content":964,"config":971,"_id":973,"_type":14,"title":974,"_source":16,"_file":975,"_stem":976,"_extension":19},"/en-us/blog/changes-to-the-preclonescript",{"title":959,"description":960,"ogTitle":959,"ogDescription":960,"noIndex":6,"ogImage":961,"ogUrl":962,"ogSiteName":673,"ogType":674,"canonicalUrls":962,"schema":963},"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":959,"description":960,"authors":965,"heroImage":961,"date":967,"body":968,"category":792,"tags":969},[966],"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,683,970],"features",{"slug":972,"featured":6,"template":686},"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":978,"_dir":244,"_draft":6,"_partial":6,"_locale":7,"seo":979,"content":985,"config":995,"_id":997,"_type":14,"title":998,"_source":16,"_file":999,"_stem":1000,"_extension":19},"/en-us/blog/chris-hill-devops-enterprise-summit-talk",{"title":980,"description":981,"ogTitle":980,"ogDescription":981,"noIndex":6,"ogImage":982,"ogUrl":983,"ogSiteName":673,"ogType":674,"canonicalUrls":983,"schema":984},"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":980,"description":981,"authors":986,"heroImage":982,"date":987,"body":988,"category":989,"tags":990},[833],"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",[728,991,9,992,993,994],"collaboration","user stories","automotive","customers",{"slug":996,"featured":6,"template":686},"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":1002,"_dir":244,"_draft":6,"_partial":6,"_locale":7,"seo":1003,"content":1009,"config":1017,"_id":1019,"_type":14,"title":1020,"_source":16,"_file":1021,"_stem":1022,"_extension":19},"/en-us/blog/ci-deployment-and-environments",{"title":1004,"description":1005,"ogTitle":1004,"ogDescription":1005,"noIndex":6,"ogImage":1006,"ogUrl":1007,"ogSiteName":673,"ogType":674,"canonicalUrls":1007,"schema":1008},"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":1004,"description":1005,"authors":1010,"heroImage":1006,"date":1013,"body":1014,"category":704,"tags":1015,"updatedDate":1016},[1011,1012],"Ivan Nemytchenko","Cesar Saavedra","2021-02-05","This post is a success story of one imaginary news portal, and you're the happy\nowner, the editor, and the only developer. Luckily, you already host your project\ncode on GitLab.com and know that you can\n[run tests with GitLab CI/CD](https://docs.gitlab.com/ee/ci/testing/).\nNow you’re curious if it can be [used for deployment](/blog/how-to-keep-up-with-ci-cd-best-practices/), and how far can you go with it.\n\nTo keep our story technology stack-agnostic, let's assume that the app is just a\nset of HTML files. No server-side code, no fancy JS assets compilation.\n\nDestination platform is also simplistic – we will use [Amazon S3](https://aws.amazon.com/s3/).\n\nThe goal of the article is not to give you a bunch of copy-pasteable snippets.\nThe goal is to show the principles and features of [GitLab CI](/solutions/continuous-integration/) so that you can easily apply them to your technology stack.\n{: .alert .alert-warning}\n\nLet’s start from the beginning. There's no continuous integration (CI) in our story yet.\n\n## At the starting line\n\n**Deployment**: In your case, it means that a bunch of HTML files should appear on your\nS3 bucket (which is already configured for\n[static website hosting](http://docs.aws.amazon.com/AmazonS3/latest/dev/HowDoIWebsiteConfiguration.html?shortFooter=true)).\n\nThere are a million ways to do it. We’ll use the\n[awscli](http://docs.aws.amazon.com/cli/latest/reference/s3/cp.html#examples) library,\nprovided by Amazon.\n\nThe full command looks like this:\n\n```shell\naws s3 cp ./ s3://yourbucket/ --recursive --exclude \"*\" --include \"*.html\"\n```\n\n![Manual deployment](https://about.gitlab.com/images/blogimages/ci-deployment-and-environments/13.jpg){: .center}\nPushing code to repository and deploying are separate processes.\n{: .note .text-center}\n\nImportant detail: The command\n[expects you](http://docs.aws.amazon.com/cli/latest/userguide/cli-chap-getting-started.html#config-settings-and-precedence)\nto provide `AWS_ACCESS_KEY_ID` and  `AWS_SECRET_ACCESS_KEY` environment\nvariables. Also you might need to specify `AWS_DEFAULT_REGION`.\n{: .alert .alert-info}\n\nLet’s try to automate it using [GitLab CI](/solutions/continuous-integration/).\n\n## The first automated deployment\n\nWith GitLab, there's no difference on what commands to run.\nYou can set up GitLab CI in a way that tailors to your specific needs, as if it was your local terminal on your computer. As long as you execute commands there, you can tell CI to do the same for you in GitLab.\nPut your script to `.gitlab-ci.yml` and push your code – that’s it: CI triggers\na _job_ and your commands are executed.\n\nNow, let's add some context to our story: Our website is small, there is 20-30 daily\nvisitors and the code repository has only one default branch: `main`.\n\nLet's start by specifying a _job_ with the command from above in the `.gitlab-ci.yml` file:\n\n```yaml\ndeploy:\n  script: aws s3 cp ./ s3://yourbucket/ --recursive --exclude \"*\" --include \"*.html\"\n```\n\nNo luck:\n![Failed command](https://about.gitlab.com/images/blogimages/ci-deployment-and-environments/fail1.png){: .shadow}\n\nIt is our _job_ to ensure that there is an `aws` executable.\nTo install `awscli` we need `pip`, which is a tool for Python packages installation.\nLet's specify Docker image with preinstalled Python, which should contain `pip` as well:\n\n```yaml\ndeploy:\n  image: python:latest\n  script:\n  - pip install awscli\n  - aws s3 cp ./ s3://yourbucket/ --recursive --exclude \"*\" --include \"*.html\"\n```\n\n![Automated deployment](https://about.gitlab.com/images/blogimages/ci-deployment-and-environments/14.jpg){: .center}\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 a big\ndeal for now. If you need to speed up the process, you can always [look for\na Docker image](https://hub.docker.com/explore/) with preinstalled `awscli`,\nor create an image by yourself.\n{: .alert .alert-warning}\n\nAlso, let’s not forget about these environment variables, which you've just grabbed\nfrom [AWS Console](https://console.aws.amazon.com/):\n\n```yaml\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```\nIt should work, but keeping secret keys open, even in a private repository,\nis not a good idea. Let's see how to deal with this situation.\n\n### Keeping secret things secret\n\nGitLab has a special place for secret variables: **Settings > CI/CD > Variables**\n\n![Picture of Variables page](https://res.cloudinary.com/about-gitlab-com/image/upload/v1749674076/Blog/Content%20Images/add-variable-updated.png)\n\nWhatever you put there will be turned into **environment variables**.\nChecking the \"Mask variable\" checkbox will obfuscate the variable in job logs. Also, checking the \"Protect variable\" checkbox will export the variable to only pipelines running on protected branches and tags. Users with Owner or Maintainer permissions to a project will have access to this section.\n\nWe could remove `variables` section from our CI configuration. However, let’s use it for another purpose.\n\n### How to specify and use variables that are not secret\n\nWhen your configuration gets bigger, it is convenient to keep some of the\nparameters as variables at the beginning of your configuration. Especially if you\nuse them in more than one place. Although it is not the case in our situation yet,\nlet's set the S3 bucket name as a [**variable**](https://docs.gitlab.com/ee/ci/variables/) for the purpose of this demonstration:\n\n```yaml\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\nSo far so good:\n\n![Successful build](https://about.gitlab.com/images/blogimages/ci-deployment-and-environments/build.png){: .shadow.medium.center}\n\nIn our hypothetical scenario, the audience of your website has grown, so you've hired a developer to help you.\nNow you have a team. Let's see how teamwork changes the GitLab CI workflow.\n\n## How to use GitLab CI with a team\n\nNow, that there are two users working in the same repository, it is no longer convenient\nto use the `main` branch for development. You decide to use separate branches\nfor both new features and new articles and merge them into `main` when they are ready.\n\nThe problem is that your current CI config doesn’t care about branches at all.\nWhenever you push anything to GitLab, it will be deployed to S3.\n\nPreventing this problem is straightforward. Just add `only: main` to your `deploy` job.\n\n![Automated deployment of main branch](https://res.cloudinary.com/about-gitlab-com/image/upload/v1749674076/Blog/Content%20Images/15-updated.png){: .center}\nYou don't want to deploy every branch to the production website but it would also be nice to preview your changes from feature-branches somehow.\n{: .note .text-center}\n\n### How to set up a separate place for testing code\n\nThe person you recently hired, let's call him Patrick, reminds you that there is a featured called\n[GitLab Pages](https://docs.gitlab.com/ee/user/project/pages/). It looks like a perfect candidate for\na place to preview your work in progress.\n\nTo [host websites on GitLab Pages](/blog/gitlab-pages-setup/) your CI configuration file should satisfy three simple rules:\n\n- The _job_ should be named `pages`\n- There should be an `artifacts` section with folder `public` in it\n- Everything you want to host should be in this `public` folder\n\nThe contents of the public folder will be hosted at `http://\u003Cusername>.gitlab.io/\u003Cprojectname>/`\n{: .alert .alert-info}\n\nAfter applying the [example config for plain-html websites](https://gitlab.com/pages/plain-html/blob/master/.gitlab-ci.yml),\nthe full CI configuration looks like this:\n\n```yaml\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\nWe specified two jobs. One job deploys the website for your customers to S3 (`deploy`).\nThe other one (`pages`) deploys the website to GitLab Pages.\nWe can name them \"Production environment\" and \"Staging environment\", respectively.\n\n![Deployment to two places](https://res.cloudinary.com/about-gitlab-com/image/upload/v1749674076/Blog/Content%20Images/16-updated.png){: .center}\nAll branches, except main, will be deployed to GitLab Pages.\n{: .note .text-center}\n\n## Introducing environments\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\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\nGitLab keeps track of your deployments, so you always know what is currently being deployed on your servers:\n\n![List of environments](https://res.cloudinary.com/about-gitlab-com/image/upload/v1749674076/Blog/Content%20Images/envs-updated.png){: .shadow.center}\n\nGitLab provides full history of your deployments for each of your current environments:\n\n![List of deployments to staging environment](https://res.cloudinary.com/about-gitlab-com/image/upload/v1749674077/Blog/Content%20Images/staging-env-detail-updated.png){: .shadow.center}\n\n![Environments](https://res.cloudinary.com/about-gitlab-com/image/upload/v1749674077/Blog/Content%20Images/17-updated.png){: .center}\n\nNow, with everything automated and set up, we’re ready for the new challenges that are just around the corner.\n\n## How to troubleshoot deployments\n\nIt has just happened again.\nYou've pushed your feature-branch to preview it on staging and a minute later Patrick pushed\nhis branch, so the staging environment was rewritten with his work. Aargh!! It was the third time today!\n\nIdea! \u003Ci class=\"far fa-lightbulb\" style=\"color:#FFD900; font-size:.85em\" aria-hidden=\"true\">\u003C/i> Let's use Slack to notify us of deployments, so that people will not push their stuff if another one has been just deployed!\n\n> Learn how to [integrate GitLab with Slack](https://docs.gitlab.com/ee/user/project/integrations/gitlab_slack_application.html).\n\n## Teamwork at scale\n\nAs the time passed, your website became really popular, and your team has grown from two people to eight people.\nPeople develop in parallel, so the situation when people wait for each other to\npreview something on Staging has become pretty common. \"Deploy every branch to staging\" stopped working.\n\n![Queue of branches for review on Staging](https://about.gitlab.com/images/blogimages/ci-deployment-and-environments/queue.jpg){: .center}\n\nIt's time to modify the process one more time. You and your team agreed that if\nsomeone wants to see their changes on the staging\nserver, they should first merge the changes to the \"staging\" branch.\n\nThe change of `.gitlab-ci.yml` is minimal:\n\n```yaml\nexcept:\n- main\n```\n\nis now changed to\n\n```yaml\nonly:\n- staging\n```\n\n![Staging branch](https://res.cloudinary.com/about-gitlab-com/image/upload/v1749674077/Blog/Content%20Images/18-updated.png){: .center}\nPeople have to merge their feature branches before preview on the staging server.\n{: .note .text-center}\n\nOf course, it requires additional time and effort for merging, but everybody agreed that it is better than waiting.\n\n### How to handle emergencies\n\nYou can't control everything, so sometimes things go wrong. Someone merged branches incorrectly and\npushed the result straight to production exactly when your site was on top of HackerNews.\nThousands of people saw your completely broken layout instead of your shiny main page.\n\nLuckily, someone found the **Rollback** button, so the\nwebsite was fixed a minute after the problem was discovered.\n\n![List of environments](https://res.cloudinary.com/about-gitlab-com/image/upload/v1749674077/Blog/Content%20Images/prod-env-rollback-arrow-updated.png){: .shadow.center}\nRollback relaunches the previous job with the previous commit\n{: .note .text-center}\n\nAnyway, you felt that you needed to react to the problem and decided to turn off\nauto-deployment to Production and switch to manual deployment.\nTo do that, you needed to add `when: manual` to your _job_.\n\nAs you expected, there will be no automatic deployment to Production after that.\nTo deploy manually go to **CI/CD > Pipelines**, and click the button:\n\n![Skipped job is available for manual launch](https://res.cloudinary.com/about-gitlab-com/image/upload/v1749674076/Blog/Content%20Images/manual-pipeline-arrow-updated.png){: .shadow.center}\n\nFast forward in time. Finally, your company has turned into a corporation. Now, you have hundreds of people working on the website,\nso all the previous compromises no longer work.\n\n### Time to start using Review Apps\n\nThe next logical step is to boot up a temporary instance of the application per feature branch for review.\n\nIn our case, we set up another bucket on S3 for that. The only difference is that\nwe copy the contents of our website to a \"folder\" with the name of the\nthe development branch, so that the URL looks like this:\n\n`http://\u003CREVIEW_S3_BUCKET_NAME>.s3-website-us-east-1.amazonaws.com/\u003Cbranchname>/`\n\nHere's the replacement for the `pages` _job_ we used before:\n\n```yaml\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\nThe interesting thing is where we got this `$CI_BUILD_REF_NAME` variable from.\nGitLab predefines [many environment variables](https://docs.gitlab.com/ee/ci/variables/predefined_variables.html) so that you can use them in your jobs.\n\nNote that we defined the `S3_BUCKET_NAME` variable inside the *job*. You can do this to rewrite top-level definitions.\n{: .alert .alert-info}\n\nVisual representation of this configuration:\n![Review apps]![How to use GitLab CI - update - 19 - updated](https://res.cloudinary.com/about-gitlab-com/image/upload/v1749674077/Blog/Content%20Images/19-updated.png){: .illustration}\n\nThe details of the Review Apps implementation varies widely, depending upon your real technology\nstack and on your deployment process, which is outside the scope of this blog post.\n\nIt will not be that straightforward, as it is with our static HTML website.\nFor example, you had to make these instances temporary, and booting up these instances\nwith all required software and services automatically on the fly is not a trivial task.\nHowever, it is doable, especially if you use Docker containers, or at least Chef or Ansible.\n\nWe'll cover deployment with Docker in a future blog post.\nI feel a bit guilty for simplifying the deployment process to a simple HTML files copying, and not\nadding some hardcore scenarios. If you need some right now, I recommend you read the article [\"Building an Elixir Release into a Docker image using GitLab CI.\"](/blog/building-an-elixir-release-into-docker-image-using-gitlab-ci-part-1/)\n\nFor now, let's talk about one final thing.\n\n### Deploying to different platforms\n\nIn real life, we are not limited to S3 and GitLab Pages. We host, and therefore,\ndeploy our apps and packages to various services.\n\nMoreover, at some point, you could decide to move to a new platform and will need to rewrite all your deployment scripts.\nYou can use a gem called `dpl` to minimize the damage.\n\nIn the examples above we used `awscli` as a tool to deliver code to an example\nservice (Amazon S3).\nHowever, no matter what tool and what destination system you use, the principle is the same:\nYou run a command with some parameters and somehow pass a secret key for authentication purposes.\n\nThe `dpl` deployment tool utilizes this principle and provides a\nunified interface for [this list of providers](https://github.com/travis-ci/dpl#supported-providers).\n\nHere's how a production deployment _job_ would look if we use `dpl`:\n\n```yaml\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\nIf you deploy to different systems or change destination platform frequently, consider\nusing `dpl` to make your deployment scripts look uniform.\n\n## Five key takeaways\n\n1. Deployment is just a command (or a set of commands) that is regularly executed. Therefore it can run inside GitLab CI.\n2. Most times you'll need to provide some secret key(s) to the command you execute. Store these secret keys in **Settings > CI/CD > Variables**.\n3. With GitLab CI, you can flexibly specify which branches to deploy to.\n4. If you deploy to multiple environments, GitLab will conserve the history of deployments,\nwhich allows you to rollback to any previous version.\n5. For critical parts of your infrastructure, you can enable manual deployment from GitLab interface, instead of automated deployment.\n\n\u003Cstyle>\nimg.illustration {\n  padding-left: 12%;\n  padding-right: 12%;\n\n}\n@media (max-width: 760px) {\n  img.illustration {\n    padding-left: 0px;\n    padding-right: 0px;\n  }\n}\n\u003C/style>\n",[9,683,729],"2024-07-22",{"slug":1018,"featured":6,"template":686},"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":1024,"_dir":244,"_draft":6,"_partial":6,"_locale":7,"seo":1025,"content":1031,"config":1037,"_id":1039,"_type":14,"title":1040,"_source":16,"_file":1041,"_stem":1042,"_extension":19},"/en-us/blog/cicd-tunnel-impersonation",{"title":1026,"description":1027,"ogTitle":1026,"ogDescription":1027,"noIndex":6,"ogImage":1028,"ogUrl":1029,"ogSiteName":673,"ogType":674,"canonicalUrls":1029,"schema":1030},"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":1032,"description":1027,"authors":1033,"heroImage":1028,"date":1034,"body":1035,"category":704,"tags":1036},"How to use fine-grained permissions via generic impersonation in CI/CD Tunnel",[1012],"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",[794,9,683,901],{"slug":1038,"featured":6,"template":686},"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":1044,"_dir":244,"_draft":6,"_partial":6,"_locale":7,"seo":1045,"content":1051,"config":1056,"_id":1058,"_type":14,"title":1059,"_source":16,"_file":1060,"_stem":1061,"_extension":19},"/en-us/blog/code-counting-in-gitlab",{"title":1046,"description":1047,"ogTitle":1046,"ogDescription":1047,"noIndex":6,"ogImage":1048,"ogUrl":1049,"ogSiteName":673,"ogType":674,"canonicalUrls":1049,"schema":1050},"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":1046,"description":1047,"authors":1052,"heroImage":1048,"date":1053,"body":1054,"category":704,"tags":1055},[678],"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",[728,707,9],{"slug":1057,"featured":6,"template":686},"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":1063,"_dir":244,"_draft":6,"_partial":6,"_locale":7,"seo":1064,"content":1070,"config":1078,"_id":1080,"_type":14,"title":1081,"_source":16,"_file":1082,"_stem":1083,"_extension":19},"/en-us/blog/continuously-improving-ci-lovability",{"title":1065,"description":1066,"ogTitle":1065,"ogDescription":1066,"noIndex":6,"ogImage":1067,"ogUrl":1068,"ogSiteName":673,"ogType":674,"canonicalUrls":1068,"schema":1069},"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":1065,"description":1066,"authors":1071,"heroImage":1067,"date":1073,"body":1074,"category":1075,"tags":1076},[1072],"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,1077,970],"UX",{"slug":1079,"featured":6,"template":686},"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":1085,"_dir":244,"_draft":6,"_partial":6,"_locale":7,"seo":1086,"content":1092,"config":1098,"_id":1100,"_type":14,"title":1101,"_source":16,"_file":1102,"_stem":1103,"_extension":19},"/en-us/blog/craftsman-looks-at-continuous-integration",{"title":1087,"description":1088,"ogTitle":1087,"ogDescription":1088,"noIndex":6,"ogImage":1089,"ogUrl":1090,"ogSiteName":673,"ogType":674,"canonicalUrls":1090,"schema":1091},"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":1087,"description":1088,"authors":1093,"heroImage":1089,"date":1095,"body":1096,"category":836,"tags":1097},[1094],"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":1099,"featured":6,"template":686},"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":1105,"_dir":244,"_draft":6,"_partial":6,"_locale":7,"seo":1106,"content":1112,"config":1121,"_id":1123,"_type":14,"title":1124,"_source":16,"_file":1125,"_stem":1126,"_extension":19},"/en-us/blog/custom-actions-rasa-gitlab-devops",{"title":1107,"description":1108,"ogTitle":1107,"ogDescription":1108,"noIndex":6,"ogImage":1109,"ogUrl":1110,"ogSiteName":673,"ogType":674,"canonicalUrls":1110,"schema":1111},"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":1113,"description":1108,"authors":1114,"heroImage":1109,"date":1116,"body":1117,"category":1118,"tags":1119},"Create and Deploy Custom Actions Containers to Rasa X using Gitlab DevOps Platform",[1115],"William Arias","2021-04-06","\n**This blog post was a collaboration between William Arias, from Gitlab, and Vincent D. Warmerdam, from Rasa. You can find the same blog post on [Rasa's blog](https://blog.rasa.com/create-and-deploy-custom-actions-containers-to-rasa-x-using-gitlab-devops-platform/)**.  \n\n## Create and Deploy Custom Actions Containers to Rasa X using Gitlab DevOps Platform\nVirtual assistants do more than just carry on conversations. They can send emails, make updates to a calendar, or call an API endpoint. Essentially, they can do actions that add significant value and convenience to the user experience.\nIn assistants built with Rasa*, this type of functionality is executed by custom code called custom actions. As with any code you run in production, you’ll need to think about how you want to deploy updates to custom actions. In this blog post, we’ll show you how to set up GitLab to deploy custom action Docker containers to your Kubernetes cluster. If we follow [good DevOps practices](/stages-devops-lifecycle/) we can greatly speed up the development and quality of our  virtual assistants.\n* Rasa Open Source is a machine learning framework for building text and voice-based virtual assistants. It provides infrastructure for understanding messages, holding conversations, and connecting to many messaging channels and APIs. Rasa X is a toolset that runs on top of Rasa Open Source, extending its capabilities. Rasa X includes key features for sharing the assistant with test users, reviewing and annotating conversation data, and deploying the assistant. [Learn more about Rasa.](https://rasa.com/docs/)\n\n## Deployment high-level overview\nThe typical workflow for deploying a new version of custom actions is outlined below.  \n![actions-process](https://about.gitlab.com/images/blogimages/actions-process.png){: .shadow}\n\nEvery change to your custom actions code will require a new container image to be built and pulled by Rasa X. Gitlab CI/CD can save you from doing a lot of manual work and automate steps like the ones described in the workflow above. Let's see how to do it.  \n\n## Using Rasa with Gitlab DevOps Platform\nLet's create a pipeline that will automate manual steps.\n\n---\n**NOTE**\nThis article assumes you have your [Gitlab Project](https://gitlab.com/warias/gl-commit-2020) with your customs Actions Code created along with a [Google Kubernetes Cluster](https://cloud.google.com/kubernetes-engine/docs/quickstart).\n\n---\n\nIf you are a Gitlab user you are probably familiar with .gitlab-ci.yml file and its CI/CD capabilities. Every time you commit a change to your customs actions code you want Gitlab to run a script that will build and update your docker containers. \n![actions-process-2](https://about.gitlab.com/images/blogimages/process2.png){: .shadow}\n\nLet's breakdown the CI/CD pipeline by describing the gitlab-ci.yml file so you can use it and customize it to your needs\n## Variables\nWe make use of environment variables created in Gitlab at the moment of running the Jobs to define our actions Docker image  \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\nThe snippet above does the following:\n- It defines the name of the Docker Image for custom actions using environment variables ```$CI_REGISTRY_IMAGE/$CI_COMMIT_REF_SLUG.``` This will make the name of the Docker image different for every commit\n- It creates a secret used to pull the Rasa Action Image from the Gitlab Private Registry to the Google Kubernetes Cluster. \n\n## Stages\nWe have two main stages in our pipeline, build and deploy:\n```\nstages:\n  - build\n  - deploy \n```\nEvery time there is a new commit with changes to our custom actions code, or when we decide to run the CI/CD Pipeline it will:\n- Build: Here, we automate the building of the Docker image using the variables defined above, and the Dockerfile. We also tag the image and push it to the GitLab container registry.\n- Deploy: Here we log-in to Kubernetes Engine on Google Cloud and deploy the newly created Actions image to Rasa X.\nLet's see it in more detail:  \n\n**Build**:\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```\nThe job build-actions-image executed on the build stage takes advantage of the CI/CD variables that are part of the environment where the pipelines run. It automates the usage of Docker commands to build the Actions image by reading its corresponding Dockerfile. The output of this stage is a new Custom Actions image per every commit with code changes.  \n\n**Deploy**:\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\nNotice the variables in ```before_script```, these ones are needed to authenticate to GCP where we have our Kubernetes cluster. This step is optional and could be skipped in cases where you have [Gitlab pre-integrated](https://docs.gitlab.com/ee/user/project/clusters/add_remove_clusters.html) with your Kubernetes cluster running on Google Cloud.  \n\nThe main and most interesting part of the script is:  \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\nWe start by creating the *namespace* for our custom actions code, and if it already exists, then we proceed to apply Kubernetes commands using kubectl and helm.  \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```\nThe snippet above adds a rasa-x Helm chart and upgrades or changes the values corresponding to the new **Custom Action Image** by assigning to it the ```$ACTIONS_CONTAINER_IMAGE``` created in the build stage.\nNote that the pipeline described above focuses only on creating and deploying the ACTIONS_CONTAINER_IMAGE. It could be extended by adding more stages, for example, code quality, security testing, and unit testing among others.  \n\n## Summary\nUsing 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.\nPushing new custom action containers to Kubernetes only scratches the surface of what you can automate with GitLab. You could also add steps for code quality, security audits and unit tests. The main goal is to automate the manual parts of deployment so that you can focus on what is important. In the case of Rasa X, that means that more time can be spent learning from your users and making a better assistant in the process.\n\nDo you want to learn more? Watch this video of Gitlab DevOps Platform and Rasa [Deploy your Rasa Chatbots like a boss with DevOps](https://youtu.be/ko9-zPDuhQo)\n\nHappy hacking!\n\nCover image by [Eric Krull](https://unsplash.com/@ekrull?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText) on [Unsplash](https://unsplash.com)\n{: .note}\n","devsecops",[9,1120,728],"cloud native",{"slug":1122,"featured":6,"template":686},"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":1128,"_dir":244,"_draft":6,"_partial":6,"_locale":7,"seo":1129,"content":1135,"config":1141,"_id":1143,"_type":14,"title":1144,"_source":16,"_file":1145,"_stem":1146,"_extension":19},"/en-us/blog/dag-manual-fix",{"title":1130,"description":1131,"ogTitle":1130,"ogDescription":1131,"noIndex":6,"ogImage":1132,"ogUrl":1133,"ogSiteName":673,"ogType":674,"canonicalUrls":1133,"schema":1134},"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":1130,"description":1131,"authors":1136,"heroImage":1132,"date":1138,"body":1139,"category":704,"tags":1140},[1137],"Dov Hershkovitch","2021-05-20","\n\n## A bug when job `needs` a manual job\n\nIn [13.12 we fixed a bug](https://gitlab.com/gitlab-org/gitlab/-/issues/31264) that might affect the existing behavior of your pipeline. We explain why we had to fix the bug, the possible impact of this change on your pipeline, and the proposed workaround if you would like to revert this behavior.\n\n## Background on a two-job pipeline\n\nIn GitLab CI/CD you can easily configure a job to require manual intervention before it runs. The job gets added to the pipeline, but doesn't run until you click the **play** button on it.\n\nLet's look at a two-job pipeline:\n\n```yaml\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\nThis is how it looks when we look at the pipeline graph:\n\n![image2](https://about.gitlab.com/images/blogimages/11-05-2021-when-job-needs-manual/blog1.png){: .shadow.medium.center.wrap-text}\n\nNotice that the manual job gets skipped, and the pipeline completes successfully even though the manual job did not get triggered. This happens because manual jobs are considered optional, and do not need to run.\n\nInternally, manual jobs have `allow_failure` set to true by default, which means that these skipped manual jobs do not cause a pipeline failure. The YAML code below demonstrates how to write the manual job, which results in the same behavior. The job doesn't automatically start, is skipped, and the pipeline passes.\n\n```yaml\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\nYou can set `allow_failure` to true for any job, including both manual and automatic jobs, and then the pipeline does not care if the job runs successfully or not.\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```yaml\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\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\nThe `needs` keyword creates a dependency between the two jobs, so `job10` runs as soon as `job1` **finishes running** successfully, regardless of the stage ordering.\n\nSo what happens if a job `needs` a manual job, that doesn't start running automatically?\n\nLet's look at the following example:\n\n```yaml\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\nBefore 13.12, this type of configuration would cause the pipeline to get stuck. The `deploy` job can only start when the `test` job completes, but the `test` job does not start automatically. The rest of the pipeline stops and waits for someone to run the manual `test` job.\n\n![image3](https://about.gitlab.com/images/blogimages/11-05-2021-when-job-needs-manual/blog2.png){: .shadow.medium.center.wrap-text}\n\nThis behavior is even worse with larger pipelines:\n\n![image4](https://about.gitlab.com/images/blogimages/11-05-2021-when-job-needs-manual/blog3.png){: .shadow.medium.center.wrap-text}\n\nThe example above shows there is a needs relationship between `post test` job and the `test` job (which is a manual job) as you can see the pipeline is stuck in a running state and any subsequent jobs will not run.\n\nThis was not the behavior most users expected, so we improved it in 13.12. Now, if there is a `needs` relationship pointing to a manual job, the pipeline doesn't stop by default anymore. The manual job is considered optional by default in all cases now. Any jobs that have a `needs` relationship to manual jobs are now also considered optional and skipped if the manual job isn't triggered. If you start the manual job, the jobs that need it can start after it completes.\n\nNote that if you start the manual job before a later job that has it in a `needs` configuration, the later job will still wait for the manual job to finishes running.\n\n## What if I don't want this new behavior?\n\nOne of the reasons we selected this solution is that you can quickly revert this change. If you made use of this inadvertent behavior and configured your pipelines to use it to block on manual jobs, it's easy to return to that previous behavior. All you have to do is override the default `allow_failure` in the manual job with `allow_failure: false`. This way the manual job is no longer optional, and the pipeline status will be marked as blocked and wait for you to run the job manually.\n\n```yaml\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\nShare any thoughts, comments, or questions, by opening an issue in GitLab and mentioning me (`@dhershkovitch`).\n",[9,795,728],{"slug":1142,"featured":6,"template":686},"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":1148,"_dir":244,"_draft":6,"_partial":6,"_locale":7,"seo":1149,"content":1155,"config":1162,"_id":1164,"_type":14,"title":1165,"_source":16,"_file":1166,"_stem":1167,"_extension":19},"/en-us/blog/demystifying-ci-cd-variables",{"title":1150,"description":1151,"ogTitle":1150,"ogDescription":1151,"noIndex":6,"ogImage":1152,"ogUrl":1153,"ogSiteName":673,"ogType":674,"canonicalUrls":1153,"schema":1154},"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":1150,"description":1151,"authors":1156,"heroImage":1152,"date":1158,"body":1159,"category":704,"tags":1160,"updatedDate":1161},[1157],"Veethika Mishra","2021-04-09","There is a lot of flexibility when it comes to defining and using variables for [CI/CD](https://about.gitlab.com/topics/ci-cd/). Variables are extremely useful for controlling jobs and pipelines, and they help you avoid hard-coding values in your `.gitlab-ci.yml` configuration file. The information in this post should weave a larger picture by bringing together all (or most) of the information around defining and handling variables, making it easier to understand the scope and capabilities. Relevant documentation is linked throughout the post.\n\nIn [GitLab CI/CD](https://docs.gitlab.com/ee/ci/), variables can be used to customize jobs by defining and storing values. When using variables there is no need to hard code values. In GitLab, CI/CD variables can be defined by going to **Settings >> CI/CD >> Variables**, or by simply defining them in the `.gitlab-ci.yml` file.\n\nVariables are useful for configuring third-party services for different deployment environments, such as `testing`, `staging`, `production`, etc. Modify the services attached to those environments by simply changing the variable that points to the API endpoint the services need to use. Also use variables to configure jobs and then make them available as environment variables within the jobs when they run.\n\n![GitLab reads the .gitlab-ci.yml file to scan the referenced variable and sends the information to the GitLab Runner. The variables are exposed on and output by the runner.](https://about.gitlab.com/images/blogimages/demystifying-ci-cd-variables/variables_processing.jpeg)\n\n## The relationship between variables and environments\n\nSoftware development as a process includes stages to test a product before rolling it out to users. [Environments](https://docs.gitlab.com/ee/ci/environments/) are used to define what those stages look like and it may differ between teams and organizations.\n\nOn the other hand, variables are data values that are likely to change as a result of user interaction with a product. For example, their age, preference, or any input you could possibly think of that might determine their next step in the product task-flow.\n\nWe often hear the term [environment variable](https://docs.gitlab.com/ee/administration/environment_variables.html). These are variables that are defined in a given environment, but outside the application. GitLab CI/CD variables provide developers with the ability to configure values in their code. Using variables is helpful because it ensures that the code is flexible. GitLab CI/CD variables allow users to modify an application deployed to a certain environment without making any change to code. It is simple to run tests or even integrate third-party services by changing a configuration environment variable outside the application.\n\n## The scope of variables for CI/CD\n\n![Order of precedence for CI/CD variables: 1) Manual pipeline run, trigger and schedule pipeline variables, 2) Project level, group level, instance level protected variables, 3) Inherited CI/CD variables, 4) Job level, global yml defined variables, 5) Deployment variables, 6) Pre-defined CI/CD variables](https://about.gitlab.com/images/blogimages/demystifying-ci-cd-variables/variables_precedence.jpeg)\n\n### `.gitlab-ci.yml` defined variables\n\nVariables that need to be available in the job environment can be added to GitLab. These CI/CD variables are meant to store non-sensitive project configuration, like the database URL in the `.gitlab-ci.yml` file. Reuse this variable in multiple jobs or scripts, wherever the value is needed. If the value changes, you only need to update the variable once, and the change is reflected everywhere the variable is used.\n\n### Project CI/CD variables\n\nMoving a step above the repository-specific requirements, you can define CI/CD variables in [project settings](https://docs.gitlab.com/ee/ci/variables/#for-a-project), which makes them available to CI/CD pipelines. These are stored out of the repository (not in the `.gitlab-ci.yml` file), but are still available to use in the CI/CD configuration and scripts. Storing the variables outside the `.gitlab-ci.yml` file keeps these values limited to a project-only scope, and not saved in plain text in the project.\n\n### Group and instance CI/CD variables\n\nSome variables are relevant at the group level, or even instance level, and could be useful to all projects in a group or instance. Define the variables in the [group or instance settings](https://docs.gitlab.com/ee/ci/variables/#for-a-group) so all projects within those scopes can use the variables without actually needing to know the value  or having to create the variables for the lower scope. For example, a common value that needs to be updated in multiple projects can be easily managed if it stays up-to-date in a single place. Alternatively, multiple projects could use a specific password without actually needing to know the value of the password itself.\n\n## Jobs and pipelines as environments\n\nGitLab CI/CD variables, besides being used as environment variables, also work in the scope of the `.gitlab-ci.yml` configuration file to configure pipeline behavior, unrelated to any environment. The variables can be stored in the project/group/instance settings and be made available to jobs in pipelines.\n\nFor example:\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\nThe variable `($CI_COMMIT_BRANCH)` in the script section runs in the scope of the job in which it was defined. This scope is the \"job environment\" – meaning, when the job starts, the GitLab runner starts up a Docker container and runs the job in that environment. The runner will make that variable (and all other predefined or custom variables) available to the job, and it can display their value in the log output if needed.\n\nBut the variable is **also** used in the `if:` section to determine when the job should run. That in itself is not an environment, which is why we call these CI/CD variables. They can be used to dynamically configure your CI/CD jobs, **as well** as be used as environment variables when the job is running.\n\n## Predefined variables\n\nA number of variables are [predefined](https://docs.gitlab.com/ee/ci/variables/predefined_variables.html) when a GitLab CI/CD pipeline starts. A user can immediately access values for things like commit, project, or pipeline details without needing to define the variables themselves.\n\n## Custom CI/CD variables\n\n![Runners can create two kinds of custom CI/CD variables: Type and File.](https://about.gitlab.com/images/blogimages/demystifying-ci-cd-variables/variable_types.jpeg)\n\nWhen creating a CI/CD variable in the settings, GitLab gives the user more configuration options for the variable. Use these extra configuration options for stricter control over more sensitive variables:\n\n**Environment scope:** If a variable only ever needs to be used in one specific environment, set it to only ever be available in that environment. For example, you can set a deploy token to only be available in the `production` environment.\n\n**Protected variables:** Similar to the environment scope, you can set a variable to be available only when the pipeline runs on a protected branch, like your default branch.\n\n**Variable type:** A few applications require configuration to be passed to it in the form of a file. If a user has an application that requires this configuration, just set the type of variable as a \"File\". Configuring the CI/CD variable this way means that when the runner makes the variable available in the environment, it actually writes it out to a temporary file, and stores the path to the file as the value. Next, a user can pass the path to the file to any applications that need it.\n\nAlong with the listed ways of defining and using variables, GitLab introduced a feature that generates pre-filled variables when there's a need to run a pipeline manually. Prefilled variables reduce the chances of running into an error and makes running the pipeline easier.\n\n**Masked variables:** [Masked variables](https://docs.gitlab.com/ee/ci/variables/#mask-a-cicd-variable) are CI variables that have been **hidden in job logs** to prevent the variable’s value from being displayed. \n\n**Masked and hidden variables:** Introduced in [GitLab 17.4](https://about.gitlab.com/releases/2024/09/19/gitlab-17-4-released/#hide-cicd-variable-values-in-the-ui), [Masked and hidden](https://docs.gitlab.com/ee/ci/variables/#hide-a-cicd-variable) variables provide the same masking feature from job logs and **keep the value hidden** **in the Settings UI**. We do not recommend using either of these variables for sensitive data (e.g. secrets) as they can be inadvertently exposed. \n\n## Secrets\n\nA secret is a sensitive credential that should be kept confidential. Examples of a secret include:\n\n* Passwords  \n* SSH keys  \n* Access tokens  \n* Any other types of credentials where exposure would be harmful to an organization\n\nGitLab currently enables its users to [use external secrets in CI](https://docs.gitlab.com/ee/ci/secrets/), by leveraging HashiCorp Vault, Google Cloud Secret Manager, and Azure Key Vault to securely manage keys, tokens, and other secrets at the project level. This allows users to separate these secrets from other CI/CD variables for security reasons.\n\n### GitLab Secrets Manager\n\nBesides providing support for external secrets in CI, GitLab is also working on introducing a [native solution to secrets management](https://gitlab.com/groups/gitlab-org/-/epics/10108) to securely and conveniently store secrets within GitLab. This solution will also help customers use the stored secrets in GitLab specific components and environments, and easily manage access at namespace groups and projects level. \n\n## Read more\n* [GitLab native secrets manager to give software supply chain security a boost](https://about.gitlab.com/blog/gitlab-native-secrets-manager-to-give-software-supply-chain-security-a-boost/)\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",[683,970,902,9,109,729],"2025-01-13",{"slug":1163,"featured":6,"template":686},"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":1169,"_dir":244,"_draft":6,"_partial":6,"_locale":7,"seo":1170,"content":1176,"config":1182,"_id":1184,"_type":14,"title":1185,"_source":16,"_file":1186,"_stem":1187,"_extension":19},"/en-us/blog/deploy-aws",{"title":1171,"description":1172,"ogTitle":1171,"ogDescription":1172,"noIndex":6,"ogImage":1173,"ogUrl":1174,"ogSiteName":673,"ogType":674,"canonicalUrls":1174,"schema":1175},"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":1171,"description":1172,"authors":1177,"heroImage":1173,"date":1179,"body":1180,"category":704,"tags":1181},[1178],"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",[1120,728,9,683],{"slug":1183,"featured":6,"template":686},"deploy-aws","content:en-us:blog:deploy-aws.yml","Deploy Aws","en-us/blog/deploy-aws.yml","en-us/blog/deploy-aws",{"_path":1189,"_dir":244,"_draft":6,"_partial":6,"_locale":7,"seo":1190,"content":1196,"config":1202,"_id":1204,"_type":14,"title":1205,"_source":16,"_file":1206,"_stem":1207,"_extension":19},"/en-us/blog/develop-c-unit-testing-with-catch2-junit-and-gitlab-ci",{"title":1191,"description":1192,"ogTitle":1191,"ogDescription":1192,"noIndex":6,"ogImage":1193,"ogUrl":1194,"ogSiteName":673,"ogType":674,"canonicalUrls":1194,"schema":1195},"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":1191,"description":1192,"authors":1197,"heroImage":1193,"date":1199,"body":1200,"category":1118,"tags":1201},[1198],"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",[729,838,9,949,773],{"slug":1203,"featured":91,"template":686},"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":1209,"_dir":244,"_draft":6,"_partial":6,"_locale":7,"seo":1210,"content":1216,"config":1222,"_id":1224,"_type":14,"title":1225,"_source":16,"_file":1226,"_stem":1227,"_extension":19},"/en-us/blog/devops-strategy",{"title":1211,"description":1212,"ogTitle":1211,"ogDescription":1212,"noIndex":6,"ogImage":1213,"ogUrl":1214,"ogSiteName":673,"ogType":674,"canonicalUrls":1214,"schema":1215},"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":1211,"description":1212,"authors":1217,"heroImage":1213,"date":1219,"body":1220,"category":299,"tags":1221},[1218],"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](/solutions/file-locking/). 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",[902,728,9],{"slug":1223,"featured":6,"template":686},"devops-strategy","content:en-us:blog:devops-strategy.yml","Devops Strategy","en-us/blog/devops-strategy.yml","en-us/blog/devops-strategy",{"_path":1229,"_dir":244,"_draft":6,"_partial":6,"_locale":7,"seo":1230,"content":1236,"config":1243,"_id":1245,"_type":14,"title":1246,"_source":16,"_file":1247,"_stem":1248,"_extension":19},"/en-us/blog/devops-workflows-json-format-jq-ci-cd-lint",{"title":1231,"description":1232,"ogTitle":1231,"ogDescription":1232,"noIndex":6,"ogImage":1233,"ogUrl":1234,"ogSiteName":673,"ogType":674,"canonicalUrls":1234,"schema":1235},"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":1237,"description":1232,"authors":1238,"heroImage":1233,"date":1240,"body":1241,"category":704,"tags":1242},"Tips for productive DevOps workflows: JSON formatting with jq and CI/CD linting automation",[1239],"Michael Friedrich","2021-04-21","\n\n## What is JSON linting?\n\nTo understand JSON linting, let’s quickly break down the two concepts of JSON and linting. \n\n***JSON*** is an acronym for JavaScript Object Notation, which is a lightweight, text-based, open standard format designed specifically for representing structured data based on the JavaScript object syntax. It is most commonly used for transmitting data in web applications. It parses data faster than XML and is easy for humans to read and write.\n\n***Linting*** is a process that automatically checks and analyzes static source code for programming and stylistic errors, bugs and suspicious constructs. \n\nJSON has become popular because it is human-readable and doesn’t require a complete markup structure like XML. It is easy to analyze into logical syntactic components, especially in JavaScript. It also has many JSON libraries for most programming languages.\n\n### Benefits of JSON linting\n\nFinding an error in JSON code can be challenging and time-consuming. The best way to find and correct errors while simultaneously saving time is to use a linting tool. When Json code is copied and pasted into the linting editor, it validates and reformats Json. It is easy to use and supports a wide range of browsers, so applications development with Json coding don’t require a lot of effort to make them browser-compatible.\n\nJSON linting is an efficient way to reduce errors and it improves the overall quality of the JSON code. This can help accelerate development and reduce costs because errors are discovered earlier.\n\n### Some common JSON linting errors\n\nIn instances where a JSON transaction fails, the error information is conveyed to the user by the API gateway. By default, the API gateway returns a very basic fault to the client when a message filter has failed.\n\nOne common JSON linting error is parsing. A “parse: unexpected character\" error occurs when passing a value that is not a valid JSON string to the JSON. parse method, for example, a native JavaScript object. To solve the error, make sure to only pass valid JSON strings to the JSON.\n\nAnother common error is NULL or inaccurate data errors, not using the right data type per column or extension for JSON files, and not ensuring every row in the JSON table is in the JSON format.\n\n### How to fix JSON linting errors\n\nIf you encounter a NULL or inaccurate data error in parsing, the first step is to make sure you use the right data type per column. For example, in the case of “age,” use 12 instead of twelve.\n\nAlso make sure you are using the right extension for JSON files. When using a compressed JSON file, it must end with “json” followed by the extension of the format, such as “.gz.”\n\nNext, make sure the JSON format is used for every row in the JSON table. Create a table with a delimiter that is not in the input files. Then, run a query equivalent to the return name of the file, row points and the file path for the null NSON rows.\n\nSometimes you may find files that are not your source code files, but ones generated by the system when compiling your project. In that instance, when the file has a .js extension, the ESLint needs to exclude that file when searching for errors. One method of doing this is by using ‘IgnorePatterns:’ in .eslintrc.json file either after or before the “rules” tag.\n\n“ignorePatterns”: [“temp.js”, “**/vendor/*.js”],\n\n“rules”: {\n\nAlternatively, you can create a separate file named‘.eslintignore’ and incorporate the files to be excluded as shown below :\n**/*.js\nIf you opt to correct instead of ignore, look for the error code in the last column. Correct all the errors in one fule and rerun ‘npx eslint . >errfile’ and ensure all the errors of that type are cleared. Then look for the next error code and repeat the procedure until all errors are cleared.\n\nOf course, there will be instances when you won’t understand an error, so in that case, open [https://eslint.org/docs/user-guide/getting-started](https://eslint.org/docs/user-guide/getting-started) and type the error code in the ‘Search’ field on the top of the document. There you will find very detailed instructions as to why that error is raised and how to fix it.\n\nFinally, you can forcibly fix errors automatically while generating the error list using:\n\nNpx eslintrc . — fix \n\nThis is not recommended until you become more well-versed with lint errors and how to fix them. Also, you should keep a backup of the files you are linting because while fixing errors, certain code may get overwritten, which could cause your program to fail.\n\n## JSON linting best practices\n\nHere are some tips for helping your consumers use your output:\n\nFirst, always enclose the **Key** **:** **Value** pair within **double quotes**. It may be convenient (not sure how) to generate with Single quotes, but JSON parser don’t like to parse JSON objects with single quotes.\n\nFor numerical values, quotes are optional but it is a good idea to enclose them in double quotes.\n\nNext, don’t ever use hyphens in your key fields because it breaks python and scala parser. Instead use underscores (_). \n\nIt’s a good idea to always create a root element, especially when you’re creating a complicated JSON.\n\n\nModern web applications come with a REST API which returns JSON. The format needs to be parsed, and often feeds into scripts and service daemons polling the API for automation.\n\nStarting with a new REST API and its endpoints can often be overwhelming. Documentation may suggest looking into a set of SDKs and libraries for various languages, or instruct you to use `curl` or `wget` on the CLI to send a request. Both CLI tools come with a variety of parameters which help to download and print the response string, for example in JSON format.\n\nThe response string retrieved from `curl` may get long and confusing. It can require parsing the JSON format and filtering for a smaller subset of results. This helps with viewing the results on the CLI, and minimizes the data to process in scripts. The following example retrieves all projects from GitLab and returns a paginated result set with the first 20 projects:\n\n```shell\n$ curl \"https://gitlab.com/api/v4/projects\"\n```\n\n![Raw JSON as API response](https://about.gitlab.com/images/blogimages/devops-workflows-json-format-jq-ci-cd-lint/gitlab_api_response_raw_json.png){: .shadow}\n\nThe [GitLab REST API documentation](https://docs.gitlab.com/ee/api/#how-to-use-the-api) guides you through the first steps with error handling and authentication. In this blog post, we will be using the [Personal Access Token](https://docs.gitlab.com/ee/api/#personalproject-access-tokens) as the authentication method. Alternatively, you can use [project access tokens](https://docs.gitlab.com/ee/user/project/settings/project_access_tokens.html) for [automated authentication](https://docs.gitlab.com/ee/api/#authentication) that avoids the use of personal credentials.\n\n### REST API authentication\n\nSince not all endpoints are accessible with anonymous access they might require authentication. Try fetching user profile data with this request:\n\n```shell\n$ curl \"https://gitlab.com/api/v4/user\"\n{\"message\":\"401 Unauthorized\"}\n```\n\nThe API request against the `/user` endpoint requires to pass the personal access token into the request, for example, as a request header. To avoid exposing credentials on the terminal, you can export the token and its value into the user's environment. You can automate the variable export with ZSH and the [.env plugin](https://github.com/ohmyzsh/ohmyzsh/tree/master/plugins/dotenv) in your shell environment. You can also source the `.env` once in the existing shell environment.\n\n```shell\n$ vim ~/.env\n\nexport GITLAB_TOKEN=”...”\n\n$ source ~/.env\n```\n\nScripts and commands being run in your shell environment can reference the `$GITLAB_TOKEN` variable. Try querying the user API endpoint again, with adding the authorization header into the request:\n\n```shell\n$ curl -H \"Authorization: Bearer $GITLAB_TOKEN\" \"https://gitlab.com/api/v4/user\"\n```\n\nA reminder that only administrators can see the attributes of all users, and the individual can only see their user profile – for example, `email` is hidden from the public domain.\n\n### How to request responses in JSON\n\nThe [GitLab API provides many resources](https://docs.gitlab.com/ee/api/api_resources.html) and URL endpoints. You can manage almost anything with the API that you’d otherwise configure using the graphic user interface.\n\nAfter sending the [API request](https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol#Request_message), the [response message](https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol#Response_message) contains the body as string, for example as a [JSON content type](https://docs.gitlab.com/ee/api/#content-type). `curl` can provide more information about the response headers which is helpful for debugging. Multiple verbose levels enable the full debug output with `-vvv`:\n\n```shell\n$ curl -vvv \"https://gitlab.com/api/v4/projects\"\n[...]\n* SSL connection using TLSv1.2 / ECDHE-RSA-CHACHA20-POLY1305\n* ALPN, server accepted to use h2\n* Server certificate:\n*  subject: CN=gitlab.com\n*  start date: Jan 21 00:00:00 2021 GMT\n*  expire date: May 11 23:59:59 2021 GMT\n*  subjectAltName: host \"gitlab.com\" matched cert's \"gitlab.com\"\n*  issuer: C=GB; ST=Greater Manchester; L=Salford; O=Sectigo Limited; CN=Sectigo RSA Domain Validation Secure Server CA\n*  SSL certificate verify ok.\n[...]\n> GET /api/v4/projects HTTP/2\n> Host: gitlab.com\n> User-Agent: curl/7.64.1\n> Accept: */*\n[...]\n\u003C HTTP/2 200\n\u003C date: Mon, 19 Apr 2021 11:25:31 GMT\n\u003C content-type: application/json\n[...]\n[{\"id\":25993690,\"description\":\"project for adding issues\",\"name\":\"project-for-issues-1e1b6d5f938fb240\",\"name_with_namespace\":\"gitlab-qa-sandbox-group / qa-test-2021-04-19-11-13-01-d7d873fd43cd34b6 / project-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[... JSON content ...]\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* Closing connection 0\n```\n\nThe `curl` command output provides helpful insights into TLS ciphers and versions, the request lines starting with `>` and response lines starting with `\u003C`. The response body string is encoded as JSON.\n\n### How to see the structure of the returned JSON\n\nTo get a quick look at the structure of the returned JSON file, try these tips:\n\n* Enclose square brackets to identify an array `[ …. ]`.\n* Enclose curly brackets identify a [dictionary](https://en.wikipedia.org/wiki/Associative_array) `{ … }`. Dictionaries are also called associative arrays, maps, etc.\n* `”key”: value` indicates a key-value pair in a dictionary, which is identified by curly brackets enclosing the key-value pairs.\n\nThe values in [JSON](https://en.wikipedia.org/wiki/JSON) consist of specific types - a string value is put in double-quotes. Boolean true/false, numbers, and floating-point numbers are also present as types. If a key exists but its value is not set, REST APIs often return `null`.\n\nVerify the data structure by running \"linters\". Python's JSON module can parse and lint JSON strings. The example below misses a closing square bracket to showcase the error:\n\n```shell\n$ echo '[{\"key\": \"broken\"}' | python -m json.tool\nExpecting object: line 1 column 19 (char 18)\n```\n\n[jq](https://stedolan.github.io/jq/) – a lightweight and flexible CLI processor – can be used as a standalone tool to parse and validate JSON data.\n\n```shell\n$ echo '[{\"key\": \"broken\"}' | jq\nparse error: Unfinished JSON term at EOF at line 2, column 0\n```\n\n[`jq` is available](https://stedolan.github.io/jq/download/) in the package managers of most operating systems.\n\n```shell\n$ brew install jq\n$ apt install jq\n$ dnf install jq\n$ zypper in jq\n$ pacman -S jq\n$ apk add jq\n```\n\n### Dive deep into JSON data structures\n\nThe true power of `jq` lies in how it can be used to parse JSON data:\n\n> `jq` is like `sed` for JSON data. It can be used to slice, filter, map, and transform structured data with the same ease that `sed`, `awk`, `grep` etc., let you manipulate text.\n\nThe output below shows how it looks to run the request against the project API again, but this time, the output is piped to `jq`.\n\n```shell\n$ curl \"https://gitlab.com/api/v4/projects\" | jq\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\nThe first difference is the format of the JSON data structure, so-called [pretty-printed](https://en.wikipedia.org/wiki/Prettyprint). New lines and indents in data structure scopes help your eyes and allow you to identify the inner and outer data structures involved. This format is needed to determine which `jq` filters and methods you want to apply next.\n\n#### About arrays and dictionaries\n\nThe set of results from an API often is returned as a list (or \"array\") of items. An item itself can be a single value or a JSON object. The following example mimics the response from the GitLab API and creates an array of dictionaries as a nested result set.\n\n```shell\n$ vim result.json\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\nUse `cat` to print the file content on stdout and pipe it into `jq`. The outer data structure is an array – use `-c .[]` to access and print all items.\n\n```shell\n$ cat result.json | jq -c '.[]'\n{\"id\":1,\"name\":\"project1\"}\n{\"id\":2,\"name\":\"project2\"}\n{\"id\":3,\"name\":\"project-internal-dev\",\"namespace\":{\"name\":\"🦊\"}}\n```\n\n### How to filter data structures with `jq`\n\nFilter items by passing `| select (...)` to `jq`. The filter takes a lambda callback function as a comparator condition. When the item matches the condition, it is returned to the caller.\n\nUse the dot indexer `.` to access dictionary keys and their values. Try to filter for all items where the name is `project2`:\n\n```shell\n$ cat result.json | jq -c '.[] | select (.name == \"project2\")'\n{\"id\":2,\"name\":\"project2\"}\n```\n\nPractice this example by selecting the `id` with the value `2` instead of the `name`.\n\n#### Filter with matching a string\n\nDuring tests, you may need to match different patterns instead of knowing the full name. Think of projects that match a specific path or are located in a group where you only know the prefix. Simple string matches can be achieved with the `| contains (...)` function. It allows you to check whether the given string is inside the target string – which requires the selected attribute to be of the string type.\n\nFor a filter with the select chain, the comparison condition needs to be changed from the equal operator `==` to checking the attribute `.name` with `| contains (\"dev\")`.\n\n```shell\n$ cat result.json | jq -c '.[] | select (.name | contains (\"dev\") )'\n{\"id\":3,\"name\":\"project-internal-dev\",\"namespace\":{\"name\":\"🦊\"}}\n```\n\nSimple matches can be achieved with the `contains` function.\n\n#### Filter with matching regular expressions\n\nFor advanced string pattern matching, it is recommended to use regular expressions. `jq` provides the [test function for this use case](https://stedolan.github.io/jq/manual/#RegularexpressionsPCRE). Try to filter for all projects which end with a number, represented by `\\d+`. Note that the backslash `\\` needs to be escaped as `\\\\` for shell execution. `^` tests for beginning of the string, `$` is the ending check.\n\n```shell\n$ cat result.json | jq -c '.[] | select (.name | test (\"^project\\\\d+$\") )'\n{\"id\":1,\"name\":\"project1\"}\n{\"id\":2,\"name\":\"project2\"}\n```\n\nTip: You can [test and build the regular expression with regex101](https://regex101.com/) before test-driving it with `jq`.\n\n#### Access nested values\n\nKey value pairs in a dictionary may have a dictionary or array as a value. `jq` filters need to take this factor into account when filtering or transforming the result. The example data structure provides `project-internal-dev` which has the key `namespace` and a value of a dictionary type.\n\n```shell\n  {\n    \"id\": 3,\n    \"name\": \"project-internal-dev\",\n    \"namespace\": {\n      \"name\": \"🦊\"\n    }\n  }\n```\n\n`jq` allows the user to specify the [array and dictionary types](https://stedolan.github.io/jq/manual/#TypesandValues) as `[]` and `{}` to be used in select chains with greater and less than comparisons. The `[]` brackets select filters for non-empty dictionaries for the `namespace` attribute, while the `{}` brackets select for all `null` (raw JSON) values.\n\n```shell\n$ cat result.json | jq -c '.[] | select (.namespace >={} )'\n{\"id\":3,\"name\":\"project-internal-dev\",\"namespace\":{\"name\":\"🦊\"}}\n\n$ cat result.json | jq -c '.[] | select (.namespace \u003C={} )'\n{\"id\":1,\"name\":\"project1\"}\n{\"id\":2,\"name\":\"project2\"}\n```\n\nThese methods can be used to access the name attribute of the namespace, but only if the namespace contains values. Tip: You can chain multiple `jq` calls by piping the result into another `jq` call. `.name` is a subkey of the primary `.namespace` key.\n\n```shell\n$ cat result.json | jq -c '.[] | select (.namespace >={} )' | jq -c '.namespace.name'\n\"🦊\"\n```\n\nThe additional select command with non-empty namespaces ensures that only initialized values for `.namespace.name` are returned. This is a safety check, and avoids receiving `null` values in the result you would need to filter again.\n\n```shell\n$ cat result.json| jq -c '.[]' | jq -c '.namespace.name'\nnull\nnull\n\"🦊\"\n```\n\nBy using the additional check with `| select (.namespace >={} )`, you only get the expected results and do not have to filter empty `null` values.\n\n### How to expand the GitLab endpoint response\n\nSave the result from the API projects call and retry the examples above with `jq`.\n\n```shell\n$ curl \"https://gitlab.com/api/v4/projects\" -o result.json 2&>1 >/dev/null\n```\n\n### Validate CI/CD YAML with `jq` for Git hooks\n\nWhile writing this blog post, I learned that you can [escape and encode YAML into JSON with `jq`](https://docs.gitlab.com/ee/api/lint.html#escape-yaml-for-json-encoding). This trick comes in handy when automating YAML linting on the CLI, for example as a Git pre-commit hook.\n\nLet’s take a look at the simplest way to test GitLab CI/CD from our [community meetup workshops](https://gitlab.com/gitlab-de/swiss-meetup-2021-jan#resources). A common mistake with the first steps of the process can be missing the two spaces indent or missing whitespace between the dash and following command. The following examples use `.gitlab-ci.error.yml` as a filename to showcase errors and `.gitlab-ci.main.yml` for working examples.\n\n```shell\n$ vim .gitlab-ci.error.yml\n\nimage: alpine:latest\n\ntest:\nscript:\n  -exit 1\n```\n\nCommitting the change and waiting for the CI/CD pipeline to validate at runtime can be time-consuming. The [GitLab API provides a resource endpoint /ci/lint](https://docs.gitlab.com/ee/api/lint.html#validate-the-ci-yaml-configuration). A POST request with JSON-encoded YAML content will return a linting result faster.\n\n#### Parse CI/CD YAML into JSON with jq\n\nYou can use jq to parse the raw YAML string into JSON:\n\n```shell\n$ jq --raw-input --slurp \u003C .gitlab-ci.error.yml\n\"image: alpine:latest\\n\\ntest:\\nscript:\\n  -exit 1\\n\"\n```\n\nThe `/ci/lint` API endpoint requires a JSON dictionary with `content` as key, and the raw YAML string as a value. You can use `jq` to format the input by using the arg parser:\n\n```shell\n§ jq --null-input --arg yaml \"$(\u003C.gitlab-ci.error.yml)\" '.content=$yaml'\n{\n  \"content\": \"image: alpine:latest\\n\\ntest:\\nscript:\\n  -exit 1\"\n}\n```\n\n#### Send POST request to /ci/lint\n\nThe next building block is to [send a POST request to the /ci/lint](https://docs.gitlab.com/ee/api/lint.html#validate-the-ci-yaml-configuration). The request needs to specify the `Content-Type` header for the body. With using the pipe `|` character, the JSON-encoded YAML configuration is fed into the curl command call.\n\n```shell\n$ jq --null-input --arg yaml \"$(\u003C.gitlab-ci.error.yml)\" '.content=$yaml' \\\n| curl \"https://gitlab.com/api/v4/ci/lint?include_merged_yaml=true\" \\\n--header 'Content-Type: application/json' --data @-\n{\"status\":\"invalid\",\"errors\":[\"jobs test config should implement a script: or a trigger: keyword\",\"jobs script config should implement a script: or a trigger: keyword\",\"jobs config should contain at least one visible job\"],\"warnings\":[],\"merged_yaml\":\"",[9,728,729],{"slug":1244,"featured":6,"template":686},"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":1250,"_dir":244,"_draft":6,"_partial":6,"_locale":7,"seo":1251,"content":1257,"config":1262,"_id":1264,"_type":14,"title":1265,"_source":16,"_file":1266,"_stem":1267,"_extension":19},"/en-us/blog/distributed-version-control",{"title":1252,"description":1253,"ogTitle":1252,"ogDescription":1253,"noIndex":6,"ogImage":1254,"ogUrl":1255,"ogSiteName":673,"ogType":674,"canonicalUrls":1255,"schema":1256},"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":1252,"description":1253,"authors":1258,"heroImage":1254,"date":1259,"body":1260,"category":1075,"tags":1261},[1115],"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":1263,"featured":6,"template":686},"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":1269,"_dir":244,"_draft":6,"_partial":6,"_locale":7,"seo":1270,"content":1276,"config":1282,"_id":1284,"_type":14,"title":1285,"_source":16,"_file":1286,"_stem":1287,"_extension":19},"/en-us/blog/docker-my-precious",{"title":1271,"description":1272,"ogTitle":1271,"ogDescription":1272,"noIndex":6,"ogImage":1273,"ogUrl":1274,"ogSiteName":673,"ogType":674,"canonicalUrls":1274,"schema":1275},"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":1271,"description":1272,"authors":1277,"heroImage":1273,"date":1279,"body":1280,"category":681,"tags":1281},[1278],"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",[903,9],{"slug":1283,"featured":6,"template":686},"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":1289,"_dir":244,"_draft":6,"_partial":6,"_locale":7,"seo":1290,"content":1295,"config":1302,"_id":1304,"_type":14,"title":1305,"_source":16,"_file":1306,"_stem":1307,"_extension":19},"/en-us/blog/efficient-devsecops-workflows-with-rules-for-conditional-pipelines",{"title":1291,"description":1292,"ogTitle":1291,"ogDescription":1292,"noIndex":6,"ogImage":741,"ogUrl":1293,"ogSiteName":673,"ogType":674,"canonicalUrls":1293,"schema":1294},"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":1296,"description":1292,"authors":1297,"heroImage":741,"date":1299,"body":1300,"category":704,"tags":1301},"How to create efficient DevSecOps workflows with rules for conditional CI/CD pipelines",[1298],"Abubakar Siddiq Ango","2023-06-27","\nCI/CD pipelines can be simple or complex – what makes them efficient are rules that define when and how they run. By using rules, you create smarter CI/CD pipelines, which increase teams' productivity and allow organizations to iterate faster. In this tutorial, you will learn about the different types of CI/CD pipelines and rules and their use cases.\n\n## What is a pipeline?\nA pipeline is a top-level component of [continuous integration](https://docs.gitlab.com/ee/ci/introduction/index.html#continuous-integration) and [continuous delivery](https://docs.gitlab.com/ee/ci/introduction/index.html#continuous-delivery)/[continuous deployment](https://docs.gitlab.com/ee/ci/introduction/index.html#continuous-deployment), and it comprises [jobs](https://docs.gitlab.com/ee/ci/jobs/index.html), which are lists of tasks to be executed. Jobs are organized in [stages](https://docs.gitlab.com/ee/ci/yaml/index.html#stages), which define when the jobs run.\n\nA pipeline can be a [basic one](https://docs.gitlab.com/ee/ci/pipelines/pipeline_architectures.html#basic-pipelines) in which jobs run concurrently in each stage. Pipelines can also be complex, like [parent-child pipelines](https://docs.gitlab.com/ee/ci/pipelines/downstream_pipelines.html#parent-child-pipelines), [merge trains](https://docs.gitlab.com/ee/ci/pipelines/merge_trains.html), [multi-project pipelines](https://docs.gitlab.com/ee/ci/pipelines/downstream_pipelines.html#multi-project-pipelines), or the more advanced [Directed Acyclic Graph pipelines](https://docs.gitlab.com/ee/ci/directed_acyclic_graph/index.html) (DAG).\n\n![Complex pipeline showing dependencies](https://about.gitlab.com/images/blogimages/2023-06-15-efficient-devsecops-workflows-with-rules-for-conditional-pipelines/complex-pipelines.png)\n\nA [gitlab-runner pipeline](https://gitlab.com/gitlab-org/gitlab-runner/-/pipelines/798871212/) showing job dependencies.\n{: .note.text-center}\n\n![Directed Acyclic Graph](https://about.gitlab.com/images/blogimages/2023-06-15-efficient-devsecops-workflows-with-rules-for-conditional-pipelines/dag-pipelines.png)\n\nDirected Acyclic Graph pipeline\n{: .note.text-center}\n\nUse cases determine how complicated a pipeline can get. A use case might require testing an application and packaging it into a container; the pipeline can even further deploy the container to an orchestrator like Kubernetes or a container registry. Another use case might involve building applications that target different platforms with varying dependencies, which is where DAG pipelines shine.\n\n## What are CI/CD rules?\nCI/CD rules are the key to managing the flow of jobs in a pipeline. One of the powerful features of GitLab CI/CD is the ability to control when a CI/CD job runs, which can depend on context, changes made, [workflow](https://docs.gitlab.com/ee/ci/yaml/workflow.html) rules, values of CI/CD variables, or custom conditions. Aside from using `rules`, you can also control the flow of CI/CD pipelines using:\n\n* [`needs`](https://docs.gitlab.com/ee/ci/yaml/index.html#needs): establishes relationships between jobs and used in DAG pipelines\n* [`only`](https://docs.gitlab.com/ee/ci/yaml/index.html#only--except): defines when a job should run\n* [`except`](https://docs.gitlab.com/ee/ci/yaml/index.html#only--except): defines when a job should not run\n* [`workflow`](https://docs.gitlab.com/ee/ci/yaml/workflow.html): controls when pipelines are created\n\n`only` and `except` should not be used with `rules` as this can lead to unexpected behavior. It is recommended to use `rules`, learn more in the following sections.\n\n## What is the `rules` feature?\n`rules` determine when and if a job runs in a pipeline. If you have multiple rules defined, they are all evaluated in order until a matching rule is found and the job is executed according to the specified configuration.\n\n[Rules](https://docs.gitlab.com/ee/ci/yaml/#rules) can be defined using the keywords: `if`, `changes`, `exists`, `allow_failure`, `variables`, `when` and `needs`.\n\n### `rules:if`\nThe `if` keyword evaluates if a job should be added to a pipeline. The evaluation is done based on the values of [CI/CD variables](https://docs.gitlab.com/ee/ci/variables/index.html) defined in the scope of the job or pipeline and [predefined CI/CD variables](https://docs.gitlab.com/ee/ci/variables/predefined_variables.html).\n\n```yaml\njob:\n  script:\n    - echo $(date)\n  rules:\n    - if: $CI_MERGE_REQUEST_SOURCE_BRANCH_NAME == $CI_DEFAULT_BRANCH\n```\n\nIn the CI/CD script above, the job prints the current date and time with the `echo` command. The job is only executed if the source branch of a merge request (`CI_MERGE_REQUEST_SOURCE_BRANCH_NAME`) is the same as the project's default branch (`CI_DEFAULT_BRANCH`) in a [merge request pipeline](https://docs.gitlab.com/ee/ci/pipelines/merge_request_pipelines.html). You can use the `==` and `!=` operators for comparison, while `=~` and `!~` allow you to compare a variable to a regular expression. You can combine multiple expressions using the `&&` (AND), `||` (OR) operators, and parentheses for grouping expressions.\n\n### `rules:changes`\nWith the `changes` keyword, you can watch for changes to certain files or folders for a job to execute. GitLab uses the output of [Git diffstat](https://git-scm.com/docs/git-diff#Documentation/git-diff.txt\n\n```yaml\njob:\n  script:\n    - terraform plan\n  rules:\n    - if: $CI_PIPELINE_SOURCE == \"merge_request_event\"\n      changes:\n        - terraform/**/*.tf\n```\n\nIn this example, the `terraform plan` is only executed when files with the `.tf` extension are changed in the `terraform` folder and its subdirectories. An additional rule ensures the job is executed for [merge request pipelines](https://docs.gitlab.com/ee/ci/pipelines/merge_request_pipelines.html).\n\nThe `changes` rule can look for changes in specific files with `paths`:\n\n```yaml\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\nChanges to files in a source reference (branch, tag, commit) can also be compared against other references in the Git repository. The CI/CD job will only execute when the source reference differs from the [specified reference value defined in `rules:changes:compare_to`](https://docs.gitlab.com/ee/ci/yaml/#ruleschangescompare_to). This value can be a Git commit SHA, tag, or branch name. The following example compares the source reference to the current `production` branch (`refs/head/production`).\n\n```yaml\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### `rules:exists`\nLike `changes`, you can execute CI/CD jobs only when specific files exist [using `rules:exists` rules](https://docs.gitlab.com/ee/ci/yaml/#rulesexists). For example, you can run a job that checks whether a `Gemfile.lock` file exists. The following example audits a Ruby project for vulnerable versions of gems or insecure gem sources using the [bundler-audit project](https://github.com/rubysec/bundler-audit).\n\n```yaml\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### `rules:allow_failure`\nThere are scenarios where the failure of a job should not affect the following jobs and stages of the pipeline. This can be useful in use cases where non-blocking tasks are required as part of a project but don't impact the project in any way. The [`rules:allow_failure` rule](https://docs.gitlab.com/ee/ci/yaml/#rulesallow_failure) can be set to `true` or `false`. It defaults to `false` implicitly when the rule is not specified.\n\n```yaml\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\nIn this example, the job can fail only if a merge request event triggers the pipeline and the target branch is not protected.\n\n### `rules:needs`\nDisabled by fault, [`rules:needs`](https://docs.gitlab.com/ee/ci/yaml/#rulesneeds) was introduced in [GitLab 16](https://about.gitlab.com/releases/2023/05/22/gitlab-16-0-released/) and can be enabled with the `introduce_rules_with_needs` [feature flag](https://docs.gitlab.com/ee/user/feature_flags.html). [`needs`](https://docs.gitlab.com/ee/ci/yaml/index.html#needs) is used to execute jobs out of order without waiting for other jobs in a stage to complete. When used with `rules`, it replaces the job's `needs` specification when the set conditions are met.\n\n```yaml\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\nIn the example above, the deploy job has the `build-dev` job as a dependency before it runs; however, when the commit branch is the project's default branch, its dependency changes to `build-prod` and `qa-checks`. This can allow for extra checks to be implemented based on context.\n\n### `rules:variables`\nIn some situations, you only need certain variables in specific conditions, or their values change based on content; you can use the [`rules:variables`](https://docs.gitlab.com/ee/ci/yaml/#rulesvariables) rule to define variables when specific conditions are met. This also allows to create more dynamic CI/CD execution workflows.\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### `workflow:rules`\nSo far, we have looked at controlling when jobs run in a pipeline using the `rules` keyword. Sometimes, you want to control how the entire pipeline behaves: That's where [`workflow:rules` provide a powerful option](https://docs.gitlab.com/ee/ci/yaml/#workflowrules). `workflow:rules` are evaluated before jobs and take precedence over the job rules. For example, if a job has rules that allow it to run against a specific branch, but the workflow rules set jobs running against the branch to `when: never`, the jobs will not run.\n\nAll the features of `rules` mentioned in the previous sections work for `workflow:rules`.\n\n```yaml\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\nIn the example above, the CI/CD pipeline runs except when a schedule or push event is triggered.\n\n## Use cases for CI/CD rules\nIn the previous section, we looked at different ways of using the `rules` feature of GitLab CI/CD. In this section, we will explore practical use cases.\n\n### Developer experience\nOne of the benefits of a DevSecOps platform is to allow developers to focus on what they do best: writing their code and doing as little operations as possible. A company's DevOps or Platform team can create CI/CD templates for different stages of their development lifecycle and use rules to add CI/CD jobs to handle specific tasks based on their technology stack. A developer only needs to include a default CI/CD script and pipelines are automatically created based on files detected, refs used, or defined variables, leading to increased productivity.\n\n### Security and quality assurance\nA major function of CI/CD pipelines is to catch bugs or vulnerabilities before they are deployed into production infrastructure. Using CI/CD rules, security and quality assurance teams can dynamically run extra checks on changes introduced when certain factors are introduced. For example, malware scans can be added when new file extensions not in an approved list are detected, or more advanced performance tests are automatically added when a certain level of change has been introduced to the codebase. With GitLab's built-in security, including security in your pipelines can be done with just a few lines of code.\n\n```yaml\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### Automation\nThe power of CI/CD rules shines through in the (nearly) limitless possibilities of automating your CI/CD pipelines. GitLab [AutoDevOps](https://docs.gitlab.com/ee/topics/autodevops/) is an example. It uses an opinionated best-practice collection of [GitLab CI/CD templates](https://gitlab.com/gitlab-org/gitlab/-/tree/master/lib/gitlab/ci/templates) and rules to detect the technology stack used. AutoDevOps creates relevant jobs that take your application all the way to production from a push. You can review the [AutoDevOps template](https://gitlab.com/gitlab-org/gitlab/-/blob/master/lib/gitlab/ci/templates/Auto-DevOps.gitlab-ci.yml) to learn how it leverages CI/CD rules for greater efficiency.\n\n### Using CI/CD components\nGrowth comes with several iterations of work and creating best practices. While building CI/CD pipelines, your DevOps team would have made several CI/CD scripts that they repurpose across pipelines using the [`include`](https://docs.gitlab.com/ee/ci/yaml/#include) keyword. In [GitLab 16](https://about.gitlab.com/releases/2023/05/22/gitlab-16-0-released/), GitLab [introduced CI/CD Components](https://about.gitlab.com/releases/2023/05/22/gitlab-16-0-released/#cicd-components), an experimental feature that allows your team to create reusable CI/CD components and publish them as a catalog that can be used to build smarter CI/CD pipelines rapidly. You can learn more [about using CI/CD components](https://docs.gitlab.com/ee/ci/components/) and the [component catalog direction](https://about.gitlab.com/direction/verify/component_catalog/).\n\nGitLab CI/CD enables you to run smarter pipelines, and it does so together with [GitLab Duo, AI-powered workflows](/gitlab-duo/) to help you build more secure software, faster.\n",[729,9,683,773,482],{"slug":1303,"featured":6,"template":686},"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":1309,"_dir":244,"_draft":6,"_partial":6,"_locale":7,"seo":1310,"content":1316,"config":1322,"_id":1324,"_type":14,"title":1325,"_source":16,"_file":1326,"_stem":1327,"_extension":19},"/en-us/blog/efficient-pipelines",{"title":1311,"description":1312,"ogTitle":1311,"ogDescription":1312,"noIndex":6,"ogImage":1313,"ogUrl":1314,"ogSiteName":673,"ogType":674,"canonicalUrls":1314,"schema":1315},"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":1311,"description":1312,"authors":1317,"heroImage":1313,"date":1319,"body":1320,"category":704,"tags":1321},[1318],"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](/blog/how-to-troubleshoot-a-gitlab-pipeline-failure/).\n",[728,9,683],{"slug":1323,"featured":6,"template":686},"efficient-pipelines","content:en-us:blog:efficient-pipelines.yml","Efficient Pipelines","en-us/blog/efficient-pipelines.yml","en-us/blog/efficient-pipelines",{"_path":1329,"_dir":244,"_draft":6,"_partial":6,"_locale":7,"seo":1330,"content":1336,"config":1344,"_id":1346,"_type":14,"title":1347,"_source":16,"_file":1348,"_stem":1349,"_extension":19},"/en-us/blog/enable-slos-as-code",{"title":1331,"description":1332,"ogTitle":1331,"ogDescription":1332,"noIndex":6,"ogImage":1333,"ogUrl":1334,"ogSiteName":673,"ogType":674,"canonicalUrls":1334,"schema":1335},"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":1331,"description":1332,"authors":1337,"heroImage":1333,"date":1341,"body":1342,"category":792,"tags":1343},[1338,1339,1340],"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",[231,9,728],{"slug":1345,"featured":6,"template":686},"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":1351,"_dir":244,"_draft":6,"_partial":6,"_locale":7,"seo":1352,"content":1358,"config":1366,"_id":1368,"_type":14,"title":1369,"_source":16,"_file":1370,"_stem":1371,"_extension":19},"/en-us/blog/ensuring-compliance",{"title":1353,"description":1354,"ogTitle":1353,"ogDescription":1354,"noIndex":6,"ogImage":1355,"ogUrl":1356,"ogSiteName":673,"ogType":674,"canonicalUrls":1356,"schema":1357},"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":1353,"description":1354,"authors":1359,"heroImage":1355,"date":1362,"body":1363,"category":771,"tags":1364,"updatedDate":1365},[1360,1361],"Beatriz Barbosa","Fernando Diaz","2022-04-04","In this article, you'll learn the different ways to ensure **separation of duties** and\n**continuous compliance** with the GitLab DevSecOps platform. But first, let's level-set on two key concepts:\n\n**Compliance** means being in accordance with guidelines and specifications that have been\ndefined either by your corporation or a regulatory agency. Compliance helps maintain\ncorporate ethics, appropriate user policies, security standards, and much more for\nthe safety of consumers.\n\nNon-compliance may result in a bundle of legal fees and fines, so it is very important to maintain compliance. While maintaining compliance, DevSecOps teams must also ensure sustained development velocity, providing necessary simplicity, visibility, and control.\n\n**Separation of duties** requires multiple actors to complete a task to increase protection from error as well as prevent malicious activity. Separation of duties ensures roles best-suited for the job are the only ones that can perform it. As an example, some of the following\nactors are observed, each with a specific purpose:\n\n- a developer will be responsible for developing new features\n- a compliance officer will be responsible for creating and enforcing the usage of a pipeline\n- an application security engineer will be responsible for approving merge requests with vulnerabilities\n\nConsidering the above roles, we can ensure that a developer cannot change a running pipeline.\nThis is a task that can only be performed by a compliance officer, ensuring only compliant code can be pushed without approval.\n\nAn application security engineer is responsible for reviewing and approving code with vulnerabilities, ensuring proper mitigation can be performed, and that nothing comes as a surprise in the future. In this scenario, developers can't merge code until compliance\nand security requirements are met.\n\n## Security policies\nGitLab provides **Security Policies**, which enable security teams to require security scans to run according to a configuration. This gives security teams confidence that the configured scans have not been changed or disabled.\n\nSecurity policies can be scoped to meet certain **Compliance Frameworks**. This means that your project has certain compliance requirements and needs additional oversight. This label can be created in **Secure > Compliance Center > Frameworks** under your top-level group.\n\n![Compliance Framework Label](https://about.gitlab.com/images/blogimages/compliance-04-2022/cf-step-2.png)\n\n**Note:** Compliance labels can only be assigned to projects within the top-level group in which we create the label.\n\nThere are three types of policies, [Scan Execution Policies](https://docs.gitlab.com/ee/user/application_security/policies/scan_execution_policies.html), [Merge Request Approval Policies](https://docs.gitlab.com/ee/user/application_security/policies/merge_request_approval_policies.html), and [Pipeline Execution Policies](https://docs.gitlab.com/ee/user/application_security/policies/pipeline_execution_policies.html).\n\n* **Scan Execution Policies:** Require that security scans run on a specified schedule or with the project pipeline.\n* **Merge Request Approval Policies:** Take action based on scan results, such as requiring approval from the security team before a merge can occur.\n* **Pipeline Execution Policies:** Enforce CI/CD jobs for applicable projects.\n\nThese policies can be configured via the Policy Editor in a few simple steps.\n\n### Scan execution\n\n1. Go to **Security & Compliance > Policies**.\n\n2. Create a new policy by pressing the **New Policy** button.\n\n3. Select **Scan Execution**.\n\n4. Create the rule. I'm creating a rule that requires [SAST](https://docs.gitlab.com/ee/user/application_security/sast/) to be configured in order for a pipeline to run.\n\n```yaml\nname: force_sast\ndescription: 'require sast to run'\nenabled: true\nrules:\n- type: pipeline\n  branches:\n  - main\nactions:\n- scan: sast\n```\n\n5. Submit the policy by creating a merge request and then merge.\n\nAll scan execution policy changes are applied through a background job that runs once every 10 minutes.\nAllow up to 10 minutes for any policy changes committed to this project to take effect.\n\n6. Try and run a pipeline. It will not be run unless SAST is defined in the YAML.\n\n**Note**: You can also force SAST to run on a timer. For more information, see the scan execution\npolicies [documentation](https://docs.gitlab.com/ee/user/application_security/policies/scan-execution-policies.html).\n\n### Merge Request Approval\n\n1. Go to **Secure > Policies**.\n\n2. Create a new policy by pressing the **New Policy** button.\n\n3. Select **Merge Request Approval Policy**.\n\n4. Define policy scope.\n\n5. Create the rule.\n\n![separation of duties update - image 1](https://res.cloudinary.com/about-gitlab-com/image/upload/v1750098241/Blog/Content%20Images/Blog/Content%20Images/image1_aHR0cHM6_1750098241214.png)\n\n6. Add action to take.\n\n![separation of duties update - image 2](https://res.cloudinary.com/about-gitlab-com/image/upload/v1750098241/Blog/Content%20Images/Blog/Content%20Images/image3_aHR0cHM6_1750098241215.png)\n\n**Note:** The policy is evaluated according to the rules you set. This means that, if the rules are invalid, or can’t be evaluated, approval is required. To prevent this, the default Fallback behavior field can be changed to `open`.\n\n![separation of duties update - image 3](https://res.cloudinary.com/about-gitlab-com/image/upload/v1750098241/Blog/Content%20Images/Blog/Content%20Images/image5_aHR0cHM6_1750098241217.png)\n\n1. Submit the policy by creating a merge request and then merging\n\n2. Create a separate merge request with vulnerabilities\n\nYou can see how to add vulnerabilities by checking out the Developer Workflow section of the GitLab DevSecOps Workshop.\n\n3. Verify Merge Request Approval Policy is being used by viewing merge request.\n\n### Pipeline Execution Policy\n\nTo set up a pipeline execution policy, you need to first create a project containing the CI files you would like to run. Make sure that only the security team and/or administrator has access to ensure separation of duties. I created the \"Compliance and Deploy\" project, which contains the YAML I wish to enforce.\n\n1. Go to **Secure > Policies**.\n\n2. Create a new policy by pressing the **New Policy** button.\n\n3. Select **Pipeline Execution Policy**.\n\n4. Define policy scope.\n\n5. Add action to take.\n\n![separation of duties update - image 4](https://res.cloudinary.com/about-gitlab-com/image/upload/v1750098241/Blog/Content%20Images/Blog/Content%20Images/image8_aHR0cHM6_1750098241219.png)\n\n6. Add conditions.\n\n7. Submit the policy by creating a merge request and then merging.\n\n8. Try and run a pipeline. You will see the policy specific jobs and stages in your pipeline.\n\n## Audit Management and Compliance Dashboard\n\nAnother important part of compliance is knowing it is actually happening in your groups/projects. GitLab has Audit Events and Compliance Reports to assist with audits.\n\n**Audit Events** allows GitLab owners and administrators to track important events such as who performed certain actions and the time they occurred.\n\n![Audit events](https://about.gitlab.com/images/blogimages/compliance-04-2022/project-audit-events.png)\n\nAudit Events records different events per group and per project, which can be seen\nin the [audit events](https://docs.gitlab.com/ee/administration/audit_events.html) documentation.\nAudit Events can be accessed by going to **Security & Compliance > Audit Events**.\nSome examples include:\n\n- user was added to project and their permissions\n- permission changes of a user assigned to a project\n- project CI/CD variable added, removed, or protected status changed\n- user was added to group and their permissions\n- group name or path changed\n\nAudit Events can also be sent to an HTTP endpoint using Audit Event Streaming. Learn how\nto implement Audit Event Streaming in this [video](https://youtu.be/zHwVF9-i7e4?t=52).\n\n**Standards Adherence** gives you the ability to see a group's merge request activity. It provides a high-level view for all projects in the group.\n\n![separation of duties update - image 5](https://res.cloudinary.com/about-gitlab-com/image/upload/v1750098241/Blog/Content%20Images/Blog/Content%20Images/image2_aHR0cHM6_1750098241222.png)\n\nYou can use the report to:\n- get an overview of the latest merge request for each project\n- see if merge requests were approved and by whom\n- see merge request authors\n- see the latest CI/CD pipeline result for each merge request\n\nThe Standards Adherence report can be accessed in the top-level group by going to **Secure > Compliance Center**, and choosing the **Standards Adherence** tab.\n\n---\n\nThanks for reading! For more information on separation of duties within GitLab, check out [Continous Software Compliance with GitLab](/solutions/compliance/)\n",[771,9,683,970],"2024-12-16",{"slug":1367,"featured":6,"template":686},"ensuring-compliance","content:en-us:blog:ensuring-compliance.yml","Ensuring Compliance","en-us/blog/ensuring-compliance.yml","en-us/blog/ensuring-compliance",{"_path":1373,"_dir":244,"_draft":6,"_partial":6,"_locale":7,"seo":1374,"content":1380,"config":1384,"_id":1386,"_type":14,"title":1387,"_source":16,"_file":1388,"_stem":1389,"_extension":19},"/en-us/blog/first-code-to-ci-cd-deployments-in-5-minutes",{"title":1375,"description":1376,"ogTitle":1375,"ogDescription":1376,"noIndex":6,"ogImage":1377,"ogUrl":1378,"ogSiteName":673,"ogType":674,"canonicalUrls":1378,"schema":1379},"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":1375,"description":1376,"authors":1381,"heroImage":1377,"date":1179,"body":1382,"category":1075,"tags":1383},[1239],"\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,683,267],{"slug":1385,"featured":6,"template":686},"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":1391,"_dir":244,"_draft":6,"_partial":6,"_locale":7,"seo":1392,"content":1398,"config":1404,"_id":1406,"_type":14,"title":1407,"_source":16,"_file":1408,"_stem":1409,"_extension":19},"/en-us/blog/five-great-phabricator-features-inspired-gitlab",{"title":1393,"description":1394,"ogTitle":1393,"ogDescription":1394,"noIndex":6,"ogImage":1395,"ogUrl":1396,"ogSiteName":673,"ogType":674,"canonicalUrls":1396,"schema":1397},"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":1393,"description":1394,"authors":1399,"heroImage":1395,"date":1400,"body":1401,"category":299,"tags":1402},[1239],"2021-08-13","\n\nInnovation often happens because competition sparks new ideas. We unpack how Phabricator inspired GitLab to add new features.\n\n## Phabricator explained\n\nTurning back time a bit, what exactly is Phabricator? Built on the concept of web-based applications, Phabricator enables developers to collaborate with code reviews, repository browser, change monitoring, bug tracking and wiki. On May 29, 2021, Phacility, the maintainer and sponsor of Phabricator [announced end-of-life](https://admin.phacility.com/phame/post/view/11/phacility_is_winding_down_operations/) and stopped maintaining Phabricator.\n\n[GitLab co-founder and CEO, Sid Sijbrandij](/company/team/#sytses) gives credit to Phabricator on [HackerNews](https://news.ycombinator.com/item?id=27334636):\n\n> Phabricator was an inspiration to me when starting GitLab. It is shutting down now. [Many of its features were years ahead of its time](https://news.ycombinator.com/item?id=27334511) and there was a lot of humor in the product. As a tribute to it shall we add Clowcoptarize as a way to merge? This would be an [opt in option introduced in GitLab 14.0](https://gitlab.com/gitlab-org/gitlab/-/issues/332215).\n\nIt got me curious: What are these inspirations Sid is referring to? Let's dive into GitLab's history together and see what we can learn.\n\n_Tip: Features in the [GitLab documentation](https://docs.gitlab.com/) often have a `Version History` box. You can use the issue URLs to dive deeper into feature proposals, discussions, etc._\n\n### Review workflows\n\nA typical engineering workflow is as follows: The engineering manager assigns a new issue as a task to a developer. The developer works in their preferred IDE – local in VS Code or in the [Gitpod](/blog/teams-gitpod-integration-gitlab-speed-up-development/) cloud environment. Changes happen in a new feature branch in Git, which gets pushed to the remote Git server for collaboration.\n\nThe Git branch is not ready yet and stays hidden in a potentially long list of branches. To keep better track of their feature branches, developers could copy-paste the branch name or URL into the related issue - which I did 10 years ago. The concept of a \"diff linked to a task for review\" in Phabricator, likewise a \"Git branch with commits linked to Merge Requests\" in GitLab was not invented yet. \n\nPhabricator inspired GitLab to create a [default workflow](https://secure.phabricator.com/phame/post/view/766/write_review_merge_publish_phabricator_review_workflow/) for reviews. The Phabricator workflow makes the review more dominant and squashes all changes into a single commit after the review is approved. There are upsides and downsides to automatically squashing commits. Squashing the commits could mean losing information from review history and create more discussion. Depending on the application architecture, the frequency of changes, and debugging requirements, this can be a good thing or a bad thing. GitLab allows you to choose to [squash commits](https://docs.gitlab.com/ee/user/project/merge_requests/squash_and_merge.html) before merging a MR and/or specifying the default project settings around squashing commits.\n\nPhabricator treated a MR (or what they call \"diff tasks\") as the single source of truth for tracking changes and the review history. We felt this was a great idea, and replicated the process of a \"diff task\" in Phabricator in GitLab MRs. One of the major upsides to GitLab's version is that collaboration and discussion that happened in issues and epics is still available even after the change is merged.\n\n#### Draft MR (or \"diff tasks\")\n\nMany times when a MR is created in GitLab, the branch requires additional work before it is ready to be merged. Phabricator introduced a [formal \"Draft\" / \"Not Yet Ready for Review\" state](https://secure.phabricator.com/T2543) in 2013 for \"diff tasks\", which helped keep track of work in this state. GitLab added [WIP MRs in 2016](/blog/feature-highlight-wip/), which we then renamed to draft merge requests in 2020. While `WIP` may make sense to some people, acronyms can exclude newcomers. We found `Draft` is more recognizable. To avoid confusion, GitLab [deprecated WIP and moved forward with draft merge requests](https://gitlab.com/gitlab-org/gitlab/-/issues/32692).\n\n#### Keep history in MRs for future debugging\n\nThe commit history in GitLab is enriched with links to the MR and the corresponding Git review history. In case of a production emergency, having everything documented allows for faster research and debugging.\n\nGitLab stores the MR short URL with `\u003Cnamespace>/\u003Cproject>!1234` in the merge commit message. Check the history of a [demo project for the Kubernetes agent](https://gitlab.com/everyonecancontribute/kubernetes/k8s-agent/-/commits/main/) to see how the merge commit is rendered.\n\n![GitLab history with MR commit links](https://about.gitlab.com/images/blogimages/phabricator-features-inspired-gitlab/gitlab_history_mr_metadata_link.png)\nGitLab commit history includes link to the MR.\n{: .note.text-center}\n\nThis raw information is stored in the Git repository, whereas the MR itself stays in GitLab's database backend. You can verify this by cloning a repository and inspecting the history with this command:\n\n```sh\n$ git log\n```\n\n![git log MR metadata](https://about.gitlab.com/images/blogimages/phabricator-features-inspired-gitlab/git_log_mr_merge_commit_metadata_link.png)\nMR metadata included in output from `git log` command.\n{: .note.text-center}\n\n### Code coverage in MRs\n\nCode coverage reports provide insight into how many lines of the source code are covered with unit tests. Reaching 100% test coverage is a developer myth - visualizing a decrease or increase can help monitor a trend in code quality. Phabricator implemented support for various languages with unit test engines and parsing the output, for example in [Golang](https://secure.phabricator.com/D12621).\n\nWith many different languages and report output formats, integrating code coverage reports into GitLab MRs was challenging. [GitLab launched the first iteration of code coverage reports in 2016](/blog/publish-code-coverage-report-with-gitlab-pages/), which generated the reports with CI/CD jobs and used GitLab pages to publish the HTML reports.\n\nIn this first iteration, the test coverage is parsed with a regular expression from the CI/CD job output, specified in the project settings or with the [coverage](https://docs.gitlab.com/ee/ci/yaml/#coverage) keyword inside the CI/CD job configuration. We can see this in the job view inside the [MR widget](https://docs.gitlab.com/ee/ci/pipelines/settings.html#add-test-coverage-results-to-a-merge-request) and as a coverage badge for the project. See the test coverage history by navigating into `Analytics > Repository`.\n\n![Test coverage as project badge in GitLab](https://about.gitlab.com/images/blogimages/phabricator-features-inspired-gitlab/gitlab_project_badge_test_coverage.png)\nThe test coverage badge in a GitLab project.\n{: .note.text-center}\n\nJUnit XML test reports were introduced as common format specification and added as an [MR widget in 2018](https://docs.gitlab.com/ee/ci/unit_test_reports.html). The test reports runs in the background, using CI/CD artifacts to upload the XML reports from the runner to the server, where the MR/pipeline view visualizes the coverage reports in a tab.\n\nThe generic JUnit integration also helped with customization requests to unit tests, updated CLI commands, or changed coverage report outputs to parse. GitLab provides [CI/CD template examples](https://docs.gitlab.com/ee/ci/examples/)\n\nThe missing piece for GitLab was having inline code coverage remarks inside MR diffs. It took about five years for [Sid's initial proposal for inline code coverage remarks](https://gitlab.com/gitlab-org/gitlab/-/issues/3708) to be implemented. In 2020, inline code coverage remarks were released in [GitLab 13.5](https://docs.gitlab.com/ee/user/project/merge_requests/test_coverage_visualization.html).\n\n![Test Coverage with Rust in GitLab](https://about.gitlab.com/images/blogimages/phabricator-features-inspired-gitlab/gitlab_mr_diff_inline_test_coverage.png)\nHow inline code coverage works in GitLab.\n{: .note.text-center}\n\nCheck out [this MR to practice verifying the test coverage](https://gitlab.com/everyonecancontribute/dev/rust-code-coverage-llvm/-/merge_requests/1/diffs?view=inline validating some Rust code). Make sure to select the inline diff view.\n\n### Automated workflows and integrated CI\n\nPhabricator provides [Herald](https://secure.phabricator.com/book/phabricator/article/herald/) as an automated task runner and rule engine to listen for changes. Herald can also be used to ensure [protected branches](https://docs.gitlab.com/ee/user/project/protected_branches.html) and [approval rules](https://docs.gitlab.com/ee/user/project/merge_requests/approvals/rules.html#add-multiple-approval-rules) to enforce a strong permission model in development workflows. There are more examples in this [HackerNews post from 2016](https://news.ycombinator.com/item?id=12501025) and somehow, I feel like an explorer seeing many great GitLab features in similar ways. 🦊\n\n[GitLab CI/CD pipeline schedules](https://docs.gitlab.com/ee/ci/pipelines/schedules.html) remind me of the task runner, similarly to [webhooks](https://docs.gitlab.com/ee/user/project/integrations/webhooks.html) and the [REST API](https://docs.gitlab.com/ee/api/) being instrumented from CI/CD jobs. The pipeline schedules are also a great way to periodically regenerate caches and rebuild container images for cloud native deployments.\n\n[Harbormaster](https://secure.phabricator.com/book/phabricator/article/harbormaster/) is Phabricator's integration for CI. It's not built from multiple tools in the [DevOps](/topics/devops/) stack, but is instead fully integrated in the product.\n\nThe first version of GitLab CI was created in [November 2012](/company/history/). In 2015, a GitLab team member came up with the idea of combining SCM with CI and [the all-in-one DevOps platform was born](/blog/gitlab-hero-devops-platform/). Built-in CI/CD inspired for more features and fostered a better way to innovate together. The [new pipeline editor](/blog/pipeline-editor-overview/) is just one example of a streamlined way to configure CI/CD pipelines in GitLab.\n\nLet's throwback to 2017 and watch as we demonstrate how to take an idea to production in GitLab, using GKE:\n\n\u003Ciframe width=\"560\" height=\"315\" src=\"https://www.youtube.com/embed/39chczWRKws\" title=\"YouTube video player\" frameborder=\"0\" allow=\"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture\" allowfullscreen>\u003C/iframe>\n\n\u003Cbr>\n\n### Work boards for issue management\n\nWork needs to be organized. Phabricator led the way with a board which allowed users to filter tasks and provide a more detailed view into planning and project management.\n\n![Phabricator work boards](https://about.gitlab.com/images/blogimages/phabricator-features-inspired-gitlab/phabricator_work_boards.png)\nInside Phabricator work boards.\n{: .note.text-center}\n\nGitLab users will recognize the similar look between Phabricator's work boards and GitLab [issue boards](https://docs.gitlab.com/ee/user/project/issue_board.html). In GitLab 14.1, we built on existing epic tracking and labeling to create [Epic boards](https://docs.gitlab.com/ee/user/group/epics/epic_boards.html) to keep teams organized and measure progress.\n\nIn Phabricator, users can drag and drop between columns, which automatically changes the work status for a particular task. This feature inspired the boards in GitLab to automatically change the labels in a [defined workflow](/blog/4-ways-to-use-gitlab-issue-boards/) by dragging and dropping between columns. Users can go a level deeper with scoped labels to switch between workflow states:\n\n* `workflow::design`\n* `workflow::planning breakdown`\n* `workflow::ready for development`\n* `workflow::in dev`\n* `workflow::verification`\n\nThe [GitLab engineering handbook](/handbook/engineering/workflow/#basics) documents the different workflows.\n\n![Epic boards in GitLab](https://about.gitlab.com/images/blogimages/phabricator-features-inspired-gitlab/gitlab_epic_boards.png)\nTake a look at the Epic boards in GitLab.\n{: .note.text-center}\n\n### Put it all together\n\nIn Phabricator, a diff task (in GitLab they're MRs) in the \"review\" state is linked to another task specifying the requirements. The UX needs to be clear so the relationship between the diffs can be accessed and understood. Unless necessary, the user shouldn't have to navigate manually. The context of the change review defines possible links to labels, states, dependent issues, diff tasks (MRs), and more.\n\nGitLab links [related issues](https://docs.gitlab.com/ee/user/project/issues/related_issues.html). If an issue is mentioned in a MR, or vice versa, [GitLab automatically links them](https://docs.gitlab.com/ee/user/project/issues/crosslinking_issues.html#from-merge-requests). The user also has the option to have the issue close automatically once a change is merged. Read a blog post from 2016 to learn more about [how issues and MRs can relate to each other in GitLab](/blog/gitlab-tutorial-its-all-connected/).\n\n![Linked issues and MRs in GitLab](https://about.gitlab.com/images/blogimages/phabricator-features-inspired-gitlab/gitlab_linked_issues_mrs.png)\nLinked issues and related MRs in GitLab.\n{: .note.text-center}\n\nUX work is challenging, and we continue to iterate to improve workflows in GitLab. For example, in GitLab 13.8, we reduced the number of clicks it takes to [download a CI/CD job artifact from the MR](https://gitlab.com/gitlab-org/gitlab/-/issues/37346).\n\n\n### Did we miss a feature Phabricator inspired?\n\nWhile writing this blog post, my research revealed more gems. For example, I found a proposal to add [visual graphs for issue dependencies](https://gitlab.com/gitlab-org/gitlab/-/issues/273597) in the [HN thread](https://news.ycombinator.com/item?id=27336818).\n\nWhich features from Phabricator are missing in GitLab? Let us know in the comments, create a new [feature proposal](https://gitlab.com/gitlab-org/gitlab/-/issues/new?issuable_template=Feature%20Proposal%20-%20lean) or start your [contribution journey](/community/contribute/) in a new MR right away! \n\nCover image by [Johannes Plenio](https://unsplash.com/photos/DKix6Un55mw) on [Unsplash](https://unsplash.com)\n{: .note}\n",[9,1403,991],"code review",{"slug":1405,"featured":6,"template":686},"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":1411,"_dir":244,"_draft":6,"_partial":6,"_locale":7,"seo":1412,"content":1418,"config":1424,"_id":1426,"_type":14,"title":1427,"_source":16,"_file":1428,"_stem":1429,"_extension":19},"/en-us/blog/five-ways-to-streamline-cloud-adoption",{"title":1413,"description":1414,"ogTitle":1413,"ogDescription":1414,"noIndex":6,"ogImage":1415,"ogUrl":1416,"ogSiteName":673,"ogType":674,"canonicalUrls":1416,"schema":1417},"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":1413,"description":1414,"authors":1419,"heroImage":1415,"date":1421,"body":1422,"category":1118,"tags":1423},[1420],"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/previous/2023/) 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",[773,1120,9,683],{"slug":1425,"featured":6,"template":686},"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":1431,"_dir":244,"_draft":6,"_partial":6,"_locale":7,"seo":1432,"content":1438,"config":1445,"_id":1447,"_type":14,"title":1448,"_source":16,"_file":1449,"_stem":1450,"_extension":19},"/en-us/blog/getting-started-with-gitlab-application-security",{"title":1433,"description":1434,"ogTitle":1433,"ogDescription":1434,"noIndex":6,"ogImage":1435,"ogUrl":1436,"ogSiteName":673,"ogType":674,"canonicalUrls":1436,"schema":1437},"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":1433,"description":1434,"authors":1439,"heroImage":1435,"date":1442,"body":1443,"category":704,"tags":1444},[1440,1441],"Victor Hernandez","Julie Byrne","2023-03-15","\nAs software security becomes increasingly important, many companies want to introduce standard code scanning processes into development workflows to find and remediate security vulnerabilities before they get to production. GitLab's DevSecOps Platform allows users to perform security scans in CI/CD pipelines, which can easily be enabled to check applications for security vulnerabilities such as unauthorized access, data leaks, and denial of service (DoS) attacks. While most of what is covered in this blog will pertain to Ultimate features, there are some features available for free and Premium tier users as well. By the end of this blog, you will have a solid starting point for adopting GitLab security scans, with any tier license, and understand the steps to take next to mature your DevSecOps practices.\n\n## Prerequisites\nTo enable security scanning for a project, you must have the following:\n- a GitLab project that meets the requirements of the security scan you choose to enable, with CI enabled\n- a `.gitlab-ci.yml` file for the project that has at least a build job defined\n- a Linux-based GitLab Runner with the Docker or Kubernetes executor\n\n## Get started: Add a scan template to your pipeline\n\nHere are the first steps to introduce security scanning.\n\n### Available security scans\n\nGitLab provides a variety of security scanners, each with its own set of criteria for adoption:\n\n| Scan type | Minimum tier | Prerequisites | Application requirements |\n| --- | --- | --- | --- |\n| [Static application security testing (SAST)](https://docs.gitlab.com/ee/user/application_security/sast/) | Free | None | See [SAST requirements](https://docs.gitlab.com/ee/user/application_security/sast/index.html#requirements) |\n| [Secret detection](https://docs.gitlab.com/ee/user/application_security/secret_detection/) | Free | None | None |\n| [Container scanning](https://docs.gitlab.com/ee/user/application_security/container_scanning/) | Free | Container image built and pushed to registry | [Docker 18.09.03 or higher installed on the same computer as the runner](https://docs.gitlab.com/ee/user/application_security/container_scanning/index.html#requirements); image uses a [supported distribution](https://docs.gitlab.com/ee/user/application_security/container_scanning/index.html#supported-distributions) |\n| [Infrastructure as code (IaC) scanning](https://docs.gitlab.com/ee/user/application_security/iac_scanning/) |  Free | None | See [supported languages and frameworks](https://docs.gitlab.com/ee/user/application_security/iac_scanning/#supported-languages-and-frameworks) |\n| [Dependency scanning](https://docs.gitlab.com/ee/user/application_security/dependency_scanning/) - includes license compliance | Ultimate | None | Application must use one of the [supported languages and package managers](https://docs.gitlab.com/ee/user/application_security/dependency_scanning/index.html#supported-languages-and-package-managers) |\n| [Dynamic application security testing (DAST)](https://docs.gitlab.com/ee/user/application_security/dast/) | Ultimate | [Deployed target application](https://docs.gitlab.com/ee/user/application_security/dast/index.html#prerequisites) | See [GitLab DAST scanning options](https://docs.gitlab.com/ee/user/application_security/dast/index.html#gitlab-dast) |\n| [Coverage-guided fuzz testing](https://docs.gitlab.com/ee/user/application_security/coverage_fuzzing/) | Ultimate | Instrumented version of application | See [supported fuzzing engines and languages](https://docs.gitlab.com/ee/user/application_security/coverage_fuzzing/index.html#supported-fuzzing-engines-and-languages) |\n| [Web API fuzz testing](https://docs.gitlab.com/ee/user/application_security/api_fuzzing/) |  Ultimate | Deployed target application | See [supported API types](https://docs.gitlab.com/ee/user/application_security/api_fuzzing/#enable-web-api-fuzzing) |\n\nMany customers will start with secret detection, dependency scanning, or SAST scanning, as they have the fewest requirements for usage.\n\n### Add the scanner template\n\nGitLab provides a [CI template for each security scan](https://docs.gitlab.com/ee/user/application_security/#security-scanning-without-auto-devops) that can be added to your existing `.gitlab-ci.yml` file. This can be done by manually editing the CI file and adding the appropriate template path in the templates section of the file. Several scanners can also be [enabled via the UI](https://docs.gitlab.com/ee/user/application_security/sast/#configure-sast-in-the-ui), where a merge request will be created to add the appropriate scanner to the `.gitlab-ci.yml` file. \n\nI will use a simple spring boot application as an example and enable dependency scanning, a scanner that is popular amongst our customers, as my first security scan. Dependency scanning will find vulnerabilities in the libraries I am using to build my application. My project is a Java application built via Maven and includes a `pom.xml` file, so it meets the requirements for dependency scanning. Since dependency scanning can be enabled via the UI, I'm going to take advantage of that feature here. \n\nFor this project, I have created a `.gitlab-ci.yml` file that contains a build and test stage and a build job. I'm using the Auto DevOps auto-build job, but you can define your own build job if desired. This is the starting pipeline code in my `.gitlab-ci.yml` file:\n\n```\nimage: alpine:latest\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- build\n- test\n\n```\n\nTo enable dependency scanning, I'll first navigate to the **Security & Compliance** menu, **Configuration** sub-menu.\n\n![web identity](https://about.gitlab.com/images/blogimages/2023-02-26-getting-started-with-gitlab-application-security/security_config.png){: .shadow}\n\nThe option to enable dependency scanning is available about halfway down the page. When I click `Configure with a merge request`, a branch is created and I am prompted to create a corresponding draft merge request. I'll click `Create Merge Request` to save the merge request.\n\nOnce the merge request has been created, I see that a new branch `set-dependency-scanning-config-1` has been created and the `.gitlab-ci.yml` file has been updated with this code:\n\n```\n# You can override the included template(s) by including variable overrides\n# SAST customization: https://docs.gitlab.com/ee/user/application_security/sast/#customizing-the-sast-settings\n# Secret Detection customization: https://docs.gitlab.com/ee/user/application_security/secret_detection/#customizing-settings\n# Dependency Scanning customization: https://docs.gitlab.com/ee/user/application_security/dependency_scanning/#customizing-the-dependency-scanning-settings\n# Container Scanning customization: https://docs.gitlab.com/ee/user/application_security/container_scanning/#customizing-the-container-scanning-settings\n# Note that environment variables can be set in several places\n# See https://docs.gitlab.com/ee/ci/variables/#cicd-variable-precedence\nimage: alpine:latest\ninclude:\n- template: Jobs/Build.gitlab-ci.yml\n- template: Security/Dependency-Scanning.gitlab-ci.yml\nstages:\n- build\n- test\n\n```\n\nThe change kicks off a pipeline, which will now include the dependency scan.\n\n![web identity](https://about.gitlab.com/images/blogimages/2023-02-26-getting-started-with-gitlab-application-security/dependency_job.png){: .shadow}\n\n## View results of the security scan\n\nFor all license tiers, you can view the results of any security scan jobs in the appropriate JSON report that can be downloaded from the merge request.\n\n![web identity](https://about.gitlab.com/images/blogimages/2023-02-26-getting-started-with-gitlab-application-security/mr_artifacts.png){: .shadow}\n\nWith GitLab Ultimate, you will also see the vulnerabilities found by the scan in the merge request widget.\n\n![web identity](https://about.gitlab.com/images/blogimages/2023-02-26-getting-started-with-gitlab-application-security/mr_widget.png){: .shadow}\n\nAt this point, the `.gitlab-ci.yml` changes that enable security scanning are only available in the `set-dependency-scanning-config-1` branch. I will merge them to `main` so that the changes will be included in all future feature branches.\n\nWith GitLab Ultimate, merging to `main` will also provide the baseline **Vulnerability Report** for our application.  \n\n![web identity](https://about.gitlab.com/images/blogimages/2023-02-26-getting-started-with-gitlab-application-security/vuln_report.png){: .shadow}\n\nNow, scan results presented in the merge request widget for any new merge requests will only show vulnerabilities introduced by those new code changes in the corresponding feature branch, and not the baseline of vulnerabilities that already exist on `main`.\n\n## Scan enforcement\n\nOnce you have enabled your first scans in your CI/CD pipelines, you might be curious to know how you can enforce security scans, or enforce a review and approval when critical vulnerabilities are found in new code changes. I recommend 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 CI/CD pipeline, check out our [Getting Started with GitLab Application Security](https://docs.gitlab.com/ee/user/application_security/get-started-security.html) documentation for recommended next steps.\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",[773,9,683,771],{"slug":1446,"featured":6,"template":686},"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":1452,"_dir":244,"_draft":6,"_partial":6,"_locale":7,"seo":1453,"content":1459,"config":1466,"_id":1468,"_type":14,"title":1469,"_source":16,"_file":1470,"_stem":1471,"_extension":19},"/en-us/blog/getting-started-with-gitlab-understanding-ci-cd",{"title":1454,"description":1455,"ogTitle":1454,"ogDescription":1455,"noIndex":6,"ogImage":1456,"ogUrl":1457,"ogSiteName":673,"ogType":674,"canonicalUrls":1457,"schema":1458},"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":1454,"description":1455,"authors":1460,"heroImage":1456,"date":1462,"body":1463,"category":1464,"tags":1465},[1461],"GitLab","2025-04-25","*Welcome to our \"Getting started with GitLab\" series, where we help newcomers get familiar with the GitLab DevSecOps platform.*\n\nImagine a workflow where every code change is automatically built, tested, and deployed to your users. That's the power of [Continuous Integration/Continuous Delivery (CI/CD)](https://about.gitlab.com/topics/ci-cd/)! CI/CD helps you catch bugs early, ensures code quality, and delivers software faster and more frequently.\n\n### What is CI/CD?\n\n* **Continuous Integration** is a development practice where developers integrate code changes into a shared repository frequently, preferably several times a day. Each integration is then verified by an automated build and test process, allowing teams to detect problems early.  \n* **Continuous Delivery** extends CI by automating the release pipeline, ensuring that your code is *always* in a deployable state. You can deploy your application to various environments (e.g., staging, production) with a single click or automatically.  \n* **Continuous Deployment** takes it a step further by automatically deploying *every successful build* to production. This requires a high degree of confidence in your automated tests and deployment process.\n\n### Why GitLab CI/CD?\n\nGitLab CI/CD is a powerful, integrated system that comes built-in with GitLab. It offers a seamless experience for automating your entire software development lifecycle. With GitLab CI/CD, you can:\n\n* **Automate everything:** Build, test, and deploy your applications with ease.  \n* **Catch bugs early:** Detect and fix errors before they reach production.  \n* **Get faster feedback:** Receive immediate feedback on your code changes.  \n* **Improve collaboration:** Work together more effectively with automated workflows.  \n* **Accelerate delivery:** Release software faster and more frequently.  \n* **Reduce risk:** Minimize deployment errors and rollbacks.\n\n### The elements of GitLab CI/CD\n\n* `.gitlab-ci.yml`**:** This [YAML file](https://docs.gitlab.com/ee/ci/yaml/), located in your project's root directory, defines your CI/CD pipeline, including stages, jobs, and runners.  \n* [**GitLab Runner**](https://docs.gitlab.com/runner/)**:** This agent executes your CI/CD jobs on your infrastructure (e.g. physical machines, virtual machines, Docker containers, or Kubernetes clusters).  \n* [**Stages**](https://docs.gitlab.com/ee/ci/yaml/#stages)**:** Stages define the order of execution for your jobs (e.g. build, test, and deploy).  \n* [**Jobs**](https://docs.gitlab.com/ee/ci/yaml/#job-keywords)**:** Jobs are individual units of work within a stage (e.g. compile code, run tests, and deploy to staging).\n\n### Setting up GitLab CI\n\nGetting started with GitLab CI is simple. Here's a basic example of a `.gitlab-ci.yml` file:\n\n```yaml\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\nThis configuration defines three stages: \"build,\" \"test,\" and \"deploy.\" Each stage contains a job that executes a simple script.\n\n### CI/CD configuration examples\n\nLet's explore some more realistic examples.\n\n**Building and deploying a Node.js application**\n\nThe pipeline definition below outlines using npm to build and test a Node.js application and [dpl](https://docs.gitlab.com/ci/examples/deployment/) to deploy the application to Heroku. The deploy stage of the pipeline makes use of [GitLab CI/CD variables](https://docs.gitlab.com/ci/variables/), which allow developers to store sensitive information (e.g. credentials) and securely use them in CI/CD processes. In this example, an API key to deploy to Heroku is stored under the variable key name `$HEROKU_API_KEY` used by the dpl tool.\n\n```yaml\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**Deploying to different environments (staging and production)**\n\nGitLab also offers the idea of [Environments](https://docs.gitlab.com/ci/environments/) with CI/CD. This feature allows users to track deployments from CI/CD to infrastructure targets. In the example below, the pipeline adds stages with an environment property for a staging and production environment. While the deploy_staging stage will always run its script, the deploy_production stage requires manual approval to prevent accidental deployment to production.  \n\n```yaml\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### GitLab Auto DevOps\n\n[GitLab Auto DevOps](https://docs.gitlab.com/ee/topics/autodevops/) simplifies CI/CD by providing a pre-defined configuration that automatically builds, tests, and deploys your applications. It leverages best practices and industry standards to streamline your workflow.\n\nTo enable Auto DevOps:\n\n1. Go to your project's **Settings > CI/CD > General pipelines**.  \n2. Enable the **Auto DevOps** option.\n\nAuto DevOps automatically detects your project's language and framework and configures the necessary build, test, and deployment stages. You don’t even need to create a `.gitlab-ci.yml` file.\n\n### CI/CD Catalog\n\nThe [CI/CD Catalog](https://about.gitlab.com/blog/faq-gitlab-ci-cd-catalog/) is a list of projects with published [CI/CD components](https://docs.gitlab.com/ee/ci/components/) you can use to extend your CI/CD workflow. Anyone can create a component project and add it to the CI/CD Catalog or contribute to an existing project to improve the available components. You can find published components in the [CI/CD Catalog](https://gitlab.com/explore/catalog) on GitLab.com.\n\n> [Tutorial: How to set up your first GitLab CI/CD component](https://about.gitlab.com/blog/tutorial-how-to-set-up-your-first-gitlab-ci-cd-component/)\n\n### CI templates\n\nYou can also create your own [CI templates](https://docs.gitlab.com/ee/ci/examples/) to standardize and reuse CI/CD configurations across multiple projects. This promotes consistency and reduces duplication.\n\nTo create a CI template:\n\n1. Create a `.gitlab-ci.yml` file in a dedicated project or repository.  \n2. Define your CI/CD configuration in the template.  \n3. In your project's `.gitlab-ci.yml` file, use the `include` keyword to include the template.\n\n## Take your development to the next level\n\nGitLab CI/CD is a powerful tool that can transform your development workflow. By understanding the concepts of CI/CD, configuring your pipelines, and leveraging features like Auto DevOps, the CI/CD Catalog, and CI templates, you can automate your entire software development lifecycle and deliver high-quality software faster and more efficiently.\n\n> Want to take your learning to the next level? Sign up for [GitLab University courses](https://university.gitlab.com/). Or you can get going right away with a [free 60-day trial of GitLab Ultimate](https://about.gitlab.com/free-trial/).\n\n## \"Getting Started with GitLab\" series\n\nCheck out more articles in our \"Getting Started with GitLab\" series:\n\n- [How to manage users](https://about.gitlab.com/blog/getting-started-with-gitlab-how-to-manage-users/)\n- [How to import your projects to GitLab](https://about.gitlab.com/blog/getting-started-with-gitlab-how-to-import-your-projects-to-gitlab/)  \n- [Mastering project management](https://about.gitlab.com/blog/getting-started-with-gitlab-mastering-project-management/)\n- [Automating Agile workflows with the gitlab-triage gem](https://about.gitlab.com/blog/automating-agile-workflows-with-the-gitlab-triage-gem/)\n- [Working with CI/CD variables](https://about.gitlab.com/blog/getting-started-with-gitlab-working-with-ci-cd-variables/)\n","product",[109,9,683,482,1464,729],{"slug":1467,"featured":91,"template":686},"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":1473,"_dir":244,"_draft":6,"_partial":6,"_locale":7,"seo":1474,"content":1479,"config":1485,"_id":1487,"_type":14,"title":1488,"_source":16,"_file":1489,"_stem":1490,"_extension":19},"/en-us/blog/getting-started-with-gitlab-working-with-ci-cd-variables",{"title":1475,"description":1476,"ogTitle":1475,"ogDescription":1476,"noIndex":6,"ogImage":1456,"ogUrl":1477,"ogSiteName":673,"ogType":674,"canonicalUrls":1477,"schema":1478},"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":1475,"description":1476,"authors":1480,"heroImage":1456,"date":1482,"body":1483,"category":1464,"tags":1484},[1481],"GitLab Team","2025-05-27","*Welcome to our \"Getting started with GitLab\" series, where we help newcomers get familiar with the GitLab DevSecOps platform.*\n\nIn an earlier article, we explored [GitLab CI/CD](https://about.gitlab.com/blog/getting-started-with-gitlab-understanding-ci-cd/). Now, let's dive deeper into the world of **CI/CD variables** and unlock their full potential.\n\n### What are CI/CD variables?\n\nCI/CD variables are dynamic key-value pairs that you can define at different levels within your GitLab environment (e.g., project, group, or instance). These variables act as placeholders for values that you can use in your `.gitlab-ci.yml` file to customize your pipelines, securely store sensitive information, and make your CI/CD configuration more maintainable.\n\n### Why are CI/CD variables important?\n\nCI/CD variables offer numerous benefits:\n\n* **Flexibility** - Easily adapt your pipelines to different environments, configurations, or deployment targets without modifying your core CI/CD script.  \n* **Security** - Securely store sensitive information like API keys, passwords, and tokens, preventing them from being exposed directly in your code.  \n* **Maintainability** - Keep your CI/CD configuration clean and organized by centralizing values in variables, making updates and modifications easier.  \n* **Reusability** - Define variables once and reuse them across multiple projects, promoting consistency and reducing duplication.\n\n### Scopes of CI/CD variables: Project, group, and instance\n\nGitLab allows you to define CI/CD variables with different scopes, controlling their visibility and accessibility:\n\n* **Project-level variables** - These variables are specific to a single project 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 within a GitLab group. They are useful for settings that are common to multiple 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 projects on a GitLab instance. They are typically used for global settings that 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\nTo define a CI/CD variable:\n\n1. Click on the **Settings > CI/CD** buttons for  your project, group, or instance.  \n2. Go to the **Variables** section.  \n3. Click **Add variable**.  \n4. Enter the **key** (e.g., `API_KEY`) and **value**.  \n5. Optionally, check the **Protect variable** box for sensitive information. This ensures that the variable is only available to pipelines running on protected branches or tags.  \n6. Optionally, check the **Mask variable** box to hide the variable's value from job logs, preventing accidental exposure.  \n7. Click **Save variable**.\n\n### Using CI/CD variables\n\nTo use a CI/CD variable in your `.gitlab-ci.yml` file, simply prefix the variable name with `$`:\n\n```yaml\ndeploy_job:\n  script:\n    - echo \"Deploying to production...\"\n    - curl -H \"Authorization: Bearer $API_KEY\" https://api.example.com/deploy\n```\n\n### Predefined CI/CD variables\n\nGitLab provides a set of [predefined CI/CD variables](https://docs.gitlab.com/ci/variables/predefined_variables/) that you can use in your pipelines. These variables provide information about the current pipeline, job, project, and more.\n\nSome commonly used predefined variables include:\n\n* `$CI_COMMIT_SHA`: The commit SHA of the current pipeline.  \n* `$CI_PROJECT_DIR`: The directory where the project is cloned.  \n* `$CI_PIPELINE_ID`: The ID of the current pipeline.  \n* `$CI_ENVIRONMENT_NAME`: The name of the environment being deployed to (if applicable).\n\n### Best practices\n\n* Securely manage sensitive variables: Use protected and masked variables for API keys, passwords, and other sensitive information.  \n* Avoid hardcoding values: Use variables to store configuration values, making your pipelines more flexible and maintainable.  \n* Organize your variables: Use descriptive names and group related variables together for better organization.  \n* Use the appropriate scope: Choose the correct scope (project, group, or instance) for your variables based on their intended use and visibility.\n\n### Unlock the power of variables\n\nCI/CD variables are a powerful tool for customizing and securing your GitLab pipelines. By mastering variables and understanding their different scopes, you can create more flexible, maintainable, and efficient workflows.\n\nWe hope you found it helpful and are now well-equipped to leverage the power of GitLab for your development projects.\n\n> Get started with CI/CD variables today with a [free, 60-day trial of GitLab Ultimate with Duo Enterprise](https://about.gitlab.com/free-trial/).\n\n## \"Getting Started with GitLab\" series\nRead more articles in our \"Getting Started with GitLab\" series:\n\n- [How to manage users](https://about.gitlab.com/blog/getting-started-with-gitlab-how-to-manage-users/)\n-  [How to import your projects to GitLab](https://about.gitlab.com/blog/getting-started-with-gitlab-how-to-import-your-projects-to-gitlab/)  \n- [Mastering project management](https://about.gitlab.com/blog/getting-started-with-gitlab-mastering-project-management/)\n- [Automating Agile workflows with the gitlab-triage gem](https://about.gitlab.com/blog/automating-agile-workflows-with-the-gitlab-triage-gem/)\n- [Understanding CI/CD](https://about.gitlab.com/blog/getting-started-with-gitlab-understanding-ci-cd/)\n",[1464,729,9,683,109,970],{"slug":1486,"featured":91,"template":686},"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":1492,"_dir":244,"_draft":6,"_partial":6,"_locale":7,"seo":1493,"content":1499,"config":1505,"_id":1507,"_type":14,"title":1508,"_source":16,"_file":1509,"_stem":1510,"_extension":19},"/en-us/blog/github-launch-continuous-integration",{"title":1494,"description":1495,"ogTitle":1494,"ogDescription":1495,"noIndex":6,"ogImage":1496,"ogUrl":1497,"ogSiteName":673,"ogType":674,"canonicalUrls":1497,"schema":1498},"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":1494,"description":1495,"authors":1500,"heroImage":1496,"date":1502,"body":1503,"category":299,"tags":1504},[1501],"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",[792,728,9],{"slug":1506,"featured":6,"template":686},"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":1512,"_dir":244,"_draft":6,"_partial":6,"_locale":7,"seo":1513,"content":1519,"config":1526,"_id":1528,"_type":14,"title":1529,"_source":16,"_file":1530,"_stem":1531,"_extension":19},"/en-us/blog/gitlab-achieves-aws-devops-competency-certification",{"title":1514,"description":1515,"ogTitle":1514,"ogDescription":1515,"noIndex":6,"ogImage":1516,"ogUrl":1517,"ogSiteName":673,"ogType":674,"canonicalUrls":1517,"schema":1518},"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":1514,"description":1515,"authors":1520,"heroImage":1516,"date":1523,"body":1524,"category":299,"tags":1525},[1521,1522],"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,728,792],{"slug":1527,"featured":6,"template":686},"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":1533,"_dir":244,"_draft":6,"_partial":6,"_locale":7,"seo":1534,"content":1540,"config":1546,"_id":1548,"_type":14,"title":1549,"_source":16,"_file":1550,"_stem":1551,"_extension":19},"/en-us/blog/gitlab-and-google-cloud",{"title":1535,"description":1536,"ogTitle":1535,"ogDescription":1536,"noIndex":6,"ogImage":1537,"ogUrl":1538,"ogSiteName":673,"ogType":674,"canonicalUrls":1538,"schema":1539},"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":1535,"description":1536,"authors":1541,"heroImage":1537,"date":1543,"body":1544,"category":1118,"tags":1545},[1542],"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",[1120,9,771,231],{"slug":1547,"featured":6,"template":686},"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":1553,"_dir":244,"_draft":6,"_partial":6,"_locale":7,"seo":1554,"content":1560,"config":1565,"_id":1567,"_type":14,"title":1568,"_source":16,"_file":1569,"_stem":1570,"_extension":19},"/en-us/blog/gitlab-apis-ci",{"title":1555,"description":1556,"ogTitle":1555,"ogDescription":1556,"noIndex":6,"ogImage":1557,"ogUrl":1558,"ogSiteName":673,"ogType":674,"canonicalUrls":1558,"schema":1559},"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":1555,"description":1556,"authors":1561,"heroImage":1557,"date":1562,"body":1563,"category":1075,"tags":1564},[1115],"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,795,728,900],{"slug":1566,"featured":6,"template":686},"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":1572,"_dir":244,"_draft":6,"_partial":6,"_locale":7,"seo":1573,"content":1579,"config":1585,"_id":1587,"_type":14,"title":1588,"_source":16,"_file":1589,"_stem":1590,"_extension":19},"/en-us/blog/gitlab-ci-cd-features-improvements",{"title":1574,"description":1575,"ogTitle":1574,"ogDescription":1575,"noIndex":6,"ogImage":1576,"ogUrl":1577,"ogSiteName":673,"ogType":674,"canonicalUrls":1577,"schema":1578},"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":1574,"description":1575,"authors":1580,"heroImage":1576,"date":1582,"body":1583,"category":299,"tags":1584},[1581],"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,267,970,902,903],{"slug":1586,"featured":6,"template":686},"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":1592,"_dir":244,"_draft":6,"_partial":6,"_locale":7,"seo":1593,"content":1599,"config":1605,"_id":1607,"_type":14,"title":1608,"_source":16,"_file":1609,"_stem":1610,"_extension":19},"/en-us/blog/gitlab-com-artifacts-cdn-change",{"title":1594,"description":1595,"ogTitle":1594,"ogDescription":1595,"noIndex":6,"ogImage":1596,"ogUrl":1597,"ogSiteName":673,"ogType":674,"canonicalUrls":1597,"schema":1598},"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":1594,"description":1595,"authors":1600,"heroImage":1596,"date":1602,"body":1603,"category":792,"tags":1604},[1601],"Stan Hu","2022-10-25","\n\nOver the next month and going forward, requests for GitLab CI artifacts downloads may be redirected\nto [Google Cloud CDN](https://cloud.google.com/cdn) instead of\n[Google Cloud Storage](https://cloud.google.com/storage). We anticipate that GitLab CI users may benefit from faster\ndownloads from edge caches closest to your location.\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## How will this work?\n\nCurrently when a CI runner or other client [downloads a CI artifact](https://docs.gitlab.com/ee/api/job_artifacts.html),\nGitLab.com responds with a 302 redirect to a time-limited, pre-signed URL with a domain of `storage.googleapis.com`.\n\nAfter this change, the domain will change to `cdn.artifacts.gitlab-static.net`.\n\nThe exception is for requests originating from within the Google Cloud\nPlatform. These will continue to be redirected to Cloud Storage.\n\n## When will this change occur?\n\nWe expect to start the transition around the end of October 2022. This will be a\ngradual transition using a percentage-based rollout, so we anticipate that you will see\nan increasing number of your requests redirected to Google Cloud\nCDN instead of Google Cloud Storage until all of the requests are served by the\nformer.\n\nYou can follow along with the progress of this initiative and raise any\nquestions in [this issue](https://gitlab.com/gitlab-com/gl-infra/production/-/issues/7894). We\nwill post more detailed timelines in that issue as we refine the rollout\nplan.\n\n## How does this change impact you?\n\nSince GitLab CI runners and certain clients automatically handle URL\nredirections already, we expect that downloads for CI artifacts should\ncontinue to work without any action.\n\nWe encourage upgrading to the latest version of the GitLab Runner in\norder to take advantage of the CDN. This feature was [introduced in\nGitLab Runner v13.1.0](https://gitlab.com/gitlab-org/gitlab-runner/-/merge_requests/2115).\nIf a runner cannot download from the CDN host, it will retry without the\nCDN and download the artifact directly through GitLab.com.\n\nHowever, if you have a firewall that only allows\n`storage.googleapis.com`, you will need to add\n`cdn.artifacts.gitlab-static.net` (34.110.204.38) to the allow list.\n\n### What do these warning messages mean?\n\nWith this change, users may see warning messages in the CI job logs:\n\n#### read: connection reset by peer\n\n```plaintext\nERROR: Downloading artifacts from coordinator... error couldn't execute GET against https://gitlab.com/api/v4/jobs/\u003Cjob id>/artifacts?direct_download=true: Get \"https://cdn.artifacts.gitlab-static.net/...\nread tcp 172.17.0.2:59332->34.110.204.38:443: read: connection reset by peer  id=1234 token=\u003Csome token>\nWARNING: Retrying...                                error=invalid argument\nDownloading artifacts from coordinator... ok        id=1234 responseStatus=200 OK token=\u003Csome token>\n```\n\nThis error suggests the runner was not able to access the CDN. Check\nyour network firewalls and allow access to the IP 34.110.204.38.\n\nNote that there are two `Downloading artifacts from coordinator`\nmessages. The second attempt succeeded because the runner retried\nwithout the CDN.\n\n#### x509: certificate signed by unknown authority\n\n```plaintext\nERROR: Downloading artifacts from coordinator... error couldn't execute GET against https://gitlab.com/api/v4/jobs/\u003Cjob id>/artifacts?direct_download=true: Get \"https://storage.googleapis.com/gitlab-gprd-artifacts/...: x509: certificate signed by unknown authority  id=1234 token=\u003Csome token>\n```\n\nIf you see this error with a Windows runner, upgrade to v15.5.0 since it\nis compiled with [Go 1.18](https://tip.golang.org/doc/go1.18), which\nsupports [using the system certificate pool](https://github.com/golang/go/issues/16736).\n\nOtherwise, this error suggests the runner is configured with [custom SSL certificates](https://docs.gitlab.com/runner/configuration/tls-self-signed.html).\nYou may need to update your certificates or include the certificates directly in the bundle.\n\n#### Authentication required\n\nSome clients may report a 401 error with `Authentication required` after\nrequesting to download a job artifact:\n\n```xml\n\u003C?xml version='1.0' encoding='UTF-8'?>\u003CError>\u003CCode>AuthenticationRequired\u003C/Code>\u003CMessage>Authentication required.\u003C/Message>\u003C/Error>\n```\n\nThis error message suggests the HTTP client is following the 302\nredirect and sending the `Authorization` header with the redirected\nURL. This is a known issue with Java HTTP clients.\n\nUpdate your client to drop the `Authorization` header the\nredirect. Google Cloud Storage ignores this header if it were set, but\nCloud CDN rejects requests that have the `Authorization` header set.\n",[728,902,994,231,9],{"slug":1606,"featured":6,"template":686},"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":1612,"_dir":244,"_draft":6,"_partial":6,"_locale":7,"seo":1613,"content":1619,"config":1625,"_id":1627,"_type":14,"title":1628,"_source":16,"_file":1629,"_stem":1630,"_extension":19},"/en-us/blog/gitlab-for-cicd-agile-gitops-cloudnative",{"title":1614,"description":1615,"ogTitle":1614,"ogDescription":1615,"noIndex":6,"ogImage":1616,"ogUrl":1617,"ogSiteName":673,"ogType":674,"canonicalUrls":1617,"schema":1618},"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":1614,"description":1615,"authors":1620,"heroImage":1616,"date":1622,"body":1623,"category":704,"tags":1624},[1621],"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-ci-cd-pipeline-with-autodeploy-to-kubernetes-using-gitlab-and-helm/) 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,683,902,900],{"slug":1626,"featured":6,"template":686},"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":1632,"_dir":244,"_draft":6,"_partial":6,"_locale":7,"seo":1633,"content":1639,"config":1644,"_id":1646,"_type":14,"title":1647,"_source":16,"_file":1648,"_stem":1649,"_extension":19},"/en-us/blog/gitlab-is-the-single-source-of-truth-for-ecommerce-provider",{"title":1634,"description":1635,"ogTitle":1634,"ogDescription":1635,"noIndex":6,"ogImage":1636,"ogUrl":1637,"ogSiteName":673,"ogType":674,"canonicalUrls":1637,"schema":1638},"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":1634,"description":1635,"authors":1640,"heroImage":1636,"date":1641,"body":1642,"category":1118,"tags":1643},[1461],"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.",[728,9,683,771,994],{"slug":1645,"featured":6,"template":686},"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":1651,"_dir":244,"_draft":6,"_partial":6,"_locale":7,"seo":1652,"content":1658,"config":1663,"_id":1665,"_type":14,"title":1666,"_source":16,"_file":1667,"_stem":1668,"_extension":19},"/en-us/blog/gitlab-leader-continuous-integration-forrester-wave",{"title":1653,"description":1654,"ogTitle":1653,"ogDescription":1654,"noIndex":6,"ogImage":1655,"ogUrl":1656,"ogSiteName":673,"ogType":674,"canonicalUrls":1656,"schema":1657},"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":1653,"description":1654,"authors":1659,"heroImage":1655,"date":1660,"body":1661,"category":299,"tags":1662},[1461],"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.",[792,9],{"slug":1664,"featured":6,"template":686},"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":1670,"_dir":244,"_draft":6,"_partial":6,"_locale":7,"seo":1671,"content":1677,"config":1682,"_id":1684,"_type":14,"title":1685,"_source":16,"_file":1686,"_stem":1687,"_extension":19},"/en-us/blog/gitlab-oracle-cloud-arm-based",{"title":1672,"description":1673,"ogTitle":1672,"ogDescription":1673,"noIndex":6,"ogImage":1674,"ogUrl":1675,"ogSiteName":673,"ogType":674,"canonicalUrls":1675,"schema":1676},"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":1672,"description":1673,"authors":1678,"heroImage":1674,"date":1679,"body":1680,"category":792,"tags":1681},[1298],"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",[792,9,683],{"slug":1683,"featured":6,"template":686},"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":1689,"_dir":244,"_draft":6,"_partial":6,"_locale":7,"seo":1690,"content":1696,"config":1701,"_id":1703,"_type":14,"title":1704,"_source":16,"_file":1705,"_stem":1706,"_extension":19},"/en-us/blog/gitlab-runner-update-required-to-use-auto-devops-and-sast",{"title":1691,"description":1692,"ogTitle":1691,"ogDescription":1692,"noIndex":6,"ogImage":1693,"ogUrl":1694,"ogSiteName":673,"ogType":674,"canonicalUrls":1694,"schema":1695},"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":1691,"description":1692,"authors":1697,"heroImage":1693,"date":897,"body":1699,"category":704,"tags":1700},[1698],"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,728,970,794,771],{"slug":1702,"featured":6,"template":686},"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":1708,"_dir":244,"_draft":6,"_partial":6,"_locale":7,"seo":1709,"content":1715,"config":1722,"_id":1724,"_type":14,"title":1725,"_source":16,"_file":1726,"_stem":1727,"_extension":19},"/en-us/blog/gitlab-workflow-with-jira-jenkins",{"title":1710,"description":1711,"ogTitle":1710,"ogDescription":1711,"noIndex":6,"ogImage":1712,"ogUrl":1713,"ogSiteName":673,"ogType":674,"canonicalUrls":1713,"schema":1714},"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":1710,"description":1711,"authors":1716,"heroImage":1712,"date":1718,"body":1719,"category":299,"tags":1720},[1717],"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,900,231,1721],"workflow",{"slug":1723,"featured":6,"template":686},"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":1729,"_dir":244,"_draft":6,"_partial":6,"_locale":7,"seo":1730,"content":1736,"config":1742,"_id":1744,"_type":14,"title":1745,"_source":16,"_file":1746,"_stem":1747,"_extension":19},"/en-us/blog/gitlabs-contributions-to-git-2-44-0",{"title":1731,"description":1732,"ogTitle":1731,"ogDescription":1732,"noIndex":6,"ogImage":1733,"ogUrl":1734,"ogSiteName":673,"ogType":674,"canonicalUrls":1734,"schema":1735},"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":1731,"description":1732,"authors":1737,"heroImage":1733,"date":1739,"body":1740,"category":681,"tags":1741},[1738],"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!",[948,903,9],{"slug":1743,"featured":6,"template":686},"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":1749,"_dir":244,"_draft":6,"_partial":6,"_locale":7,"seo":1750,"content":1756,"config":1762,"_id":1764,"_type":14,"title":1765,"_source":16,"_file":1766,"_stem":1767,"_extension":19},"/en-us/blog/hosting-vuejs-apps-using-gitlab-pages",{"title":1751,"description":1752,"ogTitle":1751,"ogDescription":1752,"noIndex":6,"ogImage":1753,"ogUrl":1754,"ogSiteName":673,"ogType":674,"canonicalUrls":1754,"schema":1755},"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":1751,"description":1752,"authors":1757,"heroImage":1753,"date":1759,"body":1760,"category":704,"tags":1761},[1758,768],"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,729,9,683],{"slug":1763,"featured":6,"template":686},"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":1769,"_dir":244,"_draft":6,"_partial":6,"_locale":7,"seo":1770,"content":1776,"config":1781,"_id":1783,"_type":14,"title":1784,"_source":16,"_file":1785,"_stem":1786,"_extension":19},"/en-us/blog/how-automation-is-making-devops-pros-jobs-easier",{"title":1771,"description":1772,"ogTitle":1771,"ogDescription":1772,"noIndex":6,"ogImage":1773,"ogUrl":1774,"ogSiteName":673,"ogType":674,"canonicalUrls":1774,"schema":1775},"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":1771,"description":1772,"authors":1777,"heroImage":1773,"date":1778,"body":1779,"category":1118,"tags":1780},[1420],"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",[728,991,9,683],{"slug":1782,"featured":6,"template":686},"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":1788,"_dir":244,"_draft":6,"_partial":6,"_locale":7,"seo":1789,"content":1795,"config":1802,"_id":1804,"_type":14,"title":1805,"_source":16,"_file":1806,"_stem":1807,"_extension":19},"/en-us/blog/how-devops-and-gitlab-cicd-enhance-a-frontend-workflow",{"title":1790,"description":1791,"ogTitle":1790,"ogDescription":1791,"noIndex":6,"ogImage":1792,"ogUrl":1793,"ogSiteName":673,"ogType":674,"canonicalUrls":1793,"schema":1794},"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":1790,"description":1791,"authors":1796,"heroImage":1792,"date":1798,"body":1799,"category":704,"tags":1800},[1797],"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",[1801,1721,9,728],"frontend",{"slug":1803,"featured":6,"template":686},"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":1809,"_dir":244,"_draft":6,"_partial":6,"_locale":7,"seo":1810,"content":1816,"config":1822,"_id":1824,"_type":14,"title":1825,"_source":16,"_file":1826,"_stem":1827,"_extension":19},"/en-us/blog/how-gitlab-can-help-mitigate-deletion-open-source-images-docker-hub",{"title":1811,"description":1812,"ogTitle":1811,"ogDescription":1812,"noIndex":6,"ogImage":1813,"ogUrl":1814,"ogSiteName":673,"ogType":674,"canonicalUrls":1814,"schema":1815},"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":1817,"description":1812,"authors":1818,"heroImage":1813,"date":1819,"body":1820,"category":704,"tags":1821},"How GitLab can help mitigate deletion of open source container images on Docker Hub",[1239],"2023-03-16","\nDocker, Inc. shared an email update to Docker Hub users that it will [sunset Free Team organizations](https://www.infoworld.com/article/3690890/docker-sunsets-free-team-subscriptions-roiling-open-source-projects.html). If accounts do not upgrade to a paid plan before April 14, 2023, their organization's images may be deleted after 30 days. This change can affect open source organizations that publish their images on Docker Hub, as well as consumers of these container images, used in CI/CD pipelines, Kubernetes cluster deployments, or docker-compose demo environments. This blog post discusses tools and features on the GitLab DevSecOps platform to help users analyze and mitigate the potential impact on production environments.\n\n_Update (March 20, 2023): Docker, Inc. [published an apology blog post](https://www.docker.com/blog/we-apologize-we-did-a-terrible-job-announcing-the-end-of-docker-free-teams/), including a FAQ, and clarifies that the company will not delete container images by themselves. Maintainers can migrate to a personal account, join the Docker-sponsored open source program, or opt into a paid plan. If open source container image maintainers do nothing, this leads into another issue: Stale container images can become a security problem. The following blog post can help with security analysis and migration too._ \n\n_Update (March 27, 2023): On March 24, 2023, Docker, Inc. [published another blog post](https://www.docker.com/blog/no-longer-sunsetting-the-free-team-plan/) announcing the reversal of the decision to sunset the Free team plan and updated its [FAQ for Free Team organization](https://www.docker.com/developers/free-team-faq/). While this is a welcome development for the entire community, it is still crucial to ensure the reliability of your software development lifecycle by ensuring redundancies are in place for your container registries, as detailed in this blog post._\n\n### Inventory of used container images\n\nCI/CD pipelines in GitLab can execute jobs in containers. This is specified by the [`image` keyword](https://docs.gitlab.com/ee/ci/yaml/#image) in jobs, job templates, or as a global [`default`](https://docs.gitlab.com/ee/ci/yaml/#default) attribute. For the first iteration, you can clone a GitLab project locally, and search for the `image` string in all CI/CD configuration files. The following example shows how to execute the `find` command on the command line interface (CLI), searching for files matching the name pattern `*ci.yml`, and looking for the `image` string in the file content. The command line prints a list of search pattern matches, and the corresponding file name to the standard output. The example inspects the [project](https://gitlab.com/gitlab-com/www-gitlab-com) for the [GitLab handbook](https://about.gitlab.com/handbook/) and [website](https://about.gitlab.com/) to analyze whether its CI/CD deployment pipelines could be affected by the Docker Hub changes.\n\n```bash\n$ git clone https://gitlab.com/gitlab-com/www-gitlab-com && cd www-gitlab-com\n\n$ find . -type f -iname '*ci.yml' -exec sh -c \"grep 'image:' '{}' && echo {}\" \\;\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\nA [discussion on Hacker News](https://news.ycombinator.com/item?id=35168802) mentions that \"official Docker images\" are not affected, but this is not officially confirmed by Docker yet. [Official Docker images](https://hub.docker.com/u/library) do not use a namespace prefix, i.e. `namespace/imagename` but instead `debian:\u003Ctagname>` for example. `registry.gitlab.com/gitlab-org/gitlab-build-images:danger` uses a full URL image string, which includes the image registry server domain, `registry.gitlab.com` in the shown example.\n\nIf there is no full URL prefix in the image string, this is an indicator that this image could be pulled from Docker Hub by default. There might be other infrastructure safety nets put in place, for example a cloud provider registry which caches the Docker Hub images (Google Cloud, AWS, Azure, etc.).\n\n#### Advanced search for images\n\nYou can use the [project lint API endpoint](https://docs.gitlab.com/ee/api/lint.html#validate-a-projects-ci-configuration) to fetch the CI configuration. The following script uses the [python-gitlab API library](https://python-gitlab.readthedocs.io/en/stable/gl_objects/ci_lint.html) to implement the API endpoint:\n\n1. Collect all projects from either a single project ID, a group ID with projects, or from the instance.\n2. Run the `project.ci_lint.get()` method to get a merged yaml configuration for CI/CD from the current GitLab project.\n3. Parse the yaml content and print only the job names, and the image keys.\n\nThe [full script is located here](https://gitlab.com/gitlab-da/use-cases/gitlab-api/gitlab-api-python/-/blob/main/get_all_cicd_job_images.py), and is open source, licensed under MIT.\n\n```python\n#!/usr/bin/env python\n\nimport gitlab\nimport os\nimport sys\nimport yaml\n\nGITLAB_SERVER = os.environ.get('GL_SERVER', 'https://gitlab.com')\nGITLAB_TOKEN = os.environ.get('GL_TOKEN') # token requires developer permissions\nPROJECT_ID = os.environ.get('GL_PROJECT_ID') #optional\n# https://gitlab.com/gitlab-da/use-cases/docker\nGROUP_ID = os.environ.get('GL_GROUP_ID', 65096153) #optional\n\n#################\n# Main\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\nThe [script](https://gitlab.com/gitlab-de/use-cases/gitlab-api/gitlab-api-python/-/blob/main/get_all_cicd_job_images.py) requires Python (tested with 3.11) and the python-gitlab and pyyaml modules. Example on macOS with Homebrew:\n\n```shell\n$ brew install python\n$ pip3 install python-gitlab pyyaml\n```\n\nYou can execute the script and set the different environment variables to control its behavior:\n\n```shell\n$ export GL_TOKEN=$GITLAB_TOKEN\n\n$ export GL_GROUP_ID=12345\n$ export GL_PROJECT_ID=98765\n\n$ python3 get_all_cicd_job_images.py\n\n# Summary of projects and their CI/CD image usage\n# Project: Developer Evangelism at GitLab  / use-cases / Docker Use cases  / Custom Container Image Python, ID: 44352983\n\nJob name: docker-build\nImage: docker:latest\n\n# Project: Developer Evangelism at GitLab  / use-cases / Docker Use cases  / Gitlab Dependency Proxy, ID: 44351128\n\nJob name: .test-python-version\nJob name: image-docker-hub\nImage: python:3.11\nJob name: image-docker-hub-dep-proxy\nImage: ${CI_DEPENDENCY_PROXY_GROUP_IMAGE_PREFIX}/python:3.11\n```\n\nPlease verify the script and fork it for your own analysis and mitigation. The missing parts are checking the image URLs, and doing a more sophisticated search. The code has been prepared to either check against a single project, a group with projects, or an instance (this may take very long, use with care).\n\nYou can perform a more history-focused analysis by fetching the CI/CD job logs from GitLab and search for the pulled container image to get an overview of past Docker executor runs – for example: `Using Docker executor with image python:3.11 ...`. The screenshot shows the CI/CD job logs UI search – you can automate the search using the GitLab API, and the [python-gitlab library](https://python-gitlab.readthedocs.io/en/stable/gl_objects/pipelines_and_jobs.html#jobs), for example.\n\n![GitLab CI/CD job logs, searching for the `image` keyword](https://about.gitlab.com/images/blogimages/docker-hub-oss-image-deletion-mitigation/cicd_gitlab_job_logs_search_image.png)\n\nThis snippet can be used in combination with the code shared for the CI lint API endpoint. It fetches the job trace logs, and searches for the `image` keyword in the log. The missing parts are splitting the log line by line, and extracting the image key information. This is left as an exercise for the reader.\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### More inventory considerations\n\nSimilar to the API script for CI/CD navigating through all projects, you will need to analyze all Kubernetes manifest configuration files – using either a pull- or push-based approach. This can be achieved by using the [python-gitlab methods to load files from the repository](https://python-gitlab.readthedocs.io/en/stable/gl_objects/projects.html#project-files) and searching the content in similar ways. Helm charts use container images, too, and will require additional analysis.\n\nAn additional search possibility: Custom-built container images that use Docker Hub images as a source. A project will consist of:\n\n1. `Dockerfile` file that uses `FROM \u003Cimagename>`\n2. `.gitlab-ci.yml` configuration file that builds container images (using Docker-in-Docker, Kaniko, etc.)\n\nAn alternative search method for customers is available by using the [Advanced Search](https://docs.gitlab.com/ee/user/search/advanced_search.html) through the GitLab UI and API. The following example uses the [scope: blobs](https://docs.gitlab.com/ee/api/search.html#scope-blobs-premium-2) to search for the `FROM` string:\n\n```shell\n$ export GITLAB_TOKEN=xxxxxxxxx\n\n# Search in https://gitlab.com/gitlab-da\n/use-cases/docker/custom-container-image-python\n\n$ curl --header \"PRIVATE-TOKEN: $GITLAB_TOKEN\" \"https://gitlab.com/api/v4/projects/44352983/search?scope=blobs&search=FROM%20filename:Dockerfile*\"\n```\n\n![Command line output from Advanced Search API, scope blobs, search `FROM` in `Dockerfile*` file names.](https://about.gitlab.com/images/blogimages/docker-hub-oss-image-deletion-mitigation/cli_gitlab_advanced_search_api_dockerfile_from.png)\n\n## Mitigations and solutions\n\nThe following sections discuss potential mitigation strategies, and long-term solutions.\n\n### Mitigation: GitLab dependency proxy\n\nThe dependency proxy provides a caching mechanism for Docker Hub images. It helps reduce the bandwidth and time required to download and pull the images. It also helped to [mitigate the Docker Hub pull rate limits introduced in 2020](/blog/minor-breaking-change-dependency-proxy/). The dependency proxy can be configured for public and private projects.\n\nThe [dependency proxy](https://docs.gitlab.com/ee/user/packages/dependency_proxy/) needs to be enabled for a group. It also needs to be enabled by an instance administrator for self-managed environments, if turned off.\n\nThe following example creates two jobs: `image-docker-hub` and `image-docker-hub-dep-proxy`. The dependency proxy job uses the `CI_DEPENDENCY_PROXY_GROUP_IMAGE_PREFIX` CI/CD variable to instruct GitLab to store the image in the cache, and only pull it once when not available.\n\n```yaml\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\nThe configuration is available in [this project](https://gitlab.com/gitlab-de/use-cases/docker/gitlab-dependency-proxy).\n\nThe stored container image is visible at the group level in the `Package and container registries > Dependency Proxy` menu.\n\n### Mitigation: Container registry mirror\n\n[This blog post](/blog/mitigating-the-impact-of-docker-hub-pull-requests-limits/) describes how to run a local container registry mirror. Skopeo from Red Hat is another alternative for syncing container image registries, a practical example is described [in this article](https://marcbrandner.com/blog/transporting-container-images-with-skopeo/).\n\nThe GitLab Cloud Native installation ([Helm charts](https://docs.gitlab.com/charts/) and [Operator](https://docs.gitlab.com/operator/)) use a [mirror of tagged images](https://gitlab.com/gitlab-org/cloud-native/mirror/images) consumed by the related projects. Other product stages follow a similar approach, the [security scanners are shipped in container images](https://docs.gitlab.com/ee/user/application_security/offline_deployments/#container-registries-and-package-repositories) maintained by GitLab. This also enables self-managed airgapped installations.\n\n### Mitigation: Custom images in GitLab container registry\n\nReproducible builds and compliance requirements may have required you to create custom container images for CI/CD and Kubernetes already. This is also key to verify that no untested and untrusted images are being used in production. GitLab provides a fully integrated [container registry](https://docs.gitlab.com/ee/user/packages/container_registry/), which can be used natively within CI/CD pipelines and [GitOps workflows with the agent for Kubernetes](https://docs.gitlab.com/ee/user/clusters/agent/gitops.html).\n\nThe following `Dockerfile` example extends an existing image layer, and installs additional tools using the Debian Apt package manager.\n\n```\nFROM python:3.11-bullseye\n\nENV DEBIAN_FRONTEND noninteractive\n\nRUN apt update && apt -y install git curl jq && rm -rf /var/lib/apt/lists/*\n```\n\nYou can [use Docker to build container images](https://docs.gitlab.com/ee/ci/docker/using_docker_build.html), and alternative options are Kaniko or Podman. On GitLab.com SaaS, you can use the Docker CI/CD template to build and push images. The following example modifies the `docker-build` job to only build the latest tag from the default branch:\n\n```yaml\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\nFor this example, we specifically want to provide a Git tag that gets used for the container image tag as well.\n\n```\n$ git tag 3-11-bullseye\n$ git push --tags\n```\n\nThe image will be available at the GitLab container registry URL and the project namespace path.This path needs to be replaced in all projects that use a Python-based image. You can [create scripts for the GitLab API](/blog/efficient-devsecops-workflows-hands-on-python-gitlab-api-automation/) to update files and create MRs automatically,\n\n```\nimage: registry.gitlab.com/gitlab-da/use-cases/docker/custom-container-image-python:3-11-bullseye\n```\n\n_Note: This is a demo project and not actively maintained. Please fork/copy it for your own needs._\n\n## Observability and security\n\nThe [number of failed CI/CD pipelines](https://docs.gitlab.com/ee/user/analytics/ci_cd_analytics.html) can be a good service level indicator (SLI) to verify whether the environment is affected by the Docker Hub changes. The same SLI applies for CI/CD jobs that build container images, using a `Dockerfile` file, which is based on Docker Hub images (FROM \u003Cimagename>).\n\nA similar SLI applies to Kubernetes cluster deployments – if they continue to generate failures in GitOps pull or CI/CD push scenarios, additional analysis and actions are required. The pod status `ErrImagePull` and [`ImagePullBackOff`](https://kubernetes.io/docs/concepts/containers/images/#imagepullbackoff) will immediately show the problems. The [image pull policy](https://kubernetes.io/docs/concepts/containers/images/#image-pull-policy) should also be revised – `Always` will immediately cause a problem, while `IfNotPresent` will use the local image cache.\n\n[This alert rule example](https://awesome-prometheus-alerts.grep.to/rules.html#rule-kubernetes-1-18) for Prometheus observing a Kubernetes cluster can help detect the pod state as not healthy.\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\nCI/CD pipeline linters and Git hooks can also be helpful to enforce using a GitLab registry URL prefix in all `image` tags, when new updates to CI/CD configurations are being pushed into merge requests.\n\nKubernetes deployment images can be controlled through additional integrations with the [Open Policy Agent Gatekeeper](https://www.openpolicyagent.org/docs/latest/kubernetes-introduction/) or [Kyverno](https://kyverno.io/policies/best-practices/restrict_image_registries/restrict_image_registries/). Kyverno also allows you to [mutate the image registry location](https://kyverno.io/policies/other/replace_image_registry/replace_image_registry/), and redirect the pod image to trusted sources.\n\n[Operational container scanning](https://docs.gitlab.com/ee/user/clusters/agent/vulnerabilities.html) in Kubernetes clusters and [container scanning in CI/CD pipelines](https://docs.gitlab.com/ee/user/application_security/container_scanning/) are recommended. This ensures that all images do not expose security vulnerabilities.\n\n## Long-term solutions\n\nAs a long-term solution, analyze the affected Docker Hub organizations images and match them against your image usage inventory. Some organizations have raised their concerns in [this Docker Hub feedback issue](https://github.com/docker/hub-feedback/issues/2314). Be sure to identify critical production CI/CD workflows and replace all external dependencies with local maintained images.\n\nFork/copy project Dockerfile files from the upstream Git repositories, and use them as the single source of truth for custom container builds. This will also require training and documentation for DevSecOps teams, for example optimizing container images for [efficient CI/CD pipelines](https://docs.gitlab.com/ee/ci/pipelines/pipeline_efficiency.html). More DevSecOps efficiency tips can be found in my Chemnitz Linux Days talk about \"Efficient DevSecOps Pipelines in a Cloud Native World\" ([slides](https://go.gitlab.com/RPog2h)).\n\n\u003Ciframe src=\"https://docs.google.com/presentation/d/e/2PACX-1vT3jcfpddKL2jq7leX01QX6S4Y8vfLLBZMz4L1ZHMLY3xzB4IGOOIExODLEzH8YQM1atCNPm07Bw9m_/embed?start=false&loop=true&delayms=3000\" frameborder=\"0\" width=\"960\" height=\"569\" allowfullscreen=\"true\" mozallowfullscreen=\"true\" webkitallowfullscreen=\"true\">\u003C/iframe>\n\nPlease share your ideas and thoughts about Docker Hub change mitigations and tools on the [GitLab community forum](https://forum.gitlab.com/). Thank you!\n\nCover image by [Roger Hoyles](https://unsplash.com/photos/sTOQyRD8m74) on [Unsplash](https://www.unsplash.com)\n{: .note}\n",[9,901,903],{"slug":1823,"featured":6,"template":686},"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":1829,"_dir":244,"_draft":6,"_partial":6,"_locale":7,"seo":1830,"content":1836,"config":1842,"_id":1844,"_type":14,"title":1845,"_source":16,"_file":1846,"_stem":1847,"_extension":19},"/en-us/blog/how-orange-uses-gitlab-ci-cd-for-modern-devops",{"title":1831,"description":1832,"ogTitle":1831,"ogDescription":1832,"noIndex":6,"ogImage":1833,"ogUrl":1834,"ogSiteName":673,"ogType":674,"canonicalUrls":1834,"schema":1835},"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":1831,"description":1832,"authors":1837,"heroImage":1833,"date":1839,"body":1840,"category":681,"tags":1841},[1838],"Pierre Smeyers","2021-07-29","\n\nCI/CD is a foundational piece to modern software development. It's a major brick in the [DevOps](/topics/devops/) \"Automation\" pillar and every company involved in IT has to implement CI/CD or they're already quite far behind the curve.\n\nBut [implementing CI/CD](/topics/ci-cd/) can be challenging especially for growing or large companies. Some of those challenges include:\n\n* DevOps expertise and technical skills\n* [DevSecOps](/topics/devsecops/)\n* Standardization\n\n## Three key hurdles that come with implementing CI/CD\n\nThis blog post unpackes these challenges and explains how [Orange](https://orange.com/) overcame them using GitLab.\n\n### DevOps and technical skills\n\nNo matter which CI/CD tool you're using, it requires some amount of expertise to implement it right.\n\n**DevOps expertise** is important because your team needs some experience with Git workflows, deployment, environments, secrets management, etc. You can't ask a complete rookie to implement a state-of-the art DevOps pipeline without expertise or experience.\n\n**Technical skills** are also important for implementing CI/CD. Any professional can tell you that getting started tutorials are insufficient. We inevitably need advanced functions, and that requires knowing the tool pretty well. This is particularly true with GitLab CI/CD, which is a fantastic functionally rich tool. GitLab CI/CD is constantly evolving, which creates an ongoing burden for projects that want to integrate new tooling as they go.\n\n### DevSecOps\n\nDevOps is all about finding the right balance between shortening the cycle and maximizing your confidence.\n\n[DevSecOps tools](/solutions/security-compliance/) are a keystone in maximizing our confidence because they detect issues with things like security, code quality, and compliance, etc., almost instantly. But DevSecOps tools are evolving quickly and today's Docker container scanner tools can be replaced by newcomers in just a few months.\n\nAlso, having each development team in the company choose and integrate various DevSecOps tools doesn't make sense and will be a waste of time and resources. Going this route means most developers won't use any DevSecOps tool because the opportunity cost isn't worth the time and effort.\n\n### Standardization\n\nThe last challenge in implementing CI/CD at a large company is the lack of standardization.\n\nGitLab CI/CD - as with most other CI/CD tools - is mainly a sophisticated scheduler, allowing a team to define technical tasks and their sequence. GitLab CI/CD cares little about the nature of these tasks, and does not give any clues as to the \"right\" way to build a DevOps pipeline. The consequence of this is that every company, project team, and developer will implement a DevOps pipeline their own way, in a manner that is probably significantly different from their colleagues'.\n\nAs a lifelong Javaist, I like to compare the current situation in CI/CD with what was the Java build in the pre-Maven era. Back then, we used non-structuring tools such as [Make](https://en.wikipedia.org/wiki/Make_(software)) or [Apache Ant](https://en.wikipedia.org/wiki/Apache_Ant). Each project created its own build system, adopted its own conventions, code, and resource files structure. In short, it was a happy mess with everyone reinventing the wheel. When joining another project, a user had to ask: \"How does the build work here?\".\n\nIn 2004, Maven was released (and Gradle three years later). For a while, there were heated debates between the proponents of standardization and the defenders of expertise and customization. Today it would not occur to anyone to build a Java project with anything other than Maven or Gradle. Now, if I join a project developed in Java, I will immediately know how files are organized and how the project is built. Java build is now standardized.\n\nI believe that CI/CD ought to go a similar route: tools should offer a more opinionated framework so that CI/CD too becomes a non-topic.\n\n## How a single GitLab feature changed the game for Orange\n\nAt Orange - probably like many other companies involved in IT - we struggled with the three challenges summarized above.\n\nThen in January 2019, the [`include`](https://docs.gitlab.com/ee/ci/yaml/#include) feature was released in the [Community Edition (version 11.7) of GitLab](/releases/2019/01/22/gitlab-11-7-released/):\n\n```yaml\ninclude:\n  - project: a-path/to-some-project'\n    file: '/very-smart-template.yml'\n```\n\nThis feature finally gave us the ability to develop and share state-of-the-art GitLab CI/CD pipeline templates!\n\nSo that's what we did.\n\nFor two years, a handful of DevOps/security/languages/cloud experts developed ready-to-use GitLab CI/CD pipeline templates. This personal initiative quickly became recognized as an internal project, attracting more users and contributors, bringing the community to 1000+ members as of June 2021, and leveraging about 30 available templates. The visible effect of this increasing adoption is the beginning of a **CI/CD standardization at Orange**.\n\nWe were so happy with our results and convinced that it's a general need that we open sourced our templates under the name [\"to be continuous\"](https://to-be-continuous.gitlab.io/doc/).\n\n![To be continuous logo](https://about.gitlab.com/images/blogimages/orange_tbc.jpg){: .shadow}\nThe \"to be continuous\" logo.\n{: .note.text-center}\n\n### What is in *to be continuous*?\n\nFor now, *to be continuous* has 26 templates of six kinds:\n\n* **Build & Test**: Angular, Bash, Go, Gradle, Maven, MkDocs, Node.js, PHP, Python\n* **Code Analysis**: Gitleaks, SonarQube\n* **Packaging**: Docker\n* **Infrastructure** (IaC): Terraform\n* **Deploy & Run**: Ansible, Cloud Foundry, Google Cloud, Helm, Kubernetes, OpenShift, S3 (Simple Storage Service)\n* **Acceptance**: Cypress, Postman, Puppeteer, Robot Framework, SSL test, k6\n* **Others**: semantic-release\n\n*To be continuous* is thoroughly documented:\n\n* [Basic notions and philosophy](https://to-be-continuous.gitlab.io/doc/understand/)\n* [General usage principles](https://to-be-continuous.gitlab.io/doc/usage/)\n* How to use *to be continuous* in a [self-managed instance of GitLab](https://to-be-continuous.gitlab.io/doc/self-managed/basic/)\n* Every template also has [its own documentation](https://to-be-continuous.gitlab.io/doc/ref/angular/)\n\nTo get started quickly, *to be continuous* provides an [interactive configurer](https://to-be-continuous.gitlab.io/kicker/) (aka *\"kicker\"*) that allows generating the `.gitlab-ci.yml` file simply by selecting the technical characteristics of your project.\n\nFinally, *to be continuous* exposes several [example projects](https://gitlab.com/to-be-continuous/samples), illustrating how to use the templates in production-like projects, combining multiple templates.\n\n### A quick glance at *to be continuous*\n\nThere are tons of resources to get started with *to be continuous*. But here is a quick example to get the taste of it.\n\nHere is the `.gitlab-ci.yml` file for a project:\n\n* Developed in Java 11 (built with Maven)\n* Code analysis with SonarQube\n* Packaged as a Docker image\n* Deployed to Kubernetes cluster\n* GUI tests with Cypress\n* API tests with Postman (Newman)\n\n```yaml\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\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\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** (any feature branch):\n\n![Screenshot of development pipeline](https://about.gitlab.com/images/blogimages/orange_development_pipeline.jpg){: .shadow}\n\n... and the following **production pipeline** (`master` or `main` depending on your preferences):\n\n![Screenshot of production pipeline](https://about.gitlab.com/images/blogimages/orange_production_pipeline.jpg){: .shadow}\n\nAlthough they look pretty much the same, they aren't:\n\n* While the production pipeline privileges sureness and completeness, development pipelines privilege short cycles and developer experience. While code analysis jobs and acceptance tests are blocked in production, they only generate a non-blocking warning in development in case of failure.\n* The production pipeline deploys to the staging environment before deploying to production (provided acceptance tests are green). Development pipelines may deploy to a dynamically generated review environment (optional).\n* Developers may prefer to use a single integration environment (associated with the develop branch) instead of one review app per feature branch. The default behavior of the integration pipeline is much closer to the production one.\n\nWhat you can't see:\n\n* Java unit tests are automatically executed, their report is [integrated to GitLab](https://docs.gitlab.com/ee/ci/unit_test_reports.html), with [code coverage](https://docs.gitlab.com/ee/ci/yaml/#coverage) too.\n* SonarQube integration automatically uses [branch analysis](https://docs.sonarqube.org/latest/branches/overview/) or [MR analysis](https://docs.sonarqube.org/latest/analysis/pull-request/) (with MR decoration) depending on the context.\n* Kubernetes environments are obviously [integrated to GitLab](https://docs.gitlab.com/ee/ci/environments/) too.\n* [Review apps](https://docs.gitlab.com/ee/ci/review_apps/index.html) can be cleaned-up manually or automatically on branch deletion.\n* Cypress and Postman tests reports are also [integrated to GitLab](https://docs.gitlab.com/ee/ci/unit_test_reports.html).\n* Docker uses the Kaniko build by default but it might be configured to use Docker-in-Docker instead. It uses the GitLab registry by default but can be configured to use any other registry.\n* Each template integrates the most appropriate DevSecOps tools: [kube-score](https://kube-score.com/) for Kubernetes, [hadolint](https://github.com/hadolint/hadolint) for Docker, [OWASP Dependency-Check](https://jeremylong.github.io/DependencyCheck/) for Maven, among others.\n* All those templates combine themselves gracefully. For example, Kubernetes may simply deploy the Docker image built upstream; Cypress and Postman tests automatically test the application deployed in the upstream jobs; Kubernetes could be replaced with OpenShift, GCP or any other supported hosting technology, it would behave the same.\n\n## Contribute to *to be continuous*\n\n[to be continuous](https://to-be-continuous.gitlab.io/doc) is out and eagerly waiting for users and contributors.\n\nHave a look and share your feedback. Whether you like our choices or not, we want to hear from you. Your inputs are even more valuable to help us improve *to be continuous* and cover as many use cases as possible.\n\nBut anyway, never forget this: [`include`](https://docs.gitlab.com/ee/ci/yaml/#include) is undoubtedly the feature that makes CI/CD standardization possible in your company (and beyond).\n\nCover image by [Graphic Node](https://unsplash.com/@graphicnode) on [Unsplash](https://unsplash.com/photos/yi1YB_FubH8)\n{: .note}\n",[728,903,9],{"slug":1843,"featured":6,"template":686},"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":1849,"_dir":244,"_draft":6,"_partial":6,"_locale":7,"seo":1850,"content":1856,"config":1861,"_id":1863,"_type":14,"title":1864,"_source":16,"_file":1865,"_stem":1866,"_extension":19},"/en-us/blog/how-to-become-more-productive-with-gitlab-ci",{"title":1851,"description":1852,"ogTitle":1851,"ogDescription":1852,"noIndex":6,"ogImage":1853,"ogUrl":1854,"ogSiteName":673,"ogType":674,"canonicalUrls":1854,"schema":1855},"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":1851,"description":1852,"authors":1857,"heroImage":1853,"date":1858,"body":1859,"category":704,"tags":1860},[1157],"2021-06-21","\nCI/CD pipelines are the preeminent solution to mitigate potential risks while integrating code changes into the repository. CI/CD pipelines help isolate the impact of potential errors, making it easier to fix them. Top that with a tool that provides effective visibility into the running tasks and there you have a recipe for success.\n\nSince the primary purpose of CI/CD pipelines is to speed up the development process and provide value to the end user faster, there's always room to make the process more efficient. This blog post unpacks some strategies that can help you get the most out of your pipeline definition in [GitLab CI](/solutions/continuous-integration/).\n\n## How Directed Acyclic Graphs (DAG) enable concurrent pipelines\n\n![By using Needs keyword you can define dependencies for jobs that need to be used from previous stages.](https://about.gitlab.com/images/blogimages/dag-explained.jpeg)\nBy using the \"Needs\" keyword you can define dependencies for jobs that need to be used from previous stages.\n{: .note.text-center}\n\nIn a [basic-pipeline](https://docs.gitlab.com/ee/ci/pipelines/pipeline_architectures.html#basic-pipelines) structure, all the jobs in a particular stage run concurrently and the jobs in the subsequent stage have to wait on those to finish to get started. This continues for all the stages.\n\nIn the image above, the first job in the second stage only depends on the first two job in the first stage to get started. But with the basic pipeline order in place, it has to wait for all three jobs in the first stage to complete before it can start executing, which slows down the overall pipeline considerably. However, by using `needs:` keywords, you can define a direct dependency for the jobs and they would only have to wait on the job they depend on to get started. By using the [DAG strategy](https://docs.gitlab.com/ee/ci/directed_acyclic_graph/), you could shed out a few minutes from the processes for a certain project, thereby increasing the pipeline execution speed and bringing down the CI minutes consumption.\n\nBy using `needs: []` you can make the job in any stage run immediately, as it doesn't have to wait on any other job to finish.\n\n## Why parallel jobs increase productivity\n\nNot all the jobs in a pipeline have an equal run-time. While some may take just a few seconds, some take much longer to finish. When there are many team members waiting on a running pipeline to finish to be able to make a contribution to the project, the productivity of the team takes a hit.\n\nGitLab provides a method to make clones of a job and run them in parallel for faster execution using the `parallel:` keyword. While [parallel jobs](https://docs.gitlab.com/ee/ci/yaml/#parallel) may not help in reducing the consumption of [CI minutes](/pricing/faq-compute-credit/), they definitely help increase work productivity.\n\n## Break down big pipelines with parallel matrix Jobs\n\nBefore the release of [parallel matrix jobs](https://docs.gitlab.com/ee/ci/yaml/#parallel-matrix-jobs), in order to run multiple instances of a job with different variable values, the jobs had to be manually defined in the `.gitlab-ci-yml` like this:\n\n```yaml\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\nParallel matrix jobs were released with GitLab 13.3 and allow you to create jobs at runtime based on specified variables. Let's say there is a need to run multiple instances a job with different variables values for each instance — with a combination of `parallel:` and `matrix:` you accomplish just that.\n\n```yaml\ntest:\n  stage: test\n  script: run-test $PLATFORM\n  parallel:\n    matrix:\n      - PLATFORM: [windows, mac, linux]\n```\n\nBy using `parallel:` and `matrix:`, big pipelines can be broken down into manageable parts for efficient maintainance.\n\n## Reduce the risk of merge conflicts with parent/child pipelines\n\n![Parent-child pipelines can include external YAML files in you configuration](https://about.gitlab.com/images/blogimages/parent-child-explained.jpeg)\nThe parent pipeline generates a child pipeline via the trigger:include keywords.\n{: .note.text-center}\n\nFor better management of dependencies, many organizations prefer a mono-repo setup for their projects. But mono-repos have a flip side too. If a repository hosts a large number of projects and a single pipeline definition is used to trigger different automated processes for different components, the pipeline performance is negatively affected. By using [parent-child pipelines](https://docs.gitlab.com/ee/ci/pipelines/downstream_pipelines.html) you can design more efficient pipelines, since you can have multiple child-pipelines that run in parallel. The keyword `include:` is used to include external YAML files in your CI/CD configuration for this purpose. In the image above a pipeline (the parent) generates a child pipeline via the trigger:include keywords.\n\nThis approach also reduces the chances of merge conflicts from happening, as it allows to only edit a section of the pipeline if necessary.\n\n## Merge trains help the target branch stay stable\n\nWhen there's a lot of merge requests flowing into a project, there is a risk of merge conflicts. [Merge trains](https://docs.gitlab.com/ee/ci/pipelines/merge_trains.html) is a powerful feature by GitLab that allows users to automatically merge a series of (queued) merge requests without breaking the target branch. Using this feature, you can add an MR to the train, and it would take care of it until it is merged.\n\n## Use multiple caches in the same job\n\nStarting 13.11, GitLab CI/CD provides the ability to [configure multiple cache keys in a single job](/releases/2021/04/22/gitlab-13-11-released/#use-multiple-caches-in-the-same-job) which will help you increase your pipeline performance. This functionality could help you save precious development time when the jobs are running.\n\n## How can an efficient pipeline save you money?\n\nBy using CI/CD strategies that ensure safe merging of new changes and a green master, organizations can worry less about unanticipated downtimes caused by infrastructural failures and code conflicts.\n\nWith faster pipelines, developers end up spending lesser time in maintenance and find time and space to bring in more thoughtfulness and creativity in their work, leading to improvements in code quality and the company atmosphere and morale.\n\nIf you are looking to bring down the cost of running your CI/CD pipelines for a large project, look up the [Artifact and cache settings](https://docs.gitlab.com/ee/ci/runners/configure_runners.html#artifact-and-cache-settings) and [Optimizing GitLab for large repositories](https://docs.gitlab.com/ee/ci/large_repositories/) sections in the documentation.\n",[9,683,970,902],{"slug":1862,"featured":6,"template":686},"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":1868,"_dir":244,"_draft":6,"_partial":6,"_locale":7,"seo":1869,"content":1875,"config":1881,"_id":1883,"_type":14,"title":1884,"_source":16,"_file":1885,"_stem":1886,"_extension":19},"/en-us/blog/how-to-bring-devops-to-the-database-with-gitlab-and-liquibase",{"title":1870,"description":1871,"ogTitle":1870,"ogDescription":1871,"noIndex":6,"ogImage":1872,"ogUrl":1873,"ogSiteName":673,"ogType":674,"canonicalUrls":1873,"schema":1874},"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":1870,"description":1871,"authors":1876,"heroImage":1872,"date":1878,"body":1879,"category":704,"tags":1880},[1877],"Tsvi Zandany","2022-01-05","\nIn the [Accelerate State of DevOps 2021 Report](https://cloud.google.com/devops/state-of-devops/), the DevOps Research and Assessment (DORA) team reveals “elite DevOps performers are 3.4 times more likely to exercise database change management compared to their low-performing counterparts.” Tracking changes with version control is not just for application code, though. It’s crucial for managing changes for one of your most important assets: your database.   \n\nThe GitLab DevOps platform enables database management teams to leverage CI/CD to track, manage, and deploy database changes, along with application development and automation and infrastructure as code. Database change management tools have become more advanced in recent years, supporting easier collaboration and communication, which are the keys to successful DevOps. In this blog post, I’ll take you through a tutorial using [Liquibase](https://www.liquibase.com), a tool that integrates seamlessly into the GitLab DevOps platform so your teams can deliver database code changes as fast as application code changes (without compromising on quality and security). \n\n## What is Liquibase?\n\nLiquibase was founded as an open source project over 15 years ago to address getting database changes into version control. With more than 75 million downloads, the company behind Liquibase expanded to paid editions and support to help teams release software faster and safer by bringing the database change process into their existing CI/CD automation.  \n\nIntegrating Liquibase with GitLab CI/CD enables database teams to leverage DevOps automation and best practices for database management. Liquibase helps teams build automated database scripts and gain insights into when, where, and how database changes are deployed. In this tutorial, we’ll demonstrate how to check database scripts for security and compliance issues, speed up database code reviews, perform easy rollbacks, and provide database snapshots to check for malware.\n\n## Adding Liquibase to GitLab’s DevOps Platform\n\nTeams can add Liquibase to GitLab to enable true CI/CD for the database. It’s easy to integrate Liquibase into your GitLab CI/CD pipeline. Before jumping into the tutorial, let’s take a look at the [example Liquibase GitLab project repository](https://gitlab.com/gitlab-com/alliances/liquibase/sandbox-projects/sql_server) you’ll be using.\n\n### Understanding the example Liquibase GitLab project repository\n\n![A CI/CD pipeline diagram](https://about.gitlab.com/images/blogimages/1_CICD_Pipeline_Diagram.png){: .shadow.small.center}\n\nFor this example, the GitLab CI/CD pipeline environments include DEV, QA, and PROD. This pipeline goes through several stages: build, test, deploy, and compare. A post stage comes into play later to capture a snapshot of your database in Production.\n\nStages:\n  - build\n  - test\n  - deploy\n  - compare\n\n### Liquibase commands in the pipeline\n\nFor each of the predefined jobs in the GitLab repository, you’ll be using several Liquibase commands to help manage database changes quickly and safely:\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 repository](https://gitlab.com/gitlab-com/alliances/liquibase/sandbox-projects/sql_server/-/blob/main/README.md). \n\n## Tutorial\n\nThe following tutorial demonstrates how to run Liquibase in a GitLab CI/CD pipeline. Follow along by watching this companion video:\n\n\u003C!-- blank line -->\n\u003Cfigure class=\"video_container\">\n  \u003Ciframe src=\"https://www.youtube.com/embed/ZBFhDayoRYo\" frameborder=\"0\" allowfullscreen=\"true\"> \u003C/iframe>\n\u003C/figure>\n\u003C!-- blank line -->\n\n### Prerequisites\n\nTo start, I’m using a Linux machine with the following:\n\n- [A GitLab account](https://www.gitlab.com)\n- Self-managed Runner on a Linux machine\n- Git\n- Java 11\n- Access to a SQL Server database with multiple environments\n\n### Download, install, and configure Liquibase\n\n[Download Liquibase v4.6.1+](https://www.liquibase.org/download)\n\n[Install Liquibase](https://docs.liquibase.com/concepts/installation/installation-linux-unix-mac.html)\n\n[Get a free Liquibase Pro license key](https://www.liquibase.com/trial). No credit card is required, so you can play with all the advanced features and get support for 30 days. You’ll use this key later when you configure environment variables within GitLab.\n\nEnsure Liquibase is installed properly by running the liquibase --version command. If everything is good you’ll see the following:\n\nStarting Liquibase at 18:10:06 (version 4.6.1 #98 built at 2021-11-04 20:16+0000)\nRunning Java under /usr/lib/jvm/java-11-openjdk-11.0.13.0.8-1.el7_9.x86_64 (Version 11.0.13)\n\nLiquibase Version: 4.6.1\nLiquibase Community 4.6.1 by Liquibase\n\n### Prepare your GitLab project\n\nFork this [example GitLab project repository](https://gitlab.com/gitlab-com/alliances/liquibase/sandbox-projects/sql_server). ([See more information about forking a repository](https://docs.gitlab.com/ee/user/project/repository/forking_workflow.html).)\n\n[Create a self-managed GitLab Runner](https://docs.gitlab.com/runner/) on your Linux instance with your newly forked GitLab project.\n\nClone your newly forked project repository:\ngit clone https://gitlab.com/\u003Cusername>/sql_server.git\n\nGo to the “sql_server” project folder.\ncd sql_server\n\nRun the following command to change your git branch to staging:\ngit checkout staging\n\nConfigure the GitLab CI/CD pipeline environment variables.\n\nYour configuration will include [CI/CD variables](https://docs.gitlab.com/ee/ci/variables/#add-a-cicd-variable-to-a-project), [Liquibase properties](https://www.liquibase.com/blog/secure-database-developer-flow-using-gitlab-pipelines), database credentials, and the Liquibase Pro trial license key so you can use all the advanced Liquibase commands.\n\nFrom the main sql_server project, go to Settings → CI/CD\n\nUnder Variables, click Expand and add the following variables:\n\n![A CI/CD pipeline diagram](https://about.gitlab.com/images/blogimages/liquibasevariables.png){: .shadow.small.center}\n\n![A CI/CD pipeline diagram](https://about.gitlab.com/images/blogimages/liquibasevariables2.png){: .shadow.small.center}\n\n### Configure the self-managed GitLab runner\n\nFrom the main sql_server project, go to Settings → CI/CD\n\nExpand the runners section, click the pencil edit icon, and add the following runner tags (comma separated):\n\ndev_db,prod_db,test_db\n\nNote: Tags are created to help choose which runner will do the job. In this example, we are associating all tags to one runner. Learn more about [configuring runners](https://docs.gitlab.com/ee/ci/runners/configure_runners.html). \n\n### Make changes to the database\n\nEdit the changelog.sql file and add the following changeset after \n\n```\nliquibase formatted sql:\n-- changeset SteveZ:createTable_salesTableZ\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--rollback DROP TABLE salesTableZ\nAdd, commit, and push all new database changes.\ngit add changelog.sql\ngit commit -m “added changelog id and a create table salesTableZ changeset”\ngit push -u origin staging\n```\n\n### Merge the changes and run the pipeline\n\nLet’s merge the changes from branch staging → main to trigger the pipeline to run all jobs.\n\nClick Merge requests → New merge request\n\nSelect staging as Source branch and main as Target branch\n\nClick Compare branches and continue\n\nOn the next screen, click Create merge request\n\nClick Merge to finish merging the changes\n\n![A look at the merge request](https://about.gitlab.com/images/blogimages/2_Merge_Request1.png){: .shadow.small.center}\n\n![Another look at the merge requestt](https://about.gitlab.com/images/blogimages/3_Merge_Request2.png){: .shadow.small.center}\n\nOnce these steps are completed, the code is merged into main and the pipeline is triggered to run.\n\n![The pipeline is triggered](https://about.gitlab.com/images/blogimages/4_Merge_Request3.png){: .shadow.small.center}\n\nTo see the pipeline running, click Pipelines.\n\nTo view the pipeline progress, click the pipeline ID link. You can view each job’s log output by clicking on each job name.\n\n![The pipeline in progress](https://about.gitlab.com/images/blogimages/5_Pipeline_Progress.png){: .shadow.small.center}\n\nClicking into the build-job example:\n\nThe liquibase checks run command validates the SQL for any violations.\n\n```\n57Starting Liquibase at 22:19:14 (version 4.6.1 #98 built at 2021-11-04 20:16+0000)\n58Liquibase Version: 4.6.1\n59Liquibase Pro 4.6.1 by Liquibase licensed to customersuccess until Mon Jun 27 04:59:59 UTC 2022\n60Executing Quality Checks against changelog.sql\n61Executing all checks because a valid Liquibase Pro license was found!\n62Changesets Validated:\n63  ID: createTable_salesTableZ; Author: SteveZ; File path: changelog.sql\n64Checks run against each changeset:\n65  Warn on Detection of 'GRANT' Statements\n66  Warn on Detection of 'REVOKE' Statements\n67  Warn when 'DROP TABLE' detected\n68  Warn when 'DROP COLUMN' detected\n69  Check for specific patterns in sql (Short Name: SqlCreateRoleCheck)\n70  Warn when 'TRUNCATE TABLE' detected\n71  Warn on Detection of grant that contains 'WITH ADMIN OPTION'\n72Liquibase command 'checks run' was executed successfully.\n```\n\nThe liquibase update command deploys the changes. If you choose, you can view a full report of your changes in [Liquibase Hub](https://docs.liquibase.com/tools-integrations/liquibase-hub/setup.html). The update command also saves the deployment log output file as an artifact.\n\n```\n227Starting Liquibase at 22:19:34 (version 4.6.1 #98 built at 2021-11-04 20:16+0000)\n228Liquibase Version: 4.6.1\n229Liquibase Pro 4.6.1 by Liquibase licensed to customersuccess until Mon Jun 27 04:59:59 UTC 2022\n230----------------------------------------------------------------------\n231View a report of this operation at https://hub.liquibase.com/r/I7ens13ooM\n232* IMPORTANT: New users of Hub first need to Sign In to your account\n233with the one-time password sent to your email, which also serves as\n234your username.\n235----------------------------------------------------------------------\n236Logs saved to /home/gitlab-runner/builds/3-UvD4aX/0/szandany/sql_server/build-job_405710044.log\n237Liquibase command 'update' was executed successfully.\n```\n\nHere’s what your Liquibase Hub report will look like:\n\n![The hub report, part one](https://about.gitlab.com/images/blogimages/6_LiquibaseHub_Report.png){: .shadow.small.center}\n\n![The hub report, part twot](https://about.gitlab.com/images/blogimages/7_LiquibaseHub_Report.png){: .shadow.small.center}\n\nThe Liquibase history command will show what changes are currently in the database.\n\n```\n255Starting Liquibase at 22:19:40 (version 4.6.1 #98 built at 2021-11-04 20:16+0000)\n256Liquibase Version: 4.6.1\n257Liquibase Pro 4.6.1 by Liquibase licensed to customersuccess until Mon Jun 27 04:59:59 UTC 2022\n258Liquibase History for jdbc: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 JDBC Driver for SQL Server;applicationIntent=readwrite;\n259- Database updated at 11/9/21, 10:19 PM. Applied 1 changeset(s), DeploymentId: 6496372605\n260  liquibase-internal::1636496372758::liquibase\n261- Database updated at 11/9/21, 10:19 PM. Applied 1 changeset(s), DeploymentId: 6496375151\n262  changelog.sql::createTable_salesTableZ::SteveZ\n263Liquibase command 'history' was executed successfully.\n```\n\n### Clicking into the DEV->QA job example from your pipeline\n\nWe run the liquibase diff command to compare the DEV and QA databases. This helps detect any drift between the databases.\n\nNotice in the log output that there are some unexpected changes: \n\ntable named bad_table\n\nprocedure named bad_proc\n\n![The diff report](https://about.gitlab.com/images/blogimages/8_LiquibaseDiff_Report.png){: .shadow.small.center}\n\nBy using the [Liquibase Pro trial license key](https://www.liquibase.com/trial), you’re able to detect any stored logic objects included in the diff report. Liquibase Pro also allows you to generate a parsable JSON output file and save it as an artifact for later use.\n\n```\n137Starting Liquibase at 22:21:10 (version 4.6.1 #98 built at 2021-11-04 20:16+0000)\n138Liquibase Version: 4.6.1\n139Liquibase Pro 4.6.1 by Liquibase licensed to customersuccess until Mon Jun 27 04:59:59 UTC 2022\n140Output saved to /home/gitlab-runner/builds/3-UvD4aX/0/szandany/sql_server/diff_between_DEV_QA.json\n141Liquibase command 'diff' was executed successfully.\n```\n\nJSON artifact output file example:\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\nNote that the [Liquibase diffChangelog](https://docs.liquibase.com/commands/diffchangelog.html) can help any baseline environments that have drifted. \n\nClicking into the snapshot PROD job example, the snapshot file contains all the current schema changes represented in a JSON file. You can obtain the PROD database snapshot file to compare two states of the same database to protect against malware with drift detection.\n\n```\n58Starting Liquibase at 22:21:32 (version 4.6.1 #98 built at 2021-11-04 20:16+0000)\n59Liquibase Version: 4.6.1\n60Liquibase Pro 4.6.1 by Liquibase licensed to customersuccess until Mon Jun 27 04:59:59 UTC 2022\n61Output saved to /home/gitlab-runner/builds/3-UvD4aX/0/szandany/sql_server/snapshot_PROD.json\n62Liquibase command 'snapshot' was executed successfully. \n64Uploading artifacts for successful job00:01\n70Cleaning up project directory and file based variables00:00\n72Job succeeded\n```\n\n### Congratulations! The pipeline ran successfully.\n\nIf all the jobs are successful, you’ll see a green checkmark right next to each one.\n\nHere’s what your database changes will look like with a database SQL query tool.\n\n![The database](https://about.gitlab.com/images/blogimages/9_Database_Changes_SQL_Query_Tool.png){: .shadow.small.center}\n\n## Summing it up\n\nYou’ve now successfully run Liquibase in a GitLab pipeline to enable true CI/CD for the database. You can easily keep adding more changes to the database by adding more Liquibase changesets to the changelog, commit them to GitLab version control, and repeat the merge request process described in this tutorial to add the changes. \n\nStill have questions or want support integrating Liquibase with your Gitlab CI/CD Pipeline? Our team of database DevOps experts is happy to help! \n\n[Contact Liquibase](https://www.liquibase.com/contact)\n\n[Contact GitLab](/sales/)\n\nContact a [certified GitLab channel partner](https://www.google.com/url?q=https://partners.gitlab.com/English/directory/&sa=D&source=docs&ust=1641393355697069&usg=AOvVaw0R5mPukwMBR2dKsn3eQzqp)\n\nContact a [Liquibase channel partner](https://www.liquibase.com/partners)\n\nOther useful links: \n\n[Gitlab CI/CD setup Liquibase documentation](https://docs.liquibase.com/concepts/installation/setup-gitlab-cicd.html)\n\n[GitLab - Liquibase repository](https://gitlab.com/gitlab-com/alliances/liquibase/sandbox-projects/liquibasegitlabcicd/-/blob/master/README.md) \n\nGet a [speedy, secure database developer flow](https://www.liquibase.com/blog/secure-database-developer-flow-using-gitlab-pipelines) using GitLab pipelines & Liquibase\n\n_Author Tsvi Zandany is a Senior Solutions Architect at Liquibase_\n",[231,9,683],{"slug":1882,"featured":6,"template":686},"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":1888,"_dir":244,"_draft":6,"_partial":6,"_locale":7,"seo":1889,"content":1894,"config":1899,"_id":1901,"_type":14,"title":1902,"_source":16,"_file":1903,"_stem":1904,"_extension":19},"/en-us/blog/how-to-continously-test-web-apps-apis-with-hurl-and-gitlab-ci-cd",{"title":1890,"description":1891,"ogTitle":1890,"ogDescription":1891,"noIndex":6,"ogImage":1813,"ogUrl":1892,"ogSiteName":673,"ogType":674,"canonicalUrls":1892,"schema":1893},"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":1890,"description":1891,"authors":1895,"heroImage":1813,"date":1896,"body":1897,"category":704,"tags":1898},[1239],"2022-12-14","\nTesting websites, web applications, or generally everything reachable with the HTTP protocol, can be a challenging exercise. Thanks to tools like `curl` and `jq`, [DevOps workflows have become more productive](/blog/devops-workflows-json-format-jq-ci-cd-lint/) and even simple monitoring tasks can be automated with CI/CD pipeline schedules. Sometimes, use cases require specialized tooling with custom HTTP headers, parsing expected responses, and building end-to-end test pipelines. Stressful incidents also need good and fast tools that help analyze the root cause and quickly mitigate and fix problems.\n\n[Hurl](https://hurl.dev) is an open-source project developed and maintained by Orange, and uses libcurl from curl to provide HTTP test capabilities. It aims to tackle complex HTTP test challenges by providing a simple plain text configuration to describe HTTP requests. It can chain requests, capture values, and evaluate queries on headers and body responses. So far, so good: Hurl does not only support fetching data, it can be used to test HTTP sessions and XML (SOAP) and JSON (REST) APIs.\n\n## Getting Started\n\nHurl comes in various package formats to [install](https://hurl.dev/docs/installation.html). On macOS, a Homebrew package is available.\n\n```sh\n$ brew install hurl\n```\n\n## First steps with Hurl\n\nHurl proposes to start with the configuration file format first, which is a great way to learn the syntax step by step. The following example creates a new `gitlab-contribute.hurl` configuration file that will do two things: execute a GET HTTP request on `https://about.gitlab.com/community/contribute/` and check whether its HTTP response contains the HTTP protocol `2` and status code `200` (OK).\n\n```sh\n$ vim gitlab-contribute.hurl\n\nGET https://about.gitlab.com/community/contribute/\n\nHTTP/2 200\n$ hurl --test gitlab-contribute.hurl\ngitlab-contribute.hurl: Running [1/1]\ngitlab-contribute.hurl: Success (1 request(s) in 413 ms)\n--------------------------------------------------------------------------------\nExecuted files:  1\nSucceeded files: 1 (100.0%)\nFailed files:    0 (0.0%)\nDuration:        415 ms\n```\n\nInstead of creating configuration files, you can also use the `echo “...” | hurl` command pattern. The following command tests against about.gitlab.com and checks whether the HTTP response protocol is 1.1 and the status is OK (200). The two newline characters `\\n` are required for separation.\n\n```sh\n$ echo \"GET https://about.gitlab.com\\n\\nHTTP/1.1 200\" | hurl --test\n```\n\n![hurl CLI run against about.gitlab.com, failed request](https://about.gitlab.com/images/blogimages/hurl-continuous-website-testing/hurl_assert_failure.png)\n\nThe command failed, and it says that the response protocol version is actually `2`. Let's adjust the test run to expect `HTTP/2`:\n\n```sh\necho \"GET https://about.gitlab.com\\n\\nHTTP/2 200\" | hurl --test\n```\n## Asserting HTTP responses\n\nHurl allows defining [assertions](https://hurl.dev/docs/asserting-response.html) to control when the tests fail. These can be defined for different HTTP response types:\n\n- Expected HTTP protocol version and status\n- Headers\n- Body\n\nThe configuration language allows users to define queries with predicates that allow to compare, chain, and execute different assertions.\n\nThis is the easiest way to verify that the HTTP response contains what is expected to be a string or sentence on the website, for example. If the string does not exist, this can indicate that it was changed unexpectedly, or that the website is down. Let's revisit the example with testing GET https://about.gitlab.com/community/contribute/ and add an expected string `Everyone can contribute` as a new assertion, `body contains \u003Cstring>` is the expected configuration syntax for [body asserts](https://hurl.dev/docs/asserting-response.html#body-assert).\n\n```sh\n$ vim gitlab-contribute.hurl\n\nGET https://about.gitlab.com/community/contribute/\n\nHTTP/2 200\n\n[Asserts]\nbody contains \"Everyone should contribute\"\n\n$ hurl --test gitlab-contribute.hurl\n```\n\n**Exercise:** Fix the test by updating the asserts line to `Everyone can contribute` and run Hurl again.\n\n### Asserting responses: JSON and XML\n\n[JSONPath](https://hurl.dev/docs/asserting-response.html#jsonpath-assert) automatically parses the JSON response (a built-in `jq with curl` parser so to speak), and allows users to compare the value to verify the asserts (more below). The XML format can be found in an [RSS feed on about.gitlab.com](https://about.gitlab.com/atom.xml) and parsed using [XPath](https://hurl.dev/docs/asserting-response.html#xpath-assert). The following example from `atom.xml` should be verified with Hurl:\n\n```xml\n\u003Cfeed xmlns=\"http://www.w3.org/2005/Atom\">\n\u003Ctitle>GitLab\u003C/title>\n\u003Cid>https://about.gitlab.com/blog\u003C/id>\n\u003Clink href=\"https://about.gitlab.com/blog/\"/>\n\u003Cupdated>2022-11-21T00:00:00+00:00\u003C/updated>\n\u003Cauthor>\n\u003Cname>The GitLab Team\u003C/name>\n\u003C/author>\n\u003Centry>\n...\n\u003C/entry>\n\u003Centry>\n...\n\u003C/entry>\n\u003Centry>\n…\n```\n\nIt is important to note that XML namespaces need to be specified for parsing. Hurl allows users to replace the first default namespace with the `_` character to avoid adding `http://www.w3.org/2005/Atom` everywhere, the XPath is now shorter with `string(//_:feed/_:entry)` to get a list of all entries. This value is captured in the `entries` variable, which can be compared to match a specific string, `GitLab` in this example. Additionally, the feed id and author name is checked.\n\n```\n$ vim gitlab-rss.hurl\n\nGET https://about.gitlab.com/atom.xml\n\nHTTP/2 200\n\n[Captures]\nentries: xpath \"string(//_:feed/_:entry)\"\n\n[Asserts]\nvariable \"entries\" matches \"GitLab\"\n\nxpath \"string(//_:feed/_:id)\" == \"https://about.gitlab.com/blog\"\nxpath \"string(//_:feed/_:author/_:name)\" == \"The GitLab Team\"\n\n$ hurl –test gitlab-rss.hurl\n```\n\nHurl allows users to capture the value from responses into [variables](https://hurl.dev/docs/templates.html#variables) that can be used later. This method can also be helpful to model end-to-end testing workflows: First, check the website health status and retrieve a CSRF token, and then try to log into the website by sending the token again.\n\nREST APIs that are expected to always return a specified field, or monitoring a website health state [becomes a breeze using Hurl](https://hurl.dev/docs/tutorial/chaining-requests.html#test-rest-api).\n\n## Use Hurl in GitLab CI/CD jobs\n\nThe easiest way to integrate Hurl into GitLab CI/CD is to use the official container image. The Hurl project provides a [container image on Docker Hub](https://hub.docker.com/r/orangeopensource/hurl), which did not work in CI/CD at first glance. After talking with the maintainers, the [entrypoint override](https://docs.gitlab.com/ee/ci/docker/using_docker_images.html#override-the-entrypoint-of-an-image) was identified as a solution for using the image in GitLab CI/CD. Note that the Alpine based image uses the libcurl library that does not support HTTP/2 yet - the test results are different to a Debian base image (follow [this issue report](https://github.com/Orange-OpenSource/hurl/issues/1082) for the problem analysis).\n\nThe following example is kept short to run the container image, override the entrypoint, and run Hurl with passing in the test using the `echo` CLI command.\n\n```yaml\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\nThe Hurl test report is printed into the CI/CD job trace log, and returns succesfully.\n\n```sh\n$ echo -e \"GET https://about.gitlab.com/community/contribute/\\n\\nHTTP/1.1 200\" | hurl --test --color\n-: Running [1/1]\n-: Success (1 request(s) in 280 ms)\n--------------------------------------------------------------------------------\nExecuted files:  1\nSucceeded files: 1 (100.0%)\nFailed files:    0 (0.0%)\nDuration:        283 ms\nCleaning up project directory and file based variables\n00:00\nJob succeeded\n```\n\nThe next iteration is to create a CI/CD job template that provides generic attributes, and allows users to dynamically run the job with an environment variable called `HURL_URL`.\n\n```yaml\n# Hurl job template\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\nRunning GET commands with expected HTTP results is not the only use case, and the Hurl maintainers thought about this already. The next section explains how to create a custom container image; you can skip to the [DevSecOps workflows](#devSecOps-workflows-with-hurl) section to learn more about efficient Hurl configuration use cases.\n\n### Custom container image with Hurl\n\nMaintaining and building a custom container image adds more work, but also helps with avoiding running unknown container images in CI/CD pipelines. The latter is often a requirement for compliance and security. _Since the Hurl Debian package supports detecting HTTP/2 as a protocol, this blog post will focus on building a custom image, and run all tests using this image. If you plan on using the upstream container image, make sure to review the test configuration for the HTTP protocol version detection._\n\nThe Hurl documentation provides multiple ways to install Hurl. For this example, Debian 11 Bullseye (slim) is used. Hurl comes with a package dependency on `libxml2` which can either be installed manually with then running the `dpkg` command, or by using `apt install` to install a local package and automatically resolve the dependencies.\n\nThe following CI/CD example uses a job template which defines the Hurl version as environment variable to avoid repetitive use, and downloads and installs the Hurl Debian package. The `hurl-gitlab-com` job extends the CI/CD job template and runs a one-line test against `https://gitlab.com` and expects to return `HTTP/2` as HTTP protocol version, and `200` as status.\n\n```yaml\n# CI/CD job template\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\nThe next section describes how to optimize the CI/CD pipelines for more efficient schedules and runs to monitor websites and not waste too many resources and CI/CD minutes. You can also skip it and [scroll down to more advanced Hurl examples in GitLab CI/CD](#devsecops-workflows-with-hurl).\n\n### CI/CD efficiency: Hurl container image\n\nThe installation steps for Hurl, and its dependencies, can waste resources and increase the pipeline job runtime every time. To make the CI/CD pipelines more efficient, we want to use a container image that already provides Hurl pre-installed. The following steps are required for creating a container image:\n\n- Use Debian 11 Slim (FROM).\n- Install dependencies to download Hurl (`curl`, `ca-certificates`). `jq` is installed for convenience to access it from CI/CD commands when needed later.\n- Download the Hurl Debian package, and use `apt install` to install its dependencies automatically.\n- Clear the apt lists cache to enforce apt update again, and avoid security issues.\n- Hurl is installed into the PATH, specify the default command being run. This allows running the container without having to specify a command.\n\nThe steps to install the packages are separated for better readability; an optimization for the `docker-build` job can happen by chaining the `RUN` commands into one long command.\n\n`Dockerfile`\n```\nFROM debian:11-slim\n\nENV DEBIAN_FRONTEND noninteractive\n\nARG HURL_VERSION=1.8.0\n\nRUN apt update && apt install -y curl jq ca-certificates\nRUN curl -LO \"https://github.com/Orange-OpenSource/hurl/releases/download/${HURL_VERSION}/hurl_${HURL_VERSION}_amd64.deb\"\n# Use apt install to determine package dependencies instead of dpkg\nRUN apt -y install \"./hurl_${HURL_VERSION}_amd64.deb\"\nRUN rm -rf /var/lib/apt/lists/*\n\nCMD [\"hurl\"]\n```\n\nNote that the `HURL_VERSION` variable can be overridden by passing the variable and value into the container build job later. It is intentionally not using an automated script that always uses the [latest release](https://github.com/Orange-OpenSource/hurl/releases) to avoid breaking the behavior, and enforces a controlled upgrade cycle for container images in production.\n\nOn GitLab.com SaaS, you can include the `Docker.gitlab-ci.yml` CI/CD template which will automatically detect the `Dockerfile` file and start building the image using the shared runners, and push it to the [GitLab container registry](https://docs.gitlab.com/ee/user/packages/container_registry/). For self-managed instances or own runners on GitLab.com SaaS, it is recommended to decide whether to use and setup [Docker-in-Docker](https://docs.gitlab.com/ee/ci/docker/using_docker_build.html) or [Kaniko](https://docs.gitlab.com/ee/ci/docker/using_kaniko.html), Podman, or other container image build tools.\n\n```yaml\ninclude:\n  - template: Docker.gitlab-ci.yml\n```\n\nTo avoid running the Docker image build job every time, the job override definition specifies to [run it manually](https://docs.gitlab.com/ee/ci/yaml/#when). You can also use rules to [choose when to run the job](https://docs.gitlab.com/ee/ci/jobs/job_control.html), only when a Git tag is pushed for example.\n\n```yaml\ninclude:\n  - template: Docker.gitlab-ci.yml\n\n# Change Docker build to manual non-blocking\ndocker-build:\n  rules:\n    - if: '$CI_COMMIT_REF_NAME == $CI_DEFAULT_BRANCH'\n      when: manual\n      allow_failure: true\n```\n\nOnce the container image is pushed to the registry, navigate into `Packages and Registries > Container Registries` and inspect the tagged image. Copy the image path for the latest tagged version and use it for the `image` attribute in the CI/CD job configuration.\n\n### Hurl container image in GitLab CI/CD example\n\nThe full example uses the previously built container image, and specifies the default `HURL_URL` variable. This can later be overridden by job definitions.\n\n_Please note that the image URL `registry.gitlab.com/everyonecancontribute/dev/hurl-playground:latest` is only used for demo purposes and not actively maintained or updated._\n\n```yaml\ninclude:\n  - template: Docker.gitlab-ci.yml\n\n# Change Docker build to manual non-blocking\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.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\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\nThe CI/CD configuration can further be optimized:\n\n- Create job templates that execute the same scripts and only differ in the `HURL_URL` variable.\n- Use Hurl configuration files that allow specifying variables on the CLI or as environment variables. More on this in the next section.\n\n## DevSecOps workflows with Hurl\n\nHurl allows users to describe HTTP instructions in a configuration file with the `.hurl` suffix. You can add the configuration files to Git, and review and approve changes in merge requests - with the changes run in CI/CD and reporting back any failures before merging.\n\nInspect the `use-cases/` directory in the [example project](https://gitlab.com/everyonecancontribute/dev/hurl-playground), and fork it to make changes and commit and run the CI/CD pipelines and reports. You can also clone the project and run the `tree` command in the terminal.\n\n```sh\n$ tree use-cases\nuse-cases\n├── dnsmichi.at.hurl\n├── gitlab-com-api.hurl\n├── gitlab-contribute.hurl\n└── hackernews.hurl\n```\n\nHurl supports the glob option which collects all configuration files matching a specific pattern.\n\n![Hurl configuration file run](https://about.gitlab.com/images/blogimages/hurl-continuous-website-testing/hurl_multiple_config_files_run.png)\n\n### Chaining requests\n\nSimilar to CI/CD pipelines, jobs, and stages, testing HTTP endpoints with Hurl can require multiple steps. First, ping the website for being reachable, and then try parsing expected results. Separating the requirements into two steps helps to analyze errors.\n\n- HTTP endpoint reachable, but expected string not in response - static website was changed, REST API misses a field, etc.\n- HTTP endpoint is unreachable, don’t try to understand why the follow-up tests fail.\n\nThe following example first sends a ping probe to the dev instance, and a check towards the production environment in the second request.\n\n```sh\n$ vim use-cases/everyonecancontribute-com.hurl\n\nGET https://everyonecancontribute.dev\n\nHTTP/2 200\n\nGET https://everyonecancontribute.com\n\nHTTP/2 200\n$ hurl --test use-cases/everyonecancontribute-com.hurl\n```\n\nIn this scenario, the TLS certificate of the dev instance expired, and Hurl halts the test immediately.\n\n![Hurl chained requests, failing the first test with TLS certificate problems](https://about.gitlab.com/images/blogimages/hurl-continuous-website-testing/hurl_chained_request_fail.png)\n\n### Hurl reports as JUnit test reports\n\nTreat website monitoring and web app tests as unit and end-to-end tests. The Hurl developers thought of that too - the CLI command provides different output options for the report: `--report-junit \u003Coutputpath>` integrates with [GitLab JUnit report](https://docs.gitlab.com/ee/ci/testing/unit_test_reports.html) support into merge requests and pipeline views.\n\nThe following configuration generates a JUnit report file into the value of the `HURL_JUNIT_REPORT` variable. It exists to avoid typing the path three times. The Hurl tests are run from the `use-cases/` directory using a glob pattern.\n\n```yaml\n# Hurl job template\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 GitLab CI/CD\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\nThe JUnit format returned by Hurl 1.8.0 defines the `id` attribute, but the GitLab JUnit integration expects the `name` attribute to be present. While writing this blog post, [the problem was discussed](https://github.com/Orange-OpenSource/hurl/issues/1067#issuecomment-1343264751) with the maintainers, and [the `name` attribute was implemented](https://github.com/Orange-OpenSource/hurl/issues/1078) and will be available in future releases. As a workaround with Hurl 1.8.0, the CI/CD [after_script](https://docs.gitlab.com/ee/ci/yaml/#after_script) section uses `sed` to replace the attributes after generating the report.\n\nThe [following example](https://gitlab.com/everyonecancontribute/dev/hurl-playground/-/merge_requests/10) fails on purpose with checking a different HTTP protocol version.\n\n```\nGET https://opsindev.news\n\n# This will fail on purpose\nHTTP/1.1 200\n\n[Asserts]\nbody contains \"Michael Friedrich\"\n```\n\n![Hurl test report in JUnit format integrated into GitLab](https://about.gitlab.com/images/blogimages/hurl-continuous-website-testing/hurl_gitlab_junit_integration_merge_request_widget_overlay.png)\n\nOnce the JUnit integration with Hurl tests from a glob pattern work, you can continue adding new `.hurl` configuration files to the GitLab repository and start testing in MRs, which will require review and approval workflows for production then.\n\n### Web review apps\n\nWebsite monitoring is only one aspect of using Hurl: Testing web applications deployed in review environments in the cloud, and in cloud-native clusters provides a native integration into [DevSecOps](https://about.gitlab.com/topics/devsecops/) workflows. The CI/CD pipelines will fail when Hurl tests are failing, and more insights are provided using merge request widgets reports.\n\n[Cloud Seed](https://docs.gitlab.com/ee/cloud_seed/index.html) provides the ability to deploy a web application to a major cloud provider, for example Google Cloud. After the deployment is successful, additional CI/CD jobs can be configured that verify that the deployed web app version does not introduce a regression, and provides all required data elements, API endpoints, etc. A similar workflow can be achieved by using review app environments with [webservers (Nginx, etc.), Docker, AWS, and Kubernetes](https://docs.gitlab.com/ee/ci/review_apps/#review-apps-examples). The review app [environment URL](https://docs.gitlab.com/ee/ci/environments/#create-a-dynamic-environment) is important for instrumenting the Hurl tests dynamically. The CI/CD variable [`CI_ENVIRONMENT_URL`](https://docs.gitlab.com/ee/ci/variables/predefined_variables.html) is available when `environment:url` is specified in the review app configuration.\n\nThe following example tests the review app for [this blog post when written in a merge request](https://gitlab.com/gitlab-com/www-gitlab-com/-/merge_requests/115548):\n\n```yaml\n# Test review apps with hurl for this blog post.\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\nThe environment is specified in the [.review-environment job template](https://gitlab.com/gitlab-com/www-gitlab-com/-/blob/91d6fd72a424a3d913e79ebc2aefb23bbab85863/.gitlab-ci.yml#L332) and used to [deploy the website review job](https://gitlab.com/gitlab-com/www-gitlab-com/-/blob/91d6fd72a424a3d913e79ebc2aefb23bbab85863/.gitlab-ci.yml#L532). The relevant configuration snippet is shown here:\n\n```yaml\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\nThe deployment of the www-gitlab-com project [uses buckets in Google Cloud](https://gitlab.com/gitlab-com/www-gitlab-com/-/blob/91d6fd72a424a3d913e79ebc2aefb23bbab85863/scripts/deploy) that serve the website content in the review app. There are different types of web applications that require different deployment methods - as long as the environment URL variable is available in CI/CD and the deployment URL is accessible from the GitLab Runner executing the CI/CD job, you can continously test web apps with Hurl!\n\n![Hurl test in GitLab CI/CD for review app environments](https://about.gitlab.com/images/blogimages/hurl-continuous-website-testing/hurl_gitlab_cicd_review_app_environment_tests_www-gitlab-com.png)\n\n## Development tips\n\nUse the [`--verbose` parameter](https://hurl.dev/docs/tutorial/debug-tips.html) to see the full request and response flow. Hurl also provides tips which `curl` command could be run to fetch more data. This can be helpful when starting to use or develop a new REST API, or learning to understand the JSON structure of HTTP responses. Chaining the `curl` command with `jq` (the `curl ... | jq` pattern) can still be helpful to fetch data, and build the HTTP tests in a second terminal or editor window.\n\n```sh\n$ curl -s 'https://gitlab.com/api/v4/projects' | jq\n$ curl -s 'https://gitlab.com/api/v4/projects' | jq -c '.[]' | jq\n\n{\"id\":41375401,\"description\":\"An example project for a GitLab pipeline.\",\"name\":\"Calculator\",\"name_with_namespace\":\"Iva Tee / Calculator\",\"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 Tee\",\"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\nThe GitLab projects API returns an array of elements, where we can inspect the `id` and `name` attributes for a simple test - the first element’s name must not be empty, the second element’s id needs to be greater than 0.\n\n```sh\n$ vim gitlab-com-api.hurl\n\nGET https://gitlab.com/api/v4/projects\n\nHTTP/2 200\n\n[Asserts]\njsonpath \"$[0].name\" != \"\"\njsonpath \"$[1].id\" > 0\n\n$ hurl --test gitlab-com-api.hurl\n\ngitlab-com-api.hurl: Running [1/1]\ngitlab-com-api.hurl: Success (1 request(s) in 728 ms)\n--------------------------------------------------------------------------------\nExecuted files:  1\nSucceeded files: 1 (100.0%)\nFailed files:    0 (0.0%)\nDuration:        730 ms\n```\n\n## More use cases\n\n- Work with HTTP sessions and [cookies](https://hurl.dev/docs/request.html#cookies), test [forms with parameters](https://hurl.dev/docs/request.html#form-parameters).\n- Review existing API tests of your applications.\n- Build advanced chained workflows with GET, POST, PUT, DELETE, and more HTTP methods.\n- Integrate simple ping/HTTP monitoring health checks into the DevSecOps Platform using alerts and incident management.\n\nIf the Hurl checks cannot be integrated directly inside the project where the application is developed and deployed, another idea could be to create a standalone GitLab project that has CI/CD pipeline schedules enabled. It can continuously run the Hurl tests, and parse the reports or trigger an event when the pipeline is failing, and [create an alert](https://docs.gitlab.com/ee/operations/incident_management/alerts.html) by sending a JSON payload from the Hurl results to the [HTTP endpoint](https://docs.gitlab.com/ee/operations/incident_management/integrations.html#single-http-endpoint). Developers can send MRs to update the Hurl tests, and maintainers review and approve the new test suites being rolled out into production. Alternatively, move the complete CI/CD configuration into a group/project with different permissions, and specify the CI/CD configuration as remote URL in the web application project. This compliance level helps to control who can make changes to important tests and CI/CD configuration.\n\nHurl supports `--json` as parameter to only return the JSON formatted test result and build own custom reports and integrations.\n\n```sh\n$ echo -e \"GET https://about.gitlab.com/teamops/\\n\\nHTTP/2 200\" | hurl --json | jq\n```\n\nFor folks in DevRel, monitoring certain websites for keywords or checking APIs whether values increase a certain threshold can be interesting. Here is an example for monitoring Hacker News using the Algolia search API, inspired by the [Zapier integration used for GitLab Slack](/handbook/marketing/developer-relations/workflows-tools/zapier/#zaps-for-hacker-news). The `QueryStringParams` section allows users to define the query parameters as a readable list, which is easier to modify. The `jsonpath` checks searches for the `hits` key and its count being zero (not on the Hacker News front page means OK in this example).\n\n```\n$ vim hackernews.hurl\n\nGET https://hn.algolia.com/api/v1/search\n[QueryStringParams]\nquery: gitlab\n#query: hurl\ntags: front_page\n\nHTTP/2 200\n\n[Asserts]\njsonpath \"$.hits\" count == 0\n\n$ hurl --test hackernews.hurl\n```\n\n## Limitations\n\nHurl works great for testing websites and web applications that serve static content, and by sending different HTTP request types, data, etc., and ensuring that responses match expectations. Compared to other end-to-end testing solutions (Selenium, etc.), Hurl does not provide a JavaScript engine and only can parse the raw DOM or JSON response. It does not support a DOM managed and rendered by JavaScript front-end frameworks. UI integration tests also need to be performed with different tools, similar to full end-to-end test workflows. Other examples are [accessibility testing](https://docs.gitlab.com/ee/ci/testing/accessibility_testing.html) and [browser performance testing](https://docs.gitlab.com/ee/ci/testing/browser_performance_testing.html). If you are curious how end-to-end testing is done for GitLab, the product, peek into the [development documentation](https://docs.gitlab.com/ee/development/testing_guide/end_to_end/).\n\n## Conclusion\n\nHurl provides an easy way to test HTTP endpoints (such as websites and APIs) in a fast and reliable way. The CLI commands can be integrated into CI/CD workflows, and the configuration syntax and files provide a single source of truth for everything. Additional support for JUnit report formats ensure that website testing is fully integrated into the [DevSecOps](https://about.gitlab.com/topics/devsecops/) platform, and increases visibility and extensibility with automating tests, and monitoring. There are known limitations with dynamic JavaScript websites and advanced UI/end-to-end testing workflows.\n\nHurl is open source, [created and maintained by Orange](https://opensource.orange.com/en/open-source-orange/), and written in Rust. This blog post inspired contributions to the [Debian/Ubuntu installation documentation](https://github.com/Orange-OpenSource/hurl/pull/1084) and [default issue templates](https://github.com/Orange-OpenSource/hurl/pull/1083).\n\n**Tip:** Practice using Hurl on the command line, and remember it when the next production incident shows a strange API behavior with POST requests.\n\nThanks to [Lee Tickett](/company/team/#leetickett-gitlab) who inspired me to test Hurl in GitLab CI/CD and write this blog post after seeing huge interest in a [Twitter share](https://twitter.com/dnsmichi/status/1595820546062778369).\n\nCover image by [Aaron Burden](https://unsplash.com/@aaronburden) on [Unsplash](https://unsplash.com)\n{: .note}\n",[838,9,728],{"slug":1900,"featured":6,"template":686},"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":1906,"_dir":244,"_draft":6,"_partial":6,"_locale":7,"seo":1907,"content":1913,"config":1920,"_id":1922,"_type":14,"title":1923,"_source":16,"_file":1924,"_stem":1925,"_extension":19},"/en-us/blog/how-to-create-a-ci-cd-pipeline-with-auto-deploy-to-kubernetes-using-gitlab",{"title":1908,"description":1909,"ogTitle":1908,"ogDescription":1909,"noIndex":6,"ogImage":1910,"ogUrl":1911,"ogSiteName":673,"ogType":674,"canonicalUrls":1911,"schema":1912},"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":1914,"description":1909,"authors":1915,"heroImage":1910,"date":1917,"body":1918,"category":704,"tags":1919},"How to create a CI/CD pipeline with Auto Deploy to Kubernetes using GitLab and Helm",[1916],"Sergey Nuzhdin","2017-09-21","Recently, I started working on a few Golang [microservices](/topics/microservices/). I decided to try GitLab’s caching and split the job into multiple steps for better feedback in the UI.\n\n\u003C!-- more -->\n\nSince my previous posts[[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/)] about [CI/CD](/topics/ci-cd/), a lot has changed. I started using Helm charts for packaging applications, and stopped using docker-in-docker in gitlab-runner.\n\nHere are a few of the main changes to my `.gitlab-ci.yml` file since my previous post:\n\n* no docker-in-docker\n* using cache for packages instead of a prebuilt image with dependencies\n* splitting everything into multiple steps\n* autodeploy to staging environment using Helm, a package manager for Kubernetes\n\n### Building Golang image\n\nSince Golang is very strict about the location of the project, we need to make some adjustments to the CI job. This is done in the `before_script` block. Simply create needed directories and link source code in there. Assuming that the official repository of the project is `gitlab.example.com/librerio/libr_files` it should look like this.\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\nWith this in place, we can install dependencies and build our binaries. To avoid the download of all packages on each build we need to configure caching. Due to the strange caching rules of GitLab, we need to add vendor directory to both cache and artifacts. Cache will give us an ability to use it between build jobs and artifacts will allow us to use it inside the same job.\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\nBuild step didn’t change, it’s still about building the binary. I add binary to artifacts.\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###  Test stage\n\nTo run golang tests with coverage reports I’m using the variation of [this shell script](https://github.com/mlafeldt/chef-runner/blob/v0.7.0/script/coverage). It runs all tests in project subdirectories and creates a [coverage report](/blog/publish-code-coverage-report-with-gitlab-pages/). I changed it a bit before putting into a gist. I exclude vendor directory from tests.\n\n* coverage regexp for gitlab-ci: `^total:\\s*\\(statements\\)\\s*(\\d+.\\d+\\%)`\n\n### Deploy stage\n\nI don’t use native GitLab’s integration with Kubernetes.\n\nFirst I thought about creating Kubernetes secrets and mounting it to the gitlab-runner pod. But it’s very complicated. You need to upgrade deployment every time you want to add new Kubernetes cluster configurations. So I’m using GitLab’s CI/CD variables with base64 encoded Kubernetes config. Each project can have any number of configurations. The process is easy – create base64 string from the configuration file and copy it to the clipboard. After this, put it into `kube_config` variable (name it whatever you like).\n\n`cat ~/.kube/config | base64 | pbcopy`\n\nIf you do not own a full GitLab installation, consider creating a Kubernetes user with restricted permissions.\n\nThen on the deploy stage, we can decode this variable back into the file and use it with kubectl.\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\nDeploy stage also covers the case when you have several versions of the same application.\n\nFor example, you have two versions of API: v1.0 and v1.1. All you need to do is set `appVersion` in Chart.yaml file. Build system will check API version and either deploy or upgrade needed release.\n\n```\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```\n\n### tl;dr\n\n```\nHere is complete `.gitlab-ci.yaml` file for reference.\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_[How to create a CI/CD pipeline with Auto Deploy to Kubernetes using GitLab and Helm](http://blog.lwolf.org/post/how-to-create-ci-cd-pipeline-with-autodeploy-k8s-gitlab-helm/) was originally published on Lwolfs Blog._\n\nPhoto by C Chapman on [Unsplash](https://unsplash.com/)",[9,683,729,992],{"slug":1921,"featured":6,"template":686},"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":1927,"_dir":244,"_draft":6,"_partial":6,"_locale":7,"seo":1928,"content":1933,"config":1939,"_id":1941,"_type":14,"title":1942,"_source":16,"_file":1943,"_stem":1944,"_extension":19},"/en-us/blog/how-to-keep-up-with-ci-cd-best-practices",{"title":1929,"description":1930,"ogTitle":1929,"ogDescription":1930,"noIndex":6,"ogImage":851,"ogUrl":1931,"ogSiteName":673,"ogType":674,"canonicalUrls":1931,"schema":1932},"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://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\":\"Valerie Silverthorne\"}],\n        \"datePublished\": \"2022-02-03\",\n      }",{"title":1929,"description":1930,"authors":1934,"heroImage":851,"date":1936,"body":1937,"category":836,"tags":1938},[1935],"Valerie Silverthorne","2022-02-03","\nContinuous integration and continuous delivery (CI/CD) are at the heart of any successful DevOps practice. Teams wanting to achieve modern software development must keep up with [CI/CD](/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 the meaning of CI/CD?\n\nIt’s a tech process, it’s a mindset, it’s a series of steps… CI/CD is all of those things. Put simply, CI enables DevOps 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 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](/resources/whitepaper-forrester-manage-your-toolchain/) required to manage the lifecycle.\n\n## What are the best practices for CICD?\n\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 DevOps is to get software to users more quickly than traditional methods, and these development practices will help make that happen.\n\nIf you ask 10 DevOps teams for their take on CI/CD best practices, granted, you'll likely get 10 different answers. However, there are several tips that are widely agreed upon:\n\n1. Only build once: Don't create a new build for each stage because you risk introducing inconsistencies. Instead, promote the same build artifacts throughout each stage of the CI/CD pipeline. This requires an environment-agnostic build.\n\n2. Streamline the tests: Strike a balance between test coverage and performance. If it takes too long for test results users will try to circumvent the process.\n\n3. Fail fast: On the CI side, devs 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 DevOps professionals.\n\n4. Make it daily: The more regular the code commits, the more benefit DevOps teams will see.\n\n5. Fix it if it’s broken: CI/CD makes it simple to fix broken builds.\n\n6. Clean pre-production environments:The longer environments are kept running, the harder it becomes to track all the configuration changes and updates that have been applied. This is good incentive to clean up pre-production environments between each deployment. \n\n7. Automation all the time: Keep tweaking the CI/CD pipeline to ensure the “continuous automation” state is achieved.\n\n8. Know the steps: Make sure the release and rollback plans are well documented and understood by the entire team.\n\n9. Keep it safe: CI/CD is a shift left, so it offers a good opportunity to integrate security earlier in the process.\n\n10. It’s a loop: Make sure there’s an easy way for the entire team to receive (and contribute to) feedback.\n\n## Continuous delivery best practices\n\nContinuous delivery/deployment feels like it deserves it’s own deep dive into best practices because CI often steals most of the headlines. Here is a roundup of CD best practices:\n\n- Start where you are: Don’t wait for a new platform. It’s always possible to tweak what you have to make it faster and more efficient.\n\n- Less is more: The best CD is done with minimal tools.\n\n- Track what’s happening: Issues and merge requests can get out of hand. If milestones are an option, they can help. Bonus: Milestones do double-duty when setting up Agile sprints and releases.\n\n- Automatically deploy changes: Streamline user acceptance testing and staging with automation.\n\n- Manage the release pipeline: Automation is the answer.\n\n- Establish monitoring: Keeping a good eye on the production process saves time and money. It also can provide key data points to the business side.\n\n- Kick off continuous deployment: Once continuous delivery is humming, bring on the hands-free deployment where it’s possible to send changes to production automatically. \n\n## How to improve the CI/CD pipeline\n\nA pipeline is just another way of characterizing the series of steps involved in deploying a new version of software. Monitoring and automation are concepts introduced in a CI/CD pipeline to improve the app development process, especially during the integration and testing phases, as well as when software is delivered and deployed.\n\nThe typical elements of a CI/CD pipeline are: plan, analyze, design, build, test, release, deploy, validation and compliance and maintenance. These steps can be done manually, but the real value of a CI/CD pipeline comes when they are automated.\n\nIf it’s time to finetune the CI/CD pipeline, consider the following performance enhancements:\n\n- Mix up the release strategy. A [canary release](https://martinfowler.com/bliki/CanaryRelease.html) (sometimes called a canary deployment) might be worth considering. In a canary release, new features are deployed to just a select group of users.\n\n- Add more automated testing because there is [never enough automated testing](/blog/want-faster-releases-your-answer-lies-in-automated-software-testing/). \n\n- Continue to pare down. Fewer tools mean fewer handoffs and steps. If CI/CD is part of a [DevOps platform](/topics/devops-platform/), everything will be in one place. \n\n- Consider a routine practice of [software composition analysis](https://www.csoonline.com/article/3640808/software-composition-analysis-explained-and-how-it-identifies-open-source-software-risks.html) to ensure the DevOps team is keeping track of critical open source software issues. \n\n## How to measure the success of CI/CD \n\nDevOps teams can’t know how well their CI/CD practices are going unless they measure them. [Metrics](https://about.gitlab.com/topics/ci-cd/continuous-integration-metrics/) play an important role in improving system performance and helping to identify where value can be added. They also provide a baseline for measuring the impact of any improvements made.\n\n Here are the best metrics to employ:\n\n### Cycle time\nThis refers to how long it takes to roll out a functional application from the time work on the code begins. To figure out the average life cycle time, measure the development process phases. This metric will provide insight into what the overall development time is and any bottlenecks in the process.\n\n### Time to value\nThis refers to how long it takes to release written code. The integration, testing, delivery, and deployment should take anywhere from minutes up to a few hours for test cycles to finish. If it takes days to move a build through the CI/CD pipeline time to value is not being realized and the process should be fine-tuned.\n\n### Uptime\nUptime is a measure of stability and reliability and whether everything is working as it should. It is one of the biggest priorities the ops team has. When the CI/CD strategy is automated, ops leaders can focus more of their time on system stability and less time on workflow issues.\n\n### Error rates\nApplication error rates is a fact of life in the development process. Tracking them is very important because not only can error rates indicate quality problems, but also ongoing performance and uptime related issues. \nIf uptime and error rates seem high, it can illustrate a [common CI/CD challenge](https://about.gitlab.com/blog/modernize-your-ci-cd/) between dev and ops teams. Operations goals are a key indicator of process success.\n\n### Infrastructure costs\nInfrastructure costs are critically important with cloud native development. Deploying and managing a CI/CD platform can result in big expenses if they are not kept in check.\nTo determine how they will set their prices, cloud providers will consider what the cost is of network hardware, infrastructure maintenance, and labor. \n\n### Team retention\nIt’s no mystery: When a developer – or anyone, really – feels valued and satisfied they’re apt to stick around. When teams work well together and know how to collaborate, retention is likely to follow. On the flip side, developers might feel uncomfortable speaking up if they don’t like how things are going, but looking at retention rates can help identify potential problems.\n\n##  What are the benefits of following CI/CD best practices?\n\nWhen best practices are followed, the [benefits of CI/CD](https://about.gitlab.com/topics/ci-cd/benefits-continuous-integration/) are felt throughout an organization: From HR to operations, teams work better and achieve goals. Establishing metrics around CI/CD performance can go beyond providing insights on development and carry over to many aspects of the business. \n\nA well-functioning CI/CD pipeline can be a game changer for DevOps teams. Here are some of the biggest benefits:\n\n**Developers aren’t fixing things, they’re writing code.** Fewer tools and toolchains mean less time spent on maintenance and more time spent actually producing high-quality software applications.\n\n**Code is in production.** Rather than sitting in a queue, code actually makes it out into the real world. This also leads to happier developers.\n\n**Developers have the bandwidth to focus on solving business problems.** A streamlined CI/CD process lets developers actually focus on what matters and not on the distractions of problem code, missed handoffs, production issues, and more.\n\n**It’s easier to innovate.** It’s a competitive world, and organizations need all the tools at their disposal to stay ahead. A well-built CI/CD process makes software development easier, faster and safer, which means DevOps teams have the time and energy to think outside the box.\n\n**Attract and retain talent.** It’s a very competitive labor market and DevOps talent can be very hard to impress. Nothing says “we take our DevOps team seriously” more than an organization that’s invested in the technology and processes around CI/CD.\n\n**Everyone does what they do best.** Dev, ops, sec and test each have a critical role to play, and CI/CD helps [clearly delineate the responsibilities](/topics/devops/build-a-devops-team/).\n\n## CI/CD deployment strategy\n\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. \n\nHere are some strategies to help make a deployment successful:\n\n- Commit to frequency in CD\n- Automate the build process\n- Run tests in parallel, and create a deployment pipeline\n- Fail fast and adopt a shift left mentality to give developers the skills and tools to accelerate without breaking things \n- Use CI tools that provide faster feedback\n\n## How can I implement CI/CD in my organization?\n\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",[9,683,728],{"slug":1940,"featured":6,"template":686},"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":1946,"_dir":244,"_draft":6,"_partial":6,"_locale":7,"seo":1947,"content":1953,"config":1958,"_id":1960,"_type":14,"title":1961,"_source":16,"_file":1962,"_stem":1963,"_extension":19},"/en-us/blog/how-to-translate-bamboo-agent-capabilities-to-gitlab-runner-tags",{"title":1948,"description":1949,"ogTitle":1948,"ogDescription":1949,"noIndex":6,"ogImage":1950,"ogUrl":1951,"ogSiteName":673,"ogType":674,"canonicalUrls":1951,"schema":1952},"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":1948,"description":1949,"authors":1954,"heroImage":1950,"date":1955,"body":1956,"category":704,"tags":1957},[1298],"2024-02-22","CI pipelines often start simple – a single job building a binary and pushing it to an artifact repository or to some production environment. Ever-changing software requirements introduce more complexities, such as adding more jobs to perform certain checks and reviewing the output before the final build job is executed.  \n\nThese complexities increase exponentially when builds are expected to target varying systems with different system architectures or resource needs. This is evident in projects like operating systems, mobile apps, or software distributions that support multiple deployment platforms. To account for the varying needs of builds in these types of environments, having multiple runners that match needed requirements is key, and that's where [GitLab Runner](https://docs.gitlab.com/runner/) tags come in. If you are coming from Atlassian's Bamboo, they are called \"agent capabilities.\"\n\nRunner tags allow organizing runners by a tag that signifies a specific use case they support; these tags are then used to make sure CI jobs run on a runner that meets their requirements. A job can require GPU resources that are only available on a handful of runners; tagging the job to the tags of the runner allows it to be scheduled on the runner with GPUs.\n\nAgent capabilities on Bamboo are used to achieve the same functionality by specifying binaries or custom identifiers that must be matched or available for a job to run on a Bamboo agent. In this blog post, we will be looking at how to translate Bamboo agent capabilities to GitLab Runner tags. \n\nBamboo has varying agent capabilities:\n- Executable capability specifies executables that are available on an agent.\n- JDK capability specifies that the Java Development Kit is installed and available for builds.\n- Version Control capability lets Bamboo know the version control systems set up on an agent and where the client application is located.\n- Docker capability is used to define the agents where Docker is installed for Docker tasks\n- Custom capability uses key/value identifiers to identify a unique functionality an agent provides.\n\nGitLab makes the process easier by using tags to identify Runners, some of which can be assigned multiple tags to denote the varying functionalities they can provide to jobs. Let's look at how you can use Runner tags in GitLab.\n\n## Adding tags to GitLab Runner\n\nWhen [registering a runner](https://docs.gitlab.com/runner/register/index.html) after installation, one of the steps requires providing a list of comma-separated tags that can be used. If none are provided at this stage, you can always edit the `/etc/gitlab-runner/config.toml` file and add any missing tags.\n\nYou can also manage the tags of a runner in GitLab by accessing the runner's edit page and updating the `Tags` field. You have the option for the runner to be exclusive to jobs that are tagged appropriately, or when there are no tagged jobs to run, it should run untagged jobs, too. Checking `Run untagged jobs` enables this behavior.\n\n## Using tags in .gitlab-ci.yaml file\n\nTo run a job on a specific runner, add the relevant tags to the job's configuration, as shown below:\n\n```yaml\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```\nIn the example above, the job builds an iOS application only on runners operating on a macOS device with an M1 chip and tagged `saas-macos-medium-m1`.\n\n## Using multiple tags\n\nA job can specify multiple tags to target a diverse range of runners, especially in organizations that run several fleets of runners as part of their software development lifecycle. A job will only run if a runner is found that has all the tags the job has been tagged with. For example, if a job has `[linux, android, fastlane]` tags, a runner with `[ android, fastlane]` or `[ linux, android]` will not execute the job because the full set of tags does not match the runner.\n\n## Dynamic jobs with tags and variables\n\nYou can use variables to determine the values of tags and thus dynamically influence which runners pick up the jobs. For example:\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\nIn this example, only runners tagged with `kubernetes` will execute the job. You can take this further in more complex pipelines with [`parallel: matrix`](https://docs.gitlab.com/ee/ci/yaml/index.html#parallelmatrix). Here is an example:\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\nThis example ends up with three parallel jobs with three different tags for each: `aws-monitoring`, `aws-app1` and `gcp-data`, thus targeting possibly three different runners.\n\nUsing tags in your GitLab CI configuration gives you the flexibility to determine where and how your applications are built, to use resources more efficiently as scarce resources can be limited to certain runners, and to determine how jobs are allocated to those runners.\n\n> Learn more about [how to make the move from Atlassian to GitLab](https://about.gitlab.com/move-to-gitlab-from-atlassian/).\n",[109,9,729],{"slug":1959,"featured":91,"template":686},"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":1965,"_dir":244,"_draft":6,"_partial":6,"_locale":7,"seo":1966,"content":1972,"config":1977,"_id":1979,"_type":14,"title":1980,"_source":16,"_file":1981,"_stem":1982,"_extension":19},"/en-us/blog/how-to-use-agent-based-gitops",{"title":1967,"description":1968,"ogTitle":1967,"ogDescription":1968,"noIndex":6,"ogImage":1969,"ogUrl":1970,"ogSiteName":673,"ogType":674,"canonicalUrls":1970,"schema":1971},"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":1967,"description":1968,"authors":1973,"heroImage":1969,"date":1974,"body":1975,"category":704,"tags":1976},[1012],"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",[537,970,9,728,900],{"slug":1978,"featured":6,"template":686},"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":1984,"_dir":244,"_draft":6,"_partial":6,"_locale":7,"seo":1985,"content":1991,"config":1997,"_id":1999,"_type":14,"title":2000,"_source":16,"_file":2001,"_stem":2002,"_extension":19},"/en-us/blog/how-we-designed-the-gitlab-reference-architectures",{"title":1986,"description":1987,"ogTitle":1986,"ogDescription":1987,"noIndex":6,"ogImage":1988,"ogUrl":1989,"ogSiteName":673,"ogType":674,"canonicalUrls":1989,"schema":1990},"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":1986,"description":1987,"authors":1992,"heroImage":1988,"date":1994,"body":1995,"category":704,"tags":1996},[1993],"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,773,706,902,994],{"slug":1998,"featured":91,"template":686},"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":2004,"_dir":244,"_draft":6,"_partial":6,"_locale":7,"seo":2005,"content":2011,"config":2017,"_id":2019,"_type":14,"title":2020,"_source":16,"_file":2021,"_stem":2022,"_extension":19},"/en-us/blog/humangeo-switches-jenkins-gitlab-ci",{"title":2006,"description":2007,"ogTitle":2006,"ogDescription":2007,"noIndex":6,"ogImage":2008,"ogUrl":2009,"ogSiteName":673,"ogType":674,"canonicalUrls":2009,"schema":2010},"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":2006,"description":2007,"authors":2012,"heroImage":2008,"date":2014,"body":2015,"category":704,"tags":2016},[2013],"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",[994,9,992],{"slug":2018,"featured":6,"template":686},"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":2024,"_dir":244,"_draft":6,"_partial":6,"_locale":7,"seo":2025,"content":2030,"config":2035,"_id":2037,"_type":14,"title":2038,"_source":16,"_file":2039,"_stem":2040,"_extension":19},"/en-us/blog/impact-of-the-file-type-variable-change-15-7",{"title":2026,"description":2027,"ogTitle":2026,"ogDescription":2027,"noIndex":6,"ogImage":961,"ogUrl":2028,"ogSiteName":673,"ogType":674,"canonicalUrls":2028,"schema":2029},"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":2026,"description":2027,"authors":2031,"heroImage":961,"date":2032,"body":2033,"category":792,"tags":2034},[966],"2023-02-13","\n\nIn GitLab 15.7, we stopped expanding `file type` variables in CI jobs. CI jobs that rely on the old expansion method will generate errors and not work. Here is a look at how this change came about, the difference in job outputs, and what to do next.\n\n## Background\n\nGitLab CI has long-supported file type CI/CD variables. This is a helpful feature for CI jobs, as a file variable is a simple way to pass values to an external system. In cases where there is a concern about environment variable size limits, putting the information in a file and using an environment variable to reference the file is a good option.\n\nBefore 15.7, variable expansion expanded the contents of the file referenced in a file type variable. Some users found this expansion behavior to be quite valuable. In looking at some metrics on GitLab.com, for example, we saw over 1,000 unique projects that used a file variable inside another variable. However, other users did not find this unintended behavior helpful and implemented workarounds.\n\nAs expected, a file referenced in a file type variable may contain sensitive data. So performing variable expansion on the file contents could expose that data in the build environment. Even though the risk could be somewhat mitigated, continuing to expand file type variables was not the right approach to ensure the most secure system.\n\n## Example of the job output before and after 15.7\n\n1. Create a file variable via the GitLab UI. For example: `A_FILE_VAR` with the value `this is some super secret content`.\n1. Create a CI job with this content:\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**Results before 15.7:**\n\n```\n$ echo $A_FILE_VAR\n/builds/test-project-repo/test-project.tmp/A_FILE_VAR\n$ cat $A_FILE_VAR\nthis is some super secret content\n$ echo $REF_FILE_VAR\nthis is some super secret content\n$ cat $REF_FILE_VAR\ncat: can't open 'this': No such file or directory\ncat: can't open 'is': No such file or directory\ncat: can't open 'some': No such file or directory\ncat: can't open 'super': No such file or directory\ncat: can't open 'secret': No such file or directory\ncat: can't open 'content': No such file or directory\n\n```\n\n**Results after 15.7:**\n\n```\n$ echo $A_FILE_VAR\n/builds/test-project-repo/test-project.tmp/A_FILE_VAR\n$ cat $A_FILE_VAR\nthis is some super secret content\n$ echo $REF_FILE_VAR\n/builds/test-project-repo/test-project.tmp/A_FILE_VAR\n$ cat $REF_FILE_VAR\nthis is some super secret content\n\n```\n\nYou will notice in the 15.7+ job output the echo command no longer prints the contents of the file.\n\n## What is the current status of the change?\n\nWe [deprecated](https://docs.gitlab.com/ee/update/deprecations.html#file-type-variable-expansion-in-gitlab-ciyml) this feature in 15.5 and removed it from the code base in [15.7](https://gitlab.com/gitlab-org/gitlab/-/issues/29407). However, we neglected to include a follow-up removal notice in the 15.7 release, so some self-managed customers now upgrading to 15.7+ may have missed the initial deprecation notice.\n\n## What do I need to do before upgrading to 15.7 or higher?\n\n1. Check your CI jobs for any instances where a file variable is referenced inside another variable.\n2. Change the references and test the CI jobs.\n\n## What’s next\n\nSecrets and variable handling are likely some of the most complex areas in a DevSecOps platform. On our end, we are continuously refining our processes to effectively communicate the potential impact of new features or the removal of existing ones. We also recommend that you reach out to us (the Verify team) directly on issues referenced in a release or deprecation notice if it's not clear how a change might affect your CI workflows.\n",[9,683,970],{"slug":2036,"featured":6,"template":686},"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":2042,"_dir":244,"_draft":6,"_partial":6,"_locale":7,"seo":2043,"content":2049,"config":2055,"_id":2057,"_type":14,"title":2058,"_source":16,"_file":2059,"_stem":2060,"_extension":19},"/en-us/blog/inside-the-improved-ci-logs-management-experience-for-multi-line-commands",{"title":2044,"description":2045,"ogTitle":2044,"ogDescription":2045,"noIndex":6,"ogImage":2046,"ogUrl":2047,"ogSiteName":673,"ogType":674,"canonicalUrls":2047,"schema":2048},"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":2044,"description":2045,"authors":2050,"heroImage":2046,"date":2052,"body":2053,"category":704,"tags":2054},[2051],"Romuald Atchadé","2024-01-25","Improving the GitLab CI/CD log experience for jobs with multi-line commands has been a long-requested feature. With the latest release of GitLab and GitLab Runner, it's now easier to work with the log section for jobs with multi-line commands. In this post, we will describe the experience with the new feature, show you how to enable the new log output in your pipelines, and discuss key points regarding CI/CD script execution and log output in various shells, such as Bash and Powershell.\n\n## Overview of multi-line commands\n\nFirst, it’s helpful to describe what we mean by a CI job with multi-line commands. In GitLab CI the script keyword is used to specify commands to execute for a CI job. In the example below, the build-job has a single command, a basic echo statement, to execute in the script block. \n\n```\n## A pipeline with a single line command in the script block for the build-job\n\nbuild-job:\n  stage: build\n  script:\n    - echo \"this is the script to run for the build job\"\n\n```\n\nIf you were to run this pipeline, then the log output in the UI would display as follows:\nLine 17 - GitLab CI automatically generates a log entry for the command that you specify in the script block.\nLine 18 - This is the output of the command that was executed.\n\n![Ci log management - image 2](https://res.cloudinary.com/about-gitlab-com/image/upload/v1750099524/Blog/Content%20Images/Blog/Content%20Images/image5_aHR0cHM6_1750099524655.png)\n\nNow as you can imagine, the script that you define in the script block will likely be more complex than the example provided and could very well span multiple lines in the CI/CD pipeline file. \n\n```\n## A pipeline with a multi-line command in the script block for the build-job\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\nIf you were to run this pipeline, then the log output in the UI would display as follows:\n\nLine 17 - As in the previous example, GitLab CI automatically generates a log entry for the command that you specify in the script block. You will notice that line 17 only includes the first command in the script block. This makes it more difficult to debug an issue with script execution as you will need to refer back to the source pipeline file to see exactly what script was executed.\n\n![CI log management - image 3](https://res.cloudinary.com/about-gitlab-com/image/upload/v1750099525/Blog/Content%20Images/Blog/Content%20Images/image6_aHR0cHM6_1750099524656.png)\n\n## So what’s new?\n\nStarting in GitLab 16.7 and GitLab Runner 16.7, you can now enable a feature flag titled FF_SCRIPT_SECTIONS, which will add a collapsible output section to the CI job log for multi-line command script blocks. This feature flag changes the log output for CI jobs that execute within the Bash shell.\n\n![CI log management - image 4](https://res.cloudinary.com/about-gitlab-com/image/upload/v1750099525/Blog/Content%20Images/Blog/Content%20Images/image3_aHR0cHM6_1750099524658.png)\n\nLine 17: Unlike the previous examples, the first thing you will notice in the screenshot above is that by default the log entry for the multi-line command is collapsed by default.\n\nSingle-line commands do not display in a collapsible element.\n\nFor multi-line scripts the multi-line command is now a collapsible element, so now, when you uncollapse the log entry for line 17, then the log will display all of the commands that were executed in the script block.\n\n![CI log management - image 1](https://res.cloudinary.com/about-gitlab-com/image/upload/v1750099525/Blog/Content%20Images/Blog/Content%20Images/image2_aHR0cHM6_1750099524659.png)\n\nThere is also the [`custom collapsible section`](https://docs.gitlab.com/ee/ci/jobs/#custom-collapsible-sections) feature, which in combination with this new multi-command output capability does provide you additional flexibility for displaying log output in the UI. Here is how you can use the two features to change the log output. \n\n```\n## A pipeline with a multi-line command in the script block for the build-job\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\nIf you were to run this pipeline with the FF_SCRIPT_SECTIONS feature flag set to false, then the log output would be as depicted in the following screenshot.\n\n![CI log management - image 5](https://res.cloudinary.com/about-gitlab-com/image/upload/v1750099524/Blog/Content%20Images/Blog/Content%20Images/image1_aHR0cHM6_1750099524661.png)\n\nBut, if you were to run this pipeline with the FF_SCRIPT_SECTIONS feature flag set to true, then the log output would be as depicted in the following screenshot.\n\n![CI log management - image 6](https://res.cloudinary.com/about-gitlab-com/image/upload/v1750099525/Blog/Content%20Images/Blog/Content%20Images/image4_aHR0cHM6_1750099524663.png)\n\n## What about other shells?\n\nAs of the 16.7 release, the collapsible output section in the CI job log for multi-line command script blocks is only visible for CI/CD jobs that are executed with the Bash shell. CI/CD jobs executed with Powershell is not currently supported. We plan to add this [capability](https://gitlab.com/gitlab-org/gitlab-runner/-/merge_requests/4494) in a future release. \n\n## What are our future plans?\n\nA few features are still needed to improve the CI/CD job log output, and the `timestamp` for each log line is one of them. This addition will add missing features such as command/section duration.\n\n> To learn more about GitLab CI/CD features, refer to the official [CI/CD documentation](https://docs.gitlab.com/ee/ci/index.html). \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",[9,683,109,729],{"slug":2056,"featured":91,"template":686},"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":2062,"_dir":244,"_draft":6,"_partial":6,"_locale":7,"seo":2063,"content":2069,"config":2075,"_id":2077,"_type":14,"title":2078,"_source":16,"_file":2079,"_stem":2080,"_extension":19},"/en-us/blog/introducing-auto-breakfast-from-gitlab",{"title":2064,"description":2065,"ogTitle":2064,"ogDescription":2065,"noIndex":6,"ogImage":2066,"ogUrl":2067,"ogSiteName":673,"ogType":674,"canonicalUrls":2067,"schema":2068},"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":2064,"description":2065,"authors":2070,"heroImage":2066,"date":2072,"body":2073,"category":704,"tags":2074},[2071],"Brendan O'Leary","2018-06-29","\n\nA big part of [GitLab's culture](/company/culture/) is [saying thanks](/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,992],{"slug":2076,"featured":6,"template":686},"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":2082,"_dir":244,"_draft":6,"_partial":6,"_locale":7,"seo":2083,"content":2089,"config":2094,"_id":2096,"_type":14,"title":2097,"_source":16,"_file":2098,"_stem":2099,"_extension":19},"/en-us/blog/introducing-ci-cd-steps-a-programming-language-for-devsecops-automation",{"title":2084,"description":2085,"ogTitle":2084,"ogDescription":2085,"noIndex":6,"ogImage":2086,"ogUrl":2087,"ogSiteName":673,"ogType":674,"canonicalUrls":2087,"schema":2088},"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":2084,"description":2085,"authors":2090,"heroImage":2086,"date":2091,"body":2092,"category":1464,"tags":2093},[966],"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",[482,109,683,9,970],{"slug":2095,"featured":91,"template":686},"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":2101,"_dir":244,"_draft":6,"_partial":6,"_locale":7,"seo":2102,"content":2108,"config":2114,"_id":2116,"_type":14,"title":2117,"_source":16,"_file":2118,"_stem":2119,"_extension":19},"/en-us/blog/keeping-your-development-dry",{"title":2103,"description":2104,"ogTitle":2103,"ogDescription":2104,"noIndex":6,"ogImage":2105,"ogUrl":2106,"ogSiteName":673,"ogType":674,"canonicalUrls":2106,"schema":2107},"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":2103,"description":2104,"authors":2109,"heroImage":2105,"date":2111,"body":2112,"category":704,"tags":2113},[768,2110],"Joe Randazzo","2023-01-03","\nMore than 20 years ago, the book [The Pragmatic Programmer](https://pragprog.com/titles/tpp20/the-pragmatic-programmer-20th-anniversary-edition/) brought attention to the DRY principle, or “Don’t Repeat Yourself.\" This principle is defined as every piece of knowledge must have a single, unambiguous, authoritative representation within a system.\n\nThe main problem to solve here is minimizing duplication. As a development project is bombarded with new requests or changing requirements, DevOps teams must balance between development of net-new features or maintaining existing code. The important part is how to reduce duplicate knowledge across projects.\n\nThis tutorial explores the mechanisms throughout GitLab that leverage the DRY principle to cut down on code duplication and standardize on knowledge. To see working examples of reusability in action, take a look at this [repository](https://gitlab.com/guided-explorations/gitlab-ci-yml-tips-tricks-and-hacks/dry-repository-a-cheatsheet).\n\n## Minimizing duplication in CI/CD\n\n### include\n[`include`](https://docs.gitlab.com/ee/ci/yaml/index.html#include) can be used to transform a single .gitlab-ci.yml file into multiple files to improve readability and minimize duplication. For example, testing, security, or deployment workflows can be broken out into separate templates. This also allows [ownership](https://docs.gitlab.com/ee/user/project/codeowners/) of the files.\n\n\n```yaml\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### YAML anchors\n[YAML anchors](https://docs.gitlab.com/ee/ci/yaml/yaml_optimization.html#anchors) can be used to reduce repeat syntax and extend blocks of CI workflow, including jobs, variables, and scripts.\n\n```yaml\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### extends\n[`extends`](https://docs.gitlab.com/ee/ci/yaml/index.html#extends) is similar to anchors with additional flexibility and readability. The major difference is it can be used with `includes`.\n\n```yaml\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### !reference\n[`!reference`](https://docs.gitlab.com/ee/ci/yaml/yaml_optimization.html#reference-tags) enables the selection of keyword configuration from other job sections and reuse in the current session.\n\n```yaml\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### Downstream pipelines\n[Downstream pipelines](https://docs.gitlab.com/ee/ci/pipelines/downstream_pipelines.html) enable the breakout of microservices and their pipelines. A .gitlab-ci.yml file can be used for each service, and when a file or directory is changed, only that pipeline needs to be triggered improving the awareness and readability of what’s deploying.\n\n```yaml\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![Dynamic child pipeline](https://about.gitlab.com/images/blogimages/2022-02-01-parent-child-vs-multi-project-pipelines/parent-child.png){: .shadow}\n\n### CI/CD variables\n[CI/CD variables](https://docs.gitlab.com/ee/ci/variables/) can be scoped to a specific level, including the project, group, instance level, or .gitlab-ci.yml level. The values can be stored and reused across a group for project inheritance or overwritten at the project level.\n\n```yaml\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## Creating consistent code reviews across multiple teams\n\n### Description templates\n[Description templates](https://docs.gitlab.com/ee/user/project/description_templates.html) enable teams to define a consistent workflow for issues or merge requests. For example, the MR template can define a checklist for rolling out to a feature to ensure it’s documented, quality tested, and reviewed by appropriate team members. Here are [MR templates](https://gitlab.com/gitlab-org/gitlab/-/tree/master/.gitlab/merge_request_templates) that GitLab team members use daily.\n\n```md\n\u003C!-- These templates can be set at the instance or group level to share amongst the organization: https://docs.gitlab.com/ee/user/project/description_templates.html#set-instance-level-description-templates -->\n\n## What does this MR do?\n\n\u003C!-- Briefly describe what this MR is about. -->\n\n## Related issues\n\n\u003C!-- Link related issues below. -->\n\n## Create a checklist for the author or reviewer\n- [ ] Optional. Consider taking this writing course before publishing a change.\n- [ ] Follow the documentation process stated here.\n- [ ] Tag this user group if this applies.\n\n\n\u003C!-- Quick Actions - See https://docs.gitlab.com/ee/user/project/quick_actions.html#issues-merge-requests-and-epics for a list of all the quick actions available. -->\n\n\u003C!-- Add a label to assign a specific workflow using scoped labels -->\n/label ~documentation ~\"type::maintenance\" ~\"docs::improvement\" ~\"maintenance::refactor\"\n\n\u003C!-- Apply draft format automatically -->\n/draft\n\n\u003C!-- Assign myself or a usergroup -->\n/assign me\n```\n\n### Project templates\n[Project templates](https://docs.gitlab.com/ee/user/group/custom_project_templates.html) can be used to define an initial project structure for when new services are being developed. This gives a consistent starting point for projects that come equipped with the latest file configurations and defaults.\n\n### File templates\n[File templates](https://docs.gitlab.com/ee/administration/settings/instance_template_repository.html) are similar to project templates but are default files to choose from when adding a new file to your repository. The team then can quickly choose from files that have best practices baked in and organization defaults.\n\n## Defining a Pipeline Center of Excellence project for CI/CD workflows\n\nAs you 'productionize' your CI/CD workflows, it’s recommended to create a “Pipeline Center of Excellence” project that contains templates, containers, or other abstracted constructs that can be adopted throughout the organization. This project contains file or CI/CD templates that have the best practices or well-formed workflows defined for development teams to quickly adopt (includes) without recreating the wheel. To explore this in practice, visit [Pipeline COE](https://gitlab-org.gitlab.io/professional-services-automation/pipelinecoe/pipeline-templates/#/) documentation written by the GitLab Professional Services team.\n\nHave a reusable component to suggest or that we missed? Add a comment to this blog post or suggest a change to this file!\n\n## Related posts\n- [How to keep up with CI/CD best practices](https://about.gitlab.com/blog/how-to-keep-up-with-ci-cd-best-practices/)\n- [How to become more productive with GitLab CI](https://about.gitlab.com/blog/how-to-become-more-productive-with-gitlab-ci/)\n- [A visual guide to GitLab CI/CD caching](https://about.gitlab.com/blog/a-visual-guide-to-gitlab-ci-caching/)\n\nCover image by [Federico Beccari](https://unsplash.com/@federize?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText) on [Unsplash](https://www.unsplash.com).\n",[9,683,728,773],{"slug":2115,"featured":6,"template":686},"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":2121,"_dir":244,"_draft":6,"_partial":6,"_locale":7,"seo":2122,"content":2127,"config":2133,"_id":2135,"_type":14,"title":2136,"_source":16,"_file":2137,"_stem":2138,"_extension":19},"/en-us/blog/little-things-make-a-difference",{"title":2123,"description":2124,"ogTitle":2123,"ogDescription":2124,"noIndex":6,"ogImage":741,"ogUrl":2125,"ogSiteName":673,"ogType":674,"canonicalUrls":2125,"schema":2126},"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":2123,"description":2124,"authors":2128,"heroImage":741,"date":2130,"body":2131,"category":1075,"tags":2132},[2129],"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,795],{"slug":2134,"featured":6,"template":686},"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":2140,"_dir":244,"_draft":6,"_partial":6,"_locale":7,"seo":2141,"content":2147,"config":2153,"_id":2155,"_type":14,"title":2156,"_source":16,"_file":2157,"_stem":2158,"_extension":19},"/en-us/blog/manager-of-frances-fr-domain-selects-gitlab",{"title":2142,"description":2143,"ogTitle":2142,"ogDescription":2143,"noIndex":6,"ogImage":2144,"ogUrl":2145,"ogSiteName":673,"ogType":674,"canonicalUrls":2145,"schema":2146},"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":2148,"description":2143,"authors":2149,"heroImage":2144,"date":2150,"body":2151,"category":1118,"tags":2152},"Manager of France's .fr domain selects GitLab for its DevSecOps capabilities",[1461],"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”.",[728,771,9,683],{"slug":2154,"featured":6,"template":686},"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":2160,"_dir":244,"_draft":6,"_partial":6,"_locale":7,"seo":2161,"content":2166,"config":2171,"_id":2173,"_type":14,"title":2174,"_source":16,"_file":2175,"_stem":2176,"_extension":19},"/en-us/blog/managing-multiple-environments-with-terraform-and-gitlab-ci",{"title":2162,"description":2163,"ogTitle":2162,"ogDescription":2163,"noIndex":6,"ogImage":1576,"ogUrl":2164,"ogSiteName":673,"ogType":674,"canonicalUrls":2164,"schema":2165},"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":2162,"description":2163,"authors":2167,"heroImage":1576,"date":2168,"body":2169,"category":771,"tags":2170},[1758,768],"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",[728,9,773,771],{"slug":2172,"featured":6,"template":686},"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":2178,"_dir":244,"_draft":6,"_partial":6,"_locale":7,"seo":2179,"content":2185,"config":2190,"_id":2192,"_type":14,"title":2193,"_source":16,"_file":2194,"_stem":2195,"_extension":19},"/en-us/blog/merge-trains-explained",{"title":2180,"description":2181,"ogTitle":2180,"ogDescription":2181,"noIndex":6,"ogImage":2182,"ogUrl":2183,"ogSiteName":673,"ogType":674,"canonicalUrls":2183,"schema":2184},"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":2180,"description":2181,"authors":2186,"heroImage":2182,"date":2187,"body":2188,"category":704,"tags":2189},[1157],"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](/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,683,970,728,902],{"slug":2191,"featured":6,"template":686},"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":2197,"_dir":244,"_draft":6,"_partial":6,"_locale":7,"seo":2198,"content":2204,"config":2211,"_id":2213,"_type":14,"title":2214,"_source":16,"_file":2215,"_stem":2216,"_extension":19},"/en-us/blog/microservices-integrated-solution",{"title":2199,"description":2200,"ogTitle":2199,"ogDescription":2200,"noIndex":6,"ogImage":2201,"ogUrl":2202,"ogSiteName":673,"ogType":674,"canonicalUrls":2202,"schema":2203},"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":2205,"description":2200,"authors":2206,"heroImage":2201,"date":2208,"body":2209,"category":704,"tags":2210},"It's raining repos: The microservices repo explosion, and what we're doing about it",[2207],"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,231,901,771,838],{"slug":2212,"featured":6,"template":686},"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":2218,"_dir":244,"_draft":6,"_partial":6,"_locale":7,"seo":2219,"content":2225,"config":2230,"_id":2232,"_type":14,"title":2233,"_source":16,"_file":2234,"_stem":2235,"_extension":19},"/en-us/blog/migrate-from-jenkins-update",{"title":2220,"description":2221,"ogTitle":2220,"ogDescription":2221,"noIndex":6,"ogImage":2222,"ogUrl":2223,"ogSiteName":673,"ogType":674,"canonicalUrls":2223,"schema":2224},"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":2220,"description":2221,"authors":2226,"heroImage":2222,"date":2227,"body":2228,"category":836,"tags":2229},[812],"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,683],{"slug":2231,"featured":6,"template":686},"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":2237,"_dir":244,"_draft":6,"_partial":6,"_locale":7,"seo":2238,"content":2244,"config":2250,"_id":2252,"_type":14,"title":2253,"_source":16,"_file":2254,"_stem":2255,"_extension":19},"/en-us/blog/new-default-container-image-gitlab-saas-linux-runnners",{"title":2239,"description":2240,"ogTitle":2239,"ogDescription":2240,"noIndex":6,"ogImage":2241,"ogUrl":2242,"ogSiteName":673,"ogType":674,"canonicalUrls":2242,"schema":2243},"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":2245,"description":2240,"authors":2246,"heroImage":2241,"date":2247,"body":2248,"category":704,"tags":2249},"How to use Ruby 3.1 as the default container image on GitLab SaaS Runners on Linux",[966],"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",[728,902,9,683,231],{"slug":2251,"featured":6,"template":686},"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":2257,"_dir":244,"_draft":6,"_partial":6,"_locale":7,"seo":2258,"content":2264,"config":2269,"_id":2271,"_type":14,"title":2272,"_source":16,"_file":2273,"_stem":2274,"_extension":19},"/en-us/blog/new-machine-types-for-gitlab-saas-runners",{"title":2259,"description":2260,"ogTitle":2259,"ogDescription":2260,"noIndex":6,"ogImage":2261,"ogUrl":2262,"ogSiteName":673,"ogType":674,"canonicalUrls":2262,"schema":2263},"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":2259,"description":2260,"authors":2265,"heroImage":2261,"date":2266,"body":2267,"category":1464,"tags":2268},[966],"2022-09-22","\nOur GitLab SaaS vision is to provide a solution where you can easily choose and use the correct type of public cloud-hosted compute resources for your CI/CD jobs. In this first iteration towards achieving that vision, we are pleased to announce that two larger compute machines are generally available for GitLab SaaS Runners on Linux.\n\nWith these two machine types, you can now access more choices for your GitLab SaaS CI/CD jobs. And with 100% job isolation on an ephemeral virtual machine, and security and autoscaling fully managed by GitLab, you can confidently run your critical [CI/CD](/topics/ci-cd/) jobs on GitLab SaaS.\n\n## New machine type details\n\nThe new [SaaS Runners on Linux](https://docs.gitlab.com/ee/ci/runners/saas/linux_saas_runner.html) are a 2 vCPU, 8GB RAM (`saas-linux-medium-amd64`), and a 4 vCPU, 16GB RAM (`saas-linux-large-amd64`) machine type. These machine types, powered by the latest generation of Google Compute N2D virtual machines, deliver significant performance improvements for general-purpose CI workloads. The medium machine type, `saas-linux-medium-amd64`,  is available to all subscriptions (Free, Premium, Ultimate). The large machine type, `saas-linux-large-amd64` is only available to paid plans (Premium and Ultimate) and GitLab for Open Source program members.\n\nNote: If you are in a Free plan and tag a CI job with the large machine type, `saas-linux-large-amd64`, you will get an error at the job level and the job will not run.\n\n```\nThis job is stuck because of one of the following problems. There are no active runners online, no runners for the protected branch, or no runners that match all of the job's tags: saas-linux-large-amd64\n\n```\n\n## Are the new machine types right for my CI job?\n\nThe answer is that it depends. If the CI job is compute-intensive, you will likely see a performance improvement measured by reduced build times. We ran a series of  [Linux kernel](https://gitlab.com/gitlab-org/ci-cd/gitlab-runner-stress/linux-kernel) builds on the medium machine type to test the potential performance gains for compute-intensive CI jobs.\n\n![Linux kernel build CI job execution time benchmark](https://about.gitlab.com/images/blogimages/new-machine-types-gitlab-saas-linux/linux-kernel-build-runner-saas-benchmark_2022-09-22.png)\n\nOur testing found an average 41% reduction in CI job execution time for the medium machine types compared to the baseline small machine type. We recommend you experiment with the new machine types for your CI jobs to determine the right choice based on your build workflows.\n\n## Getting started\n\nTo get started with the new machine types, simply add a tag to your CI file. Without the tag, a job in your pipeline will automatically run on the small machine type.\n\n### Example pipeline configuration\n\nIn this example pipeline configuration, `job_001` will run on the default Linux SaaS Runner as no machine type tag is defined. The subsequent job, `job_002`, in the build stage will run on the medium machine type, and `job_003` will run on the large machine type. So you have flexibility within a GitLab CI/CD pipeline to choose the right machine type for each job.\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## Understanding the new machine types and cost factors\n\nYou can start using the new machine types now with the CI minutes currently available in your plan. The new machine types will consume your CI minutes at a different rate than the default (small) machine type based on an applied cost factor. If you are a GitLab for Open Source program member, then refer to the [cost factor documentation page](https://docs.gitlab.com/ee/ci/pipelines/cicd_minutes.html#cost-factor) for details on how cost factors are applied to your CI/CD jobs.\n\n|  | saas-linux-small-amd64 |saas-linux-medium-amd64 |saas-linux-large-amd64 |\n| ------ | ------ |------ |------ |\n| CI minutes consumed per 1 minute of build time| 1 |2|3|\n\nToday your CI minutes usage report on GitLab SaaS will be an aggregate of all of the CI minutes consumed across all the machine types you select in your jobs. In this [issue](https://gitlab.com/gitlab-org/gitlab/-/issues/356076), we are working towards adding visibility into usage by each Runner type. So you will soon have more granular reporting of use across the various Runner classes (Linux, Windows, macOS) and machine types we plan to offer.\n\n## Feedback\n\nAt GitLab, we value your input and use it as a critical sensing mechanism in planning roadmap investments. To provide feedback on the machine types you need on GitLab SaaS Runners on Linux, add a comment to the respective comment thread in this [issue](https://gitlab.com/gitlab-org/gitlab/-/issues/373196)\n\nCover image by [Julian Hochgesang](https://unsplash.com/@julianhochgesang) on [Unsplash](https://unsplash.com)\n{: .note}\n",[9,683,706,1464],{"slug":2270,"featured":6,"template":686},"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":2276,"_dir":244,"_draft":6,"_partial":6,"_locale":7,"seo":2277,"content":2283,"config":2288,"_id":2290,"_type":14,"title":2291,"_source":16,"_file":2292,"_stem":2293,"_extension":19},"/en-us/blog/oidc",{"title":2278,"description":2279,"ogTitle":2278,"ogDescription":2279,"noIndex":6,"ogImage":2280,"ogUrl":2281,"ogSiteName":673,"ogType":674,"canonicalUrls":2281,"schema":2282},"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":2278,"description":2279,"authors":2284,"heroImage":2280,"date":2285,"body":2286,"category":1118,"tags":2287},[1137],"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",[729,773,9,683],{"slug":2289,"featured":6,"template":686},"oidc","content:en-us:blog:oidc.yml","Oidc","en-us/blog/oidc.yml","en-us/blog/oidc",{"_path":2295,"_dir":244,"_draft":6,"_partial":6,"_locale":7,"seo":2296,"content":2302,"config":2309,"_id":2311,"_type":14,"title":2312,"_source":16,"_file":2313,"_stem":2314,"_extension":19},"/en-us/blog/parent-child-vs-multi-project-pipelines",{"title":2297,"description":2298,"ogTitle":2297,"ogDescription":2298,"noIndex":6,"ogImage":2299,"ogUrl":2300,"ogSiteName":673,"ogType":674,"canonicalUrls":2300,"schema":2301},"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":2303,"description":2298,"authors":2304,"heroImage":2299,"date":2306,"body":2307,"category":704,"tags":2308},"Breaking down CI/CD complexity with parent-child and multi-project pipelines",[2305],"Fabio Pitino","2022-02-22","\nSoftware requirements change over time. Customers request more features and the application needs to scale well\nto meet user demands. As software grows in size, so does its complexity, to the point where we might decide that it's\ntime to split the project up into smaller, cohesive components.\n\nAs we proceed to tackle this complexity we want to ensure that our CI/CD pipelines continue to validate\nthat all the pieces work correctly together.\n\nThere are two typical paths to splitting up software projects:\n\n- **Isolating independent modules within the same repository**: For example, separating 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 some 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 CI/CD pipeline to match.\n\nFor the first path, [GitLab CI/CD](/topics/ci-cd/) provides [parent-child pipelines](https://docs.gitlab.com/ee/ci/pipelines/downstream_pipelines.html) as a feature that helps manage complexity while keeping it all in a monorepo.\n\nFor the second path, [multi-project pipelines](https://docs.gitlab.com/ee/ci/pipelines/downstream_pipelines.html)\nare the glue that helps ensure multiple separate repositories work together.\n\nLet's look into how these two approaches differ, and understand how to best leverage them.\n\n## Parent-child pipelines\n\nIt can be challenging to maintain complex CI/CD pipeline configurations, especially when you need to coordinate many jobs that may relate\nto different components, while at the same time keeping the pipeline efficient.\n\nLet's imagine we have an app with all code in the same repository, but split into UI and backend components. A \"one-size-fits-all\" pipeline for this app probably would have all the jobs grouped into common stages that cover all the components. The default is to use `build`, `test`, and `deploy` stages.\nUnfortunately, this could be a source of inefficiency because the UI and backend represent two separate tracks of the pipeline.\nThey each have their own independent requirements and structure and likely don't depend on each other.\nThe UI might not need the `build` stage at all, but it might instead need a `system-test` stage with jobs that test the app end-to-end.\nSimilarly, the UI jobs from `system-test` might not need to wait for backend jobs to complete.\n\n[Parent-child pipelines](https://docs.gitlab.com/ee/ci/pipelines/downstream_pipelines.html) help here,\nenabling you to extract cohesive parts of the pipeline into child pipelines that runs in isolation.\n\nWith parent-child pipelines we could break the configurations down into two separate\ntracks by having two separate jobs trigger child pipelines:\n\n- The `ui` job triggers a child pipeline that runs all the UI jobs.\n- The `backend` job triggers a separate child pipeline that runs all the backend jobs.\n\n```yaml\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\nThe modifier `strategy: depend`, which is also available for multi-project pipelines, makes the trigger job reflect the status of the\ndownstream (child) pipeline and waits for it to complete. Without `strategy: depend` the trigger job succeeds immediately after creating the downstream pipeline.\n\nNow the frontend and backend teams can manage their CI/CD configurations without impacting each other's pipelines. In addition to that, we can now explicitly visualize the two workflows.\n\n![example parent-child pipeline](https://about.gitlab.com/images/blogimages/2022-02-01-parent-child-vs-multi-project-pipelines/parent-child.png){: .shadow.medium.center}\n\nThe two pipelines run in isolation, so we can set variables or configuration in one without affecting the other. For example, we could use `rules:changes` or `workflow:rules` inside `backend/.gitlab-ci.yml`, but use something completely different in `ui/.gitlab-ci.yml`.\n\nChild pipelines run in the same context of the parent pipeline, which is the combination of project, Git ref and commit SHA. Additionally, the child pipeline inherits some information from the parent pipeline, including Git push data like `before_sha`, `target_sha`, the related merge request, etc.\nHaving the same context ensures that the child pipeline can safely run as a sub-pipeline of the parent, but be in complete isolation.\n\nA programming analogy to parent-child pipelines would be to break down long procedural code into smaller, single-purpose functions.\n\n## Multi-project pipelines\n\nIf our app spans across different repositories, we should instead leverage [multi-project pipelines](https://docs.gitlab.com/ee/ci/pipelines/downstream_pipelines.html). Each repository defines a pipeline that suits the project's needs. Then, these standalone and independent pipelines can be chained together to create essentially a much bigger pipeline that ensures all the projects are integrated correctly.\n\nThere can be endless possibilities and topologies, but let's explore a simple case of asking another project\nto run a service for our pipeline.\n\nThe app is divided into multiple repositories, each hosting an independent component of the app.\nWhen one of the components changes, that project's pipeline runs.\nIf the earlier jobs in the pipeline are successful, a final job triggers a pipeline on a different project, which is the project responsible for building, running smoke tests, and\ndeploying the whole app. If the component pipeline fails because of a bug, the process is interrupted and there is no\nneed to trigger a pipeline for the main app project.\n\nThe component project's pipeline:\n\n```yaml\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\nThe full app project's pipeline in `myorg/app` project:\n\n```yaml\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![example multi-project pipeline](https://about.gitlab.com/images/blogimages/2022-02-01-parent-child-vs-multi-project-pipelines/multi-project.png){: .shadow.center}\n\nIn our example, the component pipeline (upstream) triggers a downstream multi-project pipeline to perform a service:\nverify the components work together, then deploy the whole app.\n\nA programming analogy to multi-project pipelines would be like calling an external component or function to\neither receive a service (using `strategy:depend`) or to notify it that an event occurred (without `strategy:depend`).\n\n## Key differences between parent-child and multi-project pipelines\n\nAs seen above, the most obvious difference between parent-child and multi-project pipelines is the project\nwhere the pipelines run, but there are are other differences to be aware of.\n\nContext:\n\n- Parent-child pipelines run on the same context: same project, ref, and commit SHA.\n- Multi-project pipelines run on completely separate contexts. The upstream multi-project pipeline can indicate [a ref to use](https://docs.gitlab.com/ee/ci/pipelines/downstream_pipelines.html), which can indicate what version of the pipeline to trigger.\n\nControl:\n\n- A parent pipeline _generates_ a child pipeline, and the parent can have a high 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 upstream (triggering) pipeline does not have much control over the structure of 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- The final status of a parent pipeline, like other normal pipelines, affects the status of the ref the pipeline runs against. For example, if a parent 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 pipeline 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 canceled if interruptible when a new pipeline is created for the same ref.\n- Multi-project downstream pipelines are not automatically canceled when a new upstream pipeline runs for the same ref. The auto-cancelation feature only 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- Child pipelines are not directly visible in the pipelines index page because 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 pipelines, but just happen to be triggered by an another project's pipeline. They are all visible in the pipeline index page.\n\n## Conclusions\n\nParent-child pipelines inherit a lot of the design from multi-project pipelines, but parent-child pipelines have differences that make them a very unique type\nof pipeline relationship.\n\nSome of the parent-child pipelines work we at GitLab will be focusing on is about surfacing job reports generated in child pipelines as merge request widgets,\ncascading cancelation and removal of pipelines as well as passing variables across related pipelines.\nSome of the parent-child pipeline work we at GitLab plan to focus on relates to:\n\n- Surfacing job reports generated in child pipelines in merge request widgets.\n- Cascading cancelation down to child pipelines.\n- Cascading removal down to child pipelines.\n- Passing variables across related pipelines.\n\nYou can check [this issue](https://gitlab.com/gitlab-org/gitlab/-/issues/336884) for planned future developments on parent-child and multi-project pipelines.\nLeave feedback or let us know how we can help.\n\nCover image by [Ravi Roshan](https://unsplash.com/@ravi_roshan_inc?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText) on [Unsplash](https://unsplash.com/?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText)\n{: .note}\n",[9,683,970],{"slug":2310,"featured":6,"template":686},"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":2316,"_dir":244,"_draft":6,"_partial":6,"_locale":7,"seo":2317,"content":2323,"config":2327,"_id":2329,"_type":14,"title":2330,"_source":16,"_file":2331,"_stem":2332,"_extension":19},"/en-us/blog/pipeline-editor-overview",{"title":2318,"description":2319,"ogTitle":2318,"ogDescription":2319,"noIndex":6,"ogImage":2320,"ogUrl":2321,"ogSiteName":673,"ogType":674,"canonicalUrls":2321,"schema":2322},"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":2318,"description":2319,"authors":2324,"heroImage":2320,"date":1073,"body":2325,"category":704,"tags":2326},[1137],"\nThis blog post was originally published on the GitLab Unfiltered blog. It was reviewed and republished on 2021-03-02.\n{: .note .alert-info .text-center}\n\nIn GitLab 13.8, we introduced the first iteration of the [Pipeline Editor](/releases/2021/01/22/gitlab-13-8-released/): a dedicated editor designed for authoring your CI/CD. It is your one-stop shop for everything you need to configure your CI/CD pipelines.\n\n## Why do we need a dedicated editor for pipelines?\n\nGitLab's advanced syntax provides a high degree of customization for sophisticated and demanding CI/CD use cases. However, all of this power and flexibility comes with a fair bit of complexity. The Pipeline Editor helps you mitigate this challenge and serves as a single solution that groups all existing CI authoring features in a single location. It is our foundation, and we plan to build on it with enhancements in future iterations. \n\n## Getting started\n\nIn order for the pipeline editor to work, you'll first need to create a `.gitlab-ci.yml` file in your project. The `.gitlab-ci.yml` is a [YAML file](https://en.wikipedia.org/wiki/YAML) where you configure specific GitLab CI/CD instructions. Check out how we are working on [improving the first-time experience of creating a `.gilab-ci.yml` file directly from the Pipeline Editor](https://gitlab.com/groups/gitlab-org/-/epics/5276). \n\n### Continuous validation\nOnce you have created the `.gitlab-ci.yml` file and navigated to it in the Pipeline Editor, you can begin editing your configuration. Writing YAML can be error prone. No matter how technical or skilled you are, programming mistakes happen. Sometimes an indentation will be missed, the incorrect syntax is used, or the wrong keyword is selected, and that's OK! As you start authoring your pipeline, GitLab will inspect the pipeline configuration using our linting APIs and provide you with an indicator of whether your pipeline configuration is valid or not. We will continuously validate your pipeline without making any changes to your pipeline configuration, so you can have confidence in hitting \"merge\" and running your pipeline without any surprises. \n\n![Continuous validation of pipelines](https://about.gitlab.com/images/blogimages/2020-02-08-Pipeline-editor-overview/image1.png){: .shadow.medium.center}\nContinuous validation of your pipelines\n{: .note.text-center}\n\n### Pipeline visualizer: Seeing is believing\nIt's practically impossible to envision what a pipeline should look like when you start writing from a blank YAML file. Luckily, GitLab provides you with a full pipeline view for every running pipeline. But, what if you want to visualize your pipeline _before_ they begin to run? Well, you can do that now by navigating to the \"Visualize\" tab in the Pipeline Editor. You'll find an illustration that shows how your pipeline should look as you write it, similar to the linter, and GitLab will display the visual before making any commits, before running, or before altering your pipeline in any way.\n\nIn the visualization, we will group all your defined pipeline jobs by stages and add links between the jobs based on the [needs](https://docs.gitlab.com/ee/ci/yaml/#needs) relationships you've configured.\n\nIf we take a look at the example below, you can easily see that I've configured a three-stage pipeline, where the build stage has three jobs (step 1-3), and that step 4 needs steps 1 and 3.\n\n![Pipeline editor overview](https://about.gitlab.com/images/blogimages/2020-02-08-Pipeline-editor-overview/image2.png){: .shadow.medium.center}\nPipeline visualizer\n{: .note.text-center}\n\nHere is what the YAML looks like:\n\n ```yaml\nimage: alpine:latest\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\nWhen configuring pipelines, you use keywords like 'include' and 'extends' often. These keywords help break down one long pipeline configuration file into multiple files, which increases readability and reduces duplication. Unfortunately, those keywords can make a pipeline configuration hard to follow. In some configurations, a pipeline configuration file can be mostly composed of a list of other included configuration files.\n\nTo make the configuration easier to follow, we've added the ability to view a version of your pipeline configuration with all of the 'includes' and 'extends' configurations merged together as a fourth tab in the Pipeline Editor. Now it's much easier to understand more complex pipeline flows and this simplifies the debugging process.\n\nPipeline configuration example:\n\n![pipeline configuration](https://about.gitlab.com/images/blogimages/2020-02-08-Pipeline-editor-overview/image6.png){: .shadow.medium.center}\n\nThe expanded version of the pipeline configuration:\n\n![expanded pipeline configuration](https://about.gitlab.com/images/blogimages/2020-02-08-Pipeline-editor-overview/image7.png){: .shadow.medium.center}\n\n### Lint\n\nThe CI lint helps you validate your pipeline configuration and provides you with additional information about it. That's why we've copied the existing CI linter (which was well hidden in our jobs page) to the Pipeline Editor as a third tab.\n\nThe linter provides you with detailed information about every job you've configured in your pipeline. For each job, it provides the [before_script](https://docs.gitlab.com/ee/ci/yaml/#before_script), [after_script](https://docs.gitlab.com/ee/ci/yaml/#after_script), and [script](https://docs.gitlab.com/ee/ci/yaml/#script) fields, tags, environment names, branches it should run, and more…\n\nIf you look at the following example, just by looking at the linter tab you'll know that the `prepare` job:\n* Runs in the `prepare` stage\n* Contains `before_script`, `script`, and `after_scripts` fields \n* Runs only on master \n* Runs upon failure\n* Tag as production\n* Has the environment set to production \n\n![image3](https://about.gitlab.com/images/blogimages/2020-02-08-Pipeline-editor-overview/image3.png){: .shadow.medium.center}\n\nIn this second example, you can see that the build job is a manual job that runs on all branches and is allowed to fail:\n\n![Manual build job](https://about.gitlab.com/images/blogimages/2020-02-08-Pipeline-editor-overview/image5.png){: .shadow.medium.center}\n\n## How the Pipeline Editor came about\n\nEarlier this year, we decided to split continuous integration into two separate teams: [Continuous Integration](/direction/verify/continuous_integration/), which is responsible for improving the experience of running a CI/CD pipeline, and [Pipeline Authoring](/direction/verify/pipeline_composition/), responsible for helping you author your pipeline. We've defined the Pipeline Authoring team goal as, \"Making the authoring experience as easy as possible for both advanced and novice users.\"\n\n![Verify Groups](https://about.gitlab.com/images/handbook/engineering/verify/verify_groups_banner.jpg){: .shadow.center}\n\nAs a team, we realized that a dedicated authoring area is needed to achieve our [ambitious roadmap](https://youtu.be/hInM7JUEH4Y) – this is when the Pipeline Editor idea was formed. \n\n## Try out Pipeline Editor yourself\n\nThat's it! I hope you found this overview useful. To get started with GitLab CI, you can [try out our hosted GitLab.com solution](/free-trial/), or you can [download GitLab Self-Managed](/free-trial/) and read its documentation for more in-depth coverage of the functionality. \n\nIf you are using our Pipeline Editor, we would love it if you leave us a note on our [feedback issue](https://gitlab.com/gitlab-org/gitlab/-/issues/298928)! If you'd like to learn more about the upcoming features, feel free to read through the [Pipeline Editor second iteration epic](https://gitlab.com/groups/gitlab-org/-/epics/4814), and tag `@dhershkovitch` if you have any questions.\n",[9,683,728,970],{"slug":2328,"featured":6,"template":686},"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":2334,"_dir":244,"_draft":6,"_partial":6,"_locale":7,"seo":2335,"content":2340,"config":2348,"_id":2350,"_type":14,"title":2351,"_source":16,"_file":2352,"_stem":2353,"_extension":19},"/en-us/blog/placebo-lines-on-the-pipeline-graph",{"title":2336,"description":2337,"ogTitle":2336,"ogDescription":2337,"noIndex":6,"ogImage":1674,"ogUrl":2338,"ogSiteName":673,"ogType":674,"canonicalUrls":2338,"schema":2339},"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":2336,"description":2337,"authors":2341,"heroImage":1674,"date":2343,"body":2344,"category":1075,"tags":2345},[2342],"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,1801,2346,2347],"agile","design",{"slug":2349,"featured":6,"template":686},"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":2355,"_dir":244,"_draft":6,"_partial":6,"_locale":7,"seo":2356,"content":2362,"config":2367,"_id":2369,"_type":14,"title":2370,"_source":16,"_file":2371,"_stem":2372,"_extension":19},"/en-us/blog/prevent-crypto-mining-abuse",{"title":2357,"description":2358,"ogTitle":2357,"ogDescription":2358,"noIndex":6,"ogImage":2359,"ogUrl":2360,"ogSiteName":673,"ogType":674,"canonicalUrls":2360,"schema":2361},"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":2357,"description":2358,"authors":2363,"heroImage":2359,"date":2364,"body":2365,"category":792,"tags":2366},[1461],"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-credit/#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,792,771],{"slug":2368,"featured":6,"template":686},"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":2374,"_dir":244,"_draft":6,"_partial":6,"_locale":7,"seo":2375,"content":2381,"config":2385,"_id":2387,"_type":14,"title":2388,"_source":16,"_file":2389,"_stem":2390,"_extension":19},"/en-us/blog/product-development-management",{"title":2376,"description":2377,"ogTitle":2376,"ogDescription":2377,"noIndex":6,"ogImage":2378,"ogUrl":2379,"ogSiteName":673,"ogType":674,"canonicalUrls":2379,"schema":2380},"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":2376,"description":2377,"authors":2382,"heroImage":2378,"date":1259,"body":2383,"category":1075,"tags":2384},[1115],"\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",[2347,9],{"slug":2386,"featured":6,"template":686},"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":2392,"_dir":244,"_draft":6,"_partial":6,"_locale":7,"seo":2393,"content":2399,"config":2407,"_id":2409,"_type":14,"title":2410,"_source":16,"_file":2411,"_stem":2412,"_extension":19},"/en-us/blog/production-grade-infra-devsecops-with-five-minute-production",{"title":2394,"description":2395,"ogTitle":2394,"ogDescription":2395,"noIndex":6,"ogImage":2396,"ogUrl":2397,"ogSiteName":673,"ogType":674,"canonicalUrls":2397,"schema":2398},"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":2400,"description":2395,"authors":2401,"heroImage":2396,"date":2403,"body":2404,"category":704,"tags":2405},"Production-grade infrastructure, GitOps convergence, and DevSecOps in under 5 minutes",[2402],"Sri Rangan","2021-02-24","\nThis blog post was originally published on the GitLab Unfiltered blog. It was reviewed and republished on 2021-03-10.\n{: .note .alert-info .text-center}\n\nThis is a story about achieving production-grade infrastructure in under five minutes.\\\\\nThis is a story about achieving production-grade DevSecOps in under five minutes.\\\\\nThis is a story about achieving total convergence of GitOps in under five minutes.\n\nMy name is Sri and over the last three months and I worked closely with GitLab co-founder [DZ](/company/team/#dzaporozhets) in building \"Five Minute Production App.\"\n\nThe app blends solutions offered by AWS, Hashicorp Terraform, and GitLab, and offers production-grade infrastructure and development workflows in under five minutes.\n\n![Five Minute Production App Diagram](https://about.gitlab.com/images/blogimages/five-min-prod-01-complete-flow.png){: .shadow.medium.center}\n\nApart from the efficiencies gained from using Five Minute Production App, you benefit by achieving stateful, production-ready infrastructure on the AWS hypercloud.\n\nWe started with AWS first, as it is the hypercoud leader today. Support for Azure and Google Cloud is on the roadmap.\n\nOur vision and design decisions are explained in the [README](https://gitlab.com/gitlab-org/5-minute-production-app/deploy-template#quickly).\n\n## Quickstart \n\nWe start with your GitLab project which has the source code of your web application. Regardless of which language or framework you use, your web application is packaged as a container image and stored within your GitLab project's Container Registry.\nThis is the Build stage.\n\nThis is followed by the Provision stage where Terraform scripts connect to AWS and create a secure environment for your web application.\nThe environments provisioned relate to your Git branching workflow.\nLong-lived Git branches create long-lived environments, and short-lived Git branches correspond to short-lived environments.\n\nResources provisioned include an Ubuntu VM, scalable PostgreSQL database, a Redis cluster, and S3 object storage.\nWe consider these elements as the building blocks for majority of web applications, and many of these fall under AWS free tier.\n\nThe infra state and credentials are stored within your GitLab project's managed Terraform state.\n\nFinally, we reach the Deploy stage which:\n1. Retrieves the deployable image from the GitLab Container Registry\n1. Retrieves the infrastructure credentials from the Gitlab Managed Terraform State, and\n1. Proceeds to deploy your web application\n\nEverything is achieved by including these two lines in your `.gitlab-ci.yml` file.\n\n```yaml\ninclude:\n  remote: https://gitlab.com/gitlab-org/5-minute-production-app/deploy-template/-/raw/stable/deploy.yml\n```\n\nLet's look at the complete process in more detail.\n\n![Three stages of Five Minute Production App](https://about.gitlab.com/images/blogimages/five-min-prod-02-pipeline.png){: .shadow.medium.center}\nThe three stages of Five Minute Production App\n{: .note.text-center}\n\n## Build and package\n\nThe Build stage is where it all begins. Five Minute Production App reuses the [Auto Build stage](https://docs.gitlab.com/ee/topics/autodevops/stages.html#auto-build) from the GitLab Auto DevOps pipeline.\n\nAuto Build builds and packages web applications that are:\n1. Containerized with a Dockerfile, or\n2. Compatible with the Cloud Native buildpack, or\n3. Compatible with the Heroku buildpack\n\nThus, web applications across multitudes of technologies are supported, including web frameworks such as Rails, Django, Express, Next.js, Spring, etc.\nand programming languages including Python, Java, Node.js, Ruby, Clojure, etc.\n\nOnce the Auto Build job has finished execution, the newly created container image is stored as an artifact in your GitLab project's Container Registry.\n\n## Provision the infrastructure\n\nThe next step, Provision, prepares infrastructure resources in AWS.\nThe first requirement here is the presence of AWS credentials stored as CI/CD variables at the project or group level.\nOnce valid AWS credentials are found, a Terraform script is executed to generate resources in AWS.\n\nThese resources include:\n1. EC2 VM based on Ubuntu 20.04 LTS\n2. PostgreSQL database managed by AWS RDS\n3. Redis cluster managed by AWS ElastiCache\n4. S3 bucket for file storage\n5. Email Service credentials managed by AWS SES\n\nThe most critical resource is the PostgreSQL service which has daily backups enabled.\nPostgreSQL data is snapshotted if the infrastructure resource is \"destroyed\" through a manual user action via the Five Minute Production App pipeline.\n\nThe EC2 VM is the only service accessible publicly. Ports 22, 80 and 443 are exposed.\nEvery other resource described above is part of a secure, private network, hidden from the public web, accessible ony via the EC2 instance and your web applicable deployed there.\n\nThe stateful services and environments are tied to your Git branches.\\\\\nThis means every Git branch creates a new environment with these resource sets.\\\\\nWe don't have a preference on your Git branching and environments lifecycle.\\\\\nUse long-lived or short-lived branches as you see fit, just keep in mind that long-lived branches leads to long-lived environments and short-lived branches leads to short-lived environments.\n\n![Infrastructure resources provisioned on AWS](https://about.gitlab.com/images/blogimages/five-min-prod-03-infra-resources.png){: .shadow.medium.center}\nInfrastructure resources provisioned on AWS\n{: .note.text-center}\n\n## Deploy your web application\n\nFinally comes the Deploy stage.\n\nThis is where the deploy script retrieves your web application package (container image) from the GitLab Container Registry, then retrieves the EC2 instance\ncredentials from the GitLab Managed Terraform State, and proceeds to deploy the relevant version of your web application in its environment.\n\nModern web applications might require additional commands being executed after each deployment or after the initial deployment,\nand these commands can be defined as variables in your `.gitlab-ci.yml` file.\n\nFinally, with the help of Certbot from Letsencrypt, SSL certificates are generated and configured for your web application.\nIf you have defined the `CERT_DOMAIN` CI/CD variable the SSL certificate will be generated for your custom domain name.\nOtherwise the generated SSL certificate uses a dynamic URL that Five Minute Production App prepares for you.\n\n## Conclusion\n\nThere we have it. A simple yet production-ready setup for your web application. If you are looking for an AWS-based setup, this is ready for usage.\n\nIf you are looking for something similar but not quite Five Minute Production App, this serves as an example of how to converge infrastructure-as-code with software development and provide seamless continuous deployment workflows.\n\nIn my personal experience, this is one of the most complete examples of GitOps:\n\n1. Your application source code lives in your GitLab project\n2. Your infrastructure defined as code lives in your GitLab project\n3. Your CI/CD pipeline lives in your GitLab project\n4. Your infrastructure state lives in your GitLab project\n5. Your infrastructure secrets and credentials live in your GitLab project\n6. Your environments configuration lives in your GitLab project\n\nThis complete GitOps convergence is not specifically configured for one project. It can be included as a template from multiple projects.\nThere is no reason why the GitLab project in your organization cannot be the single source of truth for everything.\n\n### Links\n\n- [Five Minute Production App](https://gitlab.com/gitlab-org/5-minute-production-app/deploy-template/-/blob/master/README.md)\n- [Reference Examples](https://gitlab.com/gitlab-org/5-minute-production-app/examples)\n\n### About the author\n\n[Sri Rangan](mailto:sri@gitlab.com), an Enterprise Solutions Architect with GitLab, is a core-contributor and maintainer\nof [Five Minute Production App](https://gitlab.com/gitlab-org/5-minute-production-app/deploy-template/-/blob/master/README.md).",[9,683,728,1120,537,2406],"production",{"slug":2408,"featured":6,"template":686},"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":2414,"_dir":244,"_draft":6,"_partial":6,"_locale":7,"seo":2415,"content":2421,"config":2426,"_id":2428,"_type":14,"title":2429,"_source":16,"_file":2430,"_stem":2431,"_extension":19},"/en-us/blog/progressive-delivery-using-review-apps",{"title":2416,"description":2417,"ogTitle":2416,"ogDescription":2417,"noIndex":6,"ogImage":2418,"ogUrl":2419,"ogSiteName":673,"ogType":674,"canonicalUrls":2419,"schema":2420},"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":2416,"description":2417,"authors":2422,"heroImage":2418,"date":2423,"body":2424,"category":704,"tags":2425},[1581],"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,970,9],{"slug":2427,"featured":6,"template":686},"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":2433,"_dir":244,"_draft":6,"_partial":6,"_locale":7,"seo":2434,"content":2440,"config":2446,"_id":2448,"_type":14,"title":2449,"_source":16,"_file":2450,"_stem":2451,"_extension":19},"/en-us/blog/provision-group-runners-with-google-cloud-platform-and-gitlab-ci",{"title":2435,"description":2436,"ogTitle":2435,"ogDescription":2436,"noIndex":6,"ogImage":2437,"ogUrl":2438,"ogSiteName":673,"ogType":674,"canonicalUrls":2438,"schema":2439},"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":2435,"description":2436,"authors":2441,"heroImage":2437,"date":2443,"body":2444,"category":704,"tags":2445},[2442,768],"Sarah Matthies","2024-11-19","Are you interested in hosting your own servers to run your GitLab CI/CD pipelines but don’t know where to begin? Setting up a GitLab Runner to run your pipelines on your own infrastructure can seem like a daunting task as it requires infrastructure knowledge and the know-how to maintain that infrastructure. Typically this process requires the provision of infrastructure, the installing of dependency, and testing that it works with your GitLab instance.\n\nThis article highlights how easy it is to easily spin up a GitLab Runner of your own utilizing GitLab’s Google Cloud Integration. Follow this tutorial and it will teach you how to set up a new group runner on GitLab.com using Google Cloud Platform in less than 10 minutes!\n\nYou will learn how to:\n\n- Create a new group runner.\n- Configure the new group runner’s tags and description.\n- Register the new group runner by adding in configurations.\n- Provision the GitLab Runner utilizing `gcloud cli` and Terraform.\n- Have your GitLab Runner pick up its first GitLab CI job.\n\n## Prerequisites\n- A terminal with Bash installed\n- Owner access on a Google Cloud Platform project\n- Terraform (or OpenTofu) [Version 1.5](https://releases.hashicorp.com/terraform/1.5.7/) or greater \n- [gcloud CLI](https://cloud.google.com/sdk/docs/install) \n- 10 minutes\n\n## Tutorial\n1. Create a new group runner under __Build > Runners > New Group Runner__.\n\n__Note:__ Navigate to the group level.\n\n![GitLab Runner setup screen](https://res.cloudinary.com/about-gitlab-com/image/upload/v1750098317/Blog/Content%20Images/Blog/Content%20Images/image7_aHR0cHM6_1750098317126.png)\n\n2. Configure the new group runner's tags, description, and any additional configurations.\n\n![New Group Runner setup](https://res.cloudinary.com/about-gitlab-com/image/upload/v1750098317/Blog/Content%20Images/Blog/Content%20Images/image4_aHR0cHM6_1750098317127.png)\n\n3. Select __Google Cloud__.\n\n![Select Google Cloud screen](https://res.cloudinary.com/about-gitlab-com/image/upload/v1750098317/Blog/Content%20Images/Blog/Content%20Images/image3_aHR0cHM6_1750098317129.png)\n\n4. Copy your project ID from Google Cloud Platform.\n\n![Copy project ID from GCP screen](https://res.cloudinary.com/about-gitlab-com/image/upload/v1750098317/Blog/Content%20Images/Blog/Content%20Images/image1_aHR0cHM6_1750098317131.png)\n\n5. Fill out your Google Cloud project ID and choose a region, zone, and type of machine you want to use.\n\n![Screen to fill out Google Cloud information](https://res.cloudinary.com/about-gitlab-com/image/upload/v1750098317/Blog/Content%20Images/Blog/Content%20Images/image8_aHR0cHM6_1750098317132.png)\n\n6\\. Once this information is filled out, click **Setup instructions**.\n\nRun the bash script provided in Step 1 above.\n\n**Note:** This script was saved to a file called `setup.sh` for ease of use. You may copy this right into your terminal if you are confident in debugging.\n\n![Setup instructions screen](https://res.cloudinary.com/about-gitlab-com/image/upload/v1750098317/Blog/Content%20Images/Blog/Content%20Images/image6_aHR0cHM6_1750098317134.png)\n\n![Script for GitLab Runner](https://res.cloudinary.com/about-gitlab-com/image/upload/v1750098317/Blog/Content%20Images/Blog/Content%20Images/image10_aHR0cHM6_1750098317135.png)\n\n7\\. Create a `main.tf` file and follow the instructions in GitLab.\n\n**Note:** If you want to use OpenTofu instead of Terraform, you can still copy the code and only have to adjust the Terraform commands for applying the configuration. \n\n![Install and register GitLab Runner screen](https://res.cloudinary.com/about-gitlab-com/image/upload/v1750098317/Blog/Content%20Images/Blog/Content%20Images/image9_aHR0cHM6_1750098317136.png)\n\nOnce successfully provisioned, you should be see the following:\n\n![GitLab Runner code](https://res.cloudinary.com/about-gitlab-com/image/upload/v1750098317/Blog/Content%20Images/Blog/Content%20Images/image5_aHR0cHM6_1750098317137.png)\n\n8\\. If you close the instructions and click the **View runners** button, you will now have a newly provisioned runner present with \"Never contacted\" as its status.\n\n![Newly provisioned runner on screen](https://res.cloudinary.com/about-gitlab-com/image/upload/v1750098317/Blog/Content%20Images/Blog/Content%20Images/image2_aHR0cHM6_1750098317139.png)\n\n9\\. In any project, add the following `.gitlab-ci.yml`.\n\n```  \nstages:  \n  - greet\n\nhello_job:  \n  stage: greet  \n  tags:  \n    - gcp-runner  \n  script:  \n    - echo \"hello\"  \n```\n\nVolia! You have set up your first GitLab Runner utilizing Google Cloud Platform.\n\n# Next steps\n\nNow that you have provisioned your very own GitLab Runner, consider optimizing it for your specific use case. Some things to consider with your runner moving forward:\n\n- Is the runner I provisioned the right size? Does it need additional resources for my use case? \n- Does the GitLab Runner contain all the dependency my builds need?  \n- How can I store the GitLab Runner as infrastructure as code?\n\n> Make sure to bookmark the [Provisioning runners in Google Cloud documentation](https://docs.gitlab.com/ee/ci/runners/provision_runners_google_cloud.html) for easy reference.\n",[729,482,9,109,707,947,231],{"slug":2447,"featured":6,"template":686},"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":2453,"_dir":244,"_draft":6,"_partial":6,"_locale":7,"seo":2454,"content":2460,"config":2466,"_id":2468,"_type":14,"title":2469,"_source":16,"_file":2470,"_stem":2471,"_extension":19},"/en-us/blog/public-project-minute-limits",{"title":2455,"description":2456,"ogTitle":2455,"ogDescription":2456,"noIndex":6,"ogImage":2457,"ogUrl":2458,"ogSiteName":673,"ogType":674,"canonicalUrls":2458,"schema":2459},"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":2455,"description":2456,"authors":2461,"heroImage":2457,"date":2462,"body":2463,"category":792,"tags":2464},[1072],"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-credit/).\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,683,2465],"contributors",{"slug":2467,"featured":6,"template":686},"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":2473,"_dir":244,"_draft":6,"_partial":6,"_locale":7,"seo":2474,"content":2479,"config":2485,"_id":2487,"_type":14,"title":2488,"_source":16,"_file":2489,"_stem":2490,"_extension":19},"/en-us/blog/qpage-on-the-devops-platform",{"title":2475,"description":2476,"ogTitle":2475,"ogDescription":2476,"noIndex":6,"ogImage":1910,"ogUrl":2477,"ogSiteName":673,"ogType":674,"canonicalUrls":2477,"schema":2478},"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":2480,"description":2476,"authors":2481,"heroImage":1910,"date":2482,"body":2483,"category":1118,"tags":2484},"How QPage achieved automatic deployment and efficiency using the GitLab DevOps Platform",[1461],"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.",[728,994,9],{"slug":2486,"featured":6,"template":686},"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":2492,"_dir":244,"_draft":6,"_partial":6,"_locale":7,"seo":2493,"content":2498,"config":2504,"_id":2506,"_type":14,"title":2507,"_source":16,"_file":2508,"_stem":2509,"_extension":19},"/en-us/blog/second-law-of-complexity-dynamics",{"title":2494,"description":2495,"ogTitle":2494,"ogDescription":2495,"noIndex":6,"ogImage":2359,"ogUrl":2496,"ogSiteName":673,"ogType":674,"canonicalUrls":2496,"schema":2497},"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":2499,"description":2495,"authors":2500,"heroImage":2359,"date":2501,"body":2502,"category":704,"tags":2503},"When the pursuit of simplicity creates complexity in container-based CI pipelines",[678],"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,683,729,1403,707],{"slug":2505,"featured":6,"template":686},"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":2511,"_dir":244,"_draft":6,"_partial":6,"_locale":7,"seo":2512,"content":2518,"config":2524,"_id":2526,"_type":14,"title":2527,"_source":16,"_file":2528,"_stem":2529,"_extension":19},"/en-us/blog/secure-and-publish-python-packages-a-guide-to-ci-integration",{"title":2513,"description":2514,"ogTitle":2513,"ogDescription":2514,"noIndex":6,"ogImage":2515,"ogUrl":2516,"ogSiteName":673,"ogType":674,"canonicalUrls":2516,"schema":2517},"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":2513,"description":2514,"authors":2519,"heroImage":2515,"date":2521,"body":2522,"category":771,"tags":2523},[2520],"Tim Rizzi","2025-01-21","Supply chain security is a critical concern in software development. Organizations need to verify the authenticity and integrity of their software packages. This guide will show you how to implement a secure CI/CD pipeline for Python packages using GitLab CI, incorporating package signing and attestation using Sigstore's Cosign.\n\nYou'll learn:\n\n- [Why sign and attest your Python packages?](#why-sign-and-attest-your-python-packages%3F)\n- [Pipeline overview](#pipeline-overview)\n- [Complete pipeline implementation: Setting up the environment](#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\nHere are four reasons to sign and attest your Python packages:\n\n* **Supply chain security:** Package signing ensures that the code hasn't been tampered with between build and deployment, protecting against supply chain attacks.\n* **Compliance requirements:** Many organizations, especially in regulated industries, require cryptographic signatures and provenance information for all deployed software.\n* **Traceability:** Attestations provide a verifiable record of build conditions, including who built the package and under what circumstances.\n* **Trust verification:** Consumers of your package can cryptographically verify its authenticity before installation.\n\n## Pipeline overview\n\nEnsuring your code's integrity and authenticity is necessary. Imagine a pipeline that doesn't just compile your code but creates a cryptographically verifiable narrative of how, when, and by whom your package was created. Each stage acts as a guardian, checking and documenting the package's provenance.\n\nHere are six stages of a GitLab pipeline that ensure your package is secure and trustworthy:\n\n* Build: Creates a clean, standard package that can be easily shared and installed.\n* Signing: Adds a digital signature that proves the package hasn't been tampered with since it was created.\n* Verification: Double-checks that the signature is valid and the package meets all our security requirements.\n* Publishing: Uploads the verified package to GitLab's package registry, making it available for others to use.\n* Publishing Signatures: Makes signatures available for verification.\n* Consumer Verification: Simulates how end users can verify package authenticity.\n\n## Complete pipeline implementation: Setting up the environment\n\nBefore we build our package, we need to set up a consistent and secure build environment. This configuration ensures every package is created with the same tools, settings, and security checks.\n\n### Environment configuration\n\nOur pipeline requires specific tools and settings to work correctly.\n\nPrimary configurations:\n\n* Python 3.10 for consistent builds\n* Cosign 2.2.3 for package signing\n* GitLab package registry integration\n* Hardcoded package version for reproducibility\n\n**Note about versioning:** We've chosen to use a hardcoded version (`\"1.0.0\"`) in this example rather than deriving it from git tags or commits. This approach ensures complete reproducibility and makes the pipeline behavior more predictable. In a production environment, you might want to use semantic versioning based on git tags or another versioning strategy that fits your release process.\n\nTool requirements:\n\n* Basic utilities: `curl`, `wget`\n* Cosign for cryptographic signing\n* Python packaging tools: `build`, `twine`, `setuptools`, `wheel`\n\n### Configuration breakdown\n\n```yaml\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\nWe use caching to speed up subsequent builds:\n\n```yaml\ncache:\n  paths:\n    - ${PIP_CACHE_DIR}\n```\n\n## Building: Crafting the package\n\nEvery software journey begins with creation. In our pipeline, the build stage is where raw code transforms into a distributable package, ready to travel across different Python environments.\n\nThe build process creates two standardized formats:\n\n* a wheel package (.whl) for quick, efficient installation\n* a source distribution (.tar.gz) that carries the complete code\n\nHere's the build stage implementation:\n\n```yaml\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\nLet's break down what this build stage does:\n\n1. Initializes a Git repository (`git init`) and configures it with basic settings\n2. Normalizes the package name by converting hyphens to underscores, which is required for Python packaging\n3. Updates the package metadata in `pyproject.toml` to match our project settings\n4. Builds both wheel and source distribution packages using `python -m build`\n5. Preserves the built packages and configuration as artifacts for subsequent stages\n\n## Signing: The digital notarization\n\nIf attestation is the package's biography, signing is its cryptographic seal of authenticity. This is where we transform our package from a mere collection of files into a verified, tamper-evident artifact.\n\nThe signing stage uses Cosign to apply a digital signature as an unbreakable seal. This isn't just a stamp — it's a complex cryptographic handshake that proves the package's integrity and origin.\n\n```yaml\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\nThis signing stage performs several crucial operations:\n\n1. Obtains an OIDC token from GitLab for authentication with Sigstore services\n2. Processes each built package (both wheel and source distribution)\n3. Uses Cosign to create a cryptographic signature (`.sig`) for each package\n4. Generates a certificate (`.crt`) that proves the signature's authenticity\n5. Stores both signatures and certificates alongside the packages as artifacts\n\n## Verification: The security checkpoint\n\nVerification is our final quality control gate. It's not just a check — it's a security interrogation where every aspect of the package is scrutinized.\n\n```yaml\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\nThe verification stage implements several security checks:\n\n1. Examines each package file in the `dist` directory\n2. Uses Cosign to verify the signature matches the package content\n3. Confirms the certificate's identity matches our expected GitLab pipeline identity\n4. Validates our trusted OIDC provider issued the certificate\n5. Fails the entire pipeline if any verification check fails, ensuring only verified packages proceed\n\n## Publishing: The controlled release\n\nPublishing is where we make our verified packages available through GitLab's package registry. It's a carefully choreographed release that ensures only verified, authenticated packages reach their destination.\n\n```yaml\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\nThe publishing stage handles several important tasks:\n\n1. Creates a `.pypirc` configuration file with GitLab package registry credentials\n2. Uses the GitLab CI job token for secure authentication\n3. Uploads both wheel and source distribution packages to the GitLab PyPI registry\n4. Makes the packages available for installation via pip\n\n## Publishing signatures: Making verification possible\n\nAfter publishing the packages, we must make their signatures and certificates available for verification. We store these in GitLab's generic package registry, making them easily accessible to users who want to verify package authenticity.\n\n```yaml\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\nThe signature publishing stage performs these key operations:\n\n1. Processes each built package to find its corresponding signature files\n2. Uses the GitLab API to upload the signature (`.sig`) file to the generic package registry\n3. Uploads the corresponding certificate (`.crt`) file\n4. Makes these verification artifacts available for downstream package consumers\n5. Uses the same version and package name to maintain the connection between packages and signatures\n\n## Consumer verification: Testing the user experience\n\nThe final stage simulates how end users will verify your package's authenticity. This stage acts as a final check and a practical example of the verification process.\n\n```yaml\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\nThis consumer verification stage simulates the end-user experience by:\n\n1. Creating a clean environment to test package installation\n2. Downloading the published packages from the GitLab PyPI registry\n3. Retrieving the corresponding signatures and certificates from the generic package registry\n4. Performing the same verification steps that end users would perform\n5. Ensuring the entire process works from a consumer's perspective\n6. Failing the pipeline if any verification step fails, providing an early warning of any issues\n\n## Summary\n\nThis comprehensive pipeline provides a secure and reliable way to build, sign, and publish Python packages to GitLab's package registry. By following these practices and implementing the suggested security measures, you can ensure your packages are appropriately verified and safely distributed to your users.\n\nThe pipeline combines modern security practices with efficient automation to create a robust software supply chain. Using Sigstore's Cosign for signing and attestation, along with GitLab's built-in security features, you can provide users with trustworthy cryptographically verified packages.\n\n> #### Get started on your security journey today with a [free 60-day trial of GitLab Ultimate](https://gitlab.com/-/trials/new?glm_content=default-saas-trial&glm_source=about.gitlab.com).\n\n## Learn more\n- [Documentation: Use Sigstore for keyless signing and verification](https://docs.gitlab.com/ee/ci/yaml/signing_examples.html)\n- [Streamline security with keyless signing and verification in GitLab](https://about.gitlab.com/blog/keyless-signing-with-cosign/)\n- [Annotate container images with build provenance using Cosign in GitLab CI/CD](https://about.gitlab.com/blog/annotate-container-images-with-build-provenance-using-cosign-in-gitlab-ci-cd/)",[771,231,282,970,9,109,482,729,707],{"slug":2525,"featured":91,"template":686},"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":2531,"_dir":244,"_draft":6,"_partial":6,"_locale":7,"seo":2532,"content":2538,"config":2543,"_id":2545,"_type":14,"title":2546,"_source":16,"_file":2547,"_stem":2548,"_extension":19},"/en-us/blog/simple-kubernetes-management-with-gitlab",{"title":2533,"description":2534,"ogTitle":2533,"ogDescription":2534,"noIndex":6,"ogImage":2535,"ogUrl":2536,"ogSiteName":673,"ogType":674,"canonicalUrls":2536,"schema":2537},"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":2533,"description":2534,"authors":2539,"heroImage":2535,"date":2540,"body":2541,"category":704,"tags":2542},[768],"2022-11-15","\n\nKubernetes can be very complex and has dozens of tutorials out there on how to provision and manage a cluster. This tutorial aims to provide a simple, lightweight solution to provision a Kubernetes cluster and manage it with infrastructure as code (IaC) using Terraform and Helm in 20 minutes or less.\n\n**The final product of this tutorial will be two IaC repositories with fully functional CI/CD pipelines:**\n\n1. [gitlab-terraform-k8s](https://gitlab.com/gitlab-org/configure/examples/gitlab-terraform-eks) - A single source of truth to provision, configure, and manage your Kubernetes infrastructure using Terraform\n1. [cluster-management](https://gitlab.com/gitlab-org/project-templates/cluster-management) - A single source of truth to define the desired state of your Kubernetes cluster using the GitLab Agent for Kubernetes and Helm\n\n![Final Product](https://about.gitlab.com/images/blogimages/2022-11-11-simple-kubernetes-management-with-gitlab/final-product.png){: .shadow}\n\n\n### Prerequisites\n- AWS or GCP account with permissions to provision resources\n- GitLab account \n- Access to a GitLab Runner\n- 20 minutes\n\n### An overview of this tutorial is as follows:\n\n1. Set up the GitLab Terraform Kubernetes Template 🏗️\n2. Register the GitLab Agent 🕵️\n3. Add in Cloud Credentials ☁️🔑\n4. Set up the Kubernetes Cluster Management Template 🚧\n5. Enjoy your Kubernetes Cluster completely managed in code! 👏\n\n## Set up the GitLab Terraform Kubernetes Template\n\nStart by importing the example project by URL - [https://gitlab.com/projects/new#import_project](https://gitlab.com/projects/new#import_project)\n\nTo import the project:\n\n1. In GitLab, on the top bar, select **Main menu > Projects > View all projects**.\n2. On the right of the page, select **New project**.\n3. Select **Import project**.\n4. Select **Repository by URL**.\n5. For the Git repository URL:\n- [GCP Google Kubernetes Engine](https://cloud.google.com/kubernetes-engine): https://gitlab.com/gitlab-org/configure/examples/gitlab-terraform-gke.git\n- [AWS Elastic Kubernetes Service](https://aws.amazon.com/eks/): https://gitlab.com/gitlab-org/configure/examples/gitlab-terraform-eks.git\n6. Complete the fields and select **Create project**.\n\n## Register the GitLab Agent\n\nWith your newly created **gitlab-terraform-k8s** repo, create a GitLab Agent for Kubernetes:\n\n1. On the left sidebar, select **Infrastructure > Kubernetes clusters**. Select **Connect a cluster (agent).**\n2. From the **Select an agent** dropdown list, select **eks-agent/gke-agent and select **Register an agent**.\n3. GitLab generates a registration token for the agent. **Securely store this secret token, as you will need it later.**\n4. GitLab provides an address for the agent server (KAS). Securely store this as you will also need it later.\n5. Add this to the **gitlab-terraform-eks/.gitlab/agents/eks-agent/config.yaml** in order to allow the GitLab Agent to have access to your entire group.\n\n```yaml\nci_access:\n  groups:\n    - id: your-namespace-here\n```\n\n![Register GitLab Agent](https://about.gitlab.com/images/blogimages/2022-11-11-simple-kubernetes-management-with-gitlab/register-gitlab-agent.png){: .shadow}\n\n\n## Add in your Cloud Credentials to CI/CD variables\n\n### [AWS EKS](https://aws.amazon.com/eks/)\n\nOn the left sidebar, select **Settings > CI/CD. Expand Variables**.\n1. Set the variable **AWS_ACCESS_KEY_ID** to your AWS access key ID.\n2. Set the variable **AWS_SECRET_ACCESS_KEY** to your AWS secret access key.\n3. Set the variable **TF_VAR_agent_token** to the agent token displayed in the previous task.\n4. Set the variable **TF_VAR_kas_address** to the agent server address displayed in the previous task.\n\n![Add in CI/CD variables](https://about.gitlab.com/images/blogimages/2022-11-11-simple-kubernetes-management-with-gitlab/cicd-variables.png){: .shadow}\n\n\n### [GCP GKE](https://cloud.google.com/kubernetes-engine)\n\n1. To authenticate GCP with GitLab, create a GCP service account with the following roles: **Compute Network Viewer, Kubernetes Engine Admin, Service Account User, and Service Account Admin**. Both User and Admin service accounts are necessary. The User role impersonates the default service account when creating the node pool. The Admin role creates a service account in the kube-system namespace.\n2. **Download the JSON file** with the service account key you created in the previous step.\n3. On your computer, encode the JSON file to base64 (replace /path/to/sa-key.json to the path to your key):\n\n```\nbase64 -i /path/to/sa-key.json | tr -d\n```\n\n- Use the output of this command as the **BASE64_GOOGLE_CREDENTIALS** environment variable in the next step.\n\nOn the left sidebar, select **Settings > CI/CD. Expand Variables**.\n5. Set the variable **BASE64_GOOGLE_CREDENTIALS** to the base64 encoded JSON file you just created.\n6. Set the variable **TF_VAR_gcp_project** to your GCP’s project name.\n7. Set the variable **TF_VAR_agent_token** to the agent token displayed in the previous task.\n8. Set the variable **TF_VAR_kas_address** to the agent server address displayed in the previous task.\n\n## Run GitLab CI to deploy your Kubernetes cluster!\n\n![Deploy Kubernetes cluster](https://about.gitlab.com/images/blogimages/2022-11-11-simple-kubernetes-management-with-gitlab/pipeline-view.png){: .shadow}\n\nWhen successfully completed, view the cluster in the AWS/GCP console!\n\n![AWS EKS](https://about.gitlab.com/images/blogimages/2022-11-11-simple-kubernetes-management-with-gitlab/aws-eks.png){: .shadow}\n\n### You are halfway done! 👏 Keep it up!\n\n## Set up the Kubernetes Cluster Management Project\n\nCreate a project from the cluster management project template - [https://gitlab.com/projects/new#create_from_template](https://gitlab.com/projects/new#create_from_template)\n\n1. In GitLab, on the top bar, select **Main menu > Projects > View all projects**.\n2. On the right of the page, select **New project**.\n3. Select **Create from template**.\n4. From the list of templates, next to **GitLab Cluster Management**, select **Use template**.\n5. Enter the project details. Ensure this project is created in the same namespace as the gitlab-terraform-k8s project.\n6. Select **Create project**.\n7. Once the project is created on the left sidebar, select **Settings > CI/CD. Expand Variables**.\n8. Set the variable KUBE_CONTEXT to point to the GitLab Agent. For example, `noah-ing-demos/infrastructure/gitlab-terraform-eks:eks-agent`.\n\n![Set Kube Context](https://about.gitlab.com/images/blogimages/2022-11-11-simple-kubernetes-management-with-gitlab/kube-config.png){: .shadow}\n\n\n- **Uncomment the applications you'd like to be installed** into your Kubernetes cluster in the **helmfile.yaml**. In this instance I chose ingress, cert-manager, prometheus, and Vault. \n\n![Uncomment Applications in helmfile](https://about.gitlab.com/images/blogimages/2022-11-11-simple-kubernetes-management-with-gitlab/helmfile.png){: .shadow}\n\nThat will trigger your **CI/CD pipeline** and it should look like this.\n\n![Cluster Management CI/CD](https://about.gitlab.com/images/blogimages/2022-11-11-simple-kubernetes-management-with-gitlab/cluster-management-cicd.png){: .shadow}\n\nOnce completed, **go to the AWS/GCP console** and check out all the deployed resources!\n\n![Deployed EKS applications](https://about.gitlab.com/images/blogimages/2022-11-11-simple-kubernetes-management-with-gitlab/deployed-eks-applications.png){: .shadow}\n\n### Voila! 🎉\n\n## Enjoy your Kubernetes cluster completely defined in code! 👏👏👏\n\nNow with these two repositories you can **manage a Kubernetes cluster entirely through code**:\n\n- For managing the Kubernetes cluster's infrastructure and configuring its resources you can make changes to the [gitlab-terraform-eks](https://gitlab.com/gitlab-org/configure/examples/gitlab-terraform-eks) repository you have setup. This project has a **Terraform CI/CD pipeline** that will allow you to **review, provision, configure, and manage your Kubernetes** infrastructure with ease.\n\n- For managing the desired state of the Kubernetes cluster, the [cluster-management](https://gitlab.com/gitlab-org/project-templates/cluster-management) repository has a **GitLab Agent** set up and will **deploy any Kubernetes objects defined in the helm files**.\n\n➡️ Bonus: If you'd like to deploy your own application to the Kubernetes cluster, then add to your **cluster-management** `helmfile` and see the GitLab Agent for Kubernetes roll it out with ease!\n\n\n## References\n- [Create a New GKE Cluster](https://docs.gitlab.com/ee/user/infrastructure/clusters/connect/new_gke_cluster.html)\n- [Create a New EKS Cluster](https://docs.gitlab.com/ee/user/infrastructure/clusters/connect/new_eks_cluster.html)\n- [Cluster Management Project](https://docs.gitlab.com/ee/user/clusters/management_project.html)\n\n\n## Related posts\n- [The ultimate guide to GitOps with GitLab](https://about.gitlab.com/blog/the-ultimate-guide-to-gitops-with-gitlab/)\n- [GitOps with GitLab: Infrastructure provisioning with GitLab and Terraform](https://about.gitlab.com/blog/gitops-with-gitlab-infrastructure-provisioning/)\n- [GitOps with GitLab: Connect with a Kubernetes cluster](https://about.gitlab.com/blog/gitops-with-gitlab-connecting-the-cluster/)\n",[729,901,537,9,683,728],{"slug":2544,"featured":6,"template":686},"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":2550,"_dir":244,"_draft":6,"_partial":6,"_locale":7,"seo":2551,"content":2556,"config":2561,"_id":2563,"_type":14,"title":2564,"_source":16,"_file":2565,"_stem":2566,"_extension":19},"/en-us/blog/stageless-pipelines",{"title":2552,"description":2553,"ogTitle":2552,"ogDescription":2553,"noIndex":6,"ogImage":1674,"ogUrl":2554,"ogSiteName":673,"ogType":674,"canonicalUrls":2554,"schema":2555},"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":2552,"description":2553,"authors":2557,"heroImage":1674,"date":2558,"body":2559,"category":704,"tags":2560},[1137],"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,683],{"slug":2562,"featured":6,"template":686},"stageless-pipelines","content:en-us:blog:stageless-pipelines.yml","Stageless Pipelines","en-us/blog/stageless-pipelines.yml","en-us/blog/stageless-pipelines",{"_path":2568,"_dir":244,"_draft":6,"_partial":6,"_locale":7,"seo":2569,"content":2575,"config":2581,"_id":2583,"_type":14,"title":2584,"_source":16,"_file":2585,"_stem":2586,"_extension":19},"/en-us/blog/the-kubecon-summary-from-a-product-perspective",{"title":2570,"description":2571,"ogTitle":2570,"ogDescription":2571,"noIndex":6,"ogImage":2572,"ogUrl":2573,"ogSiteName":673,"ogType":674,"canonicalUrls":2573,"schema":2574},"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":2570,"description":2571,"authors":2576,"heroImage":2572,"date":2578,"body":2579,"category":1118,"tags":2580},[2577],"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",[901,9,683,537,1120,728],{"slug":2582,"featured":6,"template":686},"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":2588,"_dir":244,"_draft":6,"_partial":6,"_locale":7,"seo":2589,"content":2595,"config":2601,"_id":2603,"_type":14,"title":2604,"_source":16,"_file":2605,"_stem":2606,"_extension":19},"/en-us/blog/thelastmile-gitlab",{"title":2590,"description":2591,"ogTitle":2590,"ogDescription":2591,"noIndex":6,"ogImage":2592,"ogUrl":2593,"ogSiteName":673,"ogType":674,"canonicalUrls":2593,"schema":2594},"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":2590,"description":2591,"authors":2596,"heroImage":2592,"date":2598,"body":2599,"category":681,"tags":2600},[2597],"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",[903,992,991,902,9],{"slug":2602,"featured":6,"template":686},"thelastmile-gitlab","content:en-us:blog:thelastmile-gitlab.yml","Thelastmile Gitlab","en-us/blog/thelastmile-gitlab.yml","en-us/blog/thelastmile-gitlab",{"_path":2608,"_dir":244,"_draft":6,"_partial":6,"_locale":7,"seo":2609,"content":2615,"config":2621,"_id":2623,"_type":14,"title":2624,"_source":16,"_file":2625,"_stem":2626,"_extension":19},"/en-us/blog/tracking-down-missing-tcp-keepalives",{"title":2610,"description":2611,"ogTitle":2610,"ogDescription":2611,"noIndex":6,"ogImage":2612,"ogUrl":2613,"ogSiteName":673,"ogType":674,"canonicalUrls":2613,"schema":2614},"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":2616,"description":2611,"authors":2617,"heroImage":2612,"date":2618,"body":2619,"category":704,"tags":2620},"What tracking down missing TCP Keepalives taught me about Docker, Golang, and GitLab",[1601],"2019-11-15","\n\nThis blog post was originally published on the GitLab Unfiltered blog. It was reviewed and republished on 2019-12-03.\n{: .alert .alert-info .note}\n\nWhat began as failure in a GitLab static analysis check led to a\ndizzying investigation that uncovered a subtle [bug in the Docker client\nlibrary code](https://github.com/docker/for-linux/issues/853) used by\nthe GitLab Runner. We ultimately worked around the problem by upgrading\nthe Go compiler, but in the process we uncovered an unexpected change in\nthe Go TCP keepalive defaults that fixed an issue with Docker and GitLab\nCI.\n\nThis investigation started on October 23, when backend engineer [Luke\nDuncalfe](/company/team/#.luke) mentioned, \"I'm seeing\n[`static-analysis` failures with no output](https://gitlab.com/gitlab-org/gitlab/-/jobs/331174397).\nIs there something wrong with this job?\" He opened [a GitLab\nissue](https://gitlab.com/gitlab-org/gitlab/issues/34951) to discuss.\n\nWhen Luke ran the static analysis check locally on his laptop, he saw\nuseful debugging output when the test failed. For example, an extraneous\nnewline would accurately be reported by Rubocop. However, when the same\ntest ran in GitLab's automated test infrastructure, the test failed\nquietly:\n\n![Failed job](https://about.gitlab.com/images/blogimages/docker-tcp-keepalive-debug/job-failure.png){: .shadow.center}\n\nNotice how the job log did not include any clues after the `bin/rake\nlint:all` step. This made it difficult to determine whether a real\nproblem existed, or whether this was just a flaky test.\n\nIn the ensuing days, numerous team members reported the same problem.\nNothing kills productivity like silent test failures.\n\n## Was something wrong with the test itself?\n\nIn the past, we had seen that if that specific test generated enough\nerrors, [the output buffer would fill up, and the continuous integration\n(CI) job would lock\nindefinitely](https://gitlab.com/gitlab-org/gitlab-foss/issues/61432). We\nthought we had [fixed that issue months\nago](https://gitlab.com/gitlab-org/gitlab-foss/merge_requests/28402). Upon\nfurther review, that fix seemed to eliminate any chance of a thread\ndeadlock.\n\nDid we have to flush the buffer? No, because the Linux kernel will do\nthat for an exiting process already.\n\n## Was there a change in how CI logs were handled?\n\nWhen a test runs in GitLab CI, the [GitLab\nRunner](https://gitlab.com/gitlab-org/gitlab-runner/) launches a Docker\ncontainer that runs commands specified by a `.gitlab-ci.yml` inside the\nproject repository. As the job runs, the runner streams the output to\nthe GitLab API via PATCH requests. The GitLab backend saves this data\ninto a file. The following sequence diagram shows how this works:\n\n```plantuml\n== Get a job! ==\nRunner -> GitLab: POST /api/v4/jobs/request\nGitLab -> Runner: 201 Job was scheduled\n\n== Job sends logs (1 of 2) ==\nRunner -> GitLab: PATCH /api/v4/job/:id/trace\nGitLab -> File: Save to disk\nGitLab -> Runner: 202 Accepted\n\n== Job sends logs (2 of 2) ==\nRunner -> GitLab: PATCH /api/v4/job/:id/trace\nGitLab -> File: Save to disk\nGitLab -> Runner: 202 Accepted\n```\n\n[Henrich Lee Yu](/company/team/#engwan) mentioned\nthat we had recently [disabled a feature flag that changed how GitLab\nhandled CI job\nlogs](https://docs.gitlab.com/ee/administration/job_logs.html#new-incremental-logging-architecture). [The\ntiming seemed to line\nup](https://gitlab.com/gitlab-org/gitlab/issues/34951#note_236723888).\n\nThis feature, called live CI traces, eliminates the need for a shared\nPOSIX filesystem (e.g., NFS) when saving job logs to disk by:\n\n1. Streaming data into memory via Redis\n2. Persisting the data in the database (PostgreSQL)\n3. Archiving the final data into object storage\n\nWhen this flag is enabled, the flow of CI job logs looks something like\nthe following:\n\n```plantuml\n== Get a job! ==\nRunner -> GitLab: POST /api/v4/jobs/request\nGitLab -> Runner: 201 Job was scheduled\n\n== Job sends logs ==\nRunner -> GitLab: PATCH /api/v4/job/:id/trace\nGitLab -> Redis: Save chunk\nGitLab -> Runner: 202 Accepted\n...\n== Copy 128 KB chunks from Redis to database ==\nGitLab -> Redis: GET gitlab:ci:trace:id:chunks:0\nGitLab -> PostgreSQL: INSERT INTO ci_build_trace_chunks\n...\n== Job finishes ==\n\nRunner -> GitLab: PUT /api/v4/job/:id\nGitLab -> Runner: 200 Job was updated\n\n== Archive trace to object storage ==\n```\n\nLooking at the flow diagram above, we see that this approach has more\nsteps. After receiving data from the runner, something could have gone\nwrong with handling a chunk of data. However, we still had many\nquestions:\n\n1. Did the runners send the right data in the first place?\n1. Did GitLab drop a chunk of data somewhere?\n1. Did this new feature actually have anything to do with the problem?\n1. Are they really making another Gremlins movie?\n\n## Reproducing the bug: Simplify the `.gitlab-ci.yml`\n\nTo help answer those questions, we simplified the `.gitlab-ci.yml` to\nrun only the `static-analysis` step. We inserted a known Rubocop error,\nreplacing a `eq` with `eql`. We first ran this test on a separate GitLab\ninstance with a private runner. No luck there – the job showed the right\noutput:\n\n```\nOffenses:\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\nHowever, we repeated the test on our staging server and found that we\nreproduced the original problem. In addition, the live CI trace feature\nflag had been activated on staging. Since the problem occurred with and\nwithout the feature, we could eliminate that feature as a possible\ncause.\n\nPerhaps something with the GitLab server environment caused a\nproblem. For example, could the load balancers be rate-limiting the\nrunners? As an experiment, we pointed a private runner at the staging\nserver and re-ran the test. This time, it succeeded: the output was\nshown. That seemed to suggest that the problem had more to do with the\nrunner than with the server.\n\n## Docker Machine vs. Docker\n\nOne key difference between the two tests: One runner used a shared,\nautoscaled runner using a [Docker\nMachine](https://docs.docker.com/machine/overview/) executor, and the\nprivate runner used a [Docker\nexecutor](https://docs.gitlab.com/runner/executors/docker.html).\n\nWhat does Docker Machine do exactly? The following diagram may help\nillustrate:\n\n![Docker Machine](https://docs.docker.com/machine/img/machine.png){: .medium.center}\n\nThe top-left shows a local Docker instance. When you run Docker from the\ncommand-line interface (e.g., `docker attach my-container`), the program\njust makes [REST calls to the Docker Engine\nAPI](https://docs.docker.com/engine/api/v1.40/).\n\nThe rest of the diagram shows how Docker Machine fits into the\npicture. Docker Machine is an entirely separate program. The GitLab\nRunner shells out to `docker-machine` to create and destroy virtual\nmachines using cloud-specific (e.g. Amazon, Google, etc.) drivers. Once\na machine is running, the runner then uses the Docker Engine API to run,\nwatch, and stop containers.\n\nNote that this API is used securely over an HTTPS connection. This is an\nimportant difference between the Docker Machine executor and Docker\nexecutor: The former needs to communicate across the network, while the\nlatter can either use a local TCP socket or UNIX domain socket.\n\n## Google Cloud Platform timeouts\n\nWe've known for a while that Google Cloud [has a 10-minute idle\ntimeout](https://cloud.google.com/compute/docs/troubleshooting/general-tips),\nwhich has caused issues in the past:\n\n> Note that idle connections are tracked for a maximum of 10 minutes,\n> after which their traffic is subject to firewall rules, including the\n> implied deny ingress rule. If your instance initiates or accepts\n> long-lived connections with an external host, you should adjust TCP\n> keep-alive settings on your Compute Engine instances to less than 600\n> seconds to ensure that connections are refreshed before the timeout\n> occurs.\n\nWas the problem caused by this timeout? With the Docker Machine\nexecutor, we found that we could reproduce the problem with a simple\n`.gitlab-ci.yml`:\n\n```yaml\nimage: \"busybox:latest\"\n\ntest:\n  script:\n    - date\n    - sleep 601\n    - echo \"Hello world!\"\n    - date\n    - exit 1\n```\n\nThis would reproduce the failure, where we would never see the `Hello\nworld!` output. Changing the `sleep 601` to `sleep 599` would make the\nproblem go away. Hurrah! All we have to do is tweak the system TCP\nkeepalives, right? Google provided these sensible settings:\n\n```sh\nsudo /sbin/sysctl -w net.ipv4.tcp_keepalive_time=60 net.ipv4.tcp_keepalive_intvl=60 net.ipv4.tcp_keepalive_probes=5\n```\n\nHowever, enabling these kernel-level settings didn't solve the\nproblem. Were keepalives even being sent? Or was there some other issue?\nWe turned our attention to network traces.\n\n## Eavesdropping on Docker traffic\n\nIn order to understand what was happening, we needed to be able to\nmonitor the network communication between the runner and the Docker\ncontainer. But how exactly does the GitLab Runner stream data from a\nDocker container to the GitLab server?  The following diagram\nillustrates the flow:\n\n```plantuml\nRunner -> Docker: POST /containers/name/attach\nDocker -> Runner: \u003Ccontainer output>\nDocker -> Runner: \u003Ccontainer output>\nRunner -> GitLab: PATCH /api/v4/job/:id/trace\nGitLab -> File: Save to disk\nGitLab -> Runner: 202 Accepted\n```\n\nFirst, the runner makes a [POST request to attach to the container\noutput](https://docs.docker.com/engine/api/v1.40/#operation/ContainerAttach).\nAs soon as a process running in the container outputs some data, Docker\nwill transmit the data over this HTTPS stream. The runner then copies\nthis data to GitLab via the PATCH request.\n\nHowever, as mentioned earlier, traffic between a GitLab Runner and the\nremote Docker machine is encrypted over HTTPS on port 2376. Was there an\neasy way to disable HTTPS? Searching through the code of Docker Machine,\nwe found that it did not appear to be supported out of the box.\n\nSince we couldn't disable HTTPS, we had two ways to eavesdrop:\n\n1. Use a man-in-the-middle proxy (e.g. [mitmproxy](https://mitmproxy.org/))\n1. Record the traffic and decrypt the traffic later using the private keys\n\n## Ok, let's be the man-in-the-middle!\n\nThe first seemed more straightforward, since [we already had experience\ndoing this with the Docker\nclient](https://docs.gitlab.com/ee/administration/packages/container_registry.html#running-the-docker-daemon-with-a-proxy).\n\nHowever, after [defining the proxy variables for GitLab\nRunner](https://docs.gitlab.com/runner/configuration/proxy.html#adding-proxy-variables-to-the-runner-config),\nwe found we were only able to intercept the GitLab API calls with\n`mitmproxy`. The Docker API calls still went directly to the remote\nhost. Something wasn't obeying the proxy configuration, but we didn't\ninvestigate further. We tried the second approach.\n\n## Decrypting TLS data\n\nTo decrypt TLS data, we would need to obtain the encryption keys. Where\nwere these located for a newly-created system with `docker-machine`? It\nturns out `docker-machine` worked in the following way:\n\n1. Call the Google Cloud API to create a new machine\n1. Create a `/root/.docker/machine/machines/:machine_name` directory\n1. Generate a new SSH keypair\n1. Install the SSH key on the server\n1. Generate a new TLS certificate and key\n1. Install and configure Docker on the newly-created machine with TLS certificates\n\nAs long as the machine runs, the directory will contain the information\nneeded to decode this traffic. We ran `tcpdump` and saved the private keys.\n\nOur first attempt at decoding the traffic failed. Wireshark could not\ndecode the encrypted traffic, although general TCP traffic could still\nbe seen. Researching more, we found out why: If the encrypted traffic\nused a [Diffie-Hellman key\nexchange](https://en.wikipedia.org/wiki/Diffie%E2%80%93Hellman_key_exchange),\nhaving the private keys would not suffice! This is by design, a property\ncalled [perfect forward\nsecrecy](https://en.m.wikipedia.org/wiki/Forward_secrecy).\n\nTo get around that limitation, we modified the GitLab Runner to disable\ncipher suites that used the Diffie-Hellman key exchange:\n\n```diff\ndiff --git a/vendor/github.com/docker/go-connections/tlsconfig/config_client_ciphers.go b/vendor/github.com/docker/go-connections/tlsconfig/config_client_ciphers.go\nindex 6b4c6a7c0..a3f86d756 100644\n",[267,948,537,9,947,9,795,838,970],{"slug":2622,"featured":6,"template":686},"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":2628,"_dir":244,"_draft":6,"_partial":6,"_locale":7,"seo":2629,"content":2635,"config":2641,"_id":2643,"_type":14,"title":2644,"_source":16,"_file":2645,"_stem":2646,"_extension":19},"/en-us/blog/transform-code-quality-and-compliance-with-automated-processes",{"title":2630,"description":2631,"ogTitle":2630,"ogDescription":2631,"noIndex":6,"ogImage":2632,"ogUrl":2633,"ogSiteName":673,"ogType":674,"canonicalUrls":2633,"schema":2634},"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":2630,"description":2631,"authors":2636,"heroImage":2632,"date":2638,"body":2639,"category":1464,"tags":2640},[2637],"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,1403,482,1464,771,970],{"slug":2642,"featured":6,"template":686},"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":2648,"_dir":244,"_draft":6,"_partial":6,"_locale":7,"seo":2649,"content":2655,"config":2662,"_id":2664,"_type":14,"title":2665,"_source":16,"_file":2666,"_stem":2667,"_extension":19},"/en-us/blog/tutorial-automated-release-and-release-notes-with-gitlab",{"title":2650,"description":2651,"ogTitle":2650,"ogDescription":2651,"noIndex":6,"ogImage":2652,"ogUrl":2653,"ogSiteName":673,"ogType":674,"canonicalUrls":2653,"schema":2654},"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":2650,"description":2651,"authors":2656,"heroImage":2652,"date":2658,"body":2659,"category":1464,"tags":2660,"updatedDate":2661},[2657],"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",[729,9,109,728,773,948],"2025-06-05",{"slug":2663,"featured":6,"template":686},"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":2669,"_dir":244,"_draft":6,"_partial":6,"_locale":7,"seo":2670,"content":2675,"config":2680,"_id":2682,"_type":14,"title":2683,"_source":16,"_file":2684,"_stem":2685,"_extension":19},"/en-us/blog/ubs-gitlab-devops-platform",{"title":2671,"description":2672,"ogTitle":2671,"ogDescription":2672,"noIndex":6,"ogImage":2396,"ogUrl":2673,"ogSiteName":673,"ogType":674,"canonicalUrls":2673,"schema":2674},"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":2671,"description":2672,"authors":2676,"heroImage":2396,"date":2677,"body":2678,"category":704,"tags":2679},[1621],"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",[728,903,9,991],{"slug":2681,"featured":6,"template":686},"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":2687,"_dir":244,"_draft":6,"_partial":6,"_locale":7,"seo":2688,"content":2694,"config":2701,"_id":2703,"_type":14,"title":2704,"_source":16,"_file":2705,"_stem":2706,"_extension":19},"/en-us/blog/unreview-a-year-later-how-gitlab-is-being-transformed-by-ml-powered-code-review",{"title":2689,"description":2690,"ogTitle":2689,"ogDescription":2690,"noIndex":6,"ogImage":2691,"ogUrl":2692,"ogSiteName":673,"ogType":674,"canonicalUrls":2692,"schema":2693},"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":2695,"description":2690,"authors":2696,"heroImage":2691,"date":2698,"body":2699,"category":1118,"tags":2700},"UnReview a year later: How GitLab is transforming DevOps code review with ML-powered functionality",[2697],"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",[728,1403,9,231,949],{"slug":2702,"featured":6,"template":686},"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":2708,"_dir":244,"_draft":6,"_partial":6,"_locale":7,"seo":2709,"content":2715,"config":2721,"_id":2723,"_type":14,"title":2724,"_source":16,"_file":2725,"_stem":2726,"_extension":19},"/en-us/blog/using-child-pipelines-to-continuously-deploy-to-five-environments",{"title":2710,"description":2711,"ogTitle":2710,"ogDescription":2711,"noIndex":6,"ogImage":2712,"ogUrl":2713,"ogSiteName":673,"ogType":674,"canonicalUrls":2713,"schema":2714},"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":2710,"description":2711,"authors":2716,"heroImage":2712,"date":2718,"body":2719,"category":704,"tags":2720},[2717],"Olivier Dupré","2024-09-26","DevSecOps teams sometimes require the ability to manage continuous deployment across multiple environments — and they need to do so without changing their workflows. The [GitLab DevSecOps platform](https://about.gitlab.com/) supports this need, including temporary, on-the-fly sandboxes, with a minimalist approach. In this article, you'll learn how to run continuous deployment of infrastructure using Terraform, over multiple environments.\n\nThis strategy can easily be applied to any project, whether it is infrastructure as code (IaC) relying on another technology, such as [Pulumi](https://www.pulumi.com/) or [Ansible](https://www.ansible.com/), source code in any language, or a monorepo that mixes many languages.\n\nThe final pipeline that you will have at the end of this tutorial will deploy:\n\n* A temporary **review** environment for each feature branch.\n* An **integration** environment, easy to wipe out and deployed from the main branch.\n* A **QA** environment, also deployed from the main branch, to run quality assurance steps.\n* A **staging** environment, deployed for every tag. This is the last round before production.\n* A **production** environment, just after the staging environment. This one is triggered manually for demonstration, but can also be continuously deployed.\n\n>Here is the legend for the flow charts in this article:\n> * Round boxes are the GitLab branches.\n> * Square boxes are the environments.\n> * Text on the arrows are the actions to flow from one box to the next.\n> * Angled squares are decision steps.\n\n\u003Cpre class=\"mermaid\">\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\nOn each step, you'll learn the [why](#why) and the [what](#what) before moving to the [how](#how). This will help you fully understand and replicate this tutorial.\n\n## Why\n\n* [Continuous integration](https://about.gitlab.com/topics/ci-cd/#what-is-continuous-integration-ci) is almost a de facto standard. Most companies have implemented CI pipelines or are willing to standardize their practice.\n\n* [Continuous delivery](https://about.gitlab.com/topics/ci-cd/#what-is-continuous-delivery-cd), which pushes artifacts to a repository or registry at the end of the CI pipeline, is also popular.\n\n* Continuous deployment, which goes further and deploys these artifacts automatically, is less widespread. When it has been implemented, we see it essentially in the application field. When discussing continuously deploying  infrastructure, the picture seems less obvious, and is more about managing several environments. In contrast, testing, securing, and verifying the infrastructure's code seems more challenging. And this is one of the fields where DevOps has not yet reached its maturity. One of the other fields is to shift security left, integrating security teams and, more importantly, security concerns, earlier in the delivery lifecycle, to upgrade from DevOps to ***DevSecOps***.\n\nGiven this high-level picture, in this tutorial, you will work toward a simple, yet efficient way to implement DevSecOps for your infrastructure through the example of deploying resources to five environments, gradually progressing from development to production.\n\n__Note:__ Even if I advocate embracing a FinOps approach and reducing the number of environments, sometimes there are excellent reasons to maintain more than just dev, staging, and production. So, please, adapt the examples below to match your needs.\n\n## What\n\nThe rise of cloud technology has driven the usage of IaC. Ansible and Terraform were among the first to pave the road here. OpenTofu, Pulumi, AWS CDK, Google Deploy Manager, and many others joined the party.\n\nDefining IaC is a perfect solution to feel safe when deploying infrastructure. You can test it, deploy it, and replay it again and again until you reach your goal.\n\nUnfortunately, we often see companies maintain several branches, or even repositories, for each of their target environments. And this is where the problems start. They are no longer enforcing a process. They are no longer ensuring that any change in the production code base has been accurately tested in previous environments. And they start seeing drifts from one environment to the other.\n\nI realized this tutorial was necessary when, at a conference I attended, every participant said they do not have a workflow that enforces the infrastructure to be tested thoroughly before being deployed to production. And they all agreed that sometimes they patch the code directly in production. Sure, this is fast, but is it safe? How do you report back to previous environments? How do you ensure there are no side effects? How do you control whether you are putting your company at risk with new vulnerabilities being pushed too quickly in production?\n\nThe question of *why* DevOps teams deploy directly to production is critical here. Is it because the pipeline could be more efficient or faster? Is there no automation? Or, even worse, because there is *no way to test accurately outside of production*?\n\nIn the next section, you will learn how to implement automation for your infrastructure and ensure that your DevOps team can effectively test what you are doing before pushing to any environment impacting others. You will see how your code is secured and its deployment is controlled, end-to-end.\n\n## How\n\nAs mentioned earlier, there are many IaC languages out there nowadays and we objectively cannot cover *all* of them in a single article. So, I will rely on a basic Terraform code running on Version 1.4. Please do not focus on the IaC language itself but instead on the process that you could apply to your own ecosystem.\n\n### The Terraform code\n\nLet's start with a fundamental Terraform code.\n\nWe are going to deploy to AWS, a virtual private cloud (VPC), which is a virtual network. In that VPC, we will deploy a public and a private subnet. As their name implies, they are subnets of the main VPC. Finally, we will add an Elastic Cloud Compute (EC2) instance (a virtual machine) in the public subnet.\n\nThis demonstrates the deployment of four resources without adding too much complexity. The idea is to focus on the pipeline, not the code.\n\nHere is the target we want to reach for your repository.\n\n![target for repository](https://res.cloudinary.com/about-gitlab-com/image/upload/v1750097033/Blog/Content%20Images/Blog/Content%20Images/image5_aHR0cHM6_1750097033415.png)\n\nLet’s do it step by step.\n\nFirst, we declare all resources in a `terraform/main.tf` file:\n\n```terraform\nprovider \"aws\" {\n  region = var.aws_default_region\n}\n\nresource \"aws_vpc\" \"main\" {\n  cidr_block = var.aws_vpc_cidr\n\n  tags = {\n    Name     = var.aws_resources_name\n  }\n}\n\nresource \"aws_subnet\" \"public_subnet\" {\n  vpc_id     = aws_vpc.main.id\n  cidr_block = var.aws_public_subnet_cidr\n\n  tags = {\n    Name = \"Public Subnet\"\n  }\n}\nresource \"aws_subnet\" \"private_subnet\" {\n  vpc_id     = aws_vpc.main.id\n  cidr_block = var.aws_private_subnet_cidr\n\n  tags = {\n    Name = \"Private Subnet\"\n  }\n}\n\nresource \"aws_instance\" \"sandbox\" {\n  ami           = var.aws_ami_id\n  instance_type = var.aws_instance_type\n\n  subnet_id = aws_subnet.public_subnet.id\n\n  tags = {\n    Name     = var.aws_resources_name\n  }\n}\n```\n\nAs you can see, there are a couple of variables that are needed for this code, so let's declare them in a `terraform/variables.tf` file:\n\n```terraform\nvariable \"aws_ami_id\" {\n  description = \"The AMI ID of the image being deployed.\"\n  type        = string\n}\n\nvariable \"aws_instance_type\" {\n  description = \"The instance type of the VM being deployed.\"\n  type        = string\n  default     = \"t2.micro\"\n}\n\nvariable \"aws_vpc_cidr\" {\n  description = \"The CIDR of the VPC.\"\n  type        = string\n  default     = \"10.0.0.0/16\"\n}\n\nvariable \"aws_public_subnet_cidr\" {\n  description = \"The CIDR of the public subnet.\"\n  type        = string\n  default     = \"10.0.1.0/24\"\n}\n\nvariable \"aws_private_subnet_cidr\" {\n  description = \"The CIDR of the private subnet.\"\n  type        = string\n  default     = \"10.0.2.0/24\"\n}\n\nvariable \"aws_default_region\" {\n  description = \"Default region where resources are deployed.\"\n  type        = string\n  default     = \"eu-west-3\"\n}\n\nvariable \"aws_resources_name\" {\n  description = \"Default name for the resources.\"\n  type        = string\n  default     = \"demo\"\n}\n```\n\nAlready, we are almost good to go on the IaC side. What's missing is a way to share the Terraform states. For those who don't know, Terraform works schematically doing the following:\n\n* `plan` checks the differences between the current state of the infrastructure and what is defined in the code. Then, it outputs the differences.\n* `apply` applies the differences in the `plan` and updates the state.\n\nFirst round, the state is empty, then it is filled with the details (ID, etc.) of the resources applied by Terraform.\n\nThe problem is: Where is that state stored? How do we share it so several developers can collaborate on code?\n\nThe solution is fairly simple: Leverage GitLab to store and share the state for you through a [Terraform HTTP backend](https://docs.gitlab.com/ee/user/infrastructure/iac/terraform_state.html).\n\nThe first step in using this backend is to create the most simple `terraform/backend.tf` file. The second step will be handled in the pipeline.\n\n```terraform\nterraform {\n  backend \"http\" {\n  }\n}\n```\n\nEt voilà! We have a bare minimum Terraform code to deploy these four resources. We will provide the variable values at the runtime, so let's do that later.\n\n### The workflow\n\nThe workflow that we are going to implement now is the following:\n\n\u003Cpre class=\"mermaid\">\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\n1. Create a **feature** branch. This will continuously run all scanners on the code to ensure that it is still compliant and secured. This code will be continuously deployed to a temporary environment `review/feature_branch` with the name of the current branch. This is a safe environment where the developers and operations teams can test their code without impacting anybody. This is also where we will enforce the process, like enforcing code reviews and running scanners, to ensure that the quality and security of the code are acceptable and do not put your assets at risk. The infrastructure deployed by this branch is automatically destroyed when the branch is closed. This helps you keep your budget under control.\n\n\u003Cpre class=\"mermaid\">\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\n2. Once approved, the feature branch will be **merged** into the main branch. This is a [protected branch](https://docs.gitlab.com/ee/user/project/protected_branches.html) where no one can push. This is mandatory to ensure that every change request to production is thoroughly tested. That branch is also continuously deployed. The target here is the `integration` environment. To keep this environment slightly more stable, its deletion is not automated but can be triggered manually.\n\n\u003Cpre class=\"mermaid\">\nflowchart LR\n    D(main) -->|auto deploy| E[integration]\n\u003C/pre>\n\n3. From there, manual approval is required to trigger the next deployment. This will deploy the main branch to the `qa` environment. Here, I have set a rule to prevent deletion from the pipeline. The idea is that this environment should be quite stable (after all, it's already the third environment), and I would like to prevent deletion by mistake. Feel free to adapt the rules to match your processes.\n\n\u003Cpre class=\"mermaid\">\nflowchart LR\n    D(main)-->|auto deploy| E[integration]\n    E -->|manual| F[qa]\n\u003C/pre>\n\n4. To proceed, we will need to **tag** the code. We are relying on [protected tags](https://docs.gitlab.com/ee/user/project/protected_tags.html) here to ensure that only a specific set of users are allowed to deploy to these last two environments. This will immediately trigger a deployment to the `staging` environment.\n\n\u003Cpre class=\"mermaid\">\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\n5. Finally, we are landing to `production`. When discussing infrastructure, it is often challenging to deploy progressively (10%, 25%, etc.), so we will deploy the whole infrastructure. Still, we control that deployment with a manual trigger of this last step. And to enforce maximum control on this highly critical environment, we will control it as a [protected environment](https://docs.gitlab.com/ee/ci/environments/protected_environments.html).\n\n\u003Cpre class=\"mermaid\">\nflowchart LR\n    H[staging] -->|manual| I{plan}\n    I -->|manual| J[production]\n\u003C/pre>\n\n### The pipeline\n\nTo implement the above [workflow](#the-workflow), we are now going to implement a pipeline with two [downstream pipelines](https://docs.gitlab.com/ee/ci/pipelines/downstream_pipelines.html).\n\n#### The main pipeline\n\nLet's start with the main pipeline. This is the one that will be triggered automatically on any **push to a feature branch**, any **merge to the default branch**, or any **tag**. *The one* that will do true **continuous deployment** to the following environments: `dev`, `integration`, and `staging`. And it is declared in the `.gitlab-ci.yml` file at the root of your project.\n\n![the repository target](https://res.cloudinary.com/about-gitlab-com/image/upload/v1750097033/Blog/Content%20Images/Blog/Content%20Images/image1_aHR0cHM6_1750097033417.png)\n\n```yml\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# This tweak is needed to display vulnerability results in the merge widgets.\n# As soon as this issue https://gitlab.com/gitlab-org/gitlab/-/issues/439700 is resolved, the `include` instruction below can be removed.\n# Until then, the SAST IaC scanners will run in the downstream pipelines, but their results will not be available directly in the merge request widget, making it harder to track them.\n# Note: This workaround is perfectly safe and will not slow down your pipeline.\ninclude:\n  - template: Security/SAST-IaC.gitlab-ci.yml\n#### END TWEAK\n\n```\n\nThis pipeline runs only two stages: `test` and  `environments`. The former is needed for the *TWEAK* to run scanners. The later triggers a child pipeline with a different set of variables for each case defined above (push to the branch, merge to the default branch, or tag).\n\nWe are adding here a dependency with the keyword [strategy:depend](https://docs.gitlab.com/ee/ci/yaml/index.html#triggerstrategy) on our child pipeline so the pipeline view in GitLab will be updated only once the deployment is finished.\n\nAs you can see here, we are defining a base job, [hidden](https://docs.gitlab.com/ee/ci/jobs/#hide-jobs), and we are extending it with specific variables and rules to trigger only one deployment for each target environment.\n\nBesides the [predefined variables](https://docs.gitlab.com/ee/ci/variables/predefined_variables.html), we are using two new entries that we need to define:\n1. [The variables specific](#the-variable-definitions) to each environment: `../vars/$variables_file.tfvars`\n2. [The child pipeline](#the-child-pipeline), defined in `.gitlab-ci/.first-layer.gitlab-ci.yml`\n\nLet's start with the smallest part, the variable definitions.\n\n### The variable definitions\n\nWe are going here to mix two solutions to provide variables to Terraform:\n\n* The first one using [.tfvars files](https://developer.hashicorp.com/terraform/language/values/variables#variable-definitions-tfvars-files) for all non-sensitive input, which should be stored within GitLab.\n\n![solution one to provide variables to Terraform](https://res.cloudinary.com/about-gitlab-com/image/upload/v1750097034/Blog/Content%20Images/Blog/Content%20Images/image2_aHR0cHM6_1750097033419.png)\n\n* The second using [environment variables](https://developer.hashicorp.com/terraform/language/values/variables#environment-variables) with the prefix `TF_VAR`. That second way to inject variables, associated with the GitLab capacity to [mask variables](https://docs.gitlab.com/ee/ci/variables/#mask-a-cicd-variable), [protect them](https://docs.gitlab.com/ee/ci/variables/#protect-a-cicd-variable), and [scope them to environments](https://docs.gitlab.com/ee/ci/environments/index.html#limit-the-environment-scope-of-a-cicd-variable) is a powerful solution to **prevent sensitive information leakages**. (If you consider your production’s private CIDR very sensitive, you could protect it like this, ensuring it is only available for the `production` environment, for pipelines running against protected branches and tags, and that its value is masked in the job’s logs.)\n\n![solution two to provide variables to Terraform](https://res.cloudinary.com/about-gitlab-com/image/upload/v1750097034/Blog/Content%20Images/Blog/Content%20Images/image4_aHR0cHM6_1750097033422.png)\n\nAdditionally, each variable file should be controlled through a [`CODEOWNERS` file](https://docs.gitlab.com/ee/user/project/codeowners/) to set who can modify each of them.\n\n```\n[Production owners] \nvars/production.tfvars @operations-group\n\n[Staging owners]\nvars/staging.tfvars @odupre @operations-group\n\n[CodeOwners owners]\nCODEOWNERS @odupre\n```\n\nThis article is not a Terraform training, so we will go very fast and simply show here the `vars/review.tfvars` file. Subsequent environment files are, of course, very similar. Just set the non-sensitive variables and their values here.\n\n```shell\naws_vpc_cidr = \"10.1.0.0/16\"\naws_public_subnet_cidr = \"10.1.1.0/24\"\naws_private_subnet_cidr = \"10.1.2.0/24\"\n```\n\n#### The child pipeline\n\nThis one is where the actual work is done. So, it is slightly more complex than the first one. But there is no difficulty here that we cannot overcome together!\n\nAs we have seen in the definition of the [main pipeline](#the-main-pipeline), that downstream pipeline is declared in the file `.gitlab-ci/.first-layer.gitlab-ci.yml`.\n\n![Downstream pipeline declared in file](https://res.cloudinary.com/about-gitlab-com/image/upload/v1750097033/Blog/Content%20Images/Blog/Content%20Images/image3_aHR0cHM6_1750097033424.png)\n\nLet's break it down into small chunks. We'll see the big picture at the end.\n\n##### Run Terraform commands and secure the code\n\nFirst, we want to run a pipeline for Terraform. We, at GitLab, are open source. So, our Terraform template is open source. And you simply need to include it. This can be achieved with the following snippet:\n\n```yml\ninclude:\n  - template: Terraform.gitlab-ci.yml\n```\n\nThis template runs for you the Terraform checks on the formatting and validates your code, before planning and applying it. It also allows you to destroy what you have deployed.\n\nAnd, because GitLab is the a single, unified DevSecOps platform, we are also automatically including two security scanners within that template to find potential threats in your code and warn you before you deploy it to the next environments.\n\nNow that we have checked, secured, built, and deployed our code, let's do some tricks.\n\n##### Share cache between jobs\n\nWe will cache the job results to reuse them in subsequent pipeline jobs. This is as simple as adding the following piece of code:\n\n```yml\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\nHere, we are defining a different cache for each commit, falling back to the main branch name if needed.\n\nIf we look carefully at the templates that we are using, we can see that it has some rules to control when jobs are run. We want to run all controls (both QA and security) on all branches. So, we are going to override these settings.\n\n##### Run controls on all branches\n\nGitLab templates are a powerful feature where one can override only a piece of the template. Here, we are interested only in overwriting the rules of some jobs to always run quality and security checks. Everything else defined for these jobs will stay as defined in the template.\n\n```yml\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\nNow that we have enforced the quality and security controls, we want to differentiate how the main environments (integration and staging) in the [workflow](#the-workflow) and review environments behave. Let's start by defining the main environment’s behavior, and we will tweak this configuration for the review environments.\n\n##### CD to integration and staging\n\nAs defined earlier, we want to deploy the main branch and the tags to these two environments. We are adding rules to control that on both the `build` and `deploy` jobs. Then, we want to enable `destroy` only for the `integration` as we have defined `staging` to be too critical to be deleted with a single click. This is error-prone and we don't want to do that.\n\nFinally, we are linking the `deploy` job to the `destroy` one, so we can `stop` the environment directly from GitLab GUI.\n\nThe `GIT_STRATEGY` is here to prevent retrieving the code from the source branch in the runner when destroying. This would fail if the branch has been deleted manually, so we are relying on the cache to get everything we need to run the Terraform instructions.\n\n```yml\nbuild:  # terraform plan\n  environment:\n    name: $TF_STATE_NAME\n    action: prepare\n  rules:\n    - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH\n    - if: $CI_COMMIT_TAG\n\ndeploy: # terraform apply --> automatically deploy on corresponding env (integration or staging) when merging to default branch or tagging. Second layer environments (qa and production) will be controlled manually\n  environment: \n    name: $TF_STATE_NAME\n    action: start\n    on_stop: destroy\n  rules:\n    - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH\n    - if: $CI_COMMIT_TAG\n\ndestroy:\n  extends: .terraform:destroy\n  variables:\n    GIT_STRATEGY: none\n  dependencies:\n    - build\n  environment:\n    name: $TF_STATE_NAME\n    action: stop\n  rules:\n    - if: $CI_COMMIT_TAG  # Do not destroy production\n      when: never\n    - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH && $TF_DESTROY == \"true\" # Manually destroy integration env.\n      when: manual\n```\n\nAs said, this matches the need to deploy to `integration` and `staging`. But we are still missing a temporary environment where the developers can experience and validate their code without impacts on others. This is where the deployment to the `review` environment takes place.\n\n##### CD to review environments\n\nDeploying to review environment is not too different than deploying to `integration` and `staging`. So we will once again leverage GitLab's capacity to overwrite only pieces of job definition here.\n\nFirst, we set rules to run these jobs only on feature branches.\n\nThen, we link the `deploy_review` job to `destroy_review`. This will allow us to stop the environment **manually** from the GitLab user interface, but more importantly, it will **automatically trigger the environment destruction** when the feature branch is closed. This is a good FinOps practice to help you control your operational expenditures.\n\nSince Terraform needs a plan file to destroy an infrastructure, exactly like it needs one to build an infrastructure, then we are adding a dependency from `destroy_review` to `build_review`, to retrieve its artifacts.\n\nFinally, we see here that the environment's name is set to `$environment`. It has been set in the [main pipeline](#the-main-pipeline) to `review/$CI_COMMIT_REF_SLUG`, and forwarded to this child pipeline with the instruction `trigger:forward:yaml_variables:true`.\n\n```yml\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\nSo, to recap, we now have a pipeline that can:\n\n* Deploy temporary review environments, which are automatically cleaned up when the feature branch is closed\n* Continuously deploy the **default branch** to `integration`\n* Continuously deploy the **tags** to `staging`\n\nLet's now add an extra layer, where we will deploy, based on a manual trigger this time, to `qa` and `production` environments.\n\n##### Continously deploy to QA and production\n\nBecause not everybody is willing to deploy continuously to production, we will add a manual validation to the next two deployments. From a purely **CD** perspective, we would not add this trigger, but take this as an opportunity to learn how to run jobs from other triggers.\n\nSo far, we have started a [child pipeline](#the-child-pipeline) from the [main pipeline](#the-main-pipeline) to run all deployments.\n\nSince we want to run other deployments from the default branch and the tags, we will add another layer dedicated to these additional steps. Nothing new here. We will just repeat exactly the same process as the one we only did for the [main pipeline](#the-main-pipeline). Going this way allows you to manipulate as many layers as you need. I have already seen up to nine environments in some places.\n\nWithout arguing once again on the benefits to have fewer environments, the process that we are using here makes it very easy to implement the same pipeline all the way from early stages to final delivery, while keeping your pipeline definition simple and split in small chunks that you can maintain at no cost.\n\nTo prevent variable conflicts here, we are just using new var names to identify the Terraform state and input file.\n\n```yml\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**One important trick here is the strategy used for the new downstream pipeline.** We leave that `trigger:strategy` to its default value; otherwise, the [main pipeline](#the-main-pipeline) would wait for your [grand-child pipeline](#the-grand-child-pipeline) to finish. With a manual trigger, this could last for a very long time and make your pipeline dashboard harder to read and understand.\n\nYou have probably already wondered what is the content of that `.gitlab-ci/.second-layer.gitlab-ci.yml` file we are including here.  We will cover that in the next section.\n\n##### The first layer complete pipeline definition\n\nIf you are looking for a complete view of this first layer (stored in `.gitlab-ci/.first-layer.gitlab-ci.yml`), just expand the section below.\n\n```yml\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## Integration env. and Staging. env\n##  * Auto-deploy to Integration on merge to main.\n##  * Auto-deploy to Staging on tag.\n##  * Integration can be manually destroyed if TF_DESTROY is set to true.\n##  * Destroy of next env. is not automated to prevent errors.\n###########################################################################################################\nbuild:  # terraform plan\n  environment:\n    name: $TF_STATE_NAME\n    action: prepare\n  rules:\n    - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH\n    - if: $CI_COMMIT_TAG\n\ndeploy: # terraform apply --> automatically deploy on corresponding env (integration or staging) when merging to default branch or tagging. Second layer environments (qa and production) will be controlled manually\n  environment: \n    name: $TF_STATE_NAME\n    action: start\n    on_stop: destroy\n  rules:\n    - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH\n    - if: $CI_COMMIT_TAG\n\ndestroy:\n  extends: .terraform:destroy\n  variables:\n    GIT_STRATEGY: none\n  dependencies:\n    - build\n  environment:\n    name: $TF_STATE_NAME\n    action: stop\n  rules:\n    - if: $CI_COMMIT_TAG  # Do not destroy production\n      when: never\n    - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH && $TF_DESTROY == \"true\" # Manually destroy integration env.\n      when: manual\n###########################################################################################################\n\n###########################################################################################################\n## Dev env.\n##  * Temporary environment. Lives and dies with the Merge Request.\n##  * Auto-deploy on push to feature branch.\n##  * Auto-destroy on when Merge Request is closed.\n###########################################################################################################\nbuild_review:\n  extends: build\n  rules:\n    - if: $CI_COMMIT_TAG\n      when: never\n    - if: $CI_COMMIT_BRANCH != $CI_DEFAULT_BRANCH\n      when: on_success\n\ndeploy_review:\n  extends: deploy\n  dependencies:\n    - build_review\n  environment:\n    name: $environment\n    action: start\n    on_stop: destroy_review\n    # url: https://$CI_ENVIRONMENT_SLUG.example.com\n  rules:\n    - if: $CI_COMMIT_TAG\n      when: never\n    - if: $CI_COMMIT_BRANCH != $CI_DEFAULT_BRANCH\n      when: on_success\n\ndestroy_review:\n  extends: destroy\n  dependencies:\n    - build_review\n  environment:\n    name: $environment\n    action: stop\n  rules:\n    - if: $CI_COMMIT_TAG  # Do not destroy production\n      when: never\n    - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH   # Do not destroy staging\n      when: never\n    - when: manual\n###########################################################################################################\n\n###########################################################################################################\n## Second layer\n##  * Deploys from main branch to qa env.\n##  * Deploys from tag to production.\n###########################################################################################################\n.2nd_layer:\n  stage: 2nd_layer\n  variables:\n    TF_ROOT: terraform\n  trigger:\n    include: .gitlab-ci/.second-layer.gitlab-ci.yml\n    # strategy: depend            # Do NOT wait for the downstream pipeline to finish to mark upstream pipeline as successful. Otherwise, all pipelines will fail when reaching the pipeline timeout before deployment to 2nd layer.\n    forward:\n      yaml_variables: true      # Forward variables defined in the trigger job\n      pipeline_variables: true  # Forward manual pipeline variables and scheduled pipeline variables\n\nqa:\n  extends: .2nd_layer\n  variables:\n    TF_STATE_NAME_2: qa\n    environment: $TF_STATE_NAME_2\n    TF_CLI_ARGS_plan_2: \"-var-file=../vars/$TF_STATE_NAME_2.tfvars\"\n  rules:\n    - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH\n\nproduction:\n  extends: .2nd_layer\n  variables:\n    TF_STATE_NAME_2: production\n    environment: $TF_STATE_NAME_2\n    TF_CLI_ARGS_plan_2: \"-var-file=../vars/$TF_STATE_NAME_2.tfvars\"\n  rules:\n    - if: $CI_COMMIT_TAG\n###########################################################################################################\n```\n\nAt this stage, we are already deploying safely to three environments. That is my personal ideal recommendation. However, if you need more environments, add them to your CD pipeline.\n\nYou have certainly already noted that we include a downstream pipeline with the keyword `trigger:include`. This includes the file `.gitlab-ci/.second-layer.gitlab-ci.yml`. We want to run almost the same pipeline so obviously, its content is very similar to the one we have detailed above. The main advantage here to define this [grand-child pipeline](#the-grand-child-pipeline) is that it lives on its own, making both variables and rules way easier to define.\n\n### The grand-child pipeline\n\nThis second layer pipeline is a brand new pipeline. Hence, it needs to mimic the first layer definition with:\n\n* [Inclusion of the Terraform template](#run-terraform-commands-and-secure-the-code).\n* [Enforcement of security checks](#run-controls-on-all-branches). Terraform validation would be duplicates of the first layer, but security scanners may find threats that did not yet exist when scanners previously ran (for example, if you deploy to production a couple of days after your deployment to staging).\n* [Overwrite build and deploy jobs to set specific rules](#cd-to-review-environments). Note that the `destroy` stage is no longer automated to prevent too fast deletions.\n\nAs explained above, the `TF_STATE_NAME` and `TF_CLI_ARGS_plan` have been provided from the [main pipeline](#the-main-pipeline) to the [child pipeline](#the-child-pipeline). We needed another variable name to pass these values from the [child pipeline](#the-child-pipeline) to here, the [grand-child pipeline](#the-grand-child-pipeline). This is why they are postfixed with `_2` in the child pipeline and the value is copied back to the appropriate variable during the `before_script` here.\n\nSince we have already broken down each step above, we can zoom out here directly to the broad view of the global second layer definition (stored in `.gitlab-ci/.second-layer.gitlab-ci.yml`).\n\n```yml\n# Use to deploy a second environment on both the default branch and the tags.\n\ninclude:\n  template: Terraform.gitlab-ci.yml\n\nstages:\n  - validate\n  - test\n  - build\n  - deploy\n\nfmt:\n  rules:\n    - when: never\n\nvalidate:\n  rules:\n    - when: never\n\nkics-iac-sast:\n  rules:\n    - if: $SAST_DISABLED == 'true' || $SAST_DISABLED == '1'\n      when: never\n    - if: $SAST_EXCLUDED_ANALYZERS =~ /kics/\n      when: never\n    - when: always\n\n###########################################################################################################\n## QA env. and Prod. env\n##  * Manually trigger build and auto-deploy in QA\n##  * Manually trigger both build and deploy in Production\n##  * Destroy of these env. is not automated to prevent errors.\n###########################################################################################################\nbuild:  # terraform plan\n  cache:  # Use a shared cache or tagged runners to ensure terraform can run on apply and destroy\n    - key: $TF_STATE_NAME_2\n      fallback_keys:\n        - cache-$CI_DEFAULT_BRANCH\n      paths:\n        - .\n  environment:\n    name: $TF_STATE_NAME_2\n    action: prepare\n  before_script:  # Hack to set new variable values on the second layer, while still using the same variable names. Otherwise, due to variable precedence order, setting new value in the trigger job, does not cascade these new values to the downstream pipeline\n    - TF_STATE_NAME=$TF_STATE_NAME_2\n    - TF_CLI_ARGS_plan=$TF_CLI_ARGS_plan_2\n  rules:\n    - when: manual\n\ndeploy: # terraform apply\n  cache:  # Use a shared cache or tagged runners to ensure terraform can run on apply and destroy\n    - key: $TF_STATE_NAME_2\n      fallback_keys:\n        - cache-$CI_DEFAULT_BRANCH\n      paths:\n        - .\n  environment: \n    name: $TF_STATE_NAME_2\n    action: start\n  before_script:  # Hack to set new variable values on the second layer, while still using the same variable names. Otherwise, due to variable precedence order, setting new value in the trigger job, does not cascade these new values to the downstream pipeline\n    - TF_STATE_NAME=$TF_STATE_NAME_2\n    - TF_CLI_ARGS_plan=$TF_CLI_ARGS_plan_2\n  rules:\n    - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH\n    - if: $CI_COMMIT_TAG && $TF_AUTO_DEPLOY == \"true\"\n    - if: $CI_COMMIT_TAG\n      when: manual\n###########################################################################################################\n```\n\nEt voilà. **We are ready to go.** Feel free to change the way you control your job executions, leveraging for example GitLab's capacity to [delay a job](https://docs.gitlab.com/ee/ci/jobs/job_control.html#run-a-job-after-a-delay) before deploying to production.\n\n## Try it yourself\n\nWe finally reached our destination. We are now able to control **deployments to five different environments**, with only the **feature branches**, the **main branch**, and **tags**.\n* We are intensively reusing GitLab open source templates to ensure efficiency and security in our pipelines.\n* We are leveraging GitLab template capacities to overwrite only the blocks that need custom control.\n* We have split the pipeline in small chunks, controlling the downstream pipelines to match exactly what we need.\n\nFrom there, the floor is yours. You could, for example, easily update the main pipeline to trigger downstream pipelines for your software source code, with the [trigger:rules:changes](https://docs.gitlab.com/ee/ci/yaml/#ruleschanges) keyword. And use another [template](https://gitlab.com/gitlab-org/gitlab/-/tree/master/lib/gitlab/ci/templates/) depending on the changes that happened. But that is another story.",[109,9,683,482,729],{"slug":2722,"featured":6,"template":686},"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":2728,"_dir":244,"_draft":6,"_partial":6,"_locale":7,"seo":2729,"content":2735,"config":2741,"_id":2743,"_type":14,"title":2744,"_source":16,"_file":2745,"_stem":2746,"_extension":19},"/en-us/blog/using-gitlab-ci-to-build-gitlab-faster",{"title":2730,"description":2731,"ogTitle":2730,"ogDescription":2731,"noIndex":6,"ogImage":2732,"ogUrl":2733,"ogSiteName":673,"ogType":674,"canonicalUrls":2733,"schema":2734},"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":2730,"description":2731,"authors":2736,"heroImage":2732,"date":2738,"body":2739,"category":704,"tags":2740},[2737],"Rémy Coutable","2018-05-02","\n\nGitLab is an [open source project], but also a [commercial project]. For historic\nreasons, we have two Git repositories: [`gitlab-ce`] for GitLab Core and\n[`gitlab-ee`] for GitLab Enterprise packages (you can read [our recent blog post explaining GitLab self-managed tiers](/blog/gitlab-tiers/)).\nWhile we're working on having a [single codebase], we still need to regularly\nmerge [`gitlab-ce`] into [`gitlab-ee`] since most of the development happens on\nGitLab Core, but we also develop features on top of it for GitLab Starter, Premium, and Ultimate.\n\n## How we used to merge GitLab CE into GitLab EE\n\nUntil December 2017, the merge of [`gitlab-ce`] into [`gitlab-ee`] was manual\non a daily basis with basically the following commands ([see the full documentation]):\n\n```shell\n# the `origin` remote refers to https://gitlab.com/gitlab-org/gitlab-ee.git\n# the `ce` remote refers to https://gitlab.com/gitlab-org/gitlab-ce.git\ngit fetch origin master\ngit checkout -b ce-to-ee origin/master\ngit fetch ce master\ngit merge --no-ff ce/master\n```\n\nAt this point, since we'd merge a day's worth of GitLab Core's new commits,\nchances were good we'd see conflicts.\nMost of the time, the person responsible for this process would handle the\nconflict resolutions, commit them and push the `ce-to-ee` branch to GitLab.com.\n\nThere were a few problems with this approach:\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 the\n  developer responsible for the merge\n- The developer performing the merge wasn't always the best person to resolve the\n  conflicts\n- Significant time was spent identifying and notifying developers to help resolve conflicts\n\n## The solution\n\nOur plan was to have a single script that would automate the merge, and in the\ncase of conflicts, identify the person best suited to resolve each of them.\nIt would then create the merge request using the [GitLab API] and a\n[GitLab API Ruby wrapper], and post a message in Slack when a new merge request\nwas created or an existing one was still pending.\n\nFinally, we'd use GitLab's [pipeline schedules] to run the script every three hours.\n\n### Step 1: Write the script\n\nWe chose to write the script in our [`release-tools`] project, since it already\nhad a strong foundation for working with the relevant Git repositories.\n\nThis script was written iteratively as a set of classes over the course of a few\nmonths:\n\n1. [Add the ability to find/create a merge request][!139]\n1. [Move remotes to the `Project` classes and get rid of the `Remotes` class][!168]\n1. [Add `head`, `status`, `log`, `fetch`, `checkout_new_branch`, `pull`, `push`, and `merge` to `RemoteRepository`][!177]\n1. [Introduce a new `CommitAuthor` class][!197]\n\nThe last piece of the puzzle was the new [`upstream_merge` Rake task][!219].\n\n### Step 2: Create a pair of SSH keys and add the public key to the `gitlab-ee` project\n\nUnder **Repository Settings > Deploy Keys** of the [`gitlab-ee`] project:\n\n![Deploy key in `gitlab-ee`](https://about.gitlab.com/images/blogimages/using-gitlab-ci-to-build-gitlab-faster/step2.png){: .shadow.center.medium}\n\n### Step 3: Create secret variables in the `release-tools` project\n\nUnder **CI / CD Settings** of the [`release-tools`] project, create three secret\nvariables:\n\n- `AUTO_UPSTREAM_MERGE_BOT_SSH_PRIVATE_KEY` for the SSH private key\n- `GITLAB_API_PRIVATE_TOKEN` is a personal access token for our [`@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 variable](https://about.gitlab.com/images/blogimages/using-gitlab-ci-to-build-gitlab-faster/step3.png){: .shadow.center.medium}\n\n### Step 4: Add a new CI job that runs the `upstream_merge` Rake task for pipeline schedules only\n\n*This was heavily inspired by [GitBot – automating boring Git operations with CI].*\n\nCreate a new `upstream-merge` CI job that:\n\n- Adds the SSH private key to the `~/.ssh` folder\n- Add `gitlab.com` to the `~/.ssh/known_hosts` file\n- Runs `bundle exec rake upstream_merge`\n\n![`upstream-merge` job](https://about.gitlab.com/images/blogimages/using-gitlab-ci-to-build-gitlab-faster/step4.png){: .shadow.center.medium}\n\nYou can [check out the task for yourself](https://gitlab.com/gitlab-org/release-tools/blob/1cd437823113d4529919c29b177bb2037c19fc3c/.gitlab-ci.yml#L50-64).\n\n### Step 5: Create a pipeline schedule that runs every three hours\n\nUnder **Schedules** of the [`release-tools`] project:\n\n![Pipeline schedule](https://about.gitlab.com/images/blogimages/using-gitlab-ci-to-build-gitlab-faster/step5.png){: .shadow.center.medium}\n\n### Step 6: Let the bot work for us!\n\n**The CI job:**\n\n![CI job](https://about.gitlab.com/images/blogimages/using-gitlab-ci-to-build-gitlab-faster/step6-1.png){: .shadow.center.medium}\n\n**The Slack messages:**\n\n![Slack messages](https://about.gitlab.com/images/blogimages/using-gitlab-ci-to-build-gitlab-faster/step6-2.png){: .shadow.center.medium}\n\n**The merge request:**\n\n![Merge request](https://about.gitlab.com/images/blogimages/using-gitlab-ci-to-build-gitlab-faster/step6-3.png){: .shadow.center.medium}\n\n## What are the benefits?\n\nSince we started automating this process in December 2017, our dear\n[`@gitlab-bot`] created no fewer than [229 automatic merges], and we started\nnoticing the benefits immediately:\n\n- Automating the merge request creation saved developers time and removed a manual\nchore.\n- Automatically identifying the developer who introduced a conflict and assigning\nthem to resolve it spread out the workload and reduced bugs caused by improper\nconflict resolution.\n- Performing the merge automatically every three hours instead of manually once a\nday led to fewer changes at a time and a reduced number of conflicts.\n\nThe last, perhaps least visible, but most important benefit, is that we reduced\ndeveloper frustration and increased happiness by removing a tedious chore.\n\n[Photo](https://unsplash.com/photos/w6OniVDCfn0?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText) by Max Ostrozhinskiy on [Unsplash](https://unsplash.com/search/photos/build?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText)\n{: .note}\n\n[open source project]: /community/contribute/\n[commercial project]: /pricing/\n[`gitlab-ce`]: https://gitlab.com/gitlab-org/gitlab-ce\n[`gitlab-ee`]: https://gitlab.com/gitlab-org/gitlab-ee\n[single codebase]: https://gitlab.com/gitlab-org/gitlab-ee/issues/2952\n[see the full documentation]: https://gitlab.com/gitlab-org/release/docs/blob/master/general/merge-ce-into-ee.md\n[pipeline schedules]: https://docs.gitlab.com/ee/ci/pipelines/schedules.html\n[GitLab API]: https://docs.gitlab.com/ee/api/merge_requests.html\n[GitLab API Ruby wrapper]: https://rubygems.org/gems/gitlab\n[`release-tools`]: https://gitlab.com/gitlab-org/release-tools/\n[!139]: https://gitlab.com/gitlab-org/release-tools/merge_requests/139\n[!168]: https://gitlab.com/gitlab-org/release-tools/merge_requests/168\n[!177]: https://gitlab.com/gitlab-org/release-tools/merge_requests/177\n[!197]: https://gitlab.com/gitlab-org/release-tools/merge_requests/197\n[!219]: https://gitlab.com/gitlab-org/release-tools/merge_requests/219\n[`Slack::UpstreamMergeNotification` class]: https://gitlab.com/gitlab-org/release-tools/blob/1cd437823113d4529919c29b177bb2037c19fc3c/lib/slack/upstream_merge_notification.rb#L7\n[GitBot – automating boring Git operations with CI]: /2017/11/02/automating-boring-git-operations-gitlab-ci/\n[229 automatic merges]: https://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[`@gitlab-bot`]: https://gitlab.com/gitlab-bot\n",[902,9],{"slug":2742,"featured":6,"template":686},"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":2748,"_dir":244,"_draft":6,"_partial":6,"_locale":7,"seo":2749,"content":2755,"config":2761,"_id":2763,"_type":14,"title":2764,"_source":16,"_file":2765,"_stem":2766,"_extension":19},"/en-us/blog/using-run-parallel-jobs",{"title":2750,"description":2751,"ogTitle":2750,"ogDescription":2751,"noIndex":6,"ogImage":2752,"ogUrl":2753,"ogSiteName":673,"ogType":674,"canonicalUrls":2753,"schema":2754},"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":2750,"description":2751,"authors":2756,"heroImage":2752,"date":2758,"body":2759,"category":704,"tags":2760},[2757],"Miguel Rincon","2021-01-20","\n\nAt GitLab, we must verify simultaneous changes from the hundreds of people that contribute to GitLab each day. How can we help them contribute efficiently using our pipelines?\n\nThe pipelines that we use to build and verify GitLab have more than 90 jobs. Not all of those jobs are equal. Some are simple tasks that take a few seconds to finish, while others are long-running processes that must be optimized carefully.\n\nAt the time of this writing, we have more than 700 [pipelines running](https://gitlab.com/gitlab-org/gitlab/-/pipelines?page=1&scope=all&status=running). Each of these pipelines represent changes from team members and contributors from the wider community. All GitLab contributors must wait for the pipelines to finish to make sure the change works and integrates with the rest of the product. We want our pipelines to finish as fast as possible to maintain the productivity of our teams.\n\nThis is why we constantly monitor the duration of our pipelines. For example, in December 2020, successful merge request pipelines had a duration of [53.8 minutes](/handbook/engineering/quality/performance-indicators/#average-merge-request-pipeline-duration-for-gitlab):\n\n![Average pipeline duration was 53.8 minutes in December](https://about.gitlab.com/images/blogimages/using-run-parallel-jobs/historical-pipeline-duration.png){: .shadow.medium.center}\nThe average pipeline took 53.8 minutes to finish in December.\n{: .note.text-center}\n\nGiven that we run [around 500 merge request pipelines](https://gitlab.com/gitlab-org/gitlab/-/pipelines/charts) per day, we want to know: Can we optimize our process to change how long-running jobs _run_?\n\n## How we fixed our bottleneck jobs by making them run in parallel\n\nThe `frontend-fixtures` job uses `rspec` to generate mock data files, which are then saved as files called \"fixtures\". These files are loaded by our frontend tests, so the `frontend-fixtures` must finish before any of our frontend tests can start.\n\n> As not all of our tests need these frontend fixtures, many jobs use the [`needs` keyword](https://docs.gitlab.com/ee/ci/yaml/#needs) to start before the `frontend-fixtures` job is done.\n\nIn our pipelines, this job looked like this:\n\n![The `frontend-fixtures` job](https://about.gitlab.com/images/blogimages/using-run-parallel-jobs/fixtures-job.png){: .shadow.medium.center}\nInside the frontend fixtures job.\n{: .note.text-center}\n\n\nThis job had a normal duration of 20 minutes, and each individual fixture could be generated independently, so we knew there was an opportunity to run this process in parallel.\n\nThe next step was to configure our pipeline to split the job into multiple batches that could be run in parallel.\n\n## How to make frontend-fixtures a parallel job\n\nFortunately, GitLab CI provides an easy way to run a job in parallel using the [`parallel` keyword](https://docs.gitlab.com/ee/ci/yaml/#parallel). In the background, this creates \"clones\" of the same job, so that multiple copies of it can run simultaneously.\n\n**Before:**\n\n```yml\nfrontend-fixtures:\n  extends:\n    - .frontend-fixtures-base\n    - .frontend:rules:default-frontend-jobs\n```\n\n**After:**\n\n```yml\nrspec-ee frontend_fixture:\n  extends:\n    - .frontend-fixtures-base\n    - .frontend:rules:default-frontend-jobs\n  parallel: 2\n```\n\nYou will notice two changes. First, we changed the name of the job, so our job is picked up by [Knapsack](https://docs.knapsackpro.com/ruby/knapsack) (more on that later), and then we add the keyword `parallel`, so the job gets duplicated and runs in parallel.\n\nThe new jobs that are generated look like this:\n\n![Our fixtures job running in parallel](https://about.gitlab.com/images/blogimages/using-run-parallel-jobs/fixtures-job-parallel.png){: .shadow.medium.center}\nThe new jobs that are picked up by Knapsack and run in parallel.\n{: .note.text-center}\n\nAs we used a value of `parallel: 2`, actually two jobs are generated with the names:\n\n- `rspec-ee frontend_fixture 1/2`\n- `rspec-ee frontend_fixture 2/2`\n\nOur two \"generated\" jobs, now take three and 17 minutes respectively, giving us an overall decrease of about three minutes.\n\n![Two parallel jobs in the pipeline](https://about.gitlab.com/images/blogimages/using-run-parallel-jobs/fixtures-job-detail.png){: .shadow.medium.center}\nThe parallel jobs that are running in the pipeline.\n{: .note.text-center}\n\n## Another way we optimized the process\n\nAs we use Knapsack to distribute the test files among the parallel jobs, we were able to make more improvements by reducing the time it takes our longest-running fixtures-generator file to run.\n\nWe did this by splitting the file into smaller batches and optimizing it, so we have more tests running in parallel, which shaved off an additional [~3.5 minutes](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/47158#note_460372560).\n\n## Tips for running parallel jobs\n\nIf you want to ramp up your productivity you can leverage `parallel` on your pipelines by following these tips:\n\n1. Measure the time your pipelines take to run and identify possible bottlenecks to your jobs. You can do this by checking which jobs are slower than others.\n1. Once your slow jobs are identified, try to figure out if they can be run independently 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 running pipelines.\n\n## Learn more about our solution\n\nWe discuss how running jobs in parallel improved the speed of pipelines on GitLab Unfiltered.\n\n\u003C!-- blank line -->\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\u003C!-- blank line -->\n\nAnd here are links to some of the resources we used to run pipelines in parallel:\n\n- The [merge request that introduced `parallel` to fixtures](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/46959).\n- An important [optimization follow-up](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/47158) to make one of the slow tests faster.\n- The [Knapsack gem](https://docs.knapsackpro.com/ruby/knapsack), which we leverage to split the tests more evenly in multiple CI nodes.\n\nAnd many thanks to [Rémy Coutable](/company/team/#rymai), who helped me implement this improvement.\n\nCover image by [@dustt](https://unsplash.com/@dustt) on [Unsplash](https://unsplash.com/photos/ZqBNb7xK5s8)\n{: .note}\n",[902,9,683,706,1721],{"slug":2762,"featured":6,"template":686},"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":2768,"_dir":244,"_draft":6,"_partial":6,"_locale":7,"seo":2769,"content":2775,"config":2781,"_id":2783,"_type":14,"title":2784,"_source":16,"_file":2785,"_stem":2786,"_extension":19},"/en-us/blog/using-the-gitlab-ci-slash-cd-for-smart-home-configuration-management",{"title":2770,"description":2771,"ogTitle":2770,"ogDescription":2771,"noIndex":6,"ogImage":2772,"ogUrl":2773,"ogSiteName":673,"ogType":674,"canonicalUrls":2773,"schema":2774},"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":2770,"description":2771,"authors":2776,"heroImage":2772,"date":2778,"body":2779,"category":704,"tags":2780},[2777],"Mario de la Ossa","2018-08-02","\nSo you've read all about the [Internet of Things](https://en.wikipedia.org/wiki/Internet_of_things) and all the cool stuff you can do with it – from setting up timers for your lights to [making your breakfast](/blog/introducing-auto-breakfast-from-gitlab/) – and now you're itching to get started? Great!\n\nIf you're a power user, you've probably settled on using [Home Assistant](https://www.home-assistant.io/) as your smart home hub, but this choice has a few pitfalls:\n\n- It's annoying to SSH into the server itself to change configuration. Wouldn't you like to use your favorite local editor instead?\n- How do you keep your configuration backed up?\n- How do you protect yourself from accidentally messing up the configuration?\n\nIn this guide we'll show you how to fix these annoyances yourself, thanks to Git and the power of [GitLab Pipelines](https://docs.gitlab.com/ee/ci/pipelines/index.html)! We will set up a pipeline that will check your home-assistant configuration and deploy it to your home-assistant install, giving you the power to deploy changes from anywhere in the world with a simple `git push`!\nDid you go on vacation and forget you wanted your lights to [turn on and off randomly to make it seem like someone's home](https://community.home-assistant.io/t/set-random-time-for-random-automatic-turning-off-lights-mimic-someone-is-home/3524)? No worries! Just open GitLab's [Web IDE](https://docs.gitlab.com/ee/user/project/web_ide/) and make your changes from your hotel room.\n\nBy the end of this tutorial you'll have:\n\n- Automatic configuration backups thanks to `git`. You'll be able to see the history of every change you've made and revert changes easily.\n- Automatic configuration testing via GitLab pipelines. Never again will a simple typo have you scratching your head, wondering why things don't work!\n- An easy way to push changes to your Home Assistant configuration without having to SSH into the server.\n\n## Requirements\n\nIn this guide we'll be assuming a few things:\n\n- You installed Home Assistant using the Docker image\n- The server Home Assistant runs in is accessible from the internet via SSH (or you're using a self-managed GitLab installation in the same network)\n\n## Set up your server\n\n1.   Navigate to your Home Assistant configuration folder.\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 to 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 now set up the GitLab pipeline!\n\n## Setting up the pipeline\n\nWe have a few goals for the [CI/CD pipeline](/topics/ci-cd/):\n- Test the new configuration to ensure it's valid\n- Deploy the new configuration to the Home Assistant server\n- Bonus: Notify us of a successful deployment, since the default is to only notify for failures\n\n[The complete `.gitlab-ci.yml` can be found here.](https://gitlab.com/mdelaossa/hass-via-cicd/blob/master/.gitlab-ci.yml)\n{: .note}\n[General documentation for how to configure jobs can be found here.](https://docs.gitlab.com/ee/ci/yaml/)\n{: .note}\n\nWe will be using the following stages in our pipeline:\n- test: Will test the Home Assistant configuration to ensure it is valid\n- deploy: Will update the Home Assistant configuration in the server and restart Home Assistant\n- notify: Will send a push notification with success/failure state\n\nSince these aren't default pipeline stages we need to declare them in our `.gitlab-ci.yml` like so:\n\n```yaml\nstages:\n  - test\n  - deploy\n  - notify\n```\n\n### Automating configuration testing\n\nSince GitLab CI/CD [supports Docker images](https://docs.gitlab.com/ee/ci/docker/using_docker_images.html) and Home Assistant is available as a Docker image, this is a fairly straightforward stage to add.\n\nAdd this to your `.gitlab-ci.yml` file:\n\n```yaml\ntest:\n  stage: test\n  image: homeassistant/amd64-homeassistant\n  script:\n    - hass --script check_config -c .\n```\n\nWith this we are creating a job called `test` which will run in the `test` stage. We're using the `homeassistant/amd64-homeassistant` image because it exposes the `hass` command globally so we can use the built-in configuration checking command on our committed files. That's it!\n\nFeel free to commit and push this change to test it out!\n\n```bash\ngit add .\ngit commit -m 'Added testing stage to GitLab pipeline'\ngit push\n```\n\nYou'll now see that a pipeline gets created whenever you push:\n\n![HASS Test pipeline success](https://about.gitlab.com/images/blogimages/hass-cicd/pipeline-pass-1.png){: .shadow.center.large}\n\nIf your configuration contains any errors, they'll be shown in the `Failed Jobs` view of the pipeline and you'll get an email notifying you of the failure:\n\n![HASS Test pipeline failure](https://about.gitlab.com/images/blogimages/hass-cicd/pipeline-fail-1.png){: .shadow.center.large}\n\n### Automating deployments\n\nNow that we have automated testing, let's add another stage that will deploy our new configuration if the tests pass!\n\n\"Deploying\" in this case will consist of:\n- SSHing into the server\n- Doing a `git pull` to pull down changes from the repo\n- Restart the Home Assistant Docker image\n\n#### Preparing the server (and GitLab) for SSH access\n\nSince we will be using SSH we need to prepare our server first. We'll follow [these instructions from the GitLab documentation](https://docs.gitlab.com/ee/ci/ssh_keys/).\nWe will also set some [CI/CD Variables](https://gitlab.com/help/ci/variables/README#variables).\n\n1.   Generate a new SSH key pair. It's OK to save them to the current folder as 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 key (the file ending in `.pub`) to `/home/user_running_hass/.ssh/authorized_keys`\n1.   Go to your GitLab project's CI/CD variables (inside Settings). Add the contents of the private key file to a variable named `SSH_PRIVATE_KEY`. You can now delete the SSH key pair files if you'd like, or store them somewhere safe.\n\nWe also need to add our server's host keys to the GitLab runner so the runner will be able to SSH successfully. Alternatively we could disable host key checking, but this is not recommended.\n\n1.   On your server, run `ssh-keyscan example.com` where example.com is the domain or IP of your server.\n1.   Create a new CI/CD variable called `SSH_KNOWN_HOSTS` and add the output of `ssh-keyscan` to it.\n\nYou should also create two other CI/CD variables (optional):\n- `DEPLOY_USER`: the user running HASS that the runner with SSH into the server as to perform the deploy\n- `DEPLOY_HOST`: the domain or IP of the server\n\n#### The deploy stage\n\nNow that we have prepared our server and GitLab CI/CD variables, we can add our deploy stage to `.gitlab-ci.yml`. Please note that we are using the `only: ` keyword so that only new commits in the `master` branch will attempt a deploy.\n\n```yaml\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\nThe `before_script` above is in charge of:\n- Making sure `ssh-agent` is installed and installing it otherwise\n- Making sure `ssh-agent` is running\n- Adding the `SSH_PRIVATE_KEY` to the keys to use when logging into a server\n- Creating the `.ssh` folder with required permissions\n- Adding the values we added to the `SSH_KNOWN_HOSTS` variable to the proper location\n\nThe `script` portion is what actually deploys our new configuration:\n- We `cd` into the proper location (where the Home Assistant configuration files are kept)\n- We update the configuration with a `git pull`, since this directory is a Git repo\n- We restart Home Assistant (in this case the Docker image was created with the name `home-assistant`. Please use the name of your container)\n\nNote: If you did not create `DEPLOY_USER` and `DEPLOY_HOST` variables on GitLab, please replace the proper values in the script\n{: .note}\n\nNow let's commit and push this new stage to GitLab!\n```bash\ngit add .\ngit commit -m 'Added deploy stage to GitLab pipeline'\ngit push\n```\n\nWith this new stage added, you can now edit your configuration from anywhere (including the GitLab Web IDE!) and be confident that these changes will be pushed to your Home Assistant server if there are no issues with the configuration.\nThere's no longer a need to figure out how to connect directly to your Home Assistant server to make the edits you need.\n\n### Bonus: Successful deployment notifications\n\nYou'll notice that if the configuration is wrong or an error occurs during the deployment, you will get an email notification, but what about when everything runs successfully?\n\nWe have two options:\n\n1. Enable the `Pipeline Emails` integration and set it to notify on every pipeline\n2. Add a new stage called `notify` and use it to send push notifications to your phone\n\nWhile email is really nice, there's something really satisfying about getting push notification for your services, so let's set things up using [Pushover](https://pushover.net/).\nYou'll need to create an 'Application' and add the token you get to a GitLab variable called `PUSHOVER_API_TOKEN`. You'll also need to add your user key to a variable called `PUSHOVER_USER_TOKEN`.\n\nSince we'd like a different notification depending on whether our pipeline passed or failed, we will be adding two jobs to the `notify` stage:\n\n```yaml\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\nOur first job, `notify_success`, runs when the stage before it (`deploy`) completes successfully. This is the default for GitLab. Our `notify_fail` job on the other hand has `when: on_failure` set, which means it will _only_ run when the stage before it fails. We also set `allow_failure: true` on both these jobs so that we aren't notified of a failed pipeline if for some reason the notification commands fail. We also set the `only: - master` option since deploys only happen on the master branch.\n\nWe are using Pushover's API to send the message we want in the `script` area.\n\nWith this final stage in place, your pipeline should now look like this:\n\n![HASS pipeline overview](https://about.gitlab.com/images/blogimages/hass-cicd/pipeline-final-1.png){: .shadow.center.large}\n\n### Enjoy!\n\nThere you have it! Now you can edit your Home Assistant configuration from anywhere you'd like, using your favorite editor, by following three simple steps:\n\n1. `git clone PATH_TO_REPO` (if you have not cloned it before)\n2. Edit the configuration\n3. `git push -u remote master`\n\n[Photo](https://unsplash.com/photos/9TF54VdG0ws?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText) by Kevin Bhagat on [Unsplash](https://unsplash.com/search/photos/smart-home?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText)\n{: .note}\n",[9,948],{"slug":2782,"featured":6,"template":686},"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":2788,"_dir":244,"_draft":6,"_partial":6,"_locale":7,"seo":2789,"content":2795,"config":2800,"_id":2802,"_type":14,"title":2803,"_source":16,"_file":2804,"_stem":2805,"_extension":19},"/en-us/blog/velocity-with-confidence",{"title":2790,"description":2791,"ogTitle":2790,"ogDescription":2791,"noIndex":6,"ogImage":2792,"ogUrl":2793,"ogSiteName":673,"ogType":674,"canonicalUrls":2793,"schema":2794},"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":2790,"description":2791,"authors":2796,"heroImage":2792,"date":1839,"body":2798,"category":704,"tags":2799},[2797],"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",[728,902,9,683,537],{"slug":2801,"featured":6,"template":686},"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":2807,"_dir":244,"_draft":6,"_partial":6,"_locale":7,"seo":2808,"content":2814,"config":2820,"_id":2822,"_type":14,"title":2823,"_source":16,"_file":2824,"_stem":2825,"_extension":19},"/en-us/blog/verify-week-hackathon",{"title":2809,"description":2810,"ogTitle":2809,"ogDescription":2810,"noIndex":6,"ogImage":2811,"ogUrl":2812,"ogSiteName":673,"ogType":674,"canonicalUrls":2812,"schema":2813},"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":2809,"description":2810,"authors":2815,"heroImage":2811,"date":2817,"body":2818,"category":299,"tags":2819},[2816],"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,991,1464],{"slug":2821,"featured":6,"template":686},"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":2827,"_dir":244,"_draft":6,"_partial":6,"_locale":7,"seo":2828,"content":2834,"config":2841,"_id":2843,"_type":14,"title":2844,"_source":16,"_file":2845,"_stem":2846,"_extension":19},"/en-us/blog/vestiaire-collective-on-moving-to-a-devsecops-platform",{"title":2829,"description":2830,"ogTitle":2829,"ogDescription":2830,"noIndex":6,"ogImage":2831,"ogUrl":2832,"ogSiteName":673,"ogType":674,"canonicalUrls":2832,"schema":2833},"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":2835,"description":2830,"authors":2836,"heroImage":2831,"date":2838,"body":2839,"category":989,"tags":2840},"Vestiaire Collective VP shares wins, insights, and what's next with DevSecOps migration",[2837],"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",[773,994,706,9,683],{"slug":2842,"featured":6,"template":686},"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":2848,"_dir":244,"_draft":6,"_partial":6,"_locale":7,"seo":2849,"content":2855,"config":2860,"_id":2862,"_type":14,"title":2863,"_source":16,"_file":2864,"_stem":2865,"_extension":19},"/en-us/blog/wag-labs-blog-post",{"title":2850,"description":2851,"ogTitle":2850,"ogDescription":2851,"noIndex":6,"ogImage":2852,"ogUrl":2853,"ogSiteName":673,"ogType":674,"canonicalUrls":2853,"schema":2854},"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":2850,"description":2851,"authors":2856,"heroImage":2852,"date":2857,"body":2858,"category":681,"tags":2859},[2207],"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",[994,9,991,728,771,838,992,1721],{"slug":2861,"featured":6,"template":686},"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":2867,"_dir":244,"_draft":6,"_partial":6,"_locale":7,"seo":2868,"content":2874,"config":2881,"_id":2883,"_type":14,"title":2884,"_source":16,"_file":2885,"_stem":2886,"_extension":19},"/en-us/blog/100-runners-in-less-than-10mins-and-less-than-10-clicks",{"title":2869,"description":2870,"ogTitle":2869,"ogDescription":2870,"noIndex":6,"ogImage":2871,"ogUrl":2872,"ogSiteName":673,"ogType":674,"canonicalUrls":2872,"schema":2873},"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":2875,"description":2870,"authors":2876,"heroImage":2871,"date":2878,"body":2879,"category":704,"tags":2880},"How to provision 100 AWS Graviton GitLab Spot Runners in 10 Minutes for $2/hour",[678,2877],"Nupur Sharma","2021-08-17","\n\nManaging elastically scaled or highly available compute infrastructures is one of the key challenges the cloud was built for. Application scaling concerns can be handled by cloud services that are purpose designed, rigorously tested, and continually improved. This article dives into some specific enablement automation that brings the benefits of AWS Autoscaling Groups (ASG) to runner management. There are benefits to both the largest fleets and single instance runners.\n\nEmbedded in this article is a YouTube video that demonstrates the deployment of 100 GitLab runners on Amazon EC2 Spot compute in less than 10 minutes using less than 10 clicks. The video also shows updating this entire fleet in under 10 minutes to emphasize the time savings of built-in maintenace.\n\nThe information and automation in this article applies to GitLab Private Runners which are deployed on your own compute resources. Self-managed GitLab instances require private runners, but they can also be configured and used with GitLab.com SaaS accounts.\n\n## Well-architected runner management\n\nThere are many different reasons that a customer might need to deploy multiple runners with various characteristics. Some of the more popular ones are:\n\n- Workloads that require large-scale runner fleets.\n- To gain cost savings through Spot compute, uptime scheduling, and ARM architecture.\n- Projects with high demand of CI activity to make sure that the runner is not being held up by jobs on another project.\n- Jobs that have special security requirements, e.g., security credentials, role-based access or managed identities for Continuous Delivery (CD). These security requirements can enable instance-level (AWS IAM Instance Profile) security by allowing runners with sufficient rights to deploy in specific target environments. For example, a CD runner for non-production environments and a different runner for production.\n- Implementing role-based access control rather than user-based. This means users don't have to use secrets to manage security requirements for CI jobs to accomplish their tasks.\n- Development teams can be confident the runner has the same capabilities for CI and CD automation they test through their interactive logins by leveraging a common IAM role.\n\n### The challenges of building production-grade elastic GitLab Runners\n\n[The GitLab Runner](https://docs.gitlab.com/runner/) is the workhorse of GitLab CI and CD capabilities. The runner can handle numerous operating environments and automation functions for a GitLab instance. The GitLab Runner has become very sophisticated due to the broad range of supported environments. In order to successfully configure the GitLab Runner as a set-it-and-forget-it service, the user has to work through many different decisions and considerations. We summarize some of the GitLab Runner-specific considerations that can be challenging:\n\n- There are a lot of configuration options and scenarios to sort through. It can be an iterative process to discover what needs to be done to set up GitLab Runners.\n- Ensuring runners are a production-grade capability requires Infrastructure as Code (IaC) development so that high availability and scaling can be achieved by automatically spawning new instances.\n- Ensuring that runner deregistration happens correctly when GitLab Runners are automatically scaled in.\n- Additional cost-saving configurations, such as Spot compute and scheduled runner uptime, can complicate the automation requirements for AWS Autoscaling Groups (ASGs).\n- Large organizations often want developers to be able to easily self-service deploy runners with various configurations. Service Management Automation (SMA) has been made popular with products like Service Now, AWS Service Catalog, and AWS Control Tower. This automation is compatible with SMA.\n- It can be difficult to map runners to AWS and map AWS to runners in large organizations with numerous runners and AWS accounts.\n\n### Introducing the GitLab HA Scaling Runner Vending Machine for AWS\n\nAn effective way to handle multiple design considerations is to make a reusable tool. To help you with best practice runner deployments on AWS, we created the [GitLab HA Scaling Runner Vending Machine for AWS](https://gitlab.com/guided-explorations/aws/gitlab-runner-autoscaling-aws-asg/) (\"The GitLab Runner Vending Machine\"). It is created in AWS’ Infrastructure as Code, known as CloudFormation.\n\n> **Designed with AWS Well Architected:** This automation has many features beyond the scope of this blog post. The primary focus of this blog post is on managing costs. See the [full list of features here](https://gitlab.com/guided-explorations/aws/gitlab-runner-autoscaling-aws-asg/-/blob/main/FEATURES.md).\n\nThe GitLab Runner Vending Machine has the following cost management and scaling management benefits, exposed as a variety of parameters:\n\n- The ability to leverage Spot compute instances. This is important because it leaves CI/CD pipeline developers in charge of whether specific Gitlab CI/CD jobs run on Spot compute or not.\n- ASG-scheduled scaling so that a runner or runner fleet can be completely shutdown when not in use.\n- The GitLab Runner Vending Machine can leverage ARM compute for Linux - which runs faster and costs less.\n- It can also use ASG to update all runners in a fleet with the latest machine images and GitLab Runner version (or a specific version). When maintenance is not built-in, the labor cost of keeping things up-to-date can be significant.\n- Runner naming and tagging in AWS and GitLab, which eases the burden of locating runner instances and managing orphaned runners registrations, whether it is manual or automated.\n\n### How to save money with The GitLab Runner Vending Machine\n\nSignificant savings are possible with this IaC, whether your team wants to save on a single runner or a fleet of them.\n\nThe savings calculations below are for a single runner and should be linear for a given workload. To calculate your savings for more runners, simply multiply the final result by the number of runner instances. The available \"Runner Minutes\" per hour is calculated as the runner's job concurrency setting multiplied by the minutes in an hour. For this exercise, we'll use job concurrency of \"10\". This number should be changed depending on the instance types you are using and the load testing of your typical CI/CD workloads.\n\nJust like most performance analysis, we are assuming that hardware resource utilization is optimal and consistent. If a runner cluster can sustain respectable performance with 80% CPU loading, this calculation assumes that would be maintained regardless of the size of the cluster.\n\n#### AWS Graviton ARM and Spot savings\n\nThe GitLab Runner engineering team has completed performance testing that demonstrates performance gains of more than 30% on some AWS Graviton (ARM-based) instance types. Assuming that runners are performance-managed for optimized utilization, this gain is a direct cost savings. Just recently, we shared [how deploying GitLab on Arm-based AWS Graviton2 resulted in cost savings of 23% and 36% performance gains](/blog/achieving-23-cost-savings-and-36-performance-gain-using-gitlab-and-gitlab-runner-on-arm-neoverse-based-aws-graviton2-processor/).\n\n![ARM Efficiency Test Results For GitLab Runner](https://about.gitlab.com/images/blogimages/hundred-runners/hundredrunners-image1.png)\nGitLab Runner testing results for ARM-efficiency gains.\n{: .note.text-center}\n\n#### Scheduling savings\n\nThe savings can be dramatic when teams are able to turn off runners when not in use. For instance: Scheduling a runner to operate for 40-hours per week saves 76% when compared to the cost of running it for 168 hours. Runners that are just in use for 10 hours per week saves 94%.\n\n#### Combining scheduling, Spot, and ARM to save 97%\n\nJust for fun, let's see what savings are possible by comparing a standard runner scenario with deploying runners in customized, stand-alone instances to the maximum savings automation can deliver.\n\nImagine I am a developer who set up a custom GitLab Runner on an m5.xlarge instance, which is x86 the architecture, for a development team that works for 40 hours on the same time zone. Since there is no automation, the GitLab Runner runs 24/7. We will assume a job concurrency of 10, which gives 600 \"runner minutes\" per hour of run time. Scheduling uptime, running on Spot, and leveraging ARM can all be achieved quickly by redeploying the runner with The GitLab Runner Vending Machine.\n\nHere is the calculation to run the configuration described above, for one week: On Demand, x86, Always On: 1 x m5.xlarge = .192/hr x 168 hrs/week = **$32/week or $1664/year**\n\nHere are the savings that come from running Spot, ARM, and scheduling the Runner to be up just 40hrs/week: 1 x m6g.large Spot = .0419 x 40hrs/week x 64% (36% better performance) = **$1/week**\n\n$1/$32 x 100 = 3.125% of the original cost for the same work. In other words, **we just saved 97%** without ever impacting the ability to get the job done.\n\nIn short, The GitLab Runner Vending Machine intends to bring the many cost saving mechanisms of AWS Cloud computing to your GitLab Runner fleets.\n\nYou can save costs by using ARM/Graviton instances, Spot compute, or by scheduling uptime. In many cases, you can combine all three savings mechanisms for maximum impact.\n\n### Special pipeline building concerns for Spot Runners\n\nSpot instances can disappear with as little as two minutes of warning. This inevitably means some runners will be terminated while jobs are still in progress. CI/CD pipeline developers must take into account whether a job ought to run on compute resources that can disappear with short notice (so short as to be considered \"no notice\"). This comes down to deciding what jobs are OK to run on Spot and what jobs should instead run on AWS' persistent compute known as \"On-Demand\".\n\nThe GitLab Runner Vending Machine accounts for these constraints by tagging runner instances in GitLab with `computetype-spot` or `computetype-ondemand` – indicating in the \"tags\" segment of GitLab CI/CD jobs if a job should run on Spot compute.\n\nSome types of CI workloads, e.g., mass performance testing or large unit testing suites, may already have work queues and work tracking that make it ideal for Spot compute. Other activities, e.g., polling another system for a deployment status, could suffer a material discrepancy if terminated permaturely. Others, such as building the application, are sort of in the middle. Usually, restarting the build is sufficient.\n\n### Job configuration for Spot\n\nIf you need to reschedule terminated work, it is helpful to configure GitLab’s job `retry:` keyword. When working with a dispatching engine or work queue that automatically accounts for incompleted work by processing agents, the retry configuration is unnecessary.\n\nHere is an example that implements both of these concepts:\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\nThe usage and limitations of `retry:` are discussed in greater detail in the [GitLab CI documentation on retry](https://docs.gitlab.com/ee/ci/yaml/#retry).\n\n### How to get started\n\nThe CloudFormation templates for the [GitLab Runner Vending Machine are managed in a public project on GitLab.com](https://gitlab.com/guided-explorations/aws/gitlab-runner-autoscaling-aws-asg/). There is a lot of information in the project about how the solution works and what problems it aims to solve, and will be useful for very experienced AWS builders.\n\nBut to keep it simple for users who want the quickest path to creating runners of all sizes, it also has an \"easy button\" page that has a table that looks like this:\n\n![Easy Button Page Sample](https://about.gitlab.com/images/blogimages/hundred-runners/hundredrunners-image2.png)\nThe easy buttons launch a CloudFormation Quick Create that only requires filling in a few fields.\n{: .note.text-center}\n\nKeep in mind that easy buttons intentionally hide the high degree of customization that is possible with this automation by setting the parameters for the most common scenarios in advance. Advanced AWS users should read more of the documentation in the repository to understand that the GitLab Runner Vending Machine is also capable of creating sophisticated runner fleets.\n\nFirst, click the CloudFormation icons to launch the Easy Button template directly into the CloudFormation Quick Create console. The Quick Create console is designed for simplicity to enable you to complete the prompts and then click one button to launch the stack.\n\n![CloudFormation Quick Create Example](https://about.gitlab.com/images/blogimages/hundred-runners/hundredrunners-image3.png){: .shadow.medium.center}\nThis is a typical Quick Create form for the GitLab Vending Machine easy buttons.\n{: .note.text-center}\n\nNext, select the deploy region by using the drop down menu in the upper right of the console (where the screenshot says \"Oregon\").\n\nIn most cases, you will only need to add your GitLab instance URL (GitLab.com is fine if that is where your repositories are), and the runner token, which you retrieve from the group level or project you wish to attach the runners to. If you are registering against a self-managed instance, you can use the instance-level tokens from the administrator console to register the runner for use across the entire instance. Read on for [instructions for finding Runner Registration Tokens](https://docs.gitlab.com/runner/register/#requirements).\n\nA few other customization parameters are available for your convenience.\n\nNote that the automation attempts to use the default VPC of the region in which you deploy and the default security group for the VPC. In some organizations, default VPCs and/or their security groups are locked. You can deploy to custom VPCs by using the full template instead of an easy button. On the easy button page look for the footnote \"Not any easy button person?\"\" to find a link to the full template.\n\nWatch the video below to see the deployment of provisioning 100 GitLab Spot Runners on AWS in less than 10 minutes and in less than 10 clicks for just $5 per hour.\n\n\u003C!-- blank line -->\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\u003C!-- blank line -->\n\nCheck out the YouTube playlist for more relevant videos about [GitLab and AWS](https://youtube.com/playlist?list=PL05JrBw4t0Ko30Bkf8bAvR-8E441Fy2G9)\n\n### This automation does much, much more\n\nWhile this article focused how much you can saving while using Spot for scaled runners, the underlying automation is capable of many other scenarios. Below is a summary of the additional features and benefits covered in the documentation.\n\n- Scaled runners that are persistent (not Spot) ([see more easy buttons here](https://gitlab.com/guided-explorations/aws/gitlab-runner-autoscaling-aws-asg/-/blob/main/easybuttons.md)).\n- Supports small, single runner setups and scaled ones.\n- Supports GitLab.com SaaS or self-managed instances.\n- Automates OS patching and Runner version upgrading.\n- Supports Windows and Linux.\n- Can be reused with Amazon provisioning services such as Service Catalog and Control Tower.\n- Implements least privilege security throughout.\n- Supports deregistering runners on scale-in or Spot termination.\n\nA full feature list is in the document [Features of GitLab HA Scaling Runner Vending Machine for AWS](https://gitlab.com/guided-explorations/aws/gitlab-runner-autoscaling-aws-asg/-/blob/main/FEATURES.md)\n\n### Easy running\n\nWe hope that this automation will make deployment of runners of all sizes simple for you. We are open to your feedback, suggestions and contributions in the GitLab project.\n",[9,683,728,795],{"slug":2882,"featured":6,"template":686},"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,[666,691,714,736,757,780,802,822,846],1753733120027]