Self-Hosted Git & CI Stack

Your code, your runners, your release pipeline.

2 apps≈512 MB RAM combined1 verified 5-minute deploys

Open this stack in the Stack Builder

Gitea provides repositories, issues, and pull requests in a single lightweight binary, and Woodpecker CI — itself a community fork born from self-hosting needs — runs container-based pipelines triggered by Gitea webhooks. The pair covers the GitHub-plus-Actions workflow for a fraction of the resources GitLab needs, which is exactly the trade most homelabs and small teams want.

The apps and their roles

Last reviewed Oct 11, 2026 · 661 words

Gitea plus Woodpecker CI gives you repositories, pull requests and container-based pipelines in about 512 MB of RAM by the directory's estimates, 256 MB for each. The wiring is two things: Woodpecker logs in through a Gitea OAuth application, and Gitea sends Woodpecker a webhook on every push. Get the URLs right on both sides and run the build agent somewhere other than the Gitea host, and the pair needs little attention afterwards.

What talks to what

Gitea is the source of truth for code, users and permissions. Woodpecker does not manage passwords: you create an OAuth2 application in Gitea, give Woodpecker the client ID and secret, and users sign in to Woodpecker with their Gitea accounts. When someone activates a repository in Woodpecker, it registers a webhook on that repository in Gitea.

Woodpecker itself is two parts. The server receives webhooks, shows the UI and schedules work. One or more agents connect to the server and run each pipeline step in its own container, using the YAML file in the repository. Agents need access to a container runtime, and that is where most of the security thinking in this stack goes.

ComponentTalks toRatedDirectory RAM estimate
GiteaWoodpecker server (webhooks)Easy256 MB
Woodpecker serverGitea (OAuth, API), agentsMedium256 MB
Woodpecker agentWoodpecker server, DockerMediumGrows with builds

The agent's real footprint is whatever your builds need; a compiler or test suite will use far more than the server ever does.

Deploy in this order

Gitea first, behind your reverse proxy with its final public URL set as ROOT_URL before anyone clones anything. Every link Gitea generates, including OAuth redirects and webhook payloads, is built from it. Map container SSH to a spare host port, as the catalogue does with 222, and expect that port to appear in clone URLs.

Then create the OAuth application in Gitea with Woodpecker's callback URL, deploy the Woodpecker server with those credentials, and deploy one agent with the shared secret that lets it register. Activate one test repository and push a trivial pipeline before migrating anything real.

Why does Woodpecker never see my pushes?

Usually because Gitea refuses to deliver the webhook. Gitea restricts webhook targets by default and blocks deliveries to private network addresses, which is exactly where a homelab Woodpecker server lives. The push succeeds, Woodpecker sits idle, and the only clue is a failed delivery in the repository's webhook settings. Add your Woodpecker host to Gitea's webhook allowed-host list, or route the webhook through the public hostname, and builds start arriving. Check the delivery log before you debug anything else.

The mistake: agents on the Gitea host

A Woodpecker agent with the Docker socket mounted has root-equivalent control over its host, and it runs code from any repository you activate, including pull requests. Put that on the same machine as Gitea and a malicious or simply careless pipeline can read every repository on disk. Run agents on a separate VM or machine, and do not enable builds for pull requests from forks you do not trust.

Also decide early between Woodpecker and Gitea's built-in Gitea Actions. Running both on the same repositories means two sets of pipeline files and two places to look when something fails.

Backups

Gitea's /data volume holds repositories, the database (if you kept SQLite), attachments and configuration; back it up as a unit. Woodpecker's server database holds build history and secrets. Agents are stateless and can be rebuilt.

What I'd do

Deploy Gitea with the correct ROOT_URL, add Woodpecker with one agent on a separate VM, and fix the webhook allow-list before your first push. If you are still choosing a forge, the self-hosted Git forges guide and the Forgejo vs Gitea comparison cover the fork, and Woodpecker works with Forgejo the same way.

Related stacks