May 2026 · 4-part series on migrating CI/CD from GitHub to Codeberg
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.
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.
| Node Pool | Machine Type | Spot | Min | Max | Purpose | Est. Cost/mo |
|---|---|---|---|---|---|---|
| ci-spot-pool | e2-highcpu-8 (8/8GB) | Yes | 0 | 3 | CI runners | ~$3 |
| staging-spot-pool | e2-standard-2 (2/8GB) | Yes | 0 | 2 | Staging | ~$7 |
| production-pool-8gb | e2-standard-2 (2/8GB) | No | 2 | 4 | Production | ~$48 |
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.
Three phases: parallel lint+test (3 jobs) → parallel build (2 jobs) → deploy (auto on main). Total pipeline: ~12-15 minutes.
| Phase | Step | Duration |
|---|---|---|
| 1 | lint-backend | ~3 min |
| 1 | test-backend (8 workers) | ~7 min |
| 1 | test-frontend | ~2 min |
| 2 | build-backend | ~3 min |
| 2 | build-frontend-staging | ~2 min |
| 3 | deploy-staging | ~3 min |