Skip to content
4 min read

Migrating repositories into a managed forge

Migrate to Fjord covers the single-repository import from the web UI. This runbook is the operator’s version: moving a team or a batch of repositories with the least disruption, keeping more than just the code, and verifying the move before you cut anyone over. The mechanics are standard Forgejo migration tooling, so the same plan works for any managed forge.

Decide what you are actually moving

A repository is rarely just commits. Before you start, decide which of these need to come across, because it changes how you migrate:

  • Code: all branches and tags. Always.
  • History and metadata: issues, pull requests, releases, labels, milestones. Forgejo can pull these from supported hosts; a plain Git push cannot.
  • CI: workflows, secrets, and variables. These do not travel with the repository and must be recreated.
  • Webhooks and integrations: also recreated, pointed at the new host.
Note

Issues and pull requests come across only via Forgejo’s migration importer with a token for the source host. A git push --mirror moves refs and nothing else. If you care about the conversation history, use the importer, not a raw push.

Mirror first, switch later

For anything bigger than one quiet repository, do not do a hard one-shot cutover. Set the import up as a pull mirror so the new instance keeps syncing from the source on a schedule. That buys you a window where both sides are live and you can move people gradually:

  1. Mirror in. Create the repository on your instance as a mirror of the source. Forgejo pulls on a schedule; the repository mirror documentation covers the options.
  2. Move the humans. Have developers add the new remote and start pushing there. Keep the old host read-only in their heads, or actually read-only if you can.
  3. Freeze and final-sync. Pick a cutover moment, stop writes to the source, let the mirror pull one last time, then convert the mirror to a normal repository.
  4. Decommission. Archive or lock the source once you have verified the new home (next section).

For a one-time move with no ongoing sync, skip the mirror and do a direct import or a mirror push:

fj instances use my-instance
fj repo create my-app --private
git remote add fjord <your-instance-repo-url>
git push --mirror fjord

Scope the source token tightly

A private source needs a personal access token so the importer can read it. Treat it like the short-lived credential it should be:

  • Grant read-only scope, and only to the repositories you are moving.
  • Use it for the import, then revoke it. Do not leave a long-lived token with broad scope sitting in a migration that finished weeks ago.
  • For a batch, prefer one token you revoke at the end over many you forget.

Large repositories and batches

  • Big histories take time. The import runs server-side, so a large repository will churn for a while; that is expected, not a hang. Start it and check back rather than watching it.
  • Batch in waves. Moving fifty repositories at once makes failures hard to attribute. Move them in small waves, verify each wave, then continue.
  • Mind LFS and large binaries. Confirm large-file storage came across and is actually fetchable, not just referenced.

Repoint the tooling

Once the code is in and verified, the move is not done until everything that talked to the old host talks to the new one:

  • Local clones: update each remote to the new instance URL. A stale remote is the most common “the migration broke my repo” report, and it is just a one-line fix.
  • CI: add your Actions workflows and recreate secrets and variables. Reference Fjord Actions by full URL so runners never reach github.com.
  • Pages: wire up Pages if the project publishes a site.
  • Webhooks and bots: recreate them against the new host and disable the old ones.

Verify before you decommission

Do not lock the source until you have confirmed the new home is whole:

  • Refs match. Branch and tag counts line up between source and destination.
  • Metadata came across. Spot-check that issues, pull requests, and releases are present with their history, not just the default branch.
  • CI is green. Run a workflow on the new instance and confirm it passes, so you find missing secrets now rather than after the source is gone.
  • A fresh clone works. Clone from the new URL into a clean directory and build, as a new teammate would.
Tip

Moving a whole team or a large set of repositories? Support can help plan the cutover, including the freeze window and the order of waves.

Contributors
  • Stephen Way