Toggle theme

Moving from GitHub to Codeberg


In April 2023, I finally decided to move my source code repositories off of GitHub. There are a bunch of problems with GitHub, but I'm particularly uneasy with the monoculture that they've created and frustrated by the rampant (alleged) copyright infringement performed by their Copilot product. GitHub's current owner doesn't have the best track record of playing nicely with the open-source community, so I assume that things will only get worse.

After a tiny bit of research, I came across Codeberg, a platform based on Forgejo, a fork of Gitea (if you've used GitHub, it'll look familiar). The Codeberg site is run by Codeberg e.V., a non-profit organization based in Berlin. The What is Codeberg? page has more details about their mission, along with links to other alternatives. The past entries in the Codeberg blog give an idea of how quickly they've grown and what sort of problems they've encountered and how they dealt with them. Hosting is free for appropriately-licensed projects, but you can become a member of Codeberg e.V. for 24€/year if you'd like voting rights.

Getting started

I'd recommend skimming the Mirror to Codeberg document first. Note that it contains a lot of additional information about setting up bidirectional mirroring between GitHub and Codeberg, which I wasn't interested in — my goal was to stop using GitHub, and I'm in the enviable (?) position of not having a large community of diehard GitHub users and developers whom I need to appease.

The following instructions assume that you've already created a Codeberg account and registered your public SSH key(s) there.

Creating a GitHub personal access token

First, I went to GitHub and created an access token for Codeberg to use when mirroring my repositories. You can clone public repositories without a token, but the docs said that I'd be less likely to run into rate-limiting errors if I provided one, and it seems like a token may be needed if you also want to transfer things like issues.

You can create a new fine-grained token at (or by clicking your user icon and then SettingsDeveloper settingsPersonal access tokensFine-grained tokensGenerate new token). After authenticating, you should see a form where you can configure the token:

GitHub "New fine-grained access token" form

The Token name, Expiration, and Description fields don't really matter (you can delete this token as soon as you finish moving your repos, and there seems to be an internal one-year limit on a token's lifetime, anyway). If you don't need Codeberg updates to be mirrored back to GitHub, you can just leave Public Repositories (read-only) selected.

After you click the Generate token button, you should see the new token, which had a github_pat_ prefix in my case. Save it for later since GitHub won't show it again (although it's easy to ask GitHub to regenerate it if needed).

Performing the migration in Codeberg

To import the repository into Codeberg, go to (or click the + icon and then New MigrationGitHub). You should see a form for supplying details about the migration:

Codeberg "Migrate From GitHub" form

Enter the URL of the GitHub repository and the github_pat_ access token that you got earlier, and then select the items that you'd like to migrate:

  • Wiki - You probably don't need this unless you're using GitHub's wiki feature instead of checked-in Markdown files.
  • Labels - This name is confusing, but I think that these correspond to the status labels that can be applied to issues (e.g. bug or enhancement).
  • Issues - You probably want to check this one.
  • Pull Requests - I'd recommend against checking this unless you have in-progress pull requests that need to be migrated. Some of my repos had massive stale pull requests created by the borderline-useless Dependabot to update npm package-lock.json files (ugh), and trying to import these seemed to cause migration failures (eventually resolved by me regenerating my GitHub access token).
  • Releases - You should check this if you've created releases in the past. It seems like attached files are (slowly) copied over to Codeberg.
  • Milestones - I've never used milestones, so I just left this unchecked.

The Codeberg repository name should be automatically chosen based on the GitHub URL, and I tended to just copy-and-paste the repository's existing description from GitHub.

After you click the Migrate Repository button, you should see an animated screen reporting the rough progress of the migration. If all goes well, you'll be redirected to the new Codeberg repository after anywhere from a few seconds to a minute or more.

The migration process sometimes failed for me without much in the way of details. Retrying it without changing anything typically didn't help; I'd recommend regenerating the access token on the GitHub site (or creating a new token entirely) or selecting fewer migration items in the Codeberg form.

Updating checkouts and code

To make git push and pull from Codeberg's repository instead of GitHub's, go to each of your checkouts and run a command like the following (you can find the URL by going to the repository's page and clicking the SSH button to the right just above the file listing):

$ git remote set-url origin

You can test that everything's working by running git fetch.

At this point, I created a commit updating the code to point at rather than and pushed it to the Codeberg repo. For Go code, this meant updating the URL in the module line at the top of the go.mod file and fixing import paths. I used a command like this to update the code:

$ git grep -l 'github\.com/user/repo' | \
    xargs sed -i 's!github\.com/user/repo!!g'

I'm not sure of how much pain this introduces to anyone who's importing these packages using the URLs. Presumably they won't be able to fetch updates until they change their import paths. I haven't wrapped my head sufficiently around canonical import paths to know if there's some way I can automatically direct Go to fetch code from the new location.

I also updated comments to use URLs rather than their equivalents when referencing things like issues. Note that Codeberg unfortunately seems to use a different URL pattern for linking to files: I had to manually rewrite URLs like to

In the same vein, I noticed that source code archives attached to releases now place the code in a subdirectory named e.g. repo instead of repo-0.0.1, i.e. the tag/version is no longer included.

Archiving the GitHub repository

Once you're happy with how things are set up on the Codeberg side, the final step is updating the GitHub repo to point users to Codeberg and making it be read-only to ensure that no one accidentally pushes code or creates new issues there. I would've liked to be able to configure GitHub to issue HTTP redirects to equivalent Codeberg URLs, but that unsurprisingly doesn't seem to be supported.

In the About section at the right side of the repo's GitHub page, I changed the description to "Moved to" and also set its URL to I made a final commit (via the GitHub web interface) to replace the contents of the file with Moved to <>.

Finally, I clicked the Settings link and the scary Archive this repository button at the bottom of the page. Note that you won't be able to edit the repo's description or URL after you archive it, but you can always temporarily unarchive it if you need to make changes.

Automating builds

The trickiest part for me was figuring out CI/CD (continuous integration and deployment). For the most part, I just want tests to be compiled and executed whenever I push code to the main branch, but I have a few trickier cases where I want to cross-compile executables for different operating systems or deploy code to App Engine.

Codeberg CI is currently in a closed testing phase. It's based on Woodpecker CI, which I haven't used but which seems straightforward enough from the example configuration files that I've looked at.

I currently (and regrettably) use Cloud Build's free tier for automated builds, and I've written some additional pieces of scaffolding like cloud-build-watcher. I didn't want to switch over to a new CI/CD system while I was also moving all my repositories, so instead of applying for Codeberg CI, I spent some time convincing Cloud Build to build from my Codeberg repos (by way of Google's strange Cloud Source Repositories product). This wasn't easy (see this issue that I filed), but I eventually got it working in a convoluted way by writing a new Cloud Function named cloud-source-mirror. You can see more details about how it works by looking at its file.

Wrapping up

After I'd moved everything off of GitHub (except for a few forked repos with changes that I'm still trying to upstream), the last step was updating my GitHub profile to explain what had happened. I unpinned all of my now-archived repositories and created a new pinned moved-to-codeberg repository with a description and pointing to my Codeberg profile.

It's been about a week since I started moving repositories to Codeberg, and I don't have any complaints so far. Everything works pretty much the same as before, and my interaction with other users and developers is infrequent enough that I don't know what sort of effects the move will have.