DEV Community

Cover image for How to Migrate Your Open-Source Project Away from GitHub
Alan West
Alan West

Posted on

How to Migrate Your Open-Source Project Away from GitHub

So you've probably seen the news — Ghostty, the terminal emulator created by Mitchell Hashimoto (yes, the HashiCorp co-founder), is leaving GitHub. And if you've been following the broader conversation in the open-source community, Ghostty isn't alone. More projects are questioning whether GitHub is still the right home for their code.

This got me thinking about a practical problem: what does it actually take to migrate a project off GitHub without losing contributors, breaking workflows, or creating chaos?

I've helped move two mid-sized projects between forges, and let me tell you — the code is the easy part. Everything else is where it gets messy.

Why Projects Are Moving

Before we dig into the how, let's talk about the why. The reasons I keep hearing (and have experienced) boil down to a few themes:

  • Platform lock-in — GitHub Actions, GitHub Packages, Dependabot, code search... the more features you use, the harder it becomes to leave. That's not accidental.
  • Governance concerns — When your project's entire infrastructure depends on a single company, you're at the mercy of their decisions. Policy changes, AI training on public repos, terms of service shifts — you don't get a vote.
  • Centralization risk — A huge chunk of the world's open-source code lives on one platform. That's a single point of failure for the entire ecosystem.
  • Feature gaps for specific workflows — GitHub's issue tracker, PR review process, and CI system are good enough for most, but some projects outgrow them.

Mitchell Hashimoto wrote extensively about Ghostty's specific reasons in his blog post at mitchellh.com. Whatever your motivation, the migration process is largely the same.

Step 1: Audit Your GitHub Dependencies

Before you touch anything, figure out how deeply you're entangled with GitHub. Run through this checklist:

# Find all references to GitHub-specific features in your repo
grep -r "github.com" .github/ --include="*.yml" --include="*.yaml"
grep -r "actions/" .github/workflows/
grep -r "GITHUB_TOKEN" .github/
grep -r "github.event" .github/workflows/

# Check for GitHub-specific bot configs
ls -la .github/
# Look for: dependabot.yml, CODEOWNERS, FUNDING.yml,
# stale.yml, workflows/, ISSUE_TEMPLATE/
Enter fullscreen mode Exit fullscreen mode

Make a list. Every workflow file, every bot integration, every webhook — it all needs a replacement or needs to be dropped. In my experience, most projects have between 5 and 20 GitHub-specific touchpoints. Some have way more.

Step 2: Choose Your Destination

You've got real options now. The self-hosted forge space has matured significantly:

  • Forgejo — Community fork of Gitea, fully open-source under good governance. This is what a lot of projects are choosing. Lightweight, familiar UI for GitHub users, and has a growing ecosystem of CI runners.
  • Gitea — The original. Still solid, though the community split with Forgejo created some tension about its direction.
  • GitLab CE — The heavyweight option. More features than you'll ever need, but also more operational overhead to self-host.
  • Codeberg — If you don't want to self-host, Codeberg runs Forgejo and is backed by a nonprofit. Zero cost for open-source projects.
  • sourcehut — The minimalist choice. Email-based workflow, no JavaScript required. Not for everyone, but the people who love it really love it.

For most open-source projects, I'd recommend starting with either Codeberg (hosted) or Forgejo (self-hosted). The migration path from GitHub is the smoothest, and contributors won't feel completely lost.

Step 3: Migrate the Git History (The Easy Part)

# Clone your GitHub repo with all branches and tags
git clone --mirror https://github.com/yourorg/yourproject.git
cd yourproject.git

# Add your new forge as a remote
git remote add newforge https://your-forge.example.com/yourorg/yourproject.git

# Push everything — all branches, all tags, all refs
git push newforge --mirror

# Verify the push
git ls-remote newforge | head -20
Enter fullscreen mode Exit fullscreen mode

That's it for the code. Every commit, every branch, every tag — it all comes along. Git doesn't care where it lives.

Step 4: Migrate Issues (The Hard Part)

This is where most migrations get painful. GitHub issues contain years of context — bug reports, feature discussions, workarounds buried in comment threads. You have a few options:

# Using the GitHub API to export issues
# You'll need: pip install requests
import requests
import json

def export_github_issues(owner, repo, token):
    headers = {"Authorization": f"token {token}"}
    issues = []
    page = 1

    while True:
        # state=all gets both open and closed issues
        resp = requests.get(
            f"https://api.github.com/repos/{owner}/{repo}/issues",
            headers=headers,
            params={"state": "all", "page": page, "per_page": 100}
        )
        batch = resp.json()
        if not batch:
            break
        issues.extend(batch)
        page += 1

    with open("issues_export.json", "w") as f:
        json.dump(issues, f, indent=2)

    print(f"Exported {len(issues)} issues")
    return issues
Enter fullscreen mode Exit fullscreen mode

Forgejo and Gitea both have built-in GitHub import tools that handle issues, pull requests, labels, and milestones. It's not perfect — some formatting gets mangled, and image links pointing to GitHub's CDN may break — but it gets you 90% of the way there.

Pro tip: Don't try to migrate every single issue. Archive closed issues as a static export and only migrate open issues plus recently active closed ones. Your contributors will thank you.

Step 5: Replace Your CI Pipeline

GitHub Actions is the stickiest part of the GitHub ecosystem. Here's a rough translation guide:

  • Forgejo Actions — Uses the same YAML syntax as GitHub Actions. Many GitHub Actions work unmodified, and there's a growing compatibility layer. This is the path of least resistance.
  • Woodpecker CI — Lightweight, purpose-built for Forgejo/Gitea. Different syntax but very capable.
  • Jenkins / Drone / Buildkite — If you're self-hosting anyway, these are forge-agnostic options.

If your workflows are simple (build, test, lint), the migration is straightforward. If you've got complex matrix builds with caching and artifact uploads, budget extra time.

Step 6: The Contributor Communication Plan

This is the step people skip, and it's the one that kills migrations. Your contributors need:

  • Advance notice — At least 2-4 weeks before the switch. Post it everywhere: README, GitHub discussions, mailing list, Discord/Matrix.
  • A migration guide — Step-by-step instructions for setting up on the new platform. Don't assume everyone knows how to use Forgejo.
  • Redirects — Keep the GitHub repo around as an archive with a pinned issue or README pointing to the new home. Don't delete it.
  • Patience — Some contributors won't follow you. That's okay. Make the onboarding as frictionless as possible and accept that you'll lose some people.

Prevention: Avoiding Lock-in From Day One

If you're starting a new project, here's how to stay portable:

  • Keep CI config generic. Use Makefiles or shell scripts as your actual build logic. Let the CI YAML just call those scripts. Switching CI providers becomes trivial.
  • Don't use GitHub-specific markdown extensions in your docs. Stick to standard CommonMark.
  • Run your issue tracker separately if governance matters to you. A mailing list or a Discourse instance ages better than any platform's built-in tracker.
  • Mirror your repo to multiple forges from day one. git push to two remotes costs nothing.
# Add multiple push URLs to a single remote
git remote set-url --add origin https://codeberg.org/you/project.git
git remote set-url --add origin https://gitlab.com/you/project.git

# Now 'git push' sends to all three (GitHub + Codeberg + GitLab)
git remote -v
# origin  https://github.com/you/project.git (fetch)
# origin  https://github.com/you/project.git (push)
# origin  https://codeberg.org/you/project.git (push)
# origin  https://gitlab.com/you/project.git (push)
Enter fullscreen mode Exit fullscreen mode

The Bigger Picture

Ghostty's departure from GitHub is part of a larger trend that I think is healthy for the ecosystem. We've been through this cycle before — SourceForge to Google Code to GitHub. Platforms rise, centralize, and eventually projects diversify again.

The tools for self-hosting and federation have gotten dramatically better. Forgejo is working on ActivityPub-based federation, which could eventually let forges talk to each other the way email servers do. Imagine opening a pull request on someone's Forgejo instance from your Codeberg account. That future is being actively built.

Whether or not you move your project today, doing the audit from Step 1 is worth your time. Know your exit path. Git was designed to be decentralized — it might be time we actually used it that way.

Top comments (0)