I self-hosted (ran on my own server rather than using a third-party service) Gitea for about eight months before I wrote this down. The reason for the wait is that the first three months of any self-hosted setup are the honeymoon. The real test is month six, when you have stopped tinkering and you are just using the thing. Gitea passed that test for me. It runs on a five-dollar-a-month VPS (Virtual Private Server, a small rented Linux machine in someone else’s datacenter), it has not lost my data, and the maintenance overhead is roughly five minutes a month. If you have been thinking about moving personal projects off GitHub but the enterprise self-hosted options felt like overkill, this is the post for you.
I want to be clear about the scope. Gitea is not GitHub. GitHub has features Gitea does not have, and Gitea has features GitHub does not have. If you are running a 50-person engineering team with a security review board and a SOC 2 (Service Organization Control Type 2, a common security audit standard) compliance checklist, you want GitHub Enterprise, GitLab self-managed, or one of the other enterprise options. If you are running a personal project, a small team, or a handful of repos that you would rather not have on someone else’s platform, Gitea is the right size.
Why I moved off GitHub
The decision was not about price. GitHub’s free tier handles personal projects and small teams, and the paid tiers are not unreasonable for what you get. The decision was about control. Three things pushed me over the line.
- Pricing changes. GitHub has changed its pricing model three times in the last five years. Each change was reasonable in isolation. The cumulative effect is that the cost of running a small team on GitHub is meaningfully higher than it was in 2020. I did not want to budget around the next change.
- Account risk. A friend of mine lost access to a GitHub account that was tied to a company domain the company no longer owned. The repos were personal projects, not company work, but the account was on the company email. The recovery process took six weeks. I did not want to be one bad domain renewal away from the same situation.
- Features I did not need. The Copilot integration, the Actions marketplace, the project boards, the security advisories, the dependabot churn. None of these were hurting me, but I was paying in cognitive load for the privilege of ignoring them.
None of these are good reasons for a large team to move. They are good reasons for a solo developer with a handful of repos to move. Gitea is built for the second case.
What Gitea actually is
Gitea is a community-managed fork of Gogs (a lightweight self-hosted Git service) that split off in 2016. It is written in Go, which means it ships as a single binary with no external runtime dependencies. You download the binary, create a user, run it, and you have a GitHub-like web interface on whatever port you point it at.
The feature set is the standard self-hosted Git toolbox: repository hosting with a web UI, pull requests with code review, issue tracking with labels and milestones, a wiki per repository, releases with binary attachments, and a package registry for Docker, npm, and a few other formats. It supports Git LFS (Large File Storage, an extension for storing big files like videos or datasets outside the main Git history) out of the box. It supports webhooks. It has an API that is mostly compatible with the GitHub API, which means most GitHub-flavored CI tools work against a Gitea instance with minimal changes.
What it does not have, at least as of the 1.22 release, is GitHub Actions. Gitea has its own CI system called Gitea Actions, which is API-compatible with GitHub Actions but has a smaller ecosystem of pre-built actions. For most personal projects this is fine. For a team that has built up a library of GitHub Actions, the migration is more involved.
The actual install
I run Gitea on a $5/month VPS from Hetzner. The VPS has 2 GB of RAM, 20 GB of SSD, and 4 TB of monthly transfer. That is more than enough for my use case, which is about 30 personal repositories, a handful of issues, and a couple of CI workflows a day.
The install is short enough to fit in a blog post. Here is the sequence.
# Create a dedicated user
sudo adduser --system --shell /bin/bash --group --disabled-password git
# Download the Gitea binary
sudo -u git wget -O /opt/gitea/bin/gitea \
https://dl.gitea.com/gitea/1.22/gitea-1.22-linux-amd64
sudo chmod +x /opt/gitea/bin/gitea
# Create the data directory
sudo -u git mkdir -p /var/lib/gitea/{custom,data,log}
# Set up a basic config
sudo -u git cat > /etc/gitea/app.ini << 'EOF'
[server]
DOMAIN = git.example.com
ROOT_URL = https://git.example.com/
[database]
DB_TYPE = sqlite3
PATH = /var/lib/gitea/data/gitea.db
[security]
SECRET_KEY = <generate-something-random-here>
[service]
DISABLE_REGISTRATION = true
EOF
# Set up systemd (Linux's built-in service manager)
sudo cat > /etc/systemd/system/gitea.service << 'EOF'
[Unit]
Description=Gitea
After=syslog.target
[Service]
User=git
WorkingDirectory=/var/lib/gitea
ExecStart=/opt/gitea/bin/gitea web
Restart=always
[Install]
WantedBy=multi-user.target
EOF
sudo systemctl daemon-reload
sudo systemctl enable --now gitea
From there, you visit the URL, walk through the web installer, create your admin account, and you have a working Gitea instance. The whole loop, including setting up Nginx as a reverse proxy (a server that sits in front of Gitea, handles HTTPS, and forwards traffic to the actual Gitea process) with Let’s Encrypt for HTTPS, took me about twenty minutes on a fresh Ubuntu 24.04 server.
SQLite is fine for personal use. If you have more than about 50 active users or you are running a busy CI system, switch to PostgreSQL. The migration from SQLite to PostgreSQL is a built-in command. I have not had to do it, but the documentation is clear.
What I use it for day to day
About 80 percent of my Gitea usage is plain Git hosting. I push, I pull, I read diffs in the web UI. The remaining 20 percent is split between issues, pull requests, and the package registry.
Issues work the way you would expect. Labels, milestones, cross-references between issues. I do not use them as a full project management system, but for tracking TODOs in personal projects, they are fine. The interface is less polished than GitHub’s, but it is not a regression. I have not lost any data in eight months.
Pull requests are where Gitea surprised me. The code review interface is clean, with inline commenting and diff views that are easy to navigate. Most of the work I do is solo, so the “pull request” workflow is really just a “this is what I am about to merge” workflow. I have a Gitea Actions workflow that runs tests on every PR. It is five lines of YAML. The whole thing is closer to the GitHub experience than I expected.
The package registry is the feature I underestimated. I host a handful of Docker images for personal projects. Pushing them to GitHub’s container registry was fine, but having them on the same instance as the code that builds them is a real convenience. The setup is more involved than the rest of Gitea. You have to enable it in the config and configure storage. But once it is working, the workflow is the standard docker push.
Trade-offs
Gitea is not GitHub. The feature gap is real, and it shows up in three places.
- Ecosystem. GitHub Actions has thousands of community-contributed actions. Gitea Actions has hundreds, and the ones it has are mostly compatible. If you are relying on a specific niche action, check before you migrate.
- Discoverability. Open source projects get more visibility on GitHub. This is not a Gitea problem, it is a network effect. If discoverability matters for your project, Gitea is the wrong choice.
- Mobile experience. The Gitea mobile web interface works but is not as polished as GitHub’s. There is no first-party Gitea mobile app. If you review code from your phone, the experience is rougher.
In my case, none of these matter. My projects are personal, my code review is on a real computer, and the discoverability I care about comes from writing posts like this one rather than the GitHub trending page.
The migration took about a weekend. I had 30 repos to move, all of them under 100 MB. The git history came over cleanly. The issues did not. Gitea has an issue import tool that works for GitHub, GitLab, and a few other sources, but the result is a flat import with no cross-references between issues and commits. I imported the issues I cared about and let the rest stay on GitHub as read-only archives.
If you are running a small team and you want to evaluate Gitea seriously, set up a fresh instance, migrate one or two repos, and use it for a month. The cost of trying it is an afternoon. The cost of committing to it is a weekend. The cost of staying on GitHub is whatever the next pricing change is.
What I would tell past me
- Start with SQLite. It is fine for personal use and it removes a moving part. Switch to PostgreSQL when you actually need to, not before.
- Set up backups before you import anything. The Gitea data directory is plain files. Back it up. The database is plain SQLite. Back it up. The cost of a backup is a cron job (a scheduled task that runs automatically) and an offsite copy. The cost of losing your data is permanent.
- Disable public registration. Gitea’s default is open registration, which means anyone with the URL can sign up. If you are running a personal instance, turn this off in the config. You can always re-enable it later.
- Put a reverse proxy in front of it. Running Gitea on port 3000 with the built-in HTTP server works for testing, but in production you want Nginx or Caddy in front, handling HTTPS termination (the process of decrypting incoming HTTPS traffic so the backend can serve plain HTTP) and rate limiting (rejecting requests from clients that make too many in a short window). The Gitea docs have a recommended Nginx config. Use it.