The page
Teams wanted off Jenkins. Nobody wanted a release freeze to get there. The migration had to happen while everything kept shipping.
Triage
First, an inventory of what Jenkins actually did:
# every job and when it last ran
curl -s -u "$JENKINS_USER:$JENKINS_TOKEN" \
"https://jenkins.example.com/api/json?tree=jobs[name,lastBuild[timestamp,result]]" \
| jq -r '.jobs[] | [.name, (.lastBuild.timestamp // 0 | ./1000 | todate), .lastBuild.result] | @tsv'
A lot of jobs hadn't run in a long time. Those weren't migrated, just archived.
Root cause
Of the risk: a CI migration fails when the new pipeline does something slightly different from the old one and nobody notices until a release is wrong.
The fix
For each service, the new workflow ran next to the Jenkins job for a while, building the same commit. We compared what came out:
# same commit, two pipelines: do the artifacts match?
sha256sum jenkins-out/app.jar actions-out/app.jar
# recent runs of the new workflow
gh run list --workflow build.yml --limit 20
# rerun one by hand when something looks off
gh run rerun <run-id> --failed
When the outputs matched and the workflow had a clean run of releases, the Jenkins job was switched off. Service by service, never all at once.
What I learnt
Running old and new side by side costs some compute and removes nearly all the risk. The inventory step was also worth it on its own, because a good share of the old jobs didn't need migrating.
What I do now
- Inventory before migrating. Archive what nobody runs.
- Run old and new in parallel and compare outputs, not just pass/fail.
- Cut over one service at a time, with a way back.
stage 2 is where migrations like this get done → see the engagements