We recently transitioned our development model from a single, large monorepo to a microservices architecture with many separate repositories. While the development benefits are clear, our CI/CD spending has increased by approximately 320% month-over-month, which is unsustainable.
I've performed an initial analysis and identified the primary cost drivers, but I am seeking validation and strategies from others who have navigated this.
**Our previous setup (GitLab SaaS):**
* Single pipeline with optimized caching and dependency management.
* Average: 12,000 compute minutes/month.
* Cost: ~$400/month.
**Current setup (same GitLab SaaS tier):**
* 45+ repositories, each with its own pipeline.
* No shared caching between pipelines; each job installs dependencies independently.
* Significant duplication in linting, security scanning, and base image builds.
* Average: 51,000 compute minutes/month.
* Cost: ~$1,700/month.
The core issue appears to be the loss of orchestration efficiency. In the monorepo, a single merge request triggered one pipeline. Now, a related change across three services triggers three full pipelines. Our per-minute rate hasn't changed, but the volume has exploded due to duplicated work and the absence of cross-repo caching.
I am evaluating several paths:
* Implementing a shared cache (like S3) for dependencies across all repos.
* Moving to self-hosted runners on a Kubernetes cluster to lower the per-minute cost.
* Investigating CI tools designed for polyrepo setups (e.g., leveraging pipeline triggers and consolidated outputs).
Has anyone conducted a formal TCO comparison for a polyrepo CI strategy at this scale? I am particularly interested in concrete data on:
1. The actual efficiency gains from implementing a global shared cache.
2. The operational overhead and true cost of self-hosted runners versus the managed SaaS premium.
3. Any vendor pricing models that are more favorable for high-volume, low-duration jobs across many repositories.
— Jessica
Trust but verify. Then renegotiate.
I'm a staff engineer at a mid-size fintech (~80 devs), we run 30+ microservices across AWS and have been through this exact pipeline cost explosion.
1. **Pipeline trigger duplication**
You've already hit the main one: related changes trigger N pipelines. At my last shop, a core library update triggered 12 service pipelines, ~80% of which ran identical test suites for zero behavioral change. The fix was implementing a change detection filter using `changes:` in `.gitlab-ci.yml` to skip jobs unless specific service paths were modified.
2. **Cache sprawl and dependency re-installs**
Without shared cache, each pipeline rebuilds the world. We saw node_modules installs go from 2 minutes in the monorepo to a total of 45+ minutes across all repos. We implemented a shared S3 cache with a prefix per service, but the maintenance overhead is real. Our CI time dropped by about 40% after, but we still pay for S3 storage and bandwidth.
3. **Tooling and scanning multiplier**
Every repo runs its own SAST, lint, and container scan. That's 45 licenses or 45 parallel jobs burning minutes. We consolidated to a scheduled security scan that runs once daily across all code, not per pipeline, and cut those related costs by roughly 70%.
4. **Orchestration and deployment complexity**
The hidden cost is orchestration logic. You now need something to manage cross-repo deployments and version pinning. We use a simple internal tool, but I've seen teams adopt Backstage or similar, which is another $20-30k/year in infra and dev time.
My pick is to stick with many repos but aggressively optimize CI, because the dev velocity win is real. Implement change detection and shared caching immediately; that'll likely cut your bill in half. If your team is under 50 devs and can handle the tooling debt, many repos are fine. If you're over 150 devs and need tight coordination, you might want a hybrid monorepo for shared libs. Tell us your team size and how often services actually change together.
Just my 2 cents
Yep, you've perfectly diagnosed the orchestration tax that hits after a monorepo split. The "related change across three services triggers three full pipelines" is the exact pain point.
user831's suggestion on change detection is a solid immediate fix. I'd add that you need to audit those 45+ pipelines for uniform base images and tool versions. In one client's case, we found 14 slightly different Python base images being pulled, which added huge container registry and pull time overhead. Consolidating to three blessed, centrally maintained images cut a chunk of that "duplication in base image builds" you mentioned.
Longer term, you might need a lightweight orchestrator layer. We used a simple pipeline dispatcher that, on a merge to a shared library, would intelligently trigger downstream service pipelines only if their dependency file (like a package.json or requirements.txt) actually changed. It's more setup, but it turns that 320% surge into something manageable. Have you looked at what's common across all 45 repos that could be centralized, even just for CI?
Implementation is 80% process, 20% tool.
320% cost increase for "development benefits" is a hell of a tradeoff. You've just recreated all the problems a monorepo solved, but now you're paying for the privilege.
The real question is whether those 45+ repos are actually independent services or just a fancy way to organize code. If they're truly separate, fine, but most teams just split for the aesthetic. Now you need a "lightweight orchestrator layer" to manage the mess you just created. Irony.
Shared cache is a band-aid. The bleed is from triggering 45 pipelines for a one-line config change.
Keep it simple
Ah, the classic "architecture-driven bankruptcy" maneuver. You've quantified the exact orchestration tax, but I'd question whether the "development benefits are clear" offset a 4x cost multiplier that's apparently unsustainable.
Your analysis misses the root cause: you likely didn't actually need 45+ independent deployment units. Most teams do this split for organizational politics or a misguided sense of clean boundaries, not because they have 45 genuinely isolated services with different scaling, security, or lifecycle requirements. Now you're spending $1,700 a month to run nearly identical linting jobs 45 times.
The shared cache and change detection filters everyone is suggesting are just cost containment for a self-inflicted wound. The real fix is to re-audit what constitutes a "service" and aggressively re-consolidate anything that's just a library or module pretending to be a microservice. Otherwise you're just building a worse, more expensive monorepo with extra steps.
Test the migration.