gitself-hostinggiteadevops

Why I Self-Host My Git With Gitea

GitHub is convenient, but after years of relying on a platform I don't control, I moved my repositories to a self-hosted Gitea instance. Here's why and how.

I’ve been on GitHub since the early days. Free private repos, Actions for CI, Copilot for autocomplete — the platform gives you a lot. But a few years ago I started thinking hard about what “owning your work” actually means when every line of code lives on someone else’s server.

This post is about why I switched to self-hosting Git with Gitea, what I gained, what I gave up, and the actual setup.

The Problem With Platform Lock-In

GitHub going down takes your workflow with it. Microsoft acquiring GitHub in 2018 didn’t break anything, but it was a reminder that the ground can shift. And subtler things did change over time — the UI got heavier, features got bundled with paid tiers, and the API rate limits got tighter.

More practically: I work on a lot of small tooling projects that I don’t necessarily want indexed publicly. Private repos on GitHub are free now, but I still didn’t love the idea of the code sitting in a data center I know nothing about.

Why Gitea

I looked at GitLab (too heavy for a single-user instance), Forgejo (Gitea fork, worth considering), and Soft Serve (terminal-first, cool but not what I needed). Gitea hit the right balance:

  • Single binary, runs as a systemd service
  • Full API compatible with most GitHub tooling
  • Webhook support for CI triggers
  • Low memory footprint (~50MB idle)
  • Mirrors GitHub repos so I can keep public forks synced

The Setup

I run Gitea on a small VPS behind Caddy (which handles TLS automatically via Let’s Encrypt). The full config is in my dotfiles repo, but here’s the essence:

# /etc/systemd/system/gitea.service
[Unit]
Description=Gitea
After=network.target postgresql.service

[Service]
Type=simple
User=git
ExecStart=/usr/local/bin/gitea web --config /etc/gitea/app.ini
Restart=on-failure

[Install]
WantedBy=multi-user.target

Caddy reverse-proxies to port 3000:

git.example.com {
  reverse_proxy localhost:3000
}

Database is PostgreSQL rather than SQLite — SQLite works fine for small installs but I already had Postgres running for other services.

What I Gave Up

GitHub Actions is genuinely great. Gitea has Forgejo Actions (compatible format) but the runner ecosystem is smaller. I ended up using a simple shell-based CI approach with Woodpecker CI instead — it’s lighter and I like the pipeline syntax.

The social graph is also gone. Stars, forks from strangers, issue reports from users who found you on the explore page — none of that happens on your own instance. If discoverability matters to you, you’ll want to mirror to GitHub and keep a presence there.

What I Gained

  • Full control: I can run custom hooks, set quotas, snapshot the whole thing.
  • No rate limits: The API is unlimited because it’s my server.
  • Privacy by default: Internal projects stay internal without thinking about visibility settings.
  • Speed: Gitea is genuinely fast. Page loads under 100ms on a $6 VPS.

Would I Recommend It?

If you’re comfortable managing a small server and care about data sovereignty, yes. The setup takes an afternoon and the maintenance burden is low — I’ve had maybe one hour of unplanned downtime in two years.

If you rely heavily on the GitHub ecosystem (Actions marketplace, Dependabot, the network effect), stay on GitHub and maybe mirror important repos to a self-hosted instance as a backup.

The source for my Gitea setup lives in my dotfiles. Happy to answer questions.