Files
homelab/k8s/bootstrap
Story Crater Bot 47e7a2b1d6 feat(bootstrap): add Phase 1c nginx ingress controller
- Add p1_ingress() phase to install nginx-ingress-controller
- Create ingress-nginx namespace with privileged PodSecurity label
- Disable ServiceMonitor during bootstrap (Prometheus CRDs not installed yet)
- Add namespace.yaml with PodSecurity labels (allows hostPort)
- Filter cert-manager CRD errors (will be created by ArgoCD)
- Include ingress phase in bootstrap 'all' flow
2026-07-25 12:39:07 -07:00
..

Homelab Bootstrap — Single-Cluster, GitOps-Ready

Run once manually, GitOps forever after.

This bootstrap breaks the ArgoCD ↔ Forgejo circular dependency by:

  1. Installing infrastructure in correct dependency order
  2. Pointing ArgoCD at a GitHub mirror initially
  3. Cutting over to Forgejo once healthy
  4. Using Helm for reproducible installs
  5. Ensuring ArgoCD adopts (not duplicates) bootstrap resources

Prerequisites

  • Talos cluster running (terraform applied)
  • kubectl configured (KUBECONFIG points at cluster)
  • Helm 3 installed
  • SOPS age key at ~/.sops/homelab-age.key
  • GitHub mirror of this repo (for initial ArgoCD source)

Directory Structure

bootstrap/
├── phase1-storage/         # Longhorn via Helm
├── phase2-cnpg/           # CNPG operator via Helm
├── phase3-forgejo/        # Forgejo DB + Forgejo via Helm
├── phase4-argocd/         # ArgoCD via Helm → GitHub initially
└── phase5-cutover/        # Switch ArgoCD source to Forgejo

Usage

# From repo root:
./bootstrap.sh

# Or step-by-step:
./bootstrap.sh phase1  # Storage
./bootstrap.sh phase2  # CNPG
./bootstrap.sh phase3  # Forgejo
./bootstrap.sh phase4  # ArgoCD (GitHub mirror)
./bootstrap.sh phase5  # Cut over to Forgejo

Design Principles

  1. DRY: Helm values used by both bootstrap and ArgoCD
  2. Single Source of Truth: Manifests match what ArgoCD will manage
  3. Idempotent: Can re-run phases safely
  4. Adoption Ready: Resources have argocd.argoproj.io/sync-options: Prune=false
  5. Dependency Ordered: Each phase waits for previous to be Ready

Phase Details

Phase 1: Storage (Longhorn)

Installs Longhorn with:

  • 3-node HA configuration
  • Unified longhorn StorageClass (default)
  • Special longhorn-cnpg StorageClass with postgres UID/GID mount options
  • CSI plugin tolerations for control-plane nodes

Source of Truth: phase1-storage/longhorn-values.yaml

Phase 2: CNPG Operator

Installs CloudNativePG operator with:

  • CRD registration (blocks until CRD available)
  • Webhook configuration
  • Monitoring enabled

Source of Truth: phase2-cnpg/cnpg-values.yaml

Phase 3: Forgejo Database + Forgejo

  1. Creates forgejo-db CNPG Cluster
  2. Waits for cluster Ready (PostgreSQL accepting connections)
  3. Installs Forgejo via Helm pointing at forgejo-db-rw service
  4. Waits for Forgejo healthy

Source of Truth:

  • phase3-forgejo/forgejo-db.yaml (CNPG Cluster CR)
  • phase3-forgejo/forgejo-values.yaml (Helm values)

Phase 4: ArgoCD (GitHub Mirror)

Installs ArgoCD via Helm, then applies root app-of-apps pointing at GitHub mirror.

This is the circle-breaker: ArgoCD syncs from GitHub (not Forgejo) initially.

Source of Truth:

  • phase4-argocd/argocd-values.yaml
  • phase4-argocd/root-app-github.yaml (repoURL = GitHub)

ArgoCD adopts Phases 1-3 resources (no duplication) because manifests match.

Phase 5: Cut Over to Forgejo

  1. Push repo to Forgejo
  2. Update root app repoURL from GitHub → Forgejo
  3. ArgoCD re-syncs from Forgejo

The circle is broken. GitHub mirror is now disaster recovery only.

Post-Bootstrap

All changes via Git:

git commit -m "feat(app): add new service"
git push forgejo main
# ArgoCD auto-syncs

Troubleshooting

  • Phase stuck? Check kubectl get events -n <namespace> --sort-by='.lastTimestamp'
  • ArgoCD duplicating? Verify manifests match exactly (Helm values ↔ ArgoCD Application)
  • Forgejo won't start? Check CNPG cluster Ready: kubectl get cluster forgejo-db -n cicd
  • Can't push to Forgejo? Verify ingress-nginx healthy, DNS resolves forgejo.riotpiao.com

Migration from Old Bootstrap

If you have existing k8s/bootstrap-local/:

  1. DO NOT delete existing resources (Longhorn data!)
  2. Run refined bootstrap in "adoption mode" (no delete, just apply)
  3. Verify ArgoCD shows "Synced" for all apps
  4. Archive old bootstrap: git mv k8s/bootstrap-local k8s/archive/bootstrap-local-v1