Mirroring GitHub repositories to Gitea or Forgejo as an offsite backup

How to use pull mirrors in Gitea or Forgejo to keep an automatic copy of your GitHub repositories on your own server.

Most teams treat GitHub as the backup for their code. That works until an account is suspended, a token is leaked and repositories are deleted, or an organization owner makes a mistake. A self-hosted Gitea or Forgejo instance can keep a continuously updated copy with almost no effort, using pull mirrors.

How pull mirrors work

A pull mirror is a repository on your instance that periodically fetches from a remote. Branches, tags and commits stay in sync. The mirror is read-only on your side, so nobody accidentally pushes to it.

Create a token on GitHub

For private repositories, create a fine-grained personal access token with read-only access to repository contents, limited to the repositories you want to mirror. Give it an expiry and a calendar reminder to rotate it.

Set up a mirror

In the web UI, choose New Migration, then GitHub, paste the repository URL and token, and tick “This repository will be a mirror”. You can also import issues, labels and releases at the same time. Those are copied once, not kept in sync.

For many repositories, use the API:

curl -X POST https://git.example.com/api/v1/repos/migrate \
  -H "Authorization: token $GITEA_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"clone_addr":"https://github.com/acme/api.git","repo_name":"api",
       "repo_owner":"backups","mirror":true,"private":true,
       "auth_token":"'$GH_TOKEN'","mirror_interval":"8h"}'

Loop over the output of the GitHub API’s repository list to mirror a whole organization.

Choose an interval

The default sync interval is eight hours. Shorter intervals give fresher copies but more API calls. For backup purposes, a few hours is plenty. You can trigger a sync manually before risky operations.

Large repositories and LFS

Enable LFS on the instance if your repositories use it, and tick the LFS option when creating the mirror. Otherwise you mirror only the pointer files, and the backup is incomplete.

Back up the mirror server too

A mirror on a server with no backups is just a second copy in a different place. Include the Gitea or Forgejo data directory and database in your regular backups, and store them off the server.

Test a restore

Once a quarter, clone a mirrored repository from your instance and build it. Check that the latest commits are there. That is the only proof the backup works.

What mirrors do not cover

Pull requests, review comments, Actions secrets and project boards are not mirrored. If those matter, export them separately with the GitHub API.

For installation, sizing and SSH setup, see this guide to self-hosting Gitea or Forgejo.

Disclosure: I help build Peon.


Write a comment