[{"data":1,"prerenderedAt":800},["ShallowReactive",2],{"/en-us/blog/gitlab-duo-cli":3,"navigation-en-us":36,"banner-en-us":446,"footer-en-us":456,"blog-post-authors-en-us-John Coghlan":698,"blog-related-posts-en-us-gitlab-duo-cli":712,"assessment-promotions-en-us":751,"next-steps-en-us":790},{"id":4,"title":5,"authorSlugs":6,"body":8,"categorySlug":9,"config":10,"content":14,"description":8,"extension":25,"isFeatured":11,"meta":26,"navigation":11,"path":27,"publishedDate":20,"seo":28,"stem":32,"tagSlugs":33,"__hash__":35},"blogPosts/en-us/blog/gitlab-duo-cli.yml","Gitlab Duo Cli",[7],"john-coghlan",null,"product",{"featured":11,"template":12,"slug":13},true,"BlogPost","gitlab-duo-cli",{"title":15,"description":16,"authors":17,"heroImage":19,"date":20,"body":21,"category":9,"tags":22},"GitLab Duo CLI: Agentic AI for the development lifecycle, now in the terminal","Developers who work outside the IDE and GitLab UI can access GitLab Duo Agent Platform in the terminal with built-in security controls and headless mode support.",[18],"John Coghlan","https://res.cloudinary.com/about-gitlab-com/image/upload/v1775561395/bhe1as7ttjvzltxwgo5m.png","2026-04-07","Debugging a broken pipeline at the end of a sprint, or wiring AI into a CI/CD workflow that runs without anyone watching, is exactly where today's AI assistants fall short given their focus on coding – which is only a portion of the software lifecycle. They're built for interactive coding sessions, not automation across different stages of software development. GitLab Duo CLI, now in public beta, is built for both.\n\nGitLab Duo CLI brings agentic AI powered by [Duo Agent Platform](https://about.gitlab.com/gitlab-duo-agent-platform/) to the terminal with full support for automated workflows, alongside an interactive chat mode when you need a human in the loop. This article highlights what Duo CLI does, how its two operating modes work, and the security model behind it.\n\n## How to install GitLab Duo CLI\n\nIf you already have GLab (the GitLab CLI) installed, enter:\n\n```\nglab duo cli\n```\n\nThen follow the prompts.\n\nIf you don't have GLab yet, you can [install it here](https://gitlab.com/gitlab-org/cli/#installation) or [use GitLab Duo CLI as a standalone tool](https://docs.gitlab.com/user/gitlab_duo_cli/#without-the-gitlab-cli).\n\n## Why the terminal, and why now\n\nThe first wave of AI assistants for software development lived in the IDE, and focused solely on coding. That made sense when the job was autocomplete. But as AI agents start *doing things* across every stage of the software lifecycle, e.g. running tests, triggering pipelines, monitoring vulnerability scans, and more, the IDE may no longer be the only abstraction needed to get the job done.\n\nThe best developer tools are ones that work for both humans and machines. CLIs have had decades of design iteration toward that goal. They're composable. You can pipe output, chain commands, and drop them into scripts. They're debuggable: when something goes wrong, you run the same command yourself and see exactly what the agent saw. And they're transparent. No background processes, no initialization dance, no protocol to decode when things break.\n\nTerminal interfaces are better for automation, scripting, and environment portability. IDE interfaces are better for interactive, context-rich development. GitLab Duo CLI is designed for the former, while Duo Agentic Chat in the IDE and UI covers the latter.\n\n## What GitLab Duo CLI can do\n\nWith GitLab Duo CLI, developers can build, modify, refactor, and modernize code — similar to other AI-powered coding assistants built for the terminal. But that’s not where they stop. Any agent and flow defined within GitLab Duo Agent Platform is accessible via Duo CLI, whether it is to automate CI/CD configuration and optimize pipelines, or to perform multi-step development tasks autonomously across the entire software development lifecycle.\n\nGitLab Duo CLI runs in two modes:\n\n* **Interactive mode**, an editor-agnostic terminal chat experience with human-in-the-loop approval before any action is taken. Use it to understand codebase structure, create code, fix errors, or troubleshoot broken pipelines.  \n* **Headless mode**, non-interactive, designed for runners, scripts, and automated workflows. Drop it into CI/CD and let it work without handholding.\n\n## AI with guardrails\n\nAgentic AI that can take actions creates real security exposure. GitLab Duo CLI addresses this at the platform level, not as an afterthought:\n\n* **Human-in-the-loop by default** in interactive mode, so no action is taken without approval.  \n* **Prompt injection detection** is built into the GitLab Duo Agent Platform, not bolted on.  \n* **Composite identity** limits what the agent can access and makes every AI-driven action auditable.\n\nGitLab Duo CLI also supports [custom instruction files](https://docs.gitlab.com/user/duo_agent_platform/customize/), e.g. `chat-rules.md`, `AGENTS.md`, and `SKILL.md`, that define which tasks, resources, context, knowledge, and actions your agents are permitted to take. **This is the principle of least privilege applied to AI: Your agent does exactly what you've authorized, and nothing more.**\n\nWatch GitLab Duo CLI in action:\n\u003Ciframe src=\"https://player.vimeo.com/video/1179964611?badge=0&amp;autopause=0&amp;player_id=0&amp;app_id=58479\" frameborder=\"0\" allow=\"autoplay; fullscreen; picture-in-picture; clipboard-write; encrypted-media; web-share\" referrerpolicy=\"strict-origin-when-cross-origin\" style=\"position:absolute;top:0;left:0;width:100%;height:100%;\" title=\"GitLab Duo CLI Beta Demo V1\">\u003C/iframe>\u003Cscript src=\"https://player.vimeo.com/api/player.js\">\u003C/script>\n\n## Use GitLab Duo CLI today\n\nYou can experience the benefits of GitLab Duo CLI by [starting a free trial of GitLab Duo Agent Platform](https://about.gitlab.com/gitlab-duo-agent-platform/). \n\nIf you are already using GitLab in the free tier, you can sign up for GitLab Duo Agent Platform by [following a few simple steps](https://docs.gitlab.com/subscriptions/gitlab_credits/#for-the-free-tier-on-gitlabcom). \n\nAnd if you are an existing subscriber to GitLab Premium or Ultimate, you can take advantage of GitLab Duo CLI by simply [turning on Duo Agent Platform](https://docs.gitlab.com/user/duo_agent_platform/turn_on_off/) and start using the GitLab Credits [that are included](https://docs.gitlab.com/subscriptions/gitlab_credits/#included-credits) with your subscription.",[23,9,24],"AI/ML","features","yml",{},"/en-us/blog/gitlab-duo-cli",{"config":29,"title":31,"description":16},{"noIndex":30},false,"GitLab Duo CLI: Agentic AI now in the terminal","en-us/blog/gitlab-duo-cli",[34,9,24],"aiml","WJm25r3GsWuseqIZg-ijgDYZvjCt0o25BrVgAb70q24",{"data":37},{"logo":38,"freeTrial":43,"sales":48,"login":53,"items":58,"search":366,"minimal":397,"duo":416,"switchNav":425,"pricingDeployment":436},{"config":39},{"href":40,"dataGaName":41,"dataGaLocation":42},"/","gitlab logo","header",{"text":44,"config":45},"Get free trial",{"href":46,"dataGaName":47,"dataGaLocation":42},"https://gitlab.com/-/trial_registrations/new?glm_source=about.gitlab.com&glm_content=default-saas-trial/","free trial",{"text":49,"config":50},"Talk to sales",{"href":51,"dataGaName":52,"dataGaLocation":42},"/sales/","sales",{"text":54,"config":55},"Sign in",{"href":56,"dataGaName":57,"dataGaLocation":42},"https://gitlab.com/users/sign_in/","sign in",[59,86,181,186,287,347],{"text":60,"config":61,"cards":63},"Platform",{"dataNavLevelOne":62},"platform",[64,70,78],{"title":60,"description":65,"link":66},"The intelligent orchestration platform for DevSecOps",{"text":67,"config":68},"Explore our Platform",{"href":69,"dataGaName":62,"dataGaLocation":42},"/platform/",{"title":71,"description":72,"link":73},"GitLab Duo Agent Platform","Agentic AI for the entire software lifecycle",{"text":74,"config":75},"Meet GitLab Duo",{"href":76,"dataGaName":77,"dataGaLocation":42},"/gitlab-duo-agent-platform/","gitlab duo agent platform",{"title":79,"description":80,"link":81},"Why GitLab","See the top reasons enterprises choose GitLab",{"text":82,"config":83},"Learn more",{"href":84,"dataGaName":85,"dataGaLocation":42},"/why-gitlab/","why gitlab",{"text":87,"left":11,"config":88,"link":90,"lists":94,"footer":163},"Product",{"dataNavLevelOne":89},"solutions",{"text":91,"config":92},"View all Solutions",{"href":93,"dataGaName":89,"dataGaLocation":42},"/solutions/",[95,119,142],{"title":96,"description":97,"link":98,"items":103},"Automation","CI/CD and automation to accelerate deployment",{"config":99},{"icon":100,"href":101,"dataGaName":102,"dataGaLocation":42},"AutomatedCodeAlt","/solutions/delivery-automation/","automated software delivery",[104,108,111,115],{"text":105,"config":106},"CI/CD",{"href":107,"dataGaLocation":42,"dataGaName":105},"/solutions/continuous-integration/",{"text":71,"config":109},{"href":76,"dataGaLocation":42,"dataGaName":110},"gitlab duo agent platform - product menu",{"text":112,"config":113},"Source Code Management",{"href":114,"dataGaLocation":42,"dataGaName":112},"/solutions/source-code-management/",{"text":116,"config":117},"Automated Software Delivery",{"href":101,"dataGaLocation":42,"dataGaName":118},"Automated software delivery",{"title":120,"description":121,"link":122,"items":127},"Security","Deliver code faster without compromising security",{"config":123},{"href":124,"dataGaName":125,"dataGaLocation":42,"icon":126},"/solutions/application-security-testing/","security and compliance","ShieldCheckLight",[128,132,137],{"text":129,"config":130},"Application Security Testing",{"href":124,"dataGaName":131,"dataGaLocation":42},"Application security testing",{"text":133,"config":134},"Software Supply Chain Security",{"href":135,"dataGaLocation":42,"dataGaName":136},"/solutions/supply-chain/","Software supply chain security",{"text":138,"config":139},"Software Compliance",{"href":140,"dataGaName":141,"dataGaLocation":42},"/solutions/software-compliance/","software compliance",{"title":143,"link":144,"items":149},"Measurement",{"config":145},{"icon":146,"href":147,"dataGaName":148,"dataGaLocation":42},"DigitalTransformation","/solutions/visibility-measurement/","visibility and measurement",[150,154,158],{"text":151,"config":152},"Visibility & Measurement",{"href":147,"dataGaLocation":42,"dataGaName":153},"Visibility and Measurement",{"text":155,"config":156},"Value Stream Management",{"href":157,"dataGaLocation":42,"dataGaName":155},"/solutions/value-stream-management/",{"text":159,"config":160},"Analytics & Insights",{"href":161,"dataGaLocation":42,"dataGaName":162},"/solutions/analytics-and-insights/","Analytics and insights",{"title":164,"items":165},"GitLab for",[166,171,176],{"text":167,"config":168},"Enterprise",{"href":169,"dataGaLocation":42,"dataGaName":170},"/enterprise/","enterprise",{"text":172,"config":173},"Small Business",{"href":174,"dataGaLocation":42,"dataGaName":175},"/small-business/","small business",{"text":177,"config":178},"Public Sector",{"href":179,"dataGaLocation":42,"dataGaName":180},"/solutions/public-sector/","public sector",{"text":182,"config":183},"Pricing",{"href":184,"dataGaName":185,"dataGaLocation":42,"dataNavLevelOne":185},"/pricing/","pricing",{"text":187,"config":188,"link":190,"lists":194,"feature":274},"Resources",{"dataNavLevelOne":189},"resources",{"text":191,"config":192},"View all resources",{"href":193,"dataGaName":189,"dataGaLocation":42},"/resources/",[195,228,246],{"title":196,"items":197},"Getting started",[198,203,208,213,218,223],{"text":199,"config":200},"Install",{"href":201,"dataGaName":202,"dataGaLocation":42},"/install/","install",{"text":204,"config":205},"Quick start guides",{"href":206,"dataGaName":207,"dataGaLocation":42},"/get-started/","quick setup checklists",{"text":209,"config":210},"Learn",{"href":211,"dataGaLocation":42,"dataGaName":212},"https://university.gitlab.com/","learn",{"text":214,"config":215},"Product documentation",{"href":216,"dataGaName":217,"dataGaLocation":42},"https://docs.gitlab.com/","product documentation",{"text":219,"config":220},"Best practice videos",{"href":221,"dataGaName":222,"dataGaLocation":42},"/getting-started-videos/","best practice videos",{"text":224,"config":225},"Integrations",{"href":226,"dataGaName":227,"dataGaLocation":42},"/integrations/","integrations",{"title":229,"items":230},"Discover",[231,236,241],{"text":232,"config":233},"Customer success stories",{"href":234,"dataGaName":235,"dataGaLocation":42},"/customers/","customer success stories",{"text":237,"config":238},"Blog",{"href":239,"dataGaName":240,"dataGaLocation":42},"/blog/","blog",{"text":242,"config":243},"Remote",{"href":244,"dataGaName":245,"dataGaLocation":42},"https://handbook.gitlab.com/handbook/company/culture/all-remote/","remote",{"title":247,"items":248},"Connect",[249,254,259,264,269],{"text":250,"config":251},"GitLab Services",{"href":252,"dataGaName":253,"dataGaLocation":42},"/services/","services",{"text":255,"config":256},"Community",{"href":257,"dataGaName":258,"dataGaLocation":42},"/community/","community",{"text":260,"config":261},"Forum",{"href":262,"dataGaName":263,"dataGaLocation":42},"https://forum.gitlab.com/","forum",{"text":265,"config":266},"Events",{"href":267,"dataGaName":268,"dataGaLocation":42},"/events/","events",{"text":270,"config":271},"Partners",{"href":272,"dataGaName":273,"dataGaLocation":42},"/partners/","partners",{"backgroundColor":275,"textColor":276,"text":277,"image":278,"link":282},"#2f2a6b","#fff","Insights for the future of software development",{"altText":279,"config":280},"the source promo card",{"src":281},"https://res.cloudinary.com/about-gitlab-com/image/upload/v1758208064/dzl0dbift9xdizyelkk4.svg",{"text":283,"config":284},"Read the latest",{"href":285,"dataGaName":286,"dataGaLocation":42},"/the-source/","the source",{"text":288,"config":289,"lists":291},"Company",{"dataNavLevelOne":290},"company",[292],{"items":293},[294,299,305,307,312,317,322,327,332,337,342],{"text":295,"config":296},"About",{"href":297,"dataGaName":298,"dataGaLocation":42},"/company/","about",{"text":300,"config":301,"footerGa":304},"Jobs",{"href":302,"dataGaName":303,"dataGaLocation":42},"/jobs/","jobs",{"dataGaName":303},{"text":265,"config":306},{"href":267,"dataGaName":268,"dataGaLocation":42},{"text":308,"config":309},"Leadership",{"href":310,"dataGaName":311,"dataGaLocation":42},"/company/team/e-group/","leadership",{"text":313,"config":314},"Team",{"href":315,"dataGaName":316,"dataGaLocation":42},"/company/team/","team",{"text":318,"config":319},"Handbook",{"href":320,"dataGaName":321,"dataGaLocation":42},"https://handbook.gitlab.com/","handbook",{"text":323,"config":324},"Investor relations",{"href":325,"dataGaName":326,"dataGaLocation":42},"https://ir.gitlab.com/","investor relations",{"text":328,"config":329},"Trust Center",{"href":330,"dataGaName":331,"dataGaLocation":42},"/security/","trust center",{"text":333,"config":334},"AI Transparency Center",{"href":335,"dataGaName":336,"dataGaLocation":42},"/ai-transparency-center/","ai transparency center",{"text":338,"config":339},"Newsletter",{"href":340,"dataGaName":341,"dataGaLocation":42},"/company/contact/#contact-forms","newsletter",{"text":343,"config":344},"Press",{"href":345,"dataGaName":346,"dataGaLocation":42},"/press/","press",{"text":348,"config":349,"lists":350},"Contact us",{"dataNavLevelOne":290},[351],{"items":352},[353,356,361],{"text":49,"config":354},{"href":51,"dataGaName":355,"dataGaLocation":42},"talk to sales",{"text":357,"config":358},"Support portal",{"href":359,"dataGaName":360,"dataGaLocation":42},"https://support.gitlab.com","support portal",{"text":362,"config":363},"Customer portal",{"href":364,"dataGaName":365,"dataGaLocation":42},"https://customers.gitlab.com/customers/sign_in/","customer portal",{"close":367,"login":368,"suggestions":375},"Close",{"text":369,"link":370},"To search repositories and projects, login to",{"text":371,"config":372},"gitlab.com",{"href":56,"dataGaName":373,"dataGaLocation":374},"search login","search",{"text":376,"default":377},"Suggestions",[378,380,384,386,390,394],{"text":71,"config":379},{"href":76,"dataGaName":71,"dataGaLocation":374},{"text":381,"config":382},"Code Suggestions (AI)",{"href":383,"dataGaName":381,"dataGaLocation":374},"/solutions/code-suggestions/",{"text":105,"config":385},{"href":107,"dataGaName":105,"dataGaLocation":374},{"text":387,"config":388},"GitLab on AWS",{"href":389,"dataGaName":387,"dataGaLocation":374},"/partners/technology-partners/aws/",{"text":391,"config":392},"GitLab on Google Cloud",{"href":393,"dataGaName":391,"dataGaLocation":374},"/partners/technology-partners/google-cloud-platform/",{"text":395,"config":396},"Why GitLab?",{"href":84,"dataGaName":395,"dataGaLocation":374},{"freeTrial":398,"mobileIcon":403,"desktopIcon":408,"secondaryButton":411},{"text":399,"config":400},"Start free trial",{"href":401,"dataGaName":47,"dataGaLocation":402},"https://gitlab.com/-/trials/new/","nav",{"altText":404,"config":405},"Gitlab Icon",{"src":406,"dataGaName":407,"dataGaLocation":402},"https://res.cloudinary.com/about-gitlab-com/image/upload/v1758203874/jypbw1jx72aexsoohd7x.svg","gitlab icon",{"altText":404,"config":409},{"src":410,"dataGaName":407,"dataGaLocation":402},"https://res.cloudinary.com/about-gitlab-com/image/upload/v1758203875/gs4c8p8opsgvflgkswz9.svg",{"text":412,"config":413},"Get Started",{"href":414,"dataGaName":415,"dataGaLocation":402},"https://gitlab.com/-/trial_registrations/new?glm_source=about.gitlab.com/get-started/","get started",{"freeTrial":417,"mobileIcon":421,"desktopIcon":423},{"text":418,"config":419},"Learn more about GitLab Duo",{"href":76,"dataGaName":420,"dataGaLocation":402},"gitlab duo",{"altText":404,"config":422},{"src":406,"dataGaName":407,"dataGaLocation":402},{"altText":404,"config":424},{"src":410,"dataGaName":407,"dataGaLocation":402},{"button":426,"mobileIcon":431,"desktopIcon":433},{"text":427,"config":428},"/switch",{"href":429,"dataGaName":430,"dataGaLocation":402},"#contact","switch",{"altText":404,"config":432},{"src":406,"dataGaName":407,"dataGaLocation":402},{"altText":404,"config":434},{"src":435,"dataGaName":407,"dataGaLocation":402},"https://res.cloudinary.com/about-gitlab-com/image/upload/v1773335277/ohhpiuoxoldryzrnhfrh.png",{"freeTrial":437,"mobileIcon":442,"desktopIcon":444},{"text":438,"config":439},"Back to pricing",{"href":184,"dataGaName":440,"dataGaLocation":402,"icon":441},"back to pricing","GoBack",{"altText":404,"config":443},{"src":406,"dataGaName":407,"dataGaLocation":402},{"altText":404,"config":445},{"src":410,"dataGaName":407,"dataGaLocation":402},{"title":447,"button":448,"config":453},"See how agentic AI transforms software delivery",{"text":449,"config":450},"Watch GitLab Transcend now",{"href":451,"dataGaName":452,"dataGaLocation":42},"/events/transcend/virtual/","transcend event",{"layout":454,"icon":455,"disabled":11},"release","AiStar",{"data":457},{"text":458,"source":459,"edit":465,"contribute":470,"config":475,"items":480,"minimal":687},"Git is a trademark of Software Freedom Conservancy and our use of 'GitLab' is under license",{"text":460,"config":461},"View page source",{"href":462,"dataGaName":463,"dataGaLocation":464},"https://gitlab.com/gitlab-com/marketing/digital-experience/about-gitlab-com/","page source","footer",{"text":466,"config":467},"Edit this page",{"href":468,"dataGaName":469,"dataGaLocation":464},"https://gitlab.com/gitlab-com/marketing/digital-experience/about-gitlab-com/-/blob/main/content/","web ide",{"text":471,"config":472},"Please contribute",{"href":473,"dataGaName":474,"dataGaLocation":464},"https://gitlab.com/gitlab-com/marketing/digital-experience/about-gitlab-com/-/blob/main/CONTRIBUTING.md/","please contribute",{"twitter":476,"facebook":477,"youtube":478,"linkedin":479},"https://twitter.com/gitlab","https://www.facebook.com/gitlab","https://www.youtube.com/channel/UCnMGQ8QHMAnVIsI3xJrihhg","https://www.linkedin.com/company/gitlab-com",[481,528,582,626,653],{"title":182,"links":482,"subMenu":497},[483,487,492],{"text":484,"config":485},"View plans",{"href":184,"dataGaName":486,"dataGaLocation":464},"view plans",{"text":488,"config":489},"Why Premium?",{"href":490,"dataGaName":491,"dataGaLocation":464},"/pricing/premium/","why premium",{"text":493,"config":494},"Why Ultimate?",{"href":495,"dataGaName":496,"dataGaLocation":464},"/pricing/ultimate/","why ultimate",[498],{"title":499,"links":500},"Contact Us",[501,504,506,508,513,518,523],{"text":502,"config":503},"Contact sales",{"href":51,"dataGaName":52,"dataGaLocation":464},{"text":357,"config":505},{"href":359,"dataGaName":360,"dataGaLocation":464},{"text":362,"config":507},{"href":364,"dataGaName":365,"dataGaLocation":464},{"text":509,"config":510},"Status",{"href":511,"dataGaName":512,"dataGaLocation":464},"https://status.gitlab.com/","status",{"text":514,"config":515},"Terms of use",{"href":516,"dataGaName":517,"dataGaLocation":464},"/terms/","terms of use",{"text":519,"config":520},"Privacy statement",{"href":521,"dataGaName":522,"dataGaLocation":464},"/privacy/","privacy statement",{"text":524,"config":525},"Cookie preferences",{"dataGaName":526,"dataGaLocation":464,"id":527,"isOneTrustButton":11},"cookie preferences","ot-sdk-btn",{"title":87,"links":529,"subMenu":538},[530,534],{"text":531,"config":532},"DevSecOps platform",{"href":69,"dataGaName":533,"dataGaLocation":464},"devsecops platform",{"text":535,"config":536},"AI-Assisted Development",{"href":76,"dataGaName":537,"dataGaLocation":464},"ai-assisted development",[539],{"title":540,"links":541},"Topics",[542,547,552,557,562,567,572,577],{"text":543,"config":544},"CICD",{"href":545,"dataGaName":546,"dataGaLocation":464},"/topics/ci-cd/","cicd",{"text":548,"config":549},"GitOps",{"href":550,"dataGaName":551,"dataGaLocation":464},"/topics/gitops/","gitops",{"text":553,"config":554},"DevOps",{"href":555,"dataGaName":556,"dataGaLocation":464},"/topics/devops/","devops",{"text":558,"config":559},"Version Control",{"href":560,"dataGaName":561,"dataGaLocation":464},"/topics/version-control/","version control",{"text":563,"config":564},"DevSecOps",{"href":565,"dataGaName":566,"dataGaLocation":464},"/topics/devsecops/","devsecops",{"text":568,"config":569},"Cloud Native",{"href":570,"dataGaName":571,"dataGaLocation":464},"/topics/cloud-native/","cloud native",{"text":573,"config":574},"AI for Coding",{"href":575,"dataGaName":576,"dataGaLocation":464},"/topics/devops/ai-for-coding/","ai for coding",{"text":578,"config":579},"Agentic AI",{"href":580,"dataGaName":581,"dataGaLocation":464},"/topics/agentic-ai/","agentic ai",{"title":583,"links":584},"Solutions",[585,587,589,594,598,601,605,608,610,613,616,621],{"text":129,"config":586},{"href":124,"dataGaName":129,"dataGaLocation":464},{"text":118,"config":588},{"href":101,"dataGaName":102,"dataGaLocation":464},{"text":590,"config":591},"Agile development",{"href":592,"dataGaName":593,"dataGaLocation":464},"/solutions/agile-delivery/","agile delivery",{"text":595,"config":596},"SCM",{"href":114,"dataGaName":597,"dataGaLocation":464},"source code management",{"text":543,"config":599},{"href":107,"dataGaName":600,"dataGaLocation":464},"continuous integration & delivery",{"text":602,"config":603},"Value stream management",{"href":157,"dataGaName":604,"dataGaLocation":464},"value stream management",{"text":548,"config":606},{"href":607,"dataGaName":551,"dataGaLocation":464},"/solutions/gitops/",{"text":167,"config":609},{"href":169,"dataGaName":170,"dataGaLocation":464},{"text":611,"config":612},"Small business",{"href":174,"dataGaName":175,"dataGaLocation":464},{"text":614,"config":615},"Public sector",{"href":179,"dataGaName":180,"dataGaLocation":464},{"text":617,"config":618},"Education",{"href":619,"dataGaName":620,"dataGaLocation":464},"/solutions/education/","education",{"text":622,"config":623},"Financial services",{"href":624,"dataGaName":625,"dataGaLocation":464},"/solutions/finance/","financial services",{"title":187,"links":627},[628,630,632,634,637,639,641,643,645,647,649,651],{"text":199,"config":629},{"href":201,"dataGaName":202,"dataGaLocation":464},{"text":204,"config":631},{"href":206,"dataGaName":207,"dataGaLocation":464},{"text":209,"config":633},{"href":211,"dataGaName":212,"dataGaLocation":464},{"text":214,"config":635},{"href":216,"dataGaName":636,"dataGaLocation":464},"docs",{"text":237,"config":638},{"href":239,"dataGaName":240,"dataGaLocation":464},{"text":232,"config":640},{"href":234,"dataGaName":235,"dataGaLocation":464},{"text":242,"config":642},{"href":244,"dataGaName":245,"dataGaLocation":464},{"text":250,"config":644},{"href":252,"dataGaName":253,"dataGaLocation":464},{"text":255,"config":646},{"href":257,"dataGaName":258,"dataGaLocation":464},{"text":260,"config":648},{"href":262,"dataGaName":263,"dataGaLocation":464},{"text":265,"config":650},{"href":267,"dataGaName":268,"dataGaLocation":464},{"text":270,"config":652},{"href":272,"dataGaName":273,"dataGaLocation":464},{"title":288,"links":654},[655,657,659,661,663,665,667,671,676,678,680,682],{"text":295,"config":656},{"href":297,"dataGaName":290,"dataGaLocation":464},{"text":300,"config":658},{"href":302,"dataGaName":303,"dataGaLocation":464},{"text":308,"config":660},{"href":310,"dataGaName":311,"dataGaLocation":464},{"text":313,"config":662},{"href":315,"dataGaName":316,"dataGaLocation":464},{"text":318,"config":664},{"href":320,"dataGaName":321,"dataGaLocation":464},{"text":323,"config":666},{"href":325,"dataGaName":326,"dataGaLocation":464},{"text":668,"config":669},"Sustainability",{"href":670,"dataGaName":668,"dataGaLocation":464},"/sustainability/",{"text":672,"config":673},"Diversity, inclusion and belonging (DIB)",{"href":674,"dataGaName":675,"dataGaLocation":464},"/diversity-inclusion-belonging/","Diversity, inclusion and belonging",{"text":328,"config":677},{"href":330,"dataGaName":331,"dataGaLocation":464},{"text":338,"config":679},{"href":340,"dataGaName":341,"dataGaLocation":464},{"text":343,"config":681},{"href":345,"dataGaName":346,"dataGaLocation":464},{"text":683,"config":684},"Modern Slavery Transparency Statement",{"href":685,"dataGaName":686,"dataGaLocation":464},"https://handbook.gitlab.com/handbook/legal/modern-slavery-act-transparency-statement/","modern slavery transparency statement",{"items":688},[689,692,695],{"text":690,"config":691},"Terms",{"href":516,"dataGaName":517,"dataGaLocation":464},{"text":693,"config":694},"Cookies",{"dataGaName":526,"dataGaLocation":464,"id":527,"isOneTrustButton":11},{"text":696,"config":697},"Privacy",{"href":521,"dataGaName":522,"dataGaLocation":464},[699],{"id":700,"title":18,"body":8,"config":701,"content":703,"description":8,"extension":25,"meta":707,"navigation":11,"path":708,"seo":709,"stem":710,"__hash__":711},"blogAuthors/en-us/blog/authors/john-coghlan.yml",{"template":702},"BlogAuthor",{"name":18,"config":704},{"headshot":705,"ctfId":706},"https://res.cloudinary.com/about-gitlab-com/image/upload/v1749670167/Blog/Author%20Headshots/johncoghlan-headshot.jpg","johncoghlan",{},"/en-us/blog/authors/john-coghlan",{},"en-us/blog/authors/john-coghlan","dOj1PXtB8h9xxt68I3Ck9deyy57w1js7NkehLQ5E8Pc",[713,726,739],{"content":714,"config":724},{"title":715,"description":716,"heroImage":717,"category":9,"tags":718,"authors":720,"date":20,"body":723},"Streamline test management with the SmartBear QMetry GitLab component","Learn how to automatically upload test results from GitLab CI/CD pipelines to SmartBear QMetry Test Management Enterprise using the CI/CD Catalog component.","https://res.cloudinary.com/about-gitlab-com/image/upload/v1775486753/cswmwtygkgkbdsibo09v.png",[719,9,556],"tutorial",[721,722],"Matt Genelin","Matt Bonner","In modern software development, test management and continuous integration are two sides of the same coin. DevSecOps teams need seamless integration between their CI/CD pipelines and test management platforms to maintain visibility, traceability, and compliance across the software development lifecycle.\n\nThis becomes even more important as testing scales across automated pipelines, where execution data is spread across tools and harder to track in one place.\n\nFor organizations using GitLab for CI/CD and SmartBear QMetry for test management, manually uploading test results creates friction, delays feedback loops, and makes it harder to maintain a reliable, centralized view of testing.\n\nWhat if you could automatically publish your JUnit, TestNG, or other test results directly from your GitLab pipeline to QMetry with just a few lines of configuration?\n\nThat's exactly what the new **QMetry GitLab Component** enables. This reusable CI/CD component, now available in the [GitLab CI/CD Catalog](https://gitlab.com/explore/catalog), eliminates the manual overhead of test result management by automatically uploading test execution data to QMetry.  This is an AI-enabled, enterprise-grade test management platform that brings together test planning, execution, tracking, and reporting in one place.\n\nAs a centralized system of record for testing, QMetry helps teams understand coverage, track execution, and make more reliable release decisions.\n\nIn this guide, you'll learn:\n\n* How to set up the QMetry GitLab Component in your pipeline  \n* How to configure automated test result uploads  \n* Advanced configuration options for enterprise requirements  \n* A real-world aerospace industry use case  \n* Best practices for test management automation\n\nBy the end of this article, your GitLab pipelines will automatically feed test results into QMetry, giving your QA teams instant visibility into test execution and helping them make faster, more confident release decisions.\n\n![SmartBear QMetry GitLab integration](https://res.cloudinary.com/about-gitlab-com/image/upload/v1775488045/ojt707rzxnm2yr3vqxdh.png)\n\n## Why integrate GitLab with QMetry?\n\nBefore diving into the technical implementation, let's understand the value this integration delivers:\n\n### Eliminate manual test result uploads\n\nDevSecOps engineers and QA teams no longer need to manually export test results from CI/CD runs and import them into test management systems. The component handles this automatically after every pipeline execution.\n\nThis reduces manual effort while ensuring test data stays consistent, up to date, and easy to access across teams.\n\n![Test results with SmartBear QMetry GitLab integration](https://res.cloudinary.com/about-gitlab-com/image/upload/v1775488045/ajx64sihup2nursdpnxz.png)\n\n### Enable end-to-end traceability\n\nBy connecting GitLab's CI/CD execution data with QMetry's test management capabilities, teams gain complete traceability from requirements through test cases to actual test execution results. This is critical for regulated industries like financial services, aerospace, medical devices, and automotive, where audit trails are mandatory and regulatory compliance depends on demonstrating complete test coverage.\n\nIt also gives teams a clearer view of coverage and risk across releases, making it easier to understand what’s been tested and what still needs attention.\n\n### Accelerate feedback loops\n\nAutomated test result uploads mean QA teams, product managers, and stakeholders see test execution results immediately after pipeline completion – no waiting for manual data entry or report generation.\n\nWith faster access to results, teams can act immediately, reduce delays, and make quicker, more informed release decisions.\n\n### Support compliance and audit requirements\n\nFor organizations in regulated industries, maintaining comprehensive test records with proper versioning and traceability is non-negotiable. This integration ensures you can document every test execution properly in QMetry with links back to the specific GitLab pipeline, commit, and build.\n\nThis creates an audit-ready record of testing activity without adding manual overhead.\n\n![Audit-ready record of testing with SmartBear QMetry GitLab integration](https://res.cloudinary.com/about-gitlab-com/image/upload/v1775488045/q2tbaw5otgdywjkcquqx.png)\n\n### Leverage AI-powered test insights\n\nQMetry uses AI to analyze test execution patterns, identify flaky tests, predict test failures, and recommend optimization opportunities. Feeding it real-time data from GitLab pipelines maximizes the value of these AI capabilities.\n\nWith continuous data flowing in, teams get more accurate insights and can focus their efforts where it matters most.\n\n![Accurage insights with SmartBear QMetry GitLab integration](https://res.cloudinary.com/about-gitlab-com/image/upload/v1775488045/pl7ru4wx8ixnheedfyrs.png)\n\n## About the GitLab and SmartBear partnership\n\nThis component represents a growing partnership between GitLab and SmartBear to better connect CI/CD execution with test management in a single workflow. SmartBear brings deep expertise in testing, API management, and quality automation, while GitLab provides the most comprehensive AI-powered DevSecOps platform. Together, they help teams streamline how testing fits into the development lifecycle while maintaining the quality, security, and compliance standards their industries require.\n\nWhether you're managing test execution for aerospace flight control systems, financial services platforms, automotive safety applications, or medical device software, the combination of GitLab's CI/CD capabilities and QMetry's test management gives teams a centralized, reliable view of testing across the lifecycle, helping them track execution, maintain traceability, and make more confident release decisions.\n\n## What you'll need\n\nBefore getting started, ensure you have:\n\n* **A GitLab account** with a project containing automated tests that generate test result files (JUnit XML, TestNG XML, etc.)  \n* **QMetry Test Management Enterprise** account with API access enabled  \n* **QMetry API Key** generated  from your QMetry instance (we'll cover this shortly)  \n* **QMetry Project** already created where you will upload test results   \n* **Familiarity with GitLab CI/CD**, including understanding of basic `.gitlab-ci.yml` syntax and pipeline concepts  \n* **Test suite configuration** in QMetry (optional but recommended for better organization)\n\n### Understanding the test result flow\n\nHere's what happens when you integrate this component:\n\n1. **Test execution**: Your GitLab CI/CD pipeline runs automated tests (unit tests, integration tests, E2E tests, etc.).  \n2. **Result generation**: Tests produce output files in formats like JUnit XML, TestNG XML, or other supported formats.  \n3. **Component invocation**: The QMetry component executes as a job in your pipeline.  \n4. **Automatic upload**: The component reads your test result files and uploads them to QMetry via API.  \n5. **QMetry processing**: QMetry receives the results, processes them, and makes them available for reporting and analysis.\n\nThe beauty of this integration is that it happens automatically, with no manual intervention required once configured.\n\n## Part 1: Getting your QMetry API credentials\n\nBefore configuring the GitLab component, you need to obtain API access credentials from your QMetry instance. Here are the steps to follow:\n\n### 1. Access QMetry settings\n\n1. Log in to your **QMetry Test Management Enterprise** instance.  \n2. Navigate to your **user profile** (typically in the top-right corner).  \n3. Select **Settings** or **API Access** from the dropdown menu.\n\n### 2. Generate an API key\n\n1. In the API Access section, click **Generate New API Key.**  \n2. Provide a descriptive **name** for the key (e.g., \"GitLab CI/CD Integration\").  \n3. Set appropriate **permissions**. The key needs write access to upload test results.  \n4. Click **Generate.**  \n5. **Copy the API key immediately** as it will only be displayed once.\n\n**Important security note**: Treat your API key like a password. Never commit it directly to your `.gitlab-ci.yml` file or store it in plain text. We'll use GitLab CI/CD variables to store it securely.\n\n### 3. Note your QMetry instance URL\n\nYou'll also need your QMetry instance URL, which typically follows this format:\n\n```text\nhttps://your-company.qmetry.com\n```\n\nor, for self-hosted instances:\n\n```text\nhttps://qmetry.your-company.com\n```\n\nMake note of this URL because you'll need it in the next section.\n\n## Part 2: Configuring GitLab CI/CD variables\n\nNow that you have your QMetry credentials, let's store them securely in GitLab. Here are the next steps to follow:\n\n### 4. Navigate to CI/CD settings\n\n1. Open your **GitLab project.**  \n2. In the left sidebar, navigate to **Settings > CI/CD.**  \n3. Expand the **Variables** section.  \n4. Click **Add variable.**\n\n### 5. Add the QMetry API key\n\nConfigure the API key variable:\n\n| Field | Value |\n| ----- | ----- |\n| **Key** | `QMETRY_API_KEY` |\n| **Value** | Your QMetry API key from Step 2 |\n| **Type** | Variable |\n| **Flags** | ✅ Mask variable\u003Cbr>✅ Protect variable (recommended) |\n\nClick **Add variable** to save.\n\n### 6. Add the QMetry instance URL\n\nAdd a second variable for your instance URL:\n\n| Field | Value |\n| ----- | ----- |\n| **Key** | `INSTANCE_URL` |\n| **Value** | Your QMetry instance URL (e.g., `https://your-company.qmetry.com`) |\n| **Type** | Variable |\n| **Flags** | (optional: Protect variable) |\n\nClick **Add variable** to save.\n\n**Why use CI/CD variables?**\n\n* **Security**: Masked variables are hidden in job logs.  \n* **Reusability**: You can use the same credentials across multiple pipelines.  \n* **Flexibility**: It is easy to rotate credentials without modifying pipeline code.  \n* **Access control**: Protected variables are only available on protected branches.\n\n## Part 3: Understanding your test result files\n\nBefore integrating the component, ensure your tests generate output files that QMetry can process. Here are the next steps to follow:\n\n### 7. Verify test output format\n\nThe QMetry component supports multiple test result formats. The most common is **JUnit XML**, which most testing frameworks can generate:\n\n**Example JUnit XML output** (`results.xml`):\n\n```xml\n\u003C?xml version=\"1.0\" encoding=\"UTF-8\"?>\n\u003Ctestsuites>\n  \u003Ctestsuite name=\"Flight Control System Tests\" tests=\"15\" failures=\"1\" errors=\"0\" time=\"45.231\">\n    \u003Ctestcase classname=\"FlightControlTests\" name=\"testAltitudeHold\" time=\"2.341\">\n      \u003Csystem-out>Altitude hold engaged at 10,000 feet\u003C/system-out>\n    \u003C/testcase>\n    \u003Ctestcase classname=\"FlightControlTests\" name=\"testAutopilotEngagement\" time=\"3.125\">\n      \u003Csystem-out>Autopilot engaged successfully\u003C/system-out>\n    \u003C/testcase>\n    \u003Ctestcase classname=\"FlightControlTests\" name=\"testEmergencyLanding\" time=\"5.892\">\n      \u003Cfailure message=\"Landing gear failed to deploy\">\n        Expected: Landing gear deployed\n        Actual: Landing gear malfunction detected\n      \u003C/failure>\n    \u003C/testcase>\n    \u003C!-- Additional test cases... -->\n  \u003C/testsuite>\n\u003C/testsuites>\n```\n\nMost testing frameworks generate this format automatically:\n\n* **JUnit** (Java): Native format  \n* **pytest** (Python): Use `--junitxml=results.xml` flag  \n* **Jest** (JavaScript): Use `jest-junit` reporter  \n* **RSpec** (Ruby): Use `rspec_junit_formatter`  \n* **NUnit** (.NET): Use `nunit-console` with XML output  \n* **Go test**: Use `go-junit-report`\n\n### 8. Confirm test artifact configuration\n\nEnsure your existing pipeline saves test results as **artifacts**. This allows the QMetry component to access them:\n\n```yaml\ntest:\n  stage: test\n  script:\n    - npm install\n    - npm test -- --reporter=junit --reporter-options=output=results.xml\n  artifacts:\n    reports:\n      junit: results.xml\n    paths:\n      - results.xml\n    when: always  # Upload even if tests fail\n```\n\n**Key points**:\n\n* `artifacts.reports.junit` makes results visible in GitLab's test report UI.  \n* `artifacts.paths` ensures the file is available to downstream jobs.  \n* `when: always` ensures results upload even if tests fail.\n\n## Part 4: Integrating the QMetry component\n\nNow for the main event – adding the QMetry component to your pipeline. Here are the next steps to follow:\n\n### 9. Basic component integration\n\nAdd the component to your `.gitlab-ci.yml` file. The component should run **after** your tests complete:\n\n```yaml\ninclude:\n  - component: gitlab.com/sb9945614/qtm-gitlab-component/qmetry-import@1.0.5\n    inputs:\n      stage: test\n      project: \"Aerospace Flight Control System\"\n      file_name: \"results.xml\"\n      testing_type: \"JUNIT\"\n      instance_url: ${INSTANCE_URL}\n      api_key: ${QMETRY_API_KEY}\n```\n\nLet's break down each input parameter:\n\n| Parameter | Description | Example |\n| ----- | ----- | ----- |\n| `stage` | Which CI/CD stage runs the upload job | `test` |\n| `project` | Your QMetry project name or key | `\"Aerospace Flight Control System\"` |\n| `file_name` | Path to your test results file | `\"results.xml\"` |\n| `testing_type` | Format of your test results | `\"JUNIT\"` (also supports: `TESTNG`, `NUNIT`, etc.) |\n| `instance_url` | Your QMetry instance URL | `${INSTANCE_URL}` (from CI/CD variables) |\n| `api_key` | QMetry API key for authentication | `${QMETRY_API_KEY}` (from CI/CD variables) |\n\n### 10. Complete pipeline example\n\nHere's a complete `.gitlab-ci.yml` example showing test execution followed by QMetry upload:\n\n```yaml\nstages:\n  - test\n  - report\n\nvariables:\n  # Your app-specific variables\n  NODE_VERSION: \"18\"\n\n# Run your automated tests\nunit-tests:\n  stage: test\n  image: node:${NODE_VERSION}\n  script:\n    - npm ci\n    - npm run test:unit -- --reporter=junit --reporter-options=output=results.xml\n  artifacts:\n    reports:\n      junit: results.xml\n    paths:\n      - results.xml\n    when: always\n  tags:\n    - docker\n\n# Upload results to QMetry\ninclude:\n  - component: gitlab.com/sb9945614/qtm-gitlab-component/qmetry-import@1.0.5\n    inputs:\n      stage: test  # Runs in same stage as tests\n      project: \"Aerospace Flight Control System\"\n      file_name: \"results.xml\"\n      testing_type: \"JUNIT\"\n      instance_url: ${INSTANCE_URL}\n      api_key: ${QMETRY_API_KEY}\n```\n\n### 11. Run your pipeline\n\nCommit and push your changes:\n\n```shell\ngit add .gitlab-ci.yml\ngit commit -m \"Add QMetry test result integration\"\ngit push origin main\n```\n\nNavigate to your GitLab project's **CI/CD > Pipelines** to watch the execution.\n\n### 12. Verify successful upload\n\nAfter the pipeline completes, you should see:\n\n**In GitLab**:\n\n1. A new job in your pipeline named `qmetry-import` (or similar)  \n2. Job logs showing successful API communication  \n3. Green checkmark indicating successful upload\n\n**Example successful job log**:\n\n```json\n$ curl -X POST https://your-company.qmetry.com/api/v3/test-results/import \\\n  -H \"Authorization: Bearer ${QMETRY_API_KEY}\" \\\n  -H \"Content-Type: application/json\" \\\n  -d @payload.json\n\n{\n  \"status\": \"success\",\n  \"message\": \"Test results uploaded successfully\",\n  \"results_processed\": 15,\n  \"test_cases_created\": 3,\n  \"test_cases_updated\": 12,\n  \"execution_id\": \"EXE-12345\"\n}\n\nJob succeeded ```\n\n**In QMetry**:\n\n1. Navigate to your project dashboard.  \n2. Check the **Test Executions** section.  \n3. You should see a new test execution with results from your GitLab pipeline.  \n4. Click into the execution to see detailed test case results.\n\n\n## Part 5: Advanced configuration options\n\nNow that you have the basic integration working, let's explore advanced configuration for enterprise requirements. Here are the next steps to follow:\n\n### 13. Organizing results with test suites\n\nFor better organization, you can specify which QMetry test suite should receive results:\n\n```yaml\ninclude:\n  - component: gitlab.com/sb9945614/qtm-gitlab-component/qmetry-import@1.0.5\n    inputs:\n      stage: test\n      project: \"Aerospace Flight Control System\"\n      file_name: \"results.xml\"\n      testing_type: \"JUNIT\"\n      testsuite_name: \"Sprint 23 Regression Tests\"\n      testsuite_id: \"TS-456\"  # Optional: Use existing test suite ID\n      instance_url: ${INSTANCE_URL}\n      api_key: ${QMETRY_API_KEY}\n```\n\n**When to use test suites**:\n\n* Organizing tests by sprint or release  \n* Separating regression tests from new feature tests  \n* Grouping tests by component or subsystem  \n* Creating test execution hierarchies for reporting\n\n### 14. Configuring automation hierarchy levels\n\nQMetry supports hierarchical test organization. Use the `automation_hierarchy` parameter to specify the organization level:\n\n```yaml\ninclude:\n  - component: gitlab.com/sb9945614/qtm-gitlab-component/qmetry-import@1.0.5\n    inputs:\n      stage: test\n      project: \"Aerospace Flight Control System\"\n      file_name: \"results.xml\"\n      testing_type: \"JUNIT\"\n      automation_hierarchy: \"2\"  # Level 2 hierarchy\n      instance_url: ${INSTANCE_URL}\n      api_key: ${QMETRY_API_KEY}\n```\n\n**Hierarchy levels explained**:\n\n* **Level 1**: Top-level test suites (e.g., \"All Regression Tests\")  \n* **Level 2**: Sub-suites (e.g., \"Flight Control Tests\" under \"Regression Tests\")  \n* **Level 3**: Granular test groups (e.g., \"Altitude Hold Tests\" under \"Flight Control\")\n\n### 15. Multiple test result files\n\nFor complex projects with multiple test jobs, you can invoke the component multiple times:\n\n```yaml\nstages:\n  - test\n\n# Unit tests\nunit-tests:\n  stage: test\n  script:\n    - npm run test:unit\n  artifacts:\n    paths:\n      - unit-results.xml\n    when: always\n\n# Integration tests\nintegration-tests:\n  stage: test\n  script:\n    - npm run test:integration\n  artifacts:\n    paths:\n      - integration-results.xml\n    when: always\n\n# Upload unit test results\ninclude:\n  - component: gitlab.com/sb9945614/qtm-gitlab-component/qmetry-import@1.0.5\n    inputs:\n      stage: test\n      project: \"Aerospace Flight Control System\"\n      file_name: \"unit-results.xml\"\n      testing_type: \"JUNIT\"\n      testsuite_name: \"Unit Tests - Sprint 23\"\n      instance_url: ${INSTANCE_URL}\n      api_key: ${QMETRY_API_KEY}\n\n  # Upload integration test results\n  - component: gitlab.com/sb9945614/qtm-gitlab-component/qmetry-import@1.0.5\n    inputs:\n      stage: test\n      project: \"Aerospace Flight Control System\"\n      file_name: \"integration-results.xml\"\n      testing_type: \"JUNIT\"\n      testsuite_name: \"Integration Tests - Sprint 23\"\n      instance_url: ${INSTANCE_URL}\n      api_key: ${QMETRY_API_KEY}\n```\n\n### 16. Custom runner tags\n\nFor enterprise environments with dedicated runners, specify runner tags:\n\n```yaml\ninclude:\n  - component: gitlab.com/sb9945614/qtm-gitlab-component/qmetry-import@1.0.5\n    inputs:\n      stage: test\n      runner_tag: \"production-runners\"  # Use specific runner pool\n      project: \"Aerospace Flight Control System\"\n      file_name: \"results.xml\"\n      testing_type: \"JUNIT\"\n      instance_url: ${INSTANCE_URL}\n      api_key: ${QMETRY_API_KEY}\n```\n\n### 17. Custom test suite folders\n\nOrganize test suites into folders for better project structure:\n\n```yaml\ninclude:\n  - component: gitlab.com/sb9945614/qtm-gitlab-component/qmetry-import@1.0.5\n    inputs:\n      stage: test\n      project: \"Aerospace Flight Control System\"\n      file_name: \"results.xml\"\n      testing_type: \"JUNIT\"\n      testsuite_folder_path: \"/Regression/Sprint-23/Flight-Controls\"\n      instance_url: ${INSTANCE_URL}\n      api_key: ${QMETRY_API_KEY}\n```\n\nThis creates a folder hierarchy in QMetry:\n\n```none\nAerospace Flight Control System/\n└── Regression/\n    └── Sprint-23/\n        └── Flight-Controls/\n            └── [Your test execution]\n```\n\n### 18. Advanced field mapping\n\nFor enterprise QMetry instances with custom fields, use the `testcase_fields` and `testsuite_fields` parameters:\n\n```yaml\ninclude:\n  - component: gitlab.com/sb9945614/qtm-gitlab-component/qmetry-import@1.0.5\n    inputs:\n      stage: test\n      project: \"Aerospace Flight Control System\"\n      file_name: \"results.xml\"\n      testing_type: \"JUNIT\"\n      testcase_fields: \"priority=P1,component=FlightControl,certification=DO-178C\"\n      testsuite_fields: \"release=v2.4.0,sprint=23\"\n      instance_url: ${INSTANCE_URL}\n      api_key: ${QMETRY_API_KEY}\n```\n\nThis adds custom metadata to test cases and suites for enhanced filtering and reporting.\n\n## Part 6: Real-world use cases\n\nLet's explore how organizations across different industries are using this integration to solve critical quality and compliance challenges.\n\n### Financial services: Enterprise banking platforms\n\nLeading financial institutions are evolving their engineering practices with integrated DevOps platforms. These organizations face unique challenges when managing test automation at scale.\n\n**The challenge for financial services**:\n\n* **Regulatory compliance**: Financial services must maintain detailed audit trails for all testing activities.  \n* **Multiple compliance frameworks**: Firms must adhere to FCA, PSD2, GDPR, and internal risk management policies.  \n* **High-frequency deployments**: Multiple production deployments are required daily across microservices.  \n* **Zero-tolerance for failures**: Banking systems require extremely high reliability.  \n* **Distributed teams**: QA teams need real-time visibility across global engineering teams.\n\n**The solution**: Financial services organizations implementing the QMetry GitLab Component can automate test result uploads across their CI/CD pipelines for:\n\n* Unit tests for hundreds of microservices  \n* API contract tests for inter-service communication  \n* End-to-end transaction flow tests  \n* Security and compliance scanning results  \n* Performance and load testing results\n\n**Example implementation approach**:\n\n```yaml\n# Financial services approach: Separate test uploads by test type\nstages:\n  - test\n  - security\n  - report\n\n# Unit tests for payment processing service\nunit-tests:\n  stage: test\n  script:\n    - mvn clean test\n  artifacts:\n    paths:\n      - target/surefire-reports/TEST-*.xml\n    when: always\n\n# Upload to QMetry with compliance metadata\ninclude:\n  - component: gitlab.com/sb9945614/qtm-gitlab-component/qmetry-import@1.0.5\n    inputs:\n      stage: report\n      project: \"Payment Processing Platform\"\n      file_name: \"target/surefire-reports/TEST-*.xml\"\n      testing_type: \"JUNIT\"\n      testsuite_name: \"Payment Services - Unit Tests\"\n      testsuite_folder_path: \"/Regulatory/FCA-Compliance/Unit-Tests\"\n      testcase_fields: \"compliance=FCA,risk_level=high,service=payments\"\n      automation_hierarchy: \"2\"\n      instance_url: ${INSTANCE_URL}\n      api_key: ${QMETRY_API_KEY}\n```\n\n**Potential business outcomes for financial services**:\n\n* **Significant reduction** in manual test reporting time  \n* **Complete audit trail coverage** for regulatory reviews  \n* **Real-time visibility** for distributed QA teams  \n* **Faster time-to-production** with automated quality gates  \n* **Enhanced compliance posture** with complete traceability from requirements to test execution\n\n### Aerospace flight control testing\n\nLet's explore how an aerospace company might use this integration for critical flight control system testing.\n\n**Aerospace software development faces unique requirements and challenges:**\n\n* **DO-178C compliance**: Aviation software must follow strict certification standards  \n* **Complete traceability**: Every requirement must link to test cases and execution results  \n* **Audit trails**: Regulators require detailed records of all testing activities  \n* **Safety-critical quality**: Failures can have catastrophic consequences  \n* **Multiple test levels**: Unit, integration, system, and certification tests\n\n**The solution:** By integrating GitLab CI/CD with QMetry, the aerospace engineering team achieves automated test execution and reporting.\n\n\n```yaml\nstages:\n  - build\n  - unit-test\n  - integration-test\n  - system-test\n  - report\n\n# Build flight control firmware\nbuild-firmware:\n  stage: build\n  script:\n    - make clean\n    - make build TARGET=flight-control\n  artifacts:\n    paths:\n      - build/flight-control.bin\n\n# Unit tests (DO-178C Level A)\nunit-tests:\n  stage: unit-test\n  script:\n    - make test-unit OUTPUT=junit\n  artifacts:\n    paths:\n      - test-results/unit-tests.xml\n    when: always\n\n# Hardware-in-the-loop integration tests\nhil-integration-tests:\n  stage: integration-test\n  tags:\n    - hil-test-bench  # Dedicated hardware test environment\n  script:\n    - ./scripts/deploy-to-test-bench.sh\n    - ./scripts/run-hil-tests.sh\n  artifacts:\n    paths:\n      - test-results/hil-tests.xml\n    when: always\n\n# System-level certification tests\ncertification-tests:\n  stage: system-test\n  tags:\n    - certification-environment\n  script:\n    - ./scripts/run-certification-suite.sh\n  artifacts:\n    paths:\n      - test-results/certification-tests.xml\n    when: always\n  only:\n    - main  # Only run on main branch\n\n# Upload unit test results to QMetry\ninclude:\n  - component: gitlab.com/sb9945614/qtm-gitlab-component/qmetry-import@1.0.5\n    inputs:\n      stage: report\n      project: \"Flight Control System v2.4\"\n      file_name: \"test-results/unit-tests.xml\"\n      testing_type: \"JUNIT\"\n      testsuite_name: \"Unit Tests - DO-178C Level A\"\n      testsuite_folder_path: \"/Certification/DO-178C/Unit\"\n      testcase_fields: \"compliance=DO-178C,level=A,safety_critical=true\"\n      automation_hierarchy: \"2\"\n      instance_url: ${INSTANCE_URL}\n      api_key: ${QMETRY_API_KEY}\n\n  # Upload HIL test results\n  - component: gitlab.com/sb9945614/qtm-gitlab-component/qmetry-import@1.0.5\n    inputs:\n      stage: report\n      project: \"Flight Control System v2.4\"\n      file_name: \"test-results/hil-tests.xml\"\n      testing_type: \"JUNIT\"\n      testsuite_name: \"Hardware-in-Loop Integration Tests\"\n      testsuite_folder_path: \"/Certification/DO-178C/Integration\"\n      testcase_fields: \"compliance=DO-178C,level=A,test_type=HIL\"\n      automation_hierarchy: \"2\"\n      instance_url: ${INSTANCE_URL}\n      api_key: ${QMETRY_API_KEY}\n\n  # Upload certification test results\n  - component: gitlab.com/sb9945614/qtm-gitlab-component/qmetry-import@1.0.5\n    inputs:\n      stage: report\n      project: \"Flight Control System v2.4\"\n      file_name: \"test-results/certification-tests.xml\"\n      testing_type: \"JUNIT\"\n      testsuite_name: \"System Certification Tests\"\n      testsuite_folder_path: \"/Certification/DO-178C/System\"\n      testcase_fields: \"compliance=DO-178C,level=A,certification_ready=true\"\n      automation_hierarchy: \"1\"\n      instance_url: ${INSTANCE_URL}\n      api_key: ${QMETRY_API_KEY}\n```\n\n### The results\n\n**Before integration**:\n\n* QA engineers manually exported test results from GitLab  \n* Imported results into QMetry through UI uploads  \n* Process took 2-3 hours per test cycle  \n* Human error risk in data entry  \n* Delayed feedback to stakeholders\n\n**After integration**:\n\n* Test results automatically flow from GitLab to QMetry  \n* Complete audit trail from commit → test → result  \n* Zero manual intervention required  \n* Real-time visibility for certification auditors  \n* Compliance reports generated automatically\n\n**Example QMetry dashboard after integration**:\n\n```none\n╔════════════════════════════════════════════════════════════╗\n║  Flight Control System v2.4 - Test Execution Dashboard     ║\n╠════════════════════════════════════════════════════════════╣\n║                                                            ║\n║  📊 Test Execution Summary (Last 7 Days)                   ║\n║  ───────────────────────────────────────────────────────── ║\n║  ✓ Total Tests Executed: 1,247                             ║\n║  ✓ Passed: 1,241 (99.5%)                                   ║\n║  ✗ Failed: 6 (0.5%)                                        ║\n║  ⏸ Skipped: 0                                              ║\n║                                                            ║\n║  📁 Test Suite Organization                                ║\n║  ───────────────────────────────────────────────────────── ║\n║  └─ Certification/                                         ║\n║     └─ DO-178C/                                            ║\n║        ├─ Unit/ (487 tests, 100% pass)                     ║\n║        ├─ Integration/ (623 tests, 99.2% pass)             ║\n║        └─ System/ (137 tests, 100% pass)                   ║\n║                                                            ║\n║  🔗 Traceability                                           ║\n║  ───────────────────────────────────────────────────────── ║\n║  Requirements Covered: 342/342 (100%)                      ║\n║  Test Cases Linked: 1,247/1,247 (100%)                     ║\n║  GitLab Pipeline Executions: 47 (automated)                ║\n║                                                            ║\n║  ⚠️  Action Items                                          ║\n║  ───────────────────────────────────────────────────────── ║\n║  • 6 failed tests require investigation                    ║\n║  • Last execution: 2 minutes ago (Pipeline #1543)          ║\n║  • GitLab Commit: a7f8c23 \"Fix altitude hold logic\"        ║\n║                                                            ║\n╚════════════════════════════════════════════════════════════╝\n```\n\n### Compliance and audit benefits\n\nBoth financial services and aerospace organizations can leverage this integration for compliance:\n\n**For financial services (FCA, PSD2, SOX)**:\n\n1. **Automated traceability**: Link regulatory requirements → test cases → execution results → GitLab commits  \n2. **Audit-ready documentation**: Complete test execution history with timestamps and pipeline references  \n3. **Risk management**: Real-time quality dashboards for risk assessment  \n4. **Regulatory reporting**: Generate compliance reports directly from QMetry test data\n\n**For aerospace certification (DO-178C, DO-254)**:\n\n1. **Automated traceability matrix**: QMetry links requirements → test cases → execution results → GitLab commits  \n2. **Immutable audit trail**: Every test execution is timestamped with pipeline ID, commit SHA, and executor  \n3. **Certification package generation**: QMetry generates compliant documentation pulling data from GitLab pipelines  \n4. **Real-time compliance dashboards**: Auditors can view test coverage and execution history in real-time\n\n## Complete configuration reference\n\nHere's a comprehensive reference of all available component inputs:\n\n| Input Parameter | Required | Default | Description |\n| ----- | ----- | ----- | ----- |\n| `stage` | No | `test` | GitLab CI/CD stage for the upload job |\n| `runner_tag` | No | `\"\"` | Specific runner tag to use (empty = any available runner) |\n| `project` | Yes | - | QMetry project name or key |\n| `file_name` | Yes | - | Path to test results file (relative to project root) |\n| `testing_type` | Yes | - | Test result format: `JUNIT`, `TESTNG`, `NUNIT`, etc. |\n| `skip_warning` | No | `\"1\"` | Skip warnings during import (`\"1\"` = skip, `\"0\"` = show) |\n| `is_matching_required` | No | `\"false\"` | Match existing test cases by name (`\"true\"` or `\"false\"`) |\n| `testsuite_name` | No | `\"\"` | Name for the test suite in QMetry |\n| `testsuite_id` | No | `\"\"` | Existing test suite ID to append results to |\n| `testsuite_folder_path` | No | `\"\"` | Folder path for organizing test suites (e.g., `/Regression/Sprint-23`) |\n| `automation_hierarchy` | No | `\"\"` | Hierarchy level for test organization (`\"1\"`, `\"2\"`, `\"3\"`, etc.) |\n| `testcase_fields` | No | `\"\"` | Custom fields for test cases (comma-separated: `field1=value1,field2=value2`) |\n| `testsuite_fields` | No | `\"\"` | Custom fields for test suites (comma-separated: `field1=value1,field2=value2`) |\n| `instance_url` | Yes | - | QMetry instance URL (store in CI/CD variable) |\n| `api_key` | Yes | - | QMetry API key (store in CI/CD variable, masked) |\n\n## Best practices for production use\n\nAs you scale your integration, follow these best practices:\n\n### Security\n\n* ✅ **Always use CI/CD variables** for sensitive data (API keys, URLs)  \n* ✅ **Mask and protect** API key variables  \n* ✅ **Rotate API keys** periodically (quarterly recommended)  \n* ✅ **Restrict API key permissions** to minimum required (write to test results only)  \n* ✅ **Use protected branches** for production test uploads\n\n### Performance\n\n* ✅ **Keep test result files reasonable size** (\\\u003C 10 MB recommended)  \n* ✅ **Split large test suites** into multiple jobs/files  \n* ✅ **Use parallel test execution** to reduce pipeline duration  \n* ✅ **Cache dependencies** to speed up test execution\n\n### Organization\n\n* ✅ **Use consistent naming conventions** for test suites and folder paths  \n* ✅ **Leverage custom fields** for filtering and reporting  \n* ✅ **Create folder hierarchies** that mirror your test strategy  \n* ✅ **Document your integration** in project README files\n\n### Troubleshooting\n\n* ✅ **Review job logs** for API communication details  \n* ✅ **Verify test result file format** matches `testing_type` parameter  \n* ✅ **Check QMetry project exists** and API key has access  \n* ✅ **Ensure test result files** are available as pipeline artifacts\n\n## Summary and next steps\n\nCongratulations! You've successfully integrated GitLab CI/CD with QMetry Test Management Enterprise. Your setup now provides:\n\n* **Automated test result uploads** – No more manual exports and imports \n\n* **Real-time visibility** – QA teams see results immediately after pipeline execution \n\n* **Complete traceability** – Link GitLab commits, pipelines, and test executions \n\n* **Enhanced compliance** – Maintain audit trails for regulated industries \n\n* **Scalable quality processes** – Support growing test suites without added overhead\n\n### What happens now\n\nEvery time your GitLab pipeline runs:\n\n1. Tests execute and generate result files.  \n2. The QMetry component automatically uploads results to your instance.  \n3. QA teams, stakeholders, and auditors see results in QMetry dashboards.  \n4. AI-powered insights analyze execution patterns and identify improvements.  \n5. Compliance reports generate automatically with full traceability.\n\n### Expand your integration\n\nNow that you have the basic integration working, consider these advanced scenarios:\n\n* **Bi-directional integration**: Use QMetry's API to trigger GitLab pipelines from test management workflows.\n\n* **Multi-project deployments**: Scale the component across your organization's GitLab projects.\n\n* **Custom reporting**: Build dashboards combining GitLab pipeline metrics with QMetry test analytics.\n\n* **Scheduled test execution**: Use GitLab scheduled pipelines to run regression suites nightly.\n\n## Learn more and get help\n\n### Documentation and resources\n\n* **Component documentation**: [GitLab CI/CD Catalog](https://gitlab.com/explore/catalog)  \n* **QMetry documentation**: [QMetry Support Portal](https://qmetrysupport.atlassian.net/wiki/spaces/QPro/overview)  \n* **SmartBear resources**: [SmartBear Academy](https://smartbear.com/resources/)  \n* **GitLab CI/CD documentation**: [GitLab CI/CD Documentation](https://docs.gitlab.com/ee/ci/)\n\n### Support\n\n**For component technical questions**:\n\n* Visit the [component repository](https://gitlab.com/sb9945614/qtm-gitlab-component).  \n* Open an issue on the project.  \n* Check existing issues for common questions.\n\n**For QMetry product questions**:\n\n* Contact SmartBear support at support@smartbear.com.  \n* Visit the [QMetry Community Forum](https://community.smartbear.com/).",{"featured":11,"template":12,"slug":725},"streamline-test-management-with-the-smartbear-qmetry-gitlab-component",{"content":727,"config":737},{"title":728,"description":729,"authors":730,"heroImage":732,"body":733,"date":734,"category":9,"tags":735},"Changes to packages.gitlab.com: What you need to know","GitLab's package hosting infrastructure has changed. Learn what actions to take before old URL formats are deprecated in September 2026.",[731],"Denis Afonso","https://res.cloudinary.com/about-gitlab-com/image/upload/v1774957323/xfnzvlwc4c2vhal3v3jv.png","Over the past few months, we have been gradually migrating the infrastructure behind `packages.gitlab.com` to a new package hosting system.\n\nThe base domain `packages.gitlab.com` remains the same, but URL formats, GPG key locations, network requirements, and the package browsing UI are changing. **Your existing configuration will continue to work during the transition period until September 30, 2026** — we are maintaining backwards compatibility with old URL formats through URL rewrite rules while customers transition.\n\nThe updated [installation documentation](https://docs.gitlab.com/install/package/ubuntu/) already reflects the new URL formats. If you are setting up a new installation, follow the documentation and no further action is needed.\n\nIf you have an existing installation, read on for what's changing and what you need to do.\n\n## Timeline\n\nThe old PackageCloud system and its UI will be shut down on **March 31, 2026**. Since all traffic has been served from the new system for months, we do not expect any disruptions.\n\nThe URL rewrite rules maintaining backwards compatibility **will be removed by the end of September 2026**. After that date, only the new URL formats will work.\n\nWe recommend updating your configurations as soon as possible.\n\n## Required actions\n\nBefore the end of September 2026, you need to:\n\n1. **Re-run the installation script** ([DEB](https://packages.gitlab.com/install/repositories/gitlab/gitlab-ee/script.deb.sh) or [RPM](https://packages.gitlab.com/install/repositories/gitlab/gitlab-ee/script.rpm.sh)) or manually update repository configurations for `gitlab/*` repos to use the new URL formats.  \n2. **Update GPG key references** from `https://packages.gitlab.com/gpg.key` to `https://packages.gitlab.com/gpgkey/gpg.key`.  \n3. **Update firewall/proxy allowlists** to permit traffic to `https://storage.googleapis.com/packages-ops`.  \n4. **Update mirroring configurations** to use the new URL formats, if you mirror GitLab package repositories.  \n5. **Update Runner `noarch` RPM references** from the `noarch` path to `x86_64`, if you use Runner `noarch` RPM packages.  \n6. **Update any direct download automation**, if you relied on PackageCloud-style `download.rpm` or `download.deb` URLs.\n\nRead on for the details behind each change.\n\n## What's changing\n\n### DEB repository URLs for `gitlab/*` repos\n\nFor `gitlab/*` repositories (e.g., `gitlab-ee`, `gitlab-ce`), the DEB repository URL structure now includes the distribution codename as a path segment. This aligns with the [standard Debian repository format](https://wiki.debian.org/DebianRepository/Format), where the distribution codename is part of the base URL that your package manager uses to locate package metadata and pools. The old PackageCloud format omitted this path segment.\n\n**The easiest way to update is to re-run the installation script**, which will automatically configure the correct repository URLs:\n\n```shell\ncurl --location \"https://packages.gitlab.com/install/repositories/gitlab/gitlab-ee/script.deb.sh\" | sudo bash\n```\n\nReplace `gitlab-ee` with the appropriate repository name (e.g., `gitlab-ce`). For RPM-based systems, use `script.rpm.sh` instead.\n\nIf you prefer to update your configuration manually, here is what changed. For example, for GitLab EE on Ubuntu Jammy:\n\n**Old format (to be deprecated):**\n\n```shell\ndeb https://packages.gitlab.com/gitlab/gitlab-ee/ubuntu/ jammy main\n```\n\nThis resolved to paths like:\n\n```shell\n/gitlab/gitlab-ee/ubuntu/dists/jammy/...\n/gitlab/gitlab-ee/ubuntu/pool/...\n```\n\n**New format:**\n\n```shell\ndeb https://packages.gitlab.com/gitlab/gitlab-ee/ubuntu/jammy jammy main\n```\n\nWhich resolves to:\n\n```shell\n/gitlab/gitlab-ee/ubuntu/jammy/dists/jammy/...\n/gitlab/gitlab-ee/ubuntu/jammy/pool/...\n```\n\nNote the addition of the distribution codename (`jammy`) as a path segment before `dists/` and `pool/`.\n\n### DEB repository URLs for `runner/*` repos\n\nThe URL format for `runner/*` DEB repositories (e.g., `runner/gitlab-runner`) are unchanged. No action is needed.\n\n### GPG key URL\n\nThe GPG key URL has changed. Update any references in your configuration:\n\n|  | Old URL | New URL |\n| :---- | :---- | :---- |\n| GPG key | `https://packages.gitlab.com/gpg.key` | `https://packages.gitlab.com/gpgkey/gpg.key` |\n\n\n\n### Installation scripts\n\n**Do not reuse old installation scripts.** If you have previously saved copies of the installation scripts, download the latest versions:\n\n**DEB-based (Debian/Ubuntu):**\n\n```shell\ncurl --location \"https://packages.gitlab.com/install/repositories/gitlab/gitlab-ee/script.deb.sh\" | sudo bash\n```\n\n**RPM-based (RHEL/CentOS/etc.):**\n\n```shell\ncurl --location \"https://packages.gitlab.com/install/repositories/gitlab/gitlab-ee/script.rpm.sh\" | sudo bash\n```\n\nReplace `gitlab-ee` with the appropriate repository name (e.g., `gitlab-ce`).\n\n### Direct package download URLs\n\nThe old PackageCloud UI exposed download links in a format like `/\u003Corg>/\u003Crepo>/packages/\u003Cdistro>/\u003Cos>/\u003Cfilename>.\u003Cext>/download.\u003Cext>` (e.g., `download.deb`, `download.rpm`). The new UI links directly to the actual package paths instead.\n\nIf you navigate packages through the new UI, no action is needed. However, if you have automation that scrapped the old UI or relied on the `download.deb` / `download.rpm` URL format, you will need to update it to use the new path structure or switch to standard package manager repository access.\n\n### GitLab Runner `noarch` RPM package changes\n\nGitLab Runner `noarch` RPM packages (such as `gitlab-runner-helper-images`) have been moved from the `noarch` architecture path to `x86_64`. For example:\n\n**Old path:**\n\n```shell\n/\u003Corg>/\u003Crepo>/\u003Cdistro>/\u003Cos>/noarch/Packages/...\n```\n\n**New path:**\n\n```shell\n/\u003Corg>/\u003Crepo>/\u003Cdistro>/\u003Cos>/x86_64/Packages/...\n```\n\nThis change only affects RPM-based distributions (e.g., EL/8, EL/9). DEB-based Runner packages are not affected.\n\nIf you have automation or configuration that references the `noarch` path for Runner RPM packages, update it to use `x86_64` instead.\n\n## Firewall and network allowlist updates\n\nPackage downloads from `packages.gitlab.com` now redirect to **Google Cloud Storage**. Previously, packages were served through AWS CloudFront. If your environment has strict firewall or proxy rules, you must add the following to your allowlist:\n\n```shell\nhttps://storage.googleapis.com/packages-ops\n```\n\nWithout this change, package downloads may fail with `503` errors or connection timeouts.\n\n## Repository mirroring\n\nIf you mirror GitLab package repositories using tools like `apt-mirror`, `reposync`, or Red Hat Satellite, you **must** update to the new URL format for `gitlab/*` repos. The old URL format does not work correctly for mirroring with the new infrastructure. More detailed instructions can be found on the [installation guide](https://docs.gitlab.com/install/mirror/).\n\n## UI changes\n\nThe package browsing interface at `packages.gitlab.com` is being updated with a new UI. The old interface (previously accessible at `packages.gitlab.com/gitlab/... and`  `packages.gitlab.com/runner/...` ) will no longer be available. The new interface provides similar package browsing functionality.\n\n## Feedback\n\nIf you encounter any issues related to these changes, please report them in our [public feedback issue](https://gitlab.com/gitlab-org/gitlab/-/work_items/595277). We are actively monitoring it and will respond to reports.","2026-03-31",[736,9],"news",{"featured":30,"template":12,"slug":738},"changes-to-packages-gitlab-com-what-you-need-to-know",{"content":740,"config":749},{"title":741,"description":742,"authors":743,"heroImage":745,"date":746,"body":747,"category":9,"tags":748},"Getting started with GitLab feature flags in Python","Learn to integrate GitLab feature flags into a Python Flask app using the Unleash SDK to control feature rollouts without redeploying.",[744],"Omid Khan","https://res.cloudinary.com/about-gitlab-com/image/upload/v1774465167/n5hlvrsrheadeccyr1oz.png","2026-03-26","You've spent weeks building a new feature. It passes every test, the code review is done, and it's ready to ship. So you deploy it and within an hour your inbox is full of bug reports. The feature works fine for most users, but something about production traffic you didn't anticipate is causing failures for a subset of them. Now you're scrambling to roll back, writing incident reports, and managing the PR fallout.\n\nFeature flags prevent exactly this. They let you decouple deployment from release: push code to production whenever it's ready, then control who actually sees the new feature by flipping a toggle in GitLab. Start with your QA team using a \"User IDs\" strategy, then switch to a \"10% percent rollout,\" then flip to \"All users\" when you're confident. If something goes wrong at any point, you turn it off in seconds. No redeployment, no hotfix, no bad press.\n\nThis tutorial walks through a working Flask application that reads GitLab feature flags through the Unleash Python SDK. A complete, runnable version of the code is available at [gitlab.com/omid-blogs/gitlab-feature-flags-demo](https://gitlab.com/omid-blogs/gitlab-feature-flags-demo). Clone it into your own group or workspace, and you'll have live feature flag control in minutes.\n\nBy the end, you'll understand how the integration works and have a template you can drop straight into your own projects.\n\n## What you'll need\n\n* A GitLab project (Free, Premium, or Ultimate) with feature flags enabled. This is where you'll create and manage your flags. To enable it, go to your project and navigate to **Settings > General > Visibility, project features, permissions** and make sure the **Feature Flags** toggle is on.  \n* The [demo repo](https://gitlab.com/omid-blogs/gitlab-feature-flags-demo) forked into your own GitLab namespace, then cloned locally\n\n## How GitLab feature flags work under the hood\n\nGitLab exposes an [Unleash](https://github.com/Unleash/unleash)-compatible API for every project. That means any Unleash client SDK (Go, Ruby, Python, JavaScript, and more) can connect directly to GitLab without a separate Unleash server.\n\nOn startup, the SDK fetches all flag definitions, then re-fetches on a configurable interval (the demo uses 15 seconds). Every call to `is_enabled()` evaluates locally against the cached configuration with no network call per flag check. That makes flag evaluation near-instant and resilient to transient network issues.\n\nHere are the steps to take to integrate GitLab feature flags into a Python Flask app using the Unleash SDK.\n\n## 1. Set up your GitLab project and clone the demo\n\nYou'll need:\n\n* Your own GitLab project to host the feature flags\n\n* The demo repo cloned locally to run the app\n\n### Fork or clone the demo repo\n\nGo to [gitlab.com/omid-blogs/gitlab-feature-flags-demo](https://gitlab.com/omid-blogs/gitlab-feature-flags-demo) and fork it into your own GitLab namespace. This gives you a personal copy of the project where you can manage your own feature flags. Then clone it locally and open it in your favorite IDE:\n\n\n```shell\ngit clone https://gitlab.com/\u003Cyour-namespace>/gitlab-feature-flags-demo.git\ncd gitlab-feature-flags-demo\n```\n\n### What's inside the repo\n\n```\n.\n├── app.py                # Flask app + Unleash SDK integration\n├── requirements.txt      # Python dependencies\n├── .env.example          # Template for required environment variables\n├── .gitignore\n├── templates/\n│   └── index.html        # Web UI template\n└── static/\n    └── styles.css        # Styling\n```\n\n## 2. Create your feature flags in GitLab\n\nOpen your own GitLab project and navigate to **Deploy > Feature Flags**, then click **New feature flag**. Create the following four flags, setting each status to **Active** with a strategy of **All users**.\n\n* **dark_mode** — Switches the page to a dark color scheme.  \n* **holiday_banner** — Shows a festive banner at the top of the page.  \n* **new_layout** — Switches the card grid to a single-column layout.  \n* **fun_fonts** — Swaps the body font to a playful handwritten style.\n\n\n![All four feature flags in the GitLab UI](https://res.cloudinary.com/about-gitlab-com/image/upload/v1774466322/pifymwd6senqz3nzcyxa.png \"All four feature flags in the GitLab UI\")\n\n**Tip:** A flag must be Active and have at least one strategy to evaluate as enabled. Without a strategy, the SDK treats the flag as disabled even if it's marked \"Active.\"\n\n### Understanding strategies\n\n\"All users\" is a simple on/off toggle, but GitLab supports several more out of the box:\n\n* **Percent rollout** *(recommended)*: Gradually roll out to a percentage of users based on user ID, session ID, or randomly. This is the most flexible option and the one to reach for first.  \n* **Percent of users**: Enable for a percentage of authenticated users. Less flexible than Percent rollout since it only works with logged-in users.  \n* **User IDs**: Enable for specific user IDs only, great for internal testing with a named group.  \n* **User list**: Enable for a predefined list of users.  \n* **All users**: Enable for everyone.\n\nStrategies are where feature flags get really powerful. Start with your QA team using a User IDs strategy, switch to a 10% percent rollout, then flip to All users when you're confident. All from the GitLab UI, no code changes required.\n\n## 3. Get your Unleash credentials\n\nOn the Feature Flags page, click **Configure** in the top-right corner. You'll see two values:\n\n* **API URL**: `https://gitlab.com/api/v4/feature_flags/unleash/\u003Cyour-project-id>`  \n* **Instance ID**: A unique token scoped to your project\n\nCopy both values. You'll pass them to the app as environment variables. Note that the Instance ID is read-only. It can only fetch flag state, not modify anything, but still treats the Instance ID as a secret to prevent information disclosure.\n\n![Configure panel shows your API URL and Instance ID](https://res.cloudinary.com/about-gitlab-com/image/upload/v1774466322/bxkn0xkpe4xude0es4zx.png \"Configure panel shows your API URL and Instance ID\")\n\n## 4. Set up the project locally\n\nThe README has the full setup walkthrough, but the short version is:\n\n```shell\npip install -r requirements.txt\n```\n\nThen set your credentials. You can do this one of two ways:\n\n**Option A: Using the .env file (recommended)**\n\nThe repo includes a `.env.example` file. Copy it and fill in your values:\n\n```shell\ncp .env.example .env\n```\n\nOpen `.env` in your editor and replace the placeholder values with your credentials from Step 3:\n\n```\nUNLEASH_URL=https://gitlab.com/api/v4/feature_flags/unleash/\u003Cyour-project-id>\nUNLEASH_INSTANCE_ID=\u003Cyour-instance-id>\nUNLEASH_APP_NAME=production\n```\n\nThen export them:\n\n```shell\nexport $(grep -v '^#' .env | xargs)\n```\n\n**Option B: Export directly in your terminal**\n\n```shell\nexport UNLEASH_URL=\"https://gitlab.com/api/v4/feature_flags/unleash/\u003Cyour-project-id>\"\nexport UNLEASH_INSTANCE_ID=\"\u003Cyour-instance-id>\"\nexport UNLEASH_APP_NAME=\"production\"\n```\n\nNever commit your `.env` file to version control. The `.gitignore` in the repo already excludes it, but it's worth knowing why: your Instance ID is a secret and should stay out of git history.\n\nThree environment variables drive the entire integration:\n\n| Variable | Required | Description | Default |\n| ----- | ----- | ----- | ----- |\n| `UNLEASH_URL` | Yes | GitLab Unleash API URL for your project | — |\n| `UNLEASH_INSTANCE_ID` | Yes | Instance ID from the Configure panel | — |\n| `UNLEASH_APP_NAME` | No | Environment name, matches flag strategies | `production` |\n\n\n\n`UnleashClient` is the key dependency. It's the official Unleash Python SDK that handles polling, caching, and local flag evaluation so you don't have to build any of that yourself.\n\n## 5. Understand the application\n\nBefore running it, read through `app.py`. Here are the key patterns worth understanding so you can replicate them in your own projects.\n\n**Initializing the SDK**\n\n```py\nunleash_client = UnleashClient(\n    url=UNLEASH_URL,\n    app_name=UNLEASH_APP_NAME,\n    instance_id=UNLEASH_INSTANCE_ID,\n    refresh_interval=15,\n    metrics_interval=60,\n)\n\nunleash_client.initialize_client()\n```\n\nNo personal access tokens, no credentials hardcoded in source. The app exits immediately with a clear error message if either required variable is missing.\n\n**Checking a flag**\n\n```py\ndef is_flag_enabled(flag_name):\n    return unleash_client.is_enabled(flag_name)\n```\n\nBecause the SDK caches flag definitions in memory, `is_enabled()` returns instantly with no network roundtrip.\n\n**Gating real behavior behind flags**\n\nThe index route builds a feature dictionary, evaluating each flag and passing the results to the template:\n\n```py\nfeatures = {}\nfor flag_name, config in feature_configs.items():\n    features[flag_name] = {\n        **config,\n        'enabled': is_flag_enabled(flag_name)\n    }\n\nreturn render_template('index.html', features=features)\n```\n\nThe template uses those values to conditionally apply CSS classes and render UI elements. `dark_mode` toggles a body class, `holiday_banner` shows or hides a banner element entirely. Open `templates/index.html` to see how it's wired together.\n\nNote that `index.html` also auto-refreshes every 30 seconds via a small JavaScript snippet, so you can watch flag changes take effect without manually reloading.\n\n**Passing user context for targeted strategies**\n\nWhen you're ready to move beyond All users and use Percentage rollouts or User targeting, pass a context object to `is_enabled()`:\n\n```py\nunleash_client.is_enabled(\n    'new_layout',\n    context={'userId': current_user.id}\n)\n```\n\nThe SDK handles consistent hashing for percentage rollouts automatically. No math required on your end.\n\n## 6. Run the app\n\n```shell\npython3 app.py\n```\n\nVisit `http://localhost:8080`. You should see all four feature cards showing their current enabled/disabled state.\n\n![Demo app with all four feature flags disabled](https://res.cloudinary.com/about-gitlab-com/image/upload/v1774466322/bjc0rp7h43wetefny8cw.png \"Demo app with all four feature flags disabled\")\n\n## 7. Toggle flags in real time\n\nGo back to **Deploy > Feature Flags** in GitLab and toggle one of the flags. Try `dark_mode` or `holiday_banner` for the most visible effect. Wait about 15 seconds, then reload the page. The card updates to reflect the new state, and if you toggled `dark_mode` on, the entire page switches to a dark theme. Toggle it back off, wait, reload, and it snaps back instantly.\n\nThis is the core value of feature flags: You control application behavior from GitLab without touching code or redeploying.\n\n![Demo app with two feature flags toggled off](https://res.cloudinary.com/about-gitlab-com/image/upload/v1774466321/kfbvvazflpta4pt8vtoj.png)\n\n\n![Demo app with two feature flags toggled off](https://res.cloudinary.com/about-gitlab-com/image/upload/v1774466321/rslzfdcpronixcfokfbk.png \"Demo app with two feature flags toggled off\")\n\n\n## Why the Unleash SDK instead of the GitLab REST API?\n\nFor an app that evaluates flags on every request, the SDK is the clear winner. It's faster, simpler, and the Instance ID it uses carries no permissions beyond reading flag state. That's a much smaller security footprint than a PAT.\n\n|  | REST API | Unleash SDK |\n| ----- | ----- | ----- |\n| **Authentication** | Requires a Personal Access Token with broader project permissions | Uses only the Instance ID, read-only, scoped to flag state, no PAT needed |\n| **Flag evaluation** | Network call per check | Evaluates locally from cached config |\n| **Latency per check** | Network round-trip | Near zero (in-memory) |\n| **Strategy support** | Manual parsing required | Built-in support for percentage rollouts and user ID targeting |\n| **Rate limits** | Subject to GitLab.com API rate limits | Single polling connection per app instance |\n\n## Troubleshooting\n\n| Problem | Fix |\n| ----- | ----- |\n| App exits with `ERROR: UNLEASH_URL and UNLEASH_INSTANCE_ID...` | Set both env vars. See `.env.example`. |\n| All flags show as disabled | Check that the flags exist in GitLab and have an active strategy. Then wait 15 seconds for the SDK to refresh. |\n| Changes in GitLab don't appear | The SDK polls every 15 seconds. Reload the page after a short wait. |\n| A local IP address doesn't work | Your OS firewall may be blocking Port 8080\\. Use `localhost:8080` instead. |\n\n## A note on rate limits in production\n\nThe 15-second polling interval works well for development and small deployments. With all clients polling from the same IP, GitLab.com can support around 125 clients at a 15-second interval before hitting rate limits. If you're building a larger production app, consider running an Unleash Proxy in front of your clients. It batches requests to GitLab on behalf of all your instances and dramatically reduces upstream traffic.\n\n## Security considerations\n\n1. **`debug=False` is already set in the demo:** Keep it that way. Flask's debug mode exposes an interactive debugger that allows remote code execution.  \n2. **Keep dependencies updated**: The `requirements.txt` pins specific versions. Enable GitLab [Dependency Scanning](https://docs.gitlab.com/user/application_security/dependency_scanning/) in your CI/CD pipeline to stay on top of vulnerabilities.  \n3. **Use environment variables for credentials**: Never hardcode the Instance ID or any tokens in source code. The demo's `.env.example` shows the right pattern.  \n4. **The Instance ID is read-only**: It can only fetch flag state, not modify it. Still treat it as a secret.\n\n## Summary\n\nThis tutorial covered the full lifecycle of integrating GitLab feature flags into a Python application: creating flags with the right strategies, retrieving Unleash credentials, initializing the SDK, evaluating flags locally in Flask, and toggling behavior in real time without a redeployment.\n\nThe entire integration requires one dependency (`UnleashClient`), three environment variables, and a single method call (`is_enabled()`). No separate Unleash server, no personal access tokens, no complex infrastructure.\n\nFeature flags are one of the most practical tools available for reducing deployment risk. The ability to instantly disable a broken feature, or progressively roll one out from a targeted user group to a percentage to everyone, without touching a deployment pipeline, delivers outsized value for minimal setup. The [demo repo](https://gitlab.com/omid-blogs/gitlab-feature-flags-demo) provides a working foundation to fork and adapt for any project.\n\n## Resources\n\n* [Demo project on GitLab](https://gitlab.com/omid-blogs/gitlab-feature-flags-demo)  \n* [GitLab Feature Flags documentation](https://docs.gitlab.com/operations/feature_flags/)  \n* [Unleash Python SDK on GitHub](https://github.com/Unleash/unleash-python-sdk)  \n* [Unleash SDKs (all languages)](https://github.com/Unleash/unleash#unleash-sdks)",[24,719,9],{"featured":11,"template":12,"slug":750},"getting-started-with-gitlab-feature-flags-in-python",{"promotions":752},[753,767,778],{"id":754,"categories":755,"header":757,"text":758,"button":759,"image":764},"ai-modernization",[756],"ai-ml","Is AI achieving its promise at scale?","Quiz will take 5 minutes or less",{"text":760,"config":761},"Get your AI maturity score",{"href":762,"dataGaName":763,"dataGaLocation":240},"/assessments/ai-modernization-assessment/","modernization assessment",{"config":765},{"src":766},"https://res.cloudinary.com/about-gitlab-com/image/upload/v1772138786/qix0m7kwnd8x2fh1zq49.png",{"id":768,"categories":769,"header":770,"text":758,"button":771,"image":775},"devops-modernization",[9,566],"Are you just managing tools or shipping innovation?",{"text":772,"config":773},"Get your DevOps maturity score",{"href":774,"dataGaName":763,"dataGaLocation":240},"/assessments/devops-modernization-assessment/",{"config":776},{"src":777},"https://res.cloudinary.com/about-gitlab-com/image/upload/v1772138785/eg818fmakweyuznttgid.png",{"id":779,"categories":780,"header":782,"text":758,"button":783,"image":787},"security-modernization",[781],"security","Are you trading speed for security?",{"text":784,"config":785},"Get your security maturity score",{"href":786,"dataGaName":763,"dataGaLocation":240},"/assessments/security-modernization-assessment/",{"config":788},{"src":789},"https://res.cloudinary.com/about-gitlab-com/image/upload/v1772138786/p4pbqd9nnjejg5ds6mdk.png",{"header":791,"blurb":792,"button":793,"secondaryButton":798},"Start building faster today","See what your team can do with the intelligent orchestration platform for DevSecOps.\n",{"text":794,"config":795},"Get your free trial",{"href":796,"dataGaName":47,"dataGaLocation":797},"https://gitlab.com/-/trial_registrations/new?glm_content=default-saas-trial&glm_source=about.gitlab.com/","feature",{"text":502,"config":799},{"href":51,"dataGaName":52,"dataGaLocation":797},1775648078655]