← All work
Migration MigrationGitLabGiteaCI/CD

Migrating 700+ repositories off GitLab — without turning developer flow into collateral damage

A licensing squeeze forced a weekend cutover from GitLab to self-hosted Gitea. The hard part was never the clone — it was everything Git doesn't contain.

700+
repositories migrated in one weekend

A repository migration looks like a clone script. It is not. The Git objects are the easy 40%; the other 60% is CI templates, runners, secrets, webhooks, permissions, and the accumulated muscle memory of every developer who assumes the URL will always be there on Monday.

What made it hard

The deadline was a renewal, not a maintenance window — the move had to land over a weekend, and developers had to arrive Monday to a platform that just worked. At 700+ repositories, anything manual doesn’t scale and anything irreversible is a career risk. The failure mode isn’t “the migration fails”; it’s “the migration half-succeeds and no one can tell which half.”

What I owned

I treated it as a delivery-system migration, not a Git move: map the dependencies first, build a migration factory that runs the same validated steps every time, and make every wave reversible. CI got the most attention — a repo without its pipeline is a repo that looks migrated and isn’t.

The honest part

We left GitLab to escape a cost, and the first thing the new setup needed was… a fallback GitLab, briefly, for the long tail. Writing that down — instead of burying it — is the difference between a migration you can trust and a success story you can’t. The full account, including that, is on OOMKilled.

The reusable lesson

Before you move the Git URLs, inventory everything the platform quietly does for you. The migration ends the day the last webhook fires correctly, not the day the data copies.

How it rolled out

  1. 01
    Inventory before movement

    Enumerate repos, CI templates, runners, secrets, webhooks, permissions, LFS — the metadata that a git clone silently leaves behind.

  2. 02
    Migration factory

    A scripted, repeatable per-repo migration with validation built in — not a heroic one-off clone script.

  3. 03
    Waves + an exception queue

    Move in reversible waves; route the ugly cases to an exception queue instead of letting them block the batch.

  4. 04
    Cutover + aftercare

    Redirect, tell repository owners what changed, and absorb the post-migration support load instead of pretending it's zero.

Full field narrative on OOMKilled ↗