A couple of years ago I started using Nextcloud instead of Dropbox after having moved away from Gmail, and it was such a novel and good experience not to be constantly bombarded with bullshit, that I decided to slowly move my life away from the big businesses and their annoying practices.
Most recently, I wrote about getting rid of Windows but I’ve also been moving away from other products in favor of some self-hosted options, which is now something that’s fairly easy to set up these days.
In this particular blog post, I want to discuss where I’ve been hosting my personal Git repositories over the years. It’s a fairly timely post, I think, with lots of discussion about GitHub’s reliability due to an exponential influx of code on the platform lately.
GitHub and GitLab
Ever since GitHub was acquired by Microsoft, they’ve been heavily pushing Copilot and the platform has generally become less reliable due to frequent outages. I understand that due to the rise of large language models (“AI”) usage of the platform has skyrocketed, so this is not a huge surprise.
I hear more and more folks clamoring for the platform to be improved, while others are seeking or pursuing alternatives that already exist. I personally still use GitHub for my open source work, but I’ve been hosting my private repositories elsewhere for quite some time now.
You may not remember, but GitHub used to not allow private repositories unless you paid for the product. Because of this, I had been using GitLab.com for some time, as I was familiar with it because of work: we used a private GitLab instance at the company where I worked for over a decade. So, ever since 2015, I’d been pushing to GitLab and using their workflow infrastructure for my projects.
In 2019, GitHub made some changes to the platform which made private repositories available for everyone (with some restrictions), but I kept happily using GitLab during this time.
But at some point GitHub started getting worse after the Microsoft acquisition, and even GitLab.com started pushing AI features. That’s when I decided I was going to need to come up with an alternative solution that did not involve giving money to vendors who are actively making their platform worse with unneeded “AI features”.
I had also tried to self-host GitLab, but the system requirements were rather high for what I was trying to accomplish with my self-hosting effort. So, at the time, I decided to self-host Gitea, as an experiment.
From this point on, I will refer to these as Git forges because there’s these three different projects that share a history: Gogs, Gitea. and Forgejo.
Requirements
In order to be able to properly move away from GitHub as the primary platform, I established that following requirements for my use case were relevant:
- A private instance with some access control: You have to be able to limit access to this crucial piece of infrastructure, or at least have access control for certain content stored on the instance.
- Full data control and data sovereignty (self-hosted): In a world where self-hosting and having control of your own data is no longer the norm, this is pretty important if you don’t want your data to end up everywhere.
- As little lock-in as possible: While Git repositories are free of lock-in, issues, projects, and various other organizational metadata is generally not portable.
- Repositories, organizations, issues, pull requests: Sure, it might be just the basics, but unless you make a simple web GUI for
git, you need all of these things if you plan to get work done in a team or as a solo developer with some sense of structure. - Under active development and open source: This one is fairly self-explanatory. If something is no longer being maintained, it’s a massive security risk. If it’s open source, it’s likely to be affordable or even fully open, which gives you the ability to apply customizations if you so choose.
- Acceptable or limited system requirements: There’s a wide range of system requirements depending on what it is that you’re trying to run. For smaller teams, this can save a lot of money and prevent slowdown.
- Support for workflows with runners: GitHub Actions is pretty cool, and quite important. You want to be able to use something functionally similar if you’re serious about software development.
If I had to set something up from scratch today, I would likely go with Forgejo because it’s getting adopted by e.g. Fedora and the Dutch government.
Mirrors
So, since late 2022, I’ve been using Gitea to maintain mirrors of some of my most important repositories (and, of course) PHP Monitor. Gitea has a very easy to use “mirroring” feature, and it works both ways.
Every few hours, Gitea will attempt to fetch the latest commits from the remote, and update the copy on my VPS. This was a pretty good strategy to ensure I always had an easily accessible and backed-up copy of all of my repositories.
In 2024, after setting up a proper incremental backup strategy (which we’ll get into later) my private Gitea instance became my main “remote” that I’d use, with mirroring now in the opposite direction (with pushes).
(For example, when I work on PHP Monitor, I am usually pushing to my own server, which then immediately mirrors those changes by pushing to GitHub.)
This means that my public contributions are still accessible via GitHub since they’re automatically mirrored, but I also know I benefit from the reliability of having access to my own Git remote that only goes down when I say so. (The outages at my VPS host are far less frequent than the outages at GitHub, let me tell you.)
Customizations
There’s a couple of customizations I’ve applied to my Gitea setup, because I really felt like the default look and feel was a bit bland.
- I now use Atkinson Hyperlegible Next as the main font instead of the default system font. This gives the website a more uniform look on various operating systems, and makes it easier to read.
- Blue accents are now present throughout the entire light theme, as opposed to the normal gray look.
- The logo, favicons and theme color have been adjusted.
- In order to make it look more like GitHub, the heatmap feature in the activity feed now uses the classic green colors you may be familiar with.
- I’ve added a custom welcome page for anonymous visitors with a helpful explanation what this website is trying to accomplish.
- Custom links have been added to the navigation bar and the footer.
Forgejo
I should mention that I missed the hard Forgejo fork in 2024, probably due to a mix of burnout and not paying attention during that time. Forgejo’s hard fork is mostly related to the governance of the project.
Because there were disagreements about that, the hard fork was decided upon and both projects have now diverged in terms of priorities and progress, with both still being quite similar.
If I were setting this up from scratch today, I’d probably use Forgejo. They also dogfood their own work: they run their entire development process on Codeberg, which is based on Forgejo. If you decide to pull in the Docker image, it’s hosted on Codeberg, too.
Migrating to Forgejo used to be quite easy since there was a seamless migration path you could take if you ran Gitea, at the time. Unfortunately, unless you are running this older version of Gitea, this migration path is no longer available today.
I also automatically run CI and deploy various projects via the Act Runner and Gitea Actions, so that’d probably take some more time to migrate as well.
Due to these factors, I’ve decided to stick with Gitea (for now).
I do plan on eventually migrating to Forgejo, but I’ll be doing that when I eventually end up setting up a new server. I did a little bit of tinkering on a migration project which seems quite promising based on the results.
I should note that I’m not the only one who has tried to do this migration as of late.
Workflows
Without the ability to add workflows, going for a self-hosted solution like this might seem silly. Thankfully, both Gitea and Forgejo have support for Actions, which allows you to write your own workflows.
I should note that Forgejo works slightly differently (they did their own implementation) and I have the impression their Actions implementation and documentation is potentially better.
I use workflows to run automatic tests and deploy my projects. I have four runners on my server, and while they’re not super fast, they do the job.
The runners also automatically get access to certain credentials when provisioned (because I control them) which allows me to simplify some jobs, since it’s only my jobs that run on these runners.
Backups
Every night (when I’m generally not using the instance) the Gitea instance is stopped for a few seconds, and a full backup “dump” happens.
This creates a zip file with a full dump of all the relevant data. This includes repositories, packages, assets, database dump, and so on. That’s enough to restore the full instance at any point since I don’t have assets located elsewhere.
After that, an automatic backup is created with restic on a separate storage drive which is later duplicated to my Raspberry Pi on my local network. I really enjoy using Restic. I should write a post on that at some point, honestly.
I make sure to keep an archive of 7 daily, 4 weekly, 12 monthly and unlimited yearly backups with Restic. To make sure incremental backups are possible, the zip is first unzipped and then backed up.
I have a helpful script in case I want to make a manual backup, too. This is a streamlined version without additional logging, and dumps that backup in a particular fixed location:
#!/bin/bash
set -euo pipefail
if ((EUID != 0)); then
echo >&2 "Error: script not running as root or with sudo! Exiting..."
exit 1
fi
trap 'sudo systemctl start gitea' EXIT
mkdir -p "/home/nico/backups"
sudo systemctl stop gitea
runuser -l git -c "gitea dump -c /etc/gitea/app.ini \
--work-path /var/lib/gitea \
-f /tmp/gitea.zip"
sudo systemctl start gitea
mv -f /tmp/gitea.zip /home/nico/backups/gitea.zip
sudo chown nico:nico /home/nico/backups/gitea.zip
Scrapers
Unfortunately, I’ve been dealing with some trouble as of late. There’s been a bit of a trend where certain companies appear to be mass-scraping the contents of public Git forges running software like Gitea and Forgejo.
I do keep some repositories public on there (mostly mirrors of my open source projects) which comes in handy when GitHub goes down, so it can be a bit tricky dealing with the scrapers from various “AI” companies. In many cases, robots.txt is certainly not respected, which is a shame.
There are certain mitigation techniques and some are definitely necessary if you plan on self-hosting at least a partially public Gitea or Forgejo instance, unless you’d like said data to be… uh, sucked up and your server slowed down.
Fortunately, there are some measures you can put into place to ensure that the majority of your Git forge isn’t accessible without signing in, or by putting up a firewall or something similar to prevent public access to certain roots without a “challenge”.
Anubis is a good example if you don’t want to use Cloudflare, and it’s what I use for my Gitea instance. In the future, I may still use Cloudflare for this, but I currently prefer as few external servers to impact access to my instance as possible.
Considerations
If this blog post is inspiring you to consider switching to your own private self-hosted forge, keep a few key things in mind:
-
For a private instance, you definitely want to make sure user registration is disabled via
DISABLE_REGISTRATIONinapp.ini. Even better, link your instance via an OAuth2 provider. If you open up your instance you’re setting yourself up for a world of pain and potential security risks. Don’t make a public instance unless you know what you’re getting yourself into. -
If your private instance needs to expose some public repositories, you definitely want to throttle or limit public access to your Git forge via Cloudflare or Anubis. If not: prepare to be scraped, which isn’t good for your server load.
-
GitHub Actions aren’t entirely compatible, so you may need to make some changes to your workflows to make sure they are compatible.
-
Some maintenance is required to make sure runners are functional, your instance is updated periodically, and things keep running smoothly. You will want to keep an eye on disk usage, too.
-
Make sure you have a good backup strategy. Test your backups. It’s easy to spin up another Docker container to test if your backups work, don’t skip this.
Recommendations
Migrating to your own Git forge doesn’t mean you need to use it for everything. I generally recommend that you migrate slowly, with a few repositories at first. If you’re happy with the way things work, you can migrate some more.
You can always set up mirroring in both directions in case a repository needs to be available on another platform like GitHub, so you have no excuse unless it’s too costly for you to run the server.
(A small VPS is sufficient for a small team, but I don’t have a lot of experience with how these forges scale for a huge amount of users, so keep that in mind.)
Happy hosting!
Update
May 20, 2026
A few hours ago GitHub announced that many of their internal repositories were compromised. While it’s too soon to see if there’s any major fallout from this particular incident, I think it makes sense to elaborate on security a bit.
Security Recommendations
Running your own git forge, whether it’s Gitea or Forgejo, obviously comes with various risks. If you’re managing your own infrastructure you need to make sure your environment is secure.
For a partially public instance, you may want to consider that:
- you have limited administrative access to your instance (preferably by setting
DISABLE_REGISTRATIONtotrueso unknown users cannot create an account and push code which can be used as a malware distribution vector) - you are installing security updates in a timely fashion (e.g. with unattended upgrades for security patches and ensuring you install security updates for Gitea and Forgejo ASAP)
- you are managing your secrets and SSH keys carefully (it’s obvious, but do not commit secrets; if you do commit them, rotate your secrets)
- you are running Actions securely
- you have backups that you restore periodically to ensure backup consistency
Securing Actions
You can do even more like limiting access to your instance via a VPN or such (like a Cloudflare tunnel), which significantly reduces the potential attack vector as your instance stops being public on the internet.
Securing Actions runners is quite important, too: after all, in many cases projects and people are compromised via supply chain attacks which means that auditing your dependencies is more important than ever.
Should something go wrong, if your Actions runners are properly isolated and secured, your risk surface is reduced significantly.
Ensuring that your Actions workflows themselves are secure is also important, even if you plan on staying on GitHub. Recently, Sebastian Bergmann of PHPUnit fame wrote an excellent post on hardening your GitHub Actions, which I recommend checking out.
This was not generated: Unless explicitly specified, the posts on my blog are my own creative work. 100% written (typos included) and edited by me; not generated using e.g. large language models.

Comments
Nothing yet. Say the first thing.
Sign in to join the conversation.