Building a Self-Hosted CI/CD Pipeline with Gitea Actions
Running your own CI/CD end-to-end is one of the most satisfying pieces of homelab plumbing. Here's the shape of the pipeline that builds every container image on my cluster.
The pieces
- Gitea — source hosting, Actions orchestration, and OCI package registry in one binary
- A self-hosted runner — labelled
k8s-host, with Docker available - Kubernetes — the deploy target, pulling images with a scoped read-only registry token
The workflow
Each repo carries a workflow under .gitea/workflows/ that clones with the ephemeral per-run token, logs into the registry with a scoped secret, builds with classic Docker, and pushes both a latest tag and an immutable commit-SHA tag.
The immutable SHA tag is what actually gets pinned in Kubernetes manifests — latest is for humans.
Lessons learned
- Copy conventions from a working sibling repo. Runner labels, clone patterns, and secret names on a self-hosted instance are all local conventions — don't assume GitHub defaults.
- A queued run is usually a label mismatch, not a broken workflow.
- Pin deployments to immutable tags.
latestplus a rollout restart works until it doesn't.