Building a Self-Hosted CI/CD Pipeline with Gitea Actions

giteaci/cddocker

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

  1. 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.
  2. A queued run is usually a label mismatch, not a broken workflow.
  3. Pin deployments to immutable tags. latest plus a rollout restart works until it doesn't.