2023 – 2025 · Principal Software Engineer · 6 min read
GitLab CI/CD Migration
End-to-end owner of the migration of 152+ Node and Angular projects from Jenkins Groovy pipelines to native GitLab CI/CD components. Build times cut by 3–6× depending on project size; flakiness eliminated via job isolation.
- GitLab CI/CD
- Pipeline Components
- Node.js
- Angular
- Docker
- Cloudflare Workers
An hour-long build now finishes in about ten minutes. Getting there meant owning the migration of 152+ Node and Angular projects off Jenkins Groovy pipelines and onto native, reusable GitLab CI/CD components, end to end. I designed a shared component library covering six distinct archetypes, cut pipeline build times by 3–6× depending on project size, and put an end to the Jenkins “noisy neighbor” flakiness that had plagued the estate for years.
The problem
Jenkins was end-of-life at OneTrust, and the broader organization was consolidating its infrastructure onto GitLab. With every pipeline written in bespoke Groovy DSL, maintenance cost scaled linearly with project count, and the estate offered none of the structural primitives a modern CI/CD platform needs: parallel jobs, DAG dependencies, per-job log retrieval, or testable pipeline code. Every new compliance requirement (SBOM generation, signed artifacts, SAST gates) meant a PR against every Jenkinsfile.
The migration was two decisions at once. The business one was to decommission a platform going end of life. The strategic one was to rewrite for consistency, security, and testability while we were already in there.
The approach
I catalogued the estate into six archetypes, which surfaced the real distribution of the codebase:
| Archetype | Approx. count |
|---|---|
| Node + Angular libraries | ~70 |
| Angular micro-frontends (Docker) | ~50 |
| Node services (Docker) | ~24 |
| Cloudflare Workers | a few |
| Node apps on Cloudflare Pages deploys | a few |
| SDKs and misc | a few |
The last three are small, roughly a dozen projects between them.
The migration went one archetype at a time, starting with libraries. They were the largest bucket and the simplest, so the early wins compounded.
Cloudflare Workers were the hardest archetype technically. Those projects shared no standardization at all, so building the golden path (standardizations, controls, guardrails) had to happen before the migration could start. Angular was the hardest operationally: roughly 120 projects meant the pipeline work itself was straightforward, and PR approvals across distributed, busy teams became the bottleneck. We pushed through by swarming, with focused communication, coordinated review passes, and a rollout run as one team instead of 120 independent migrations.
One assumption needed updating mid-migration. I had assumed composable
templates would carry their tooling with them. They do not. A template composes
other templates, and the scripts and utilities it calls need a delivery path of
their own. Two patterns ended up
shipping side by side: clone-in-at-runtime (git clone or curl) for simple
one-off scripts, and bake-in (packaging utilities into the runner’s Docker
image) for complex tooling and common dependencies.
The outcome
Pipeline build times dropped 3–6× depending on project size, with the worst-case hour-long builds compressing to around ten minutes:
| Project size | Before (Jenkins) | After (GitLab) |
|---|---|---|
| Small | ~5 min | under 1 min |
| Medium | ~10 min | 3–5 min |
| Large | 20–40 min (some up to 60) | ~10 min |
The speedup came from parallelized jobs, dependency caching, fail-fast gating, and the end of Jenkins’ shared-runner flakiness. Per-job encapsulation meant artifacts could be verified at each stage, and pipelines that used to fail at random became consistently performant.
Structurally: per-project pipeline files shrank from hundreds of lines to
roughly 20. One central component release now ships the next compliance gate to
every project with zero per-project PRs. Onboarding a new service went from
“read the runbook” to include one component.
What I’d change
Observability should have come first. Across 152+ projects and six archetypes, each with its own variations, the biggest gap was visibility into the actual state of the code across the estate. Cataloging by hand was expensive, and retrofitting pipeline telemetry mid-migration cost time that instrumenting the estate up front would have saved. Next time I instrument the estate before touching the first pipeline.