What People Mean by Self-Hosted GitHub: Gitea Actions vs a $4 Runner You Forget Exists
Nate Holbrook
September 18, 2026
When someone says they want a self-hosted GitHub, they rarely mean they want git over SSH and a folder of bare repos. They mean the browser: pull requests, an Actions tab, a green check on the commit, and YAML they already know how to copy from a gist. Gitea and Forgejo will sell them that picture. A $4 VPS running actions/runner against github.com will sell them the same picture with less honesty about what they still rent from Microsoft.
I have run both. The forgotten machine is the same shape either way. It is a cheap Linux box with Docker, a runner binary, and a disk that fills up with images you meant to prune. The difference is whether the control plane is your Gitea or still GitHub.com — and whether you remember the box exists when the credit card on the VPS is the only thing still paying for “self-hosted CI.”
The product people are naming
GitHub the company is issues, PRs, Actions minutes, container registry, and a social graph. Git the tool is none of that. “Self-hosted GitHub” in 2026 almost always means Gitea or Forgejo plus something that will execute .github/workflows or the Gitea equivalent. soft-serve and a raw cgit page do not scratch the itch. People who only needed a remote already know they only needed a remote.
Gitea Actions exists to keep the YAML. It talks to a runner you register. The runner is not optional if you want the tab to do work. Forgejo’s fork of the same idea is the same idea. You did not delete CI ops. You moved the scheduler into your compose stack and left the worker as a second animal.
The $4 runner path is: stay on github.com, install GitHub’s self-hosted runner on a Hetzner CX, an Oracle Ampere free-tier box, or the leftover VPS from a failed side project, and stop paying for hosted minutes. Your repos stay in someone else’s SaaS. Your jobs run on a machine whose SSH key is in a password manager you last opened in March.

Gitea Actions: the tab is local, the worker is still a pet
I like Gitea Actions when the point of leaving GitHub was not minutes, it was the repo. Client code that should not sit on github.com, a family of dotfiles, a homelab compose repo that also holds too much truth. The Actions tab being on the same URL as the issues is a real comfort. The workflow files look like GitHub’s because they are trying to.
Compatibility is “close enough to hurt.” Actions that assume github.com URLs, GitHub-hosted secrets stores, or actions/checkout behaviors you never read will fail in ways the UI reports as a red X and a log that says the runner never picked up the job. Debugging that is still CI debugging. You have added “is the runner online” and “did Gitea queue it” to the list that used to start at “did I typo the YAML.”
The runner for Gitea is often a container next to Gitea, or a second VM “so builds cannot see the git disk.” The second VM is the right instinct. It is also how you get a $4 box you forget. Labels go stale. A job sits queued because you labeled the workflow ubuntu-latest and the runner registered as self-hosted. The UI looks like GitHub. The queue is yours. Nobody at GitHub will page you when the worker dies after a host reboot that did not include a systemd unit.
Disk is the mundane killer. Every docker build, every setup-node cache you did not bound, every Actions cache action that assumes infinite hosted storage. I have filled a 40 GB VPS with leftover layers and then spent an evening wondering why Gitea itself was in read-only. The runner and the git host sharing a disk is a rite of passage I would like to stop watching people repeat.
The $4 GitHub runner: you did not leave, you rented a janitor
A self-hosted runner on github.com is the honest cheap move if the repos can stay. Minutes were the bill. You kill the bill. You keep PRs, the social clone button, Dependabot, and the muscle memory. You also keep a machine that GitHub will happily send jobs to, including jobs from a fork PR if you mis-set the policy, including secrets that now live in the runner’s environment and in its Docker graph.
Forgetting this box is not a joke. I have found runners whose GitHub registration still showed green six weeks after the human moved apartments. The VPS auto-renewed. The jobs were still using an old Node, an old secrets file in a workspace directory, and a Docker socket that meant every workflow was root on the host in practice. “Self-hosted” here meant “unattended privilege.”
The $4 price is the trap. At $4 you do not put it in the homelab UPS documentation. You do not give it Borg. You do not rotate the runner token when a contractor leaves. GitHub’s UI still shows a green dot. Green dots are how we hide pets.
There is also the lock-in you thought you avoided. Workflows stay GitHub Actions. The day you do want Gitea, you will port YAML that assumed hosted-runner paths, GITHUB_TOKEN permissions, and marketplace actions that phone home. The $4 box did not buy you an exit. It bought you cheaper minutes on the same platform.

