All the apps I run are in containers: my registry, my proxy, my automation, most of what I touch day to day, and yet I'm totally reliant on an external party for the source code driving that. Generally, I didn't see this as an issue. It was a nice external service that didn't really have any problems, didn't annoy me much, and stayed out of the way of what I needed it to do.
Every previous attempt at leaving fizzled out at the sheer effort of moving: repos, container images, CI, webhooks, and all the plumbing that relies on them.
GitHub has also spent the last couple of years becoming increasingly unappealing to me. Its reliability has been a particular concern: GitHub itself reported nine incidents causing degraded performance in May 2026, including a broad service disruption affecting pull requests, issues, Actions, webhooks and Git operations, while reporting on its wider availability shows a marked deterioration in 2025 and early 20261. There has also been significant change in the people and organisation behind the service: GitHub announced a reduction of up to 10% of its workforce in 2023, and CEO Thomas Dohmke left in 2025 as GitHub moved under Microsoft's CoreAI organisation23. Meanwhile, Copilot has increasingly appeared throughout the GitHub experience, with developers raising objections to being unable to disable some Copilot features and to its integration into issues, pull requests and code review4. Add to this the fact that GitHub is owned by Microsoft, and the increasingly unappealing state of American politics — having my data ultimately subject to the whims of US law5 was becoming untenable.
Adding all of that together, the scales have tipped in favour of putting in a lot of effort and annoyance and just moving on, so time to look at requirements and then alternatives.
- Open source
- Lightweight
- Ability to keep certain repositories private
- Simple CI/CD
- Being able to mirror repos elsewhere automatically
- A UI I can find things in without thinking
GitLab: The open source edition is deliberately limited, a teaser for the paid tiers, and even where it does work, the UI is, to put it kindly, hard to navigate.
Sourcehut: Is admirably minimal, but it's very fractured in use, and it comes bundled with a large amount of politics and some very strong opinions from Drew DeVault.
Codeberg: Good software, good philosophy, but it has its own governance debates and its legacy CI platform is external rather than built-in, and not terribly user-friendly; its recent anti-AI stance also means I'd have to be careful about hosting my throwaway LLM projects on it.
Tangled: Genuinely interesting — Git hosting bolted onto the AT Protocol, so your repos live in a decentralised network rather than anybody's database. But it doesn't support private repos and its CI was weird and Nix-based (it now isn't, but slightly too late).
Which brings us to Forgejo. It's the community fork of Gitea, born when Gitea's governance steered company-ward and the volunteers decided they'd rather own the roadmap than watch it happen to them. Development happens under a non-profit association; it's a proper open source project with a good community around it, and crucially it has no objectionable political baggage yet.
So, migration.
Forgejo itself needed to be built; you can't possibly use a pre-existing container. This is an easy Alpine-based image, built on my standard Docker base image; the instance barely registers on the host; nice and lightweight. The CI is obviously going to use a good chunk of resources, but this is expected; the server itself shouldn't add to this.
Moving the repository data itself is always going to be quite simple - it is, after all, just another remote; Git was designed to keep this simple. A quick script and all the repos were migrated in about 20 minutes. Forgejo also allows you to auto-create repositories on push, so no need to poke an API either: just update the existing origin remote and push.
CI, however, was a lot more work. This was always going to be the case and the step I gave up on in the past.
Step one was writing a Forgejo runner container, but this was mostly just my standard Alpine images with a few additions — easy. I'm getting good at writing OTT self-built containers now.
Step two was making the GitHub Actions work on Forgejo, but luckily Chris had already migrated, so I could just steal his idea. Ditch the horrible JavaScript actions, ditch quite possibly the most bloated CI image ever conceived, and ditch all the outdated tools this image came with. Then rewrite the actions I actually needed in Go instead: small, statically linked, no dependencies to speak of, and using the same Alpine base image everything else I ship uses.
With the repos mirrored, CI working, containers building and pushing, I now just need to do a massive reorganisation of everything to make use of Forgejo and wire up all the plumbing still linked to GitHub. For the moment everything mirrors back to GitHub, so I can just use the webhooks from that. I'll start migrating that soon™ (probably in the next large GitHub outage that annoys me).
-
https://www.theregister.com/software/2026/02/10/github-seems-to-be-struggling-with-three-nines-availability/4881718 ↩︎
-
https://techcrunch.com/2023/02/09/github-lays-off-10-and-goes-fully-remote/ ↩︎
-
https://github.blog/news-insights/company-news/goodbye-github/ ↩︎
-
https://www.eff.org/deeplinks/2018/02/cloud-act-dangerous-expansion-police-snooping-cross-border-data ↩︎