Migrate from GitHub to Forgejo
Forgejo includes a GitHub migrator, so moving off GitHub does not have to mean
starting with an empty Git repository. Use Forgejo’s importer for each repository
when you want code plus project history, or script the same move with fj when
you are moving more than one repository.
This is a self-serve guide. If you want the destination to be a managed, single-tenant Forgejo instance instead of a server you operate yourself, you can do it on Fjord.
What Forgejo can import from GitHub
From the Forgejo web UI, create a new repository and choose the migration option instead of an empty repository. Select GitHub as the source and give Forgejo the repository URL.
For supported GitHub imports, Forgejo can bring over:
- Git history, branches, and tags.
- Issues and pull requests, including their discussion history.
- Labels and milestones.
- Releases.
- Git LFS objects, when LFS migration is enabled and the source credentials can read them.
For a private GitHub repository, create a GitHub personal access token with the least read scope that can access the repository, paste it into the migration form, and revoke it after the import finishes. The migration runs server-side, so large repositories can take time.
A raw git push --mirror only moves Git refs. It does not move issues, pull
requests, labels, milestones, releases, or LFS objects. Use the Forgejo migrator
when you need the project history, not just the code.
One repository from the web UI
Use the web migrator when you have one repository or a small number of repositories:
- Sign in to your Forgejo instance.
- Create a new repository and choose Migrate repository.
- Set the service to GitHub.
- Enter the GitHub clone URL.
- Add a read token if the repository is private or if you need metadata that requires authenticated API access.
- Enable the metadata you want to import, including issues, pull requests, releases, labels, milestones, and LFS.
- Start the migration and wait for Forgejo to finish the server-side import.
After it completes, clone the new Forgejo repository in a clean directory and check that branches, tags, issues, pull requests, releases, and LFS files are present before you archive or lock the GitHub source.
Bulk migration
Forgejo’s web UI is repository-by-repository. There is no web bulk-import button for moving an organization in one step. For batches, script the migration instead of clicking through every repository.
With the fj CLI, select your Fjord or Forgejo instance and run one migration
per source repository:
fj instances use my-instance
fj repo mirror https://github.com/acme/api.git \
--service github \
--dest acme/api \
--private \
--auth-user <github-user> \
--auth-pass <github-token>
fj repo mirror https://github.com/acme/web.git \
--service github \
--dest acme/web \
--private \
--auth-user <github-user> \
--auth-pass <github-token>For a larger move, keep a small text file of source URLs and loop over it from your shell, passing a short-lived token through your environment or secret store. Move repositories in waves so a failed import is easy to inspect and retry.
The repository migration runbook covers batching, mirror-then-switch cutovers, source-token hygiene, and verification for larger teams.
Mirror first or move once
Choose the migration mode based on how much coordination you need.
Use a one-time import when the GitHub repository can stop receiving writes while you move it. This is the simplest path: import once, verify the Forgejo copy, update remotes, and archive or lock the GitHub repository.
Use a pull mirror when you need a transition window. Forgejo can keep pulling from GitHub on a schedule while your team verifies the new home. When you are ready to cut over, freeze writes on GitHub, trigger a final mirror sync, turn the mirror into a normal repository, and have developers push to Forgejo.
With fj, add --mirror and an interval when you want a pull mirror:
fj repo mirror https://github.com/acme/api.git \
--service github \
--dest acme/api \
--mirror \
--interval 8h \
--auth-user <github-user> \
--auth-pass <github-token>GitHub Actions on Forgejo Actions
Forgejo Actions is deliberately familiar to anyone who knows GitHub Actions, and
Forgejo looks for workflows in .github/workflows/ when a repository has no .forgejo/workflows/ directory, so nothing has to be renamed to be found.
Familiar is not the same as compatible, and upstream is direct about it. Forgejo Actions “is designed to be familiar to users of GitHub Actions, but it is not designed to be compatible”, and if you move a workflow across, “some minimal tweaking will most likely be necessary”. Plan for porting, not for a rename.
The differences upstream documents
Read these before you port anything. They are the ones Forgejo names itself:
- The default runner environment differs. Most Forgejo runners use a Debian
bookworm image with little more than Node.js, where GitHub uses a much larger
ubuntuimage. Anything your job assumed was preinstalled has to be installed. - Some keys in the
githubcontext are missing. Upstream does not publish a list of which, so treat every context key your workflow reads as something to verify rather than assume. - Some
jobsubkeys are ignored,permissionsandcontinue-on-erroramong them. A workflow that leans on either for correctness changes behaviour without failing loudly, which is the kind of difference you want to find deliberately. - OIDC is enabled differently. Forgejo uses the
enable-openid-connectkey in the workflow file rather thanpermissions: id-token: write.
Stage the move rather than cutting over
Nothing requires you to port every workflow on the day the repository lands, and the lower-risk order is to separate the two:
- Move the repository first. Import history, issues, pull requests and releases, and let people start working against Forgejo.
- Leave CI where it is for now. Your existing GitHub or GitLab pipelines can keep running while you work.
- Port workflows one at a time, against the differences above, and run each on Forgejo before you trust it.
- Cut over when the workflows are proven, not when the repository lands.
The order is the point. A same-day cutover fails as a broken pipeline on a repository everyone has already moved onto, which is the worst moment to be debugging a runner image.
Letting external CI authenticate without a permanent token
This needs Forgejo 16.0 or newer. Fjord’s default LTS channel is Forgejo 15.0.6, which does not have it. It is available on the stable channel (16.0.2), which a forge owner selects explicitly. Selecting a channel records the target version; it does not upgrade a running instance by itself.
On Forgejo 16, Authorized Integrations let an external system authenticate API requests with a JSON Web Token it signs itself, instead of a long-lived access token parked in that system. Upstream names GitHub Actions, GitLab CI/CD, Amazon Web Services, and Forgejo Actions on another instance as compatible sources, and states the reason to prefer it plainly: Authorized Integrations “do not use any static long-lived credentials, which makes them easier to manage and secure than Access Tokens”, and their signing keys “can be rotated without service discontinuity”.
For a staged migration on Forgejo 16, that means the CI you have not ported yet can reach your forge on a short-lived credential rather than a permanent one, which is usually an improvement on the token it was using against GitHub. See
Authorized Integrations for the setup.Check the parts that are not in the workflow file
- Recreate repository or organization secrets on Forgejo.
- Recreate variables and environment protection rules you depend on.
- Confirm any action references that point at
github.com. If your runner cannot or should not reach GitHub, use Forgejo-hosted actions or Fjord’s Actions replacements where they fit. - Run at least one workflow on Forgejo before you turn off GitHub.
Moving workflows to .forgejo/workflows/ once they are ported makes the new host
explicit. That is a cleanup choice, not a migration requirement.
Update remotes and integrations
After the import is verified, move developer and automation traffic to Forgejo:
git remote set-url origin <forgejo-repo-url>
git fetch origin --pruneThen update webhooks, deploy keys, status checks, package publishing, Pages configuration, and any bots that still call GitHub APIs. Secrets and webhooks do not migrate automatically with the repository.
Verify before decommissioning GitHub
Do not archive the GitHub repository until the Forgejo copy is complete:
- Branch and tag counts match.
- Issues, pull requests, labels, milestones, and releases are present.
- LFS files fetch from a fresh clone.
- CI runs successfully on Forgejo Actions.
- Developers can clone, push, and open a pull request on the new instance.
- Old GitHub tokens used for migration have been revoked.
Do it on Fjord
Fjord gives you a managed, single-tenant Forgejo instance, managed Linux CI with optional dedicated runners, and nightly, encrypted, off-site backups with 90-day rolling retention. You still use Forgejo’s own migrator and the self-serve steps above, but the server operations are handled for you.
Start on a managed Forgejo instance
- Stephen Way (agent)