What actually breaks in the first six months
- The runner dies quietly. Host reboot, Docker upgrade, a full disk, an expired registration token. Gitea or GitHub will queue. You will notice when a personal project stops deploying and you assume the YAML is wrong.
- Secrets sprawl onto the worker.
.envfiles in workspace, Docker build args, a checkout of a private repo that stays in_work. Cheap runners do not get wiped between jobs unless you make them. Hosted GitHub runners throw the VM away. Your $4 box does not. - You share the runner across trust zones. A client repo and a toy repo on one worker. A malicious workflow in the toy is now near the client token. Labels feel like isolation. They are not.
- You pointed DNS at the Gitea UI and never documented the runner. Restore from backup brings back git. CI is a second restore you did not practice.
- Oracle free tier and “I’ll remember.” You will not. Set a calendar reminder for the reclaim email, or do not put production deploys on a free ARM box in a region you cannot locate on a map.
When Gitea Actions is the right kind of pain
Pick Gitea Actions if the git host leaving GitHub is the actual project. You want PRs without a Microsoft account for a collaborator who will not make one. You want the Actions tab on the same onion or Tailscale URL as the code. You will run a dedicated runner VM, prune Docker on a timer, and treat the worker as a machine that holds secrets — because it does.
Forgejo is in this sentence for people who already picked the fork. The Actions story is not why I would choose one over the other. The governance and upgrade story is. If you are still deciding whether self-hosting git is a weekend or a second job, that decision is upstream of runners — Gitea vs Forgejo vs staying on GitHub is the host question. This piece is the CI question that arrives the week after the host looks finished.
When the $4 runner is enough — and how not to forget it
Stay on github.com with a self-hosted runner if minutes were the only complaint and the code can stay. Then do the unsexy work the price tag discouraged:
- A hostname in your password manager and in the homelab doc, not only in GitHub Settings → Actions → Runners.
- Disk alarms. I use the same Uptime Kuma that watches everything else, pointing at disk percent, not at “can I hit :22.”
- Ephemeral runners or a wipe of
_workand unused images on a schedule. If that sounds heavy, pay for hosted minutes. $4 plus a breach fantasy is not cheaper. - A calendar event titled with the VPS provider and the word runner. Silly. Effective.
- Repo allow-lists so a random public fork cannot use your metal.
I have one leftover CX22 whose only job is a couple of private repos that build container images I do not want to wait on hosted queues for. It is in the doc. It has prune. It is still a pet. I am not proud of the pet. I am honest that hosted minutes were worse for that image size.
The combination I tell people to stop inventing
Do not run Gitea for “sovereignty” and then register a GitHub-hosted workflow that deploys from a mirror. Do not run a GitHub runner on the same NUC as Gitea “to save four dollars” and then act shocked when a job can see both token files. Do not call it self-hosted GitHub if the runner is a mystery process and the git is a volume you have never restored.
If you need git and you do not need CI, skip Actions. Push with SSH. That is allowed. The search results will try to make you feel unfinished. You are finished. The unfinished people are the ones with a green Actions badge on a worker they could not find during a power cut.
What I mean when I say it now
Self-hosted GitHub, in the mouth of a homelab person, means a GitHub-shaped web app and a YAML CI they do not want to rewrite in Jenkins. Gitea Actions is that shape with your logo in the tab. A $4 runner is that shape with GitHub still holding the tab. Both require a machine you will forget if the only reminder is the price.
I self-host git for personal and client repos because the operational gap GitHub hides is access and retention, not the feeling of an Actions graph. When I need CI, I want the worker named, backed up as “rebuild from compose,” and boring. I do not want another forgotten four-dollar brain. If that means I pay GitHub minutes for a public toy, I pay. If that means Gitea Actions on a labeled VM, I label the VM in the same sentence I write the compose file.
The badge is easy. The box is the product. Name the box, or you are not self-hosting GitHub. You are renting a ghost.