The best compliment my work ever got was silence.
We ran a CI/CD pipeline migration with zero downtime — 200 pipelines, 22+ repositories, 15 teams moving off a legacy platform onto a modern stack — and the loudest reaction was a shrug. No war room. No 2 a.m. rollback. No furious Slack thread. If you grade a platform migration by how many developers complained, this one scored a perfect zero.
People hear that and assume we failed to communicate. It's the opposite. When nobody notices the ground move under their feet, that is the win.
The mess we started with
Twenty-two repositories, and every single one was a special snowflake. Each had its own Jenkinsfile, its own quirks, its own undocumented tribal knowledge. A pile of Bitbucket pipelines that only the person who wrote them could debug — and that person had left six months ago. Every service was a one-off. Every deployment was a small prayer.
You know the type of setup: it works, barely, and nobody wants to touch it. The moment you change one line, three things break in a service you've never opened. That's not infrastructure. That's a haunted house.
One template instead of twenty-two
The goal was brutally simple to say and hard to earn: replace all of it with one universal pipeline template on GitLab CI. A single 634-line file every service references through include:. Change the template once, and every service inherits the update. No more copy-paste drift. No more "why does staging build differently from prod."
And one non-negotiable gate baked in: 75% test coverage. Not a suggestion, not a "we'll get to it." If your service didn't clear the bar, the pipeline stopped. Migrating pipelines became the moment to pay down years of testing debt too, and we refused to let it slide.
Getting that one file right took 350+ commits. Every edge case, every stray environment variable, every secret rotation, every weird build step someone added in 2022 and forgot — all of it had to be handled by the template, not by a special exception. The template was only worth it if it was genuinely universal.
Migrate in dependency order, not by deadline
Here's where most migrations die. Someone picks a cutover date. Teams scramble. Something breaks. Rollback. Trust evaporates. The whole thing slips six months. I've watched that exact movie play out three times at different companies.
So we refused to set a deadline. Instead:
- Sequence by dependencies. Eight core services migrated in a deliberate order — the things everyone depends on first, the consumers last. Get the order wrong and one broken build cascades through everything downstream.
- Run new alongside old. New pipelines ran in parallel with the old ones for weeks before anyone was asked to switch. Both built the same code at the same time.
- Automated parity checks. We compared the outputs of the old and new pipeline on every run. If anything differed — even slightly — we stopped and investigated before touching the next service.
- One-flag rollback. Any team could revert to the old pipeline by flipping a single flag. No ticket, no meeting, no permission.
- "Migrate when ready," not "by Thursday." The timeline belonged to each team, not to a Gantt chart.
We started small on purpose — 3 low-risk pipelines, then 10, then 50. Every batch that went quietly bought us the confidence to widen the next one.
The real secret was psychological, not technical
The parity checks and the rollback flag mattered. But the thing that actually made this work wasn't in the YAML.
We never once asked a team to "trust the new system."
Instead, we let the new system run their actual builds — correctly — for two full weeks before we asked them to flip over. By the time a team switched, the new pipeline had already been shipping their code for a fortnight. There was nothing to trust. There was only something they'd already watched work.
Trust isn't declared in a kickoff meeting. It's demonstrated, quietly, over enough runs that switching feels like a formality.
What "boring" bought us
Four months. 200 pipelines. Zero incidents. Not one service dropped a request across the entire migration.
The best migrations are the boring ones — the invisible ones, where the only sign anything changed is that builds got a little faster and the on-call phone stopped ringing.
If you're staring down your own legacy CI/CD platform, the playbook is short:
- Consolidate to one template so a fix lands everywhere at once.
- Run old and new in parallel and let parity checks — not opinions — tell you when it's safe.
- Make rollback a single flag, so switching costs nothing to undo.
- Show the system working before you ask anyone to believe in it.
Do that, and your reward is the quietest, most anticlimactic launch of your career. Take the silence as applause.