I have a Gitea instance I self-host at home. I keep most of my repositories there, but I recognize that most other developers and potential employers will want to see my work on GitHub.


  • Setup an SSH key for your Gitea instance on the relevant external repositories
  • Leverage post-receive git hooks in Gitea to push changes to the relevant external repositories while identifying with your new SSH key

Also, most of the magic is here.


In order to achieve this with minimal effort, I was going to need to utilize mirroring. Mirroring is a popular git concept that allow a repository to effectively exist in multiple places at once, though generally mirrors are read-only.

Mirroring isn’t exactly a specific procedure or command, it’s simply the act of reflecting all (or some specific subset) of commits that exist elsewhere, generally in an automated fashion. Forks might be considered “mirrors” of a repository at a given point in time.

GitLab, for example, supports mirroring pretty seamlessly. Gitea, however, is pretty minimal, which is one of its perks to me. That does not, however, mean that it is lacking in features.

Gitea supports a few git hooks, which are a simple way to run a script when something happens. As far as a repository manager is concerned, the only real hooks that matter are the following (which Gitea supports):

  • pre-receive: Runs when a client pushes code to the repository. You can use this to prevent, for example, code that fails linters, doesn’t pass tests, or even that can’t be merged using a specific merge strategy.
  • update: Runs for each branch being updated when a client pushes code to the repository. This is similar to pre-receive, but allows for more fine-grained control. Maybe you want to only make the previous restrictions on your master branch. This would be the way to do it.
  • post-receive: Runs after your pre-receive and update hooks have finished when a client pushes code to the repository. This is what we’ll be leveraging to push code downstream!

With that lengthy introduction, let’s dive in!


Alrighty, this has a few simple steps, so let’s outline what we need to do first:

  1. Setup SSH keys for Gitea and your other repository management services
    1. Generate fresh keys (ssh-keygen -f gitea will generate a private key in the gitea file and a public key in the gitea.pub file)
    2. Add the public key (gitea.pub) to your “mirrors-to-be” repositories with write access
    3. Note: I recommend at the very least to create one Gitea key and add it to the individual repositories, though individual keys for each repository is tighter security in case your Gitea instance becomes compromised
    4. Note: Your “mirrors-to-be” repositories must be blank or have related histories!
  2. Setup the post-receive hook on your Gitea repository to push using the newly generated private key to the mirror(s)

I’m not going to explain much on how to add Deploy Keys for the various repository management systems out there, so here’s a link explaining the process for GitHub.

Hookin’ Around

Now we’re all set for the magic! Also, for reference and sanity, I’m running Gitea in Docker on an Arch Linux server with the following version (but this should work pretty much regardless):

  • Gitea Version: 3b612ce built with go1.11.5 : bindata, sqlite, sqlite_unlock_notify
  • Git Version: 2.18.1

Let’s go ahead and open up our Gitea repository’s index page.

My dotfiles repository index

And head to the repository’s “Settings” tab… (oh yes, you’ll need to have the proper permissions on the repository itself!)

Click the "Settings" tab in the

And now to the “Git Hooks” tab…

Click the "Git Hooks"

Let’s edit the “Post Receive” hook…

Edit the "Post Receieve"

And you will be presented with a form where you can put any kind of script you want! Remember the SSH keys you generated so long ago? We’re going to need the contents of the private key now. Here are the script contents you’re going to use, replacing the variables as necessary.

Post-Receive Script

#!/usr/bin/env bash

# if tmp worries you, put it somewhere else!

if [ ! -e "$pkfile" ]; then # unindented block for heredoc's sake
cat > "$pkfile" << PRIVATEKEY

chmod 400 "$pkfile"
export GIT_SSH_COMMAND="ssh -oStrictHostKeyChecking=no -i \"$pkfile\""
# if you want strict host key checking, just add the host to the known_hosts for
# your Gitea server/user beforehand
git push --mirror "$downstream_repo"

Click “Update Hook” and you’re all set! Now just push to the repo and watch it magically become mirrored to the downstream repository!