---
title: CI/CD pipeline migration
slug: ci-cd-pipeline-migration
summary: How to move a repository's pipeline from one CI system to another, one repo at a time, with parity checks and no big-bang cutover.
order: 9
updated: 2026-11-11
status: ready
---

Use this page when a team is moving source control or CI to a new platform. The same method works in any direction. The table below is one common mapping. Swap the syntax for your systems.

## Migrate in waves, never all at once

```mermaid
flowchart LR
  I[Inventory repos] --> R[Rank by risk and value]
  R --> P[Pilot 1 low-risk repo]
  P --> T[Turn the pilot into a template]
  T --> W[Wave of 5-10 repos]
  W --> C[Cut over and archive old pipeline]
  C --> W
```

- [ ] **Inventory:** every repo, its CI file, what it deploys, to which account and environment, and whether it is still used.
- [ ] **Rank:** archive dead repos first (cheapest migration is none), then pilot with a low-traffic service, then high-value services, then the hard ones.
- [ ] **Template:** a shared CI template (`include:`) that every migrated repo uses, so fixes land once.
- [ ] **Waves:** small batches with a cutover date each.

## Map the old pipeline to the new one

| Example source A | Example source B | Example destination |
| --- | --- | --- |
| `pipelines.default` | multibranch default | jobs with `rules: - if: $CI_PIPELINE_SOURCE == "push"` |
| `pipelines.branches.main` | `when { branch 'main' }` | `rules: - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH` |
| `pipelines.pull-requests` | PR builder | `rules: - if: $CI_PIPELINE_SOURCE == "merge_request_event"` |
| `pipelines.custom` (manual) | parameterized job | `when: manual` |
| `step` | `stage` / `sh` | job |
| `parallel` | `parallel {}` | jobs in the same stage |
| `caches` | workspace | `cache:` |
| `artifacts` | `archiveArtifacts` | `artifacts:` |
| `deployment: production` | input step | `environment: production` plus protected environment |
| repository variables | credentials store | CI/CD variables (masked, protected) or OIDC |
| `pipe: atlassian/aws-*` | AWS plugin | OIDC role plus `aws` CLI in an image |

## Port one repo

- [ ] Clean clone, on a new branch `ci/migration`.
- [ ] Translate every step. Leave a `# TODO(port):` comment where behaviour is unclear rather than guessing.
- [ ] Replace stored cloud keys with [OIDC](./oidc-from-ci-to-cloud.md).
- [ ] Production stays manual and protected; see [blue-green deploys](./blue-green-deploys-with-rollback.md).
- [ ] Validate the file before opening the MR.

```bash
# Lint the new CI file against your CI provider (read-only call)
ci-lint ci.yml

# Open as a draft MR; never push to the default branch
git switch -c ci/migration
git add ci.yml && git commit -m "ci: port pipeline to the new system"
git push -u origin ci/migration
# Open a draft merge request with your git host's CLI
```

## Prove parity before cutover

- [ ] Same build artifact (compare image digest inputs, test counts, artifact names).
- [ ] Same tests run and the same number pass.
- [ ] A deploy to dev from the new pipeline works and the service is healthy.
- [ ] Required CI/CD variables exist, are masked and protected where needed.
- [ ] Run both pipelines side by side for at least one real change.

## Cut over

1. Freeze merges to the old repo for the cutover window.
2. Final mirror sync of branches and tags.
3. Make the new repo the source of truth; set the old one read-only or archived.
4. Disable the old pipeline (do not delete it yet; keep it for 30 days).
5. Update webhooks, deploy keys and anything else that points at the old URL.
6. Announce, with the new URL and who to ask.

## Rollback

Until the old pipeline is deleted, rollback is: re-enable the old pipeline, un-archive the old repo, and push any commits made in the new repo back with a mirror sync. Keep that window at least one release cycle.

## Pitfalls

- Copying secrets into new CI variables by hand without rotating them. Treat migration as a rotation opportunity; see [secret rotation](./secret-rotation-and-exposed-secrets.md).
- Plaintext secrets inside the old CI file or build specs. Find them first and rotate them.
- Self-hosted runners with broad cloud rights. Scope runners per environment.
- Repos nobody owns. Assign an owner or archive them before migrating.
