← Back 02

Architecture & Topology Deep-Dive

May 2026 · 4-part series on migrating CI/CD from GitHub to Codeberg

Before: GitHub-Centric

github.com source
GitHub Actions runners
GHCR ghcr.io
deploy pull
GKE Cluster
backend frontend

Everything funneled through GitHub. Source on github.com, CI on GitHub Actions runners, container images on GHCR. The only part we owned was the GKE cluster at the bottom. When any piece of that GitHub stack had issues — runner queues, GHCR 503s, dropped webhooks — our entire pipeline stalled.

After: Self-Hosted on GKE

codeberg.org source
Forgejo Runner on GKE
runner 1CPU / 1.5Gi DinD 6CPU / 6Gi
GAR GCP
deploy push
K8s secrets from Codeberg org
GKE Cluster
staging pods production pods

The key shift: the runner is our pod in our namespace on our cluster. The registry is in our GCP project. Secrets flow from Codeberg org secrets into K8s secrets. When something breaks, we can kubectl logs it.

GKE Node Pool Topology

Node PoolMachine TypeSpotMinMaxPurposeEst. Cost/mo
ci-spot-poole2-highcpu-8 (8/8GB)Yes03CI runners~$3
staging-spot-poole2-standard-2 (2/8GB)Yes02Staging~$7
production-pool-8gbe2-standard-2 (2/8GB)No24Production~$48

Forgejo Runner Architecture

Two containers share the pod: DinD (6 CPU / 6Gi) handles building container images, Runner (1 CPU / 1.5Gi) picks up jobs from Codeberg. The runner is pinned to the ci-spot-pool via node affinity and tolerations. Capacity is 2 concurrent jobs.

CI Job Graph

Three phases: parallel lint+test (3 jobs) → parallel build (2 jobs) → deploy (auto on main). Total pipeline: ~12-15 minutes.

PhaseStepDuration
1lint-backend~3 min
1test-backend (8 workers)~7 min
1test-frontend~2 min
2build-backend~3 min
2build-frontend-staging~2 min
3deploy-staging~3 min
← Why We Left GitHub Next: What Broke →