Goodbye GitHub
I migrated all my personal repositories off GitHub and onto a self-hosted Forgejo instance running on my homelab NixOS machine. Here’s why.
The tipping point
GitHub used to be the obvious place to put code. It had a reputation that was genuinely earned. Good tooling, a huge community, reliable enough that you didn’t think about it.
That reputation has taken a beating. GitHub has had multiple outages over the last year, the kind that block pushes and CI runs at the worst times. And then there’s the Copilot training on public repositories which I’m not a big fan of.
Neither of those things alone would have moved me. What changed the calculus was Forgejo. It’s a mature, actively maintained Git hosting platform with a straightforward migration path. When the alternative is good enough, the reasons to stay somewhere that’s quietly gotten worse start to feel thin.
So I moved.
What I moved
All my personal repositories, public and private. The goal was to own my own stuff. It was also a useful forcing function to clean out old repos that had been sitting there doing nothing.
Forgejo is running on my NixOS homelab box. It’s LAN-only for now. I’m not exposing it publicly. That’s a deliberate choice. If I want to share something, I’ll figure that out when it comes up.
The GitHub repos are deleted.
How the migration went
Easier than expected. Forgejo has a built-in migration tool that imports a GitHub repo including its history, issues, and pull requests. If you want to keep pushing to GitHub in parallel, it supports setting the old repo as a push mirror.
The only thing that needed reworking was CI. Forgejo Actions uses the same syntax as GitHub Actions, but with one immediate difference: action references need a full https:// URL instead of the shorthand owner/repo@sha format. So actions/checkout@abc123 becomes https://github.com/actions/checkout@abc123. Mechanical, but you have to do it.
That said, I’m using the migration as an opportunity to rethink CI entirely. Rather than stitching together marketplace actions, I’m moving toward a flake-based Nix approach where the pipeline drops into a Nix dev shell and runs commands from there. The goal is that local and CI environments are identical, down to the commit hash of each package. The pre-commit workflow for tf-infra already looks like this:
- uses: https://github.com/DeterminateSystems/nix-installer-action@ef8a148080ab6020fd15196c2084a2eea5ff2d25
with:
extra-conf: "sandbox = false"
- run: nix develop --command pre-commit runOne less thing that only works on GitHub.
Was it worth it?
Yes. Not because anything dramatic changed day-to-day. The repos are still there, git still works the same way. But there’s something satisfying about knowing the authoritative copy of my code sits on hardware I own, in my house, running software I can inspect and modify.
It’s part of the same impulse that got me into self-hosting everything else. I’d rather manage the complexity myself than rent the illusion of simplicity from a platform that can change its terms, train on my data, or simply disappear.