GitHub → Codeberg: my experience
Published . Estimated reading time: 11 minutes.
In which I talk about the process involved in switching forges, and how well that went.
Spoiler alert: this very site that you’re reading this on is not served from GitHub Pages anymore! At this point, I’d call my migration successful. But it took more than clicking a single button, so let’s talk about the steps involved, at least for me. I’m hoping that it can help be an example for other people, and show that it’s actually not that complicated.
(My) migration process
First, I took an hour or so to set up my profile picture, email address(es), SSH keys…
Step 1: migrating the repos
This wasn’t difficult, because Forgejo (the forge software that powers Codeberg) offers a “migrate from GitHub” functionality. You need to generate a PAT on GitHub to import things like issues (which is awesome!), and as a bonus it also speeds up the process.
It was, however, tedious, because the process was entirely manual (perhaps there’s a way to automate it, like by using some Forgejo CLI tool, but I didn’t bother looking into that). And, due to GitHub API rate limits, whenever I tried importing two repos at the same time, one or both would fail. (It wasn’t too bad, though, since I could fill out the migration page for the next while one was in progress; and generally, it took me roughly as long to fill it out as it took Codeberg to perform the import.)
I’m really happy that issues, PRs, wikis, and releases can be imported flawlessly: this makes it possible to not have to refer to GitHub anymore!
Step 2: repointing links to Codeberg
Of course I don’t control all links that point to my stuff, but I could at least run rg -F github.com/ISSOtm in my home directory, to catch those within my own repos. It’s possible to automate the replacing process:
…and if you’re feeling like bulk-replacing all files in a directory:
Repositories, however, may still be pointing to GitHub:
)
)
You can either manually git remote set-url origin git@codeberg.org:ISSOtm/rsgbds.git (or the equivalent if you’re using HTTPS), or use one of the replace commands above, since remote URLs are stored textually:
# Within a single repo:
# For all repos within the current directory: (requires `shopt -s globstar` if using Bash)
…then it’s a matter of pushing the changes to all of the repos.
Step 3: stubbing out the GitHub repos
I also wanted to make it clear that my repos were now living on Codeberg; so, I created a little script in an empty directory:
#!/bin/bash
Then, to run it:
# ...etc.
The automation made it not painful, so this went pretty well.
Step 4: porting CI
Now, onto the harder stuff :)
The first interesting thing that I noticed is this section of Codeberg’s CI documentation:
Running CI/CD pipelines can use significant amounts of energy. As much as it is tempting to have green checkmarks everywhere, running the jobs costs real money and has environmental costs.
Unlike other giant platforms, we do not encourage you to write “heavy” pipelines and charge you for the cost later. We expect you to carefully consider the costs and benefits from your pipelines and reduce CI/CD usage to a minimum amount necessary to guarantee consistent quality for your projects.
That got me to think about which projects of mine really need CI, and ultimately, I decided that I would only need CI for publishing my website, and the documentation of gb-starter-kit and fortISSimO; the rest of my projects don’t get contributions anyway, so I can live without CI on them, at least for now.
Anyway, Codeberg actually has two different CI solutions: Woodpecker, and Forgejo Actions; the former seems to be more powerful, but you need to apply for access, and the latter is very close to GitHub Actions, which should facilitate the migration. So I picked Forgejo Actions, even though it’s marked as being in beta.
It’s not very difficult to port a YAML file from GHA to Forgejo Actions; for example, look at the commit porting gb-starter-kit’s publishing CI. (This doesn’t really appear as a diff, since I’ve moved the file; but it’s small, so it’s easy to compare manually.)
Here are some salient points:
- Actions are normally just referred to as
owner/repo, but Forgejo supports cloning any Git repo, especially across forges. It’s actually recommended to use full URLs always, so you don’t rely on the default prefix, which is configurable by the instance admin and thus not necessarily portable. - I could have kept the files in
.github/workflows, since Forgejo picks up that directory automatically if.forgejo/workflowsdoesn’t exist; however, I think it’s more convenient to keep un-migrated scripts in.githuband migrated ones in.forgejo. - Most Actions (the individual steps, not the workflow files) actually work out of the box on Forgejo Actions. Nice!
- Codeberg’s runners differ from GitHub’s significantly: they have way less software installed by default, fewer resources, and only Linux runners are provided (Ubuntu by default, but you can use any Docker container image). macOS and Windows being non-free OSes, Codeberg has no plans to offer either of those! For both philosophical and financial reasons. If this is a deal-breaker for you, consider cross-compiling, or bringing your own runner.
- Unless low latency is crucial, consider using the lazy runners for better load balancing and possibly greener energy consumption. In practice I haven’t seen delays beyond a few minutes, which is acceptable to me.
I actually spent some extra time trying to use less compute to perform my CI jobs, somewhat motivated by the small size of the runners, and because I’m guessing that the smaller the runner you’re picking, the faster your job will be able to be scheduled. Here is one such commit; note in particular line 50, where I tried1 using a Docker image with LaTeX preinstalled, which saves the time taken by apt install and requires fewer writes to the filesystem, freeing up RAM.
Unfortunately, due to a version discrepancy with noweb, I had to revert to the base Ubuntu image; but a “regular” LaTeX workflow would have had no problem.
Step 5: re-hosting my website
All of the previous steps were done within the span of a few days; however, since my website (this very website) was hosted using GitHub Pages, I couldn’t migrate its repos (yes, plural: you can configure individual repos to be published separately, which is how e.g. https://eldred.fr/fortISSimO is published, despite not being in the website’s main repo).
Nominally, Codeberg has an equivalent, Codeberg Pages; however, as mentioned on that page, the software behind this feature is currently in maintenance mode
, because of complexity and performance issues2. So I left it at that for roughly a month, hoping there’ll eventually be an update. Also, subprojects are published as subdomains instead of subdirectories, which would have broken links (e.g. http://eldred.fr/fortISSimO would have become http://fortISSimO.eldred.fr). Meh…
And then (by chance lol) I discovered git-pages and its public instance Grebedoc3! It functions much like GitHub Pages, though with a bit more setup since it’s not integrated within the forge itself.
git-pages actually has several niceties:
- My website had zero downtime during the entire migration, as git-pages supports uploading your website before updating your DNS records!
- It also supports server-side redirects, which lets me redirect people who still go to http://eldred.fr/gb-asm-tutorial/* to its new home, for example. People have been getting 404s because of incomplete client-side coverage on my side, but no more!
- It also also supports custom headers; I’m not particularly interested in CORS, but I’ve used that file to pay my respects4.
Oh, and also, Codeberg’s November 2025 newsletter mentions that Codeberg is planning to gradually migrate to [git-pages]
. Exciting!
I’m actually much happier using this than GitHub Pages; so, I’ve joined Catherine’s Patreon, because I want to see this go far.
To quote Catherine’s motivation for creating git-pages: I started out wanting to just use Codeberg Pages and then I found out that Codeberg Pages is in maintenance mode and has such poor architecture that not only does it have rather low uptime but it also regularly crashes Codeberg’s Forgejo instance itself.
🦃. It is intensely looking at you…….
Here is some context as to what this means.
Time tracking
Steps 1 through 3 (migrating the repos) took me the better part of an afternoon; step 4 (porting CI) took me another afternoon, mostly to learn the new CI system; and step 5 (the website) took me… well, it should have taken an afternoon, but I used the opportunity to also pay down some tech debt (merging my slides repo into my main website), which took a few days due to required rearchitecting.
All in all, even with 45 repos migrated, this basically took a weekend. And I didn’t find it annoying!
Since the task seemed really daunting, my anxiety caused me to procrastinate this a lot, but in the end it was little work. One of the reasons I’m writing this is to let other people know that, so they can overcome their own anxiety. Maybe. :P
What now?
All in all, I’m very happy with this migration! As far as I can tell, nothing on this website has broken, and I’ve tried reasonably containing the breakage over on GitHub: I have truncated the master branches, but all other branches and tags remain in place (mostly due to laziness lol), permalinks (e.g. https://github.com/ISSOtm/gb-bootroms/blob/c8ed9e106e0ab1193a57071820e46358006c79d0/src/dmg.asm) still work, only non-perma links (e.g. https://github.com/ISSOtm/gb-bootroms/blob/master/src/dmg.asm) are broken, but those are unreliable in the first place anyway.
Since that means that all of my code is still on GitHub, I want to delete my repos; but that would be a bad idea at this point, due to leaving no redirects or anything. I’ll consider that again in… idk, a year or something. I would also like to delete my GitHub account (like I have deleted my Twitter account when… *gestures vaguely*), but not only do I need my repos to be up, I also need my account to contribute to projects that are still on GitHub.
One downside of this migration is that since I’m moving off of The Main Forge, my projects are likely to get fewer contributions… But I wasn’t getting many in the first place, and some people have already made accounts on Codeberg to keep contributing to my stuff. Likewise, I’m not really worried about discoverability. We’ll see I guess lol 🤷♂️
Lastly, I’m writing this after the migration, and I haven’t really taken notes during it; so, if I’ve forgotten any steps, feel free to let me know in the comments below or by opening an issue, and I’ll edit this article.
Cheers!
Special thanks
- Catherine ‘whitequark’ for her work on git-pages and for being part of the ops team for Grebedoc
- SERVFAIL network (domi, Merlin, famfo, aprl, and all of #servfail) for being my awesome DNS providers
- Codeberg team and Forgejo contributors for making all of this possible in the first place
