{"id":1792,"date":"2026-10-27T09:50:00","date_gmt":"2026-10-27T09:50:00","guid":{"rendered":"https:\/\/devopsschool.org\/blog\/?p=1792"},"modified":"2026-10-27T09:50:00","modified_gmt":"2026-10-27T09:50:00","slug":"devopsschool-org-gitlabops-team-story","status":"publish","type":"post","link":"https:\/\/devopsschool.org\/blog\/devopsschool-org-gitlabops-team-story\/","title":{"rendered":"A Team Adopts GitLabOps: Six Months, Four Fixes"},"content":{"rendered":"<p>The team builds logistics software \u2014 twelve engineers, one product, and a GitLab instance that five different generations of developers had configured, each leaving their assumptions behind. Their new platform lead arrived in January and kept a diary. What follows is that half-year, compressed: four failures, four fixes, and the quiet month at the end where nothing caught fire. It is the most honest introduction to <strong>GitLabOps<\/strong> that a story can carry. The Atlas coins the term for people who run software delivery on GitLab, and the team made every mistake in the recommended order.<\/p>\n<h2>January: Counting Before Fixing<\/h2>\n<p>The lead&#8217;s first act was unheroic: a spreadsheet. Every pipeline, every registered runner, every credential sitting in CI variables, dated. The count embarrassed everyone \u2014 pipelines outnumbered repositories, because the same commit triggered two builds each, and nobody had written rules saying when a pipeline should exist at all. The Atlas calls this loop <em>configuration<\/em>, and its signature failure is exactly that: duplicates and invalid syntax. The fix was a week of writing workflow rules and declaring job dependencies explicitly, so the graph matched intention instead of accumulation.<\/p>\n<h2>February: The Queue That Ate a Sprint<\/h2>\n<p>With duplicates gone, work piled up somewhere new: jobs parked in pending while developers watched. The scheduling loop had struck its signature blow \u2014 jobs asking for tags that no live runner wore. Renaming sprees had left the fleet&#8217;s labels aspirational rather than accurate. The team spent three days matching every request to a machine that could honor it, then went further and split the fleet by trust: protected runners for the repositories that could reach production, disposable one-job machines for anything from outside, and a standing ban on shell executors anywhere the public could submit code. The lead&#8217;s diary entry ends with a rule the team still repeats: no job waits more than two minutes, at the 95th percentile, before someone hears about it.<\/p>\n<h2>March: Naming the GitLabOps Loops<\/h2>\n<p>By March the lead had a framework, borrowed from the Atlas, that turned two months of firefighting into a map. The team pinned it to the wall:<\/p>\n<ul>\n<li><strong>Configuration<\/strong> \u2014 files merge into a pipeline graph; fails as duplicates and syntax errors. (January&#8217;s wound.)<\/li>\n<li><strong>Scheduling<\/strong> \u2014 tagged runners take matching jobs; fails as pending queues. (February&#8217;s.)<\/li>\n<li><strong>Delivery<\/strong> \u2014 releases move through environments; fails as unapproved, unsigned changes.<\/li>\n<li><strong>Platform<\/strong> \u2014 version, capacity, backups; fails as API errors and sluggish repository reads.<\/li>\n<\/ul>\n<p>The labels mattered less than the habit they enabled: every incident got a loop within minutes, and every loop got an owner within a week. When April&#8217;s incident arrived, nobody had to ask which part of the map was burning.<\/p>\n<h2>April: The Release That Jumped the Queue<\/h2>\n<p>The incident that justified the whole exercise arrived on a quiet Thursday. Production changed without a name attached to the approval, because there was no approval to attach. Two engineers had deployed past each other in the same hour, and the slower pipeline had overwritten the faster one \u2014 a delivery-loop failure, textbook. The reconstruction took a weekend; the fix took a day. Environments marked protected, with approvers named in writing. A queue that lines up competing releases instead of letting them interleave. And the guard that retires a superseded deployment before it can overwrite its successor&#8217;s work. And a written answer to the question the auditors would eventually ask: who may move production, and how would we know?<\/p>\n<h2>May: The Upgrade Debt Comes Due<\/h2>\n<p>May exposed the loop nobody owns: the platform itself. The instance trailed four minor versions, background migrations from a rushed patch months earlier had never finished, and repository operations had begun to crawl. The diagnosis was platform-loop in the purest sense, and the lead resisted the heroic big-bang upgrade, doing it the Atlas&#8217;s way instead \u2014 a modest hop every month or two, pausing at each required stopping point in sequence, confirming every background migration completed before the next hop. Unremarkable, calendar-driven, and by June the crawl was gone.<\/p>\n<h2>June: Numbers Instead of Nostalgia<\/h2>\n<p>The diary&#8217;s last entries are measurements, which is the point the whole story turns on. Merge request feedback at the 95th percentile: eleven minutes, inside the fifteen-minute target. Queue time: under two. Success rate: north of ninety percent. Cost per successful pipeline, newly visible and newly argued over at budget season. The fine print got read too, once someone discovered the platform&#8217;s duration figure politely ignores queue time \u2014 the very delay February had been about. The team replaced its old arguments \u2014 &#8220;the pipeline feels slower&#8221; \u2014 with readings anyone could check, which is the practical difference between a platform that is run and one that is merely tolerated. Arguments did not vanish; they just moved to a room with numbers on the wall.<\/p>\n<h2>What This Team Would Tell You About GitLabOps<\/h2>\n<p>Asked for takeaways, the lead offers three. First, inventory before improvement; the spreadsheet found problems the complaints never mentioned, including a nightly job nobody could attribute. Second, every loop needs an owner, because unowned loops fail in their signature way, right on schedule. Third, invest at the pace of scale \u2014 this discipline earns its cost for a shared, self-managed platform, and would have been ceremony for a lone team on GitLab.com. The full playbooks the team worked from live in the Atlas&#8217;s <a href=\"https:\/\/www.xopsschool.com\/gitlabops\/\">GitLabOps playbooks<\/a>. Students mapping their own path should read our guide to the <a href=\"https:\/\/devopsschool.org\/blog\/devops-knowledge-gaps-students-must-fill-a-practical-guide\/\">knowledge gaps worth filling before interviews<\/a>, and weigh the credentials question separately in our honest look at <a href=\"https:\/\/devopsschool.org\/blog\/the-real-value-of-devops-certifications-in-modern-tech-careers\/\">what certifications do and do not signal<\/a>. Stories end; the loops keep turning, and someone has to tend them. That is the whole trade, and it is a good one.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>The story of a twelve-person platform team&#8217;s first half-year with GitLabOps: duplicate pipelines, parked jobs, a rogue release, and the fixes that held.<\/p>\n","protected":false},"author":5,"featured_media":1793,"comment_status":"","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[486],"tags":[],"class_list":["post-1792","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-devops"],"_links":{"self":[{"href":"https:\/\/devopsschool.org\/blog\/wp-json\/wp\/v2\/posts\/1792","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/devopsschool.org\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/devopsschool.org\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/devopsschool.org\/blog\/wp-json\/wp\/v2\/users\/5"}],"replies":[{"embeddable":true,"href":"https:\/\/devopsschool.org\/blog\/wp-json\/wp\/v2\/comments?post=1792"}],"version-history":[{"count":1,"href":"https:\/\/devopsschool.org\/blog\/wp-json\/wp\/v2\/posts\/1792\/revisions"}],"predecessor-version":[{"id":1803,"href":"https:\/\/devopsschool.org\/blog\/wp-json\/wp\/v2\/posts\/1792\/revisions\/1803"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/devopsschool.org\/blog\/wp-json\/wp\/v2\/media\/1793"}],"wp:attachment":[{"href":"https:\/\/devopsschool.org\/blog\/wp-json\/wp\/v2\/media?parent=1792"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devopsschool.org\/blog\/wp-json\/wp\/v2\/categories?post=1792"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devopsschool.org\/blog\/wp-json\/wp\/v2\/tags?post=1792"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}