https://eldred.fr/blog/forge-migration/ * ISSOtm's warehouse * About * GB ASM tutorial * Me on Fediverse * Me on Codeberg * RSS feed * Home * Blog * Forge migration GitHub - Codeberg: my experience Published Sat 29 Nov 2025. Estimated reading time: 11 minutes. --------------------------------------------------------------------- In which I talk about the process involved in switching forges, and how well that went. [?] Motivation Why am I getting out of GitHub? See the previous episode. 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: $ sed --in-place --regexp-extended 's,github.com/ISSOtm,codeberg.org/ISSOtm,' ...and if you're feeling like bulk-replacing all files in a directory: $ find -type f -exec sed -Ei 's,github.com/ISSOtm,codeberg.org/ISSOtm' {} + Repositories, however, may still be pointing to GitHub: $ git remote -v origin git@github.com:ISSOtm/rsgbds.git (fetch) origin git@github.com:ISSOtm/rsgbds.git (push) 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: $ find .git -name config -exec sed -Ei 's,github.com:ISSOtm,codeberg.org:ISSOtm,' {} + # Replace the colons with slashes if you're using HTTPS! # For all repos within the current directory: (requires `shopt -s globstar` if using Bash) $ find **/.git -name config -exec sed -Ei 's,github.com:ISSOtm,codeberg.org:ISSOtm,' {} + # Ditto the above. ...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 set -euo pipefail git remote set-url origin git@github.com:ISSOtm/$1 cat <README.md # Moved to https://codeberg.org/ISSOtm/$1 [See my blog](http://eldred.fr/blog/codeberg) as to why. EOF git add README.md git commit --amend --message 'Add move notice' git push --force gh repo edit --description "Moved to https://codeberg.org/ISSOtm/$1" --homepage "https://codeberg.org/ISSOtm/$1" gh repo archive --yes Then, to run it: $ chmod +x stub_out.sh $ git init $ git remote add origin '' $ ./stub_out.sh rsgbds $ ./stub_out.sh fortISSimO # ...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/workflows doesn't exist; however, I think it's more convenient to keep un-migrated scripts in .github and 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 tried^1 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. ^1 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 issues^2. 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 Grebedoc^3! 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 respects^4. 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. ^2 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.' ^3 . It is intensely looking at you....... ^4 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 --------------------------------------------------------------------- * Earlier post: Migrating my code to Codeberg --------------------------------------------------------------------- Javascript needs to be activated to view comments. (c) 2020-2025 Eldred Habert. This website's contents are licensed under CC BY-SA 4.0[cc][by][sa]. Source code (report an issue: on Codeberg, by e-mail) badge: proud member of the 250KB Club [webring_lo] A text adventure with heart. "I am absolutely in love with the writing, and more just keeps on coming." --Improbable Island PlayerThis'd be a non-animated 468x60 banner thatdoesn't track you. I know I use one myself, butplease consider disabling your ad blocker! ^^' This ad is for a site that I, not some algorithm, think you may enjoy. No tracking, no strings attached, I'm not even paid! Here is some context. Go back to the top of the page [script]