This repository is a template for managing a bare-metal Kubernetes cluster via GitOps using FluxCD.
It is explicitly designed to work seamlessly with the bootc-homelab-k0s OS image. Using that image ensures your nodes come pre-baked with the necessary dependencies for this configuration.
Included Components:
- SOPS: Secret encryption (Hybrid GPG/Age).
- MetalLB: Bare-metal LoadBalancing.
- Nginx Gateway Fabric: Gateway API implementation.
- Local Path Provisioner: Automatic local storage management.
Before bootstrapping, ensure you have the following:
- The Infrastructure:
- Nodes running the bootc-homelab-k0s image.
- A running k0s cluster accessible via your local
kubeconfig.
- The CLI Tools:
This template follows a modular GitOps structure to separate core infrastructure from user applications.
.
├── apps/ # User-facing workloads (e.g., echo-server)
├── cluster/ # FluxCD entry point (bootstrapped directory)
│ ├── apps.yaml # Activates the 'apps' folder
│ ├── infrastructure.yaml# Activates the 'infrastructure' folder
│ └── helmrepos.yaml # Activates the 'helmrepos' folder
├── helmrepos/ # Source definitions for Helm Charts (Bitnami, etc.)
├── infrastructure/ # System components (MetalLB, Storage, Gateway)
└── .sops.yaml # Encryption rules for SOPS
cluster/: The "Control Center." Flux monitors this directory. It containsKustomizationobjects that tell Flux to go look at the infrastructure and apps directories.infrastructure/: The "Plumbing." Contains system-level software that must be running before your apps (e.g.,metallbfor networking,local-path-provisionerfor storage).apps/: Your actual applications. This is where you add new services (like Plex, Home Assistant, etc.).helmrepos/: A central list of external Helm repositories used by your charts.
Initialize Flux on your cluster and link it to this GitHub repository.
Note: Replace variables with your actual GitHub username and repository name.
flux bootstrap github \
--token-auth \
--owner=<githubUsername> \
--repository=<githubRepo> \
--branch=main \
--path=cluster \
--personal \
--components-extra=image-reflector-controller,image-automation-controllerWe configure SOPS so you can decrypt secrets locally (GPG) and Flux can decrypt them inside the cluster (Age).
-
Generate Keys:
- Admin (GPG): Run
gpg --list-secret-keys --keyid-format=longand copy your key ID. - Cluster (Age): Run
age-keygen -o age.agekeyand copy the public key (starts withage1...).
- Admin (GPG): Run
-
Update Config:
- Open
.sops.yamlin the root of this repo. - Paste your GPG Key ID into the
YOUR_FULL_GPG_FINGERPRINT_HEREplaceholder. - Paste the Age Public Key into the
age1_YOUR_PUBLIC_KEY_HEREplaceholder.
- Open
-
Upload Age Key to Cluster: Flux needs the private Age key to decrypt secrets.
cat age.agekey | kubectl create secret generic sops-age \ --namespace=flux-system \ --from-file=age.agekey=/dev/stdin -
Cleanup:
⚠️ IMPORTANT: Deleteage.agekeyfrom your local machine. DO NOT push it to GitHub.
Edit infrastructure/metallb-config/resources/pools.yaml. Define the IPs MetalLB is allowed to use:
- Local IP Pool: For services accessible only inside your home network.
- Public IP Pool: For services you intend to expose (requires port forwarding).
Review infrastructure/nginx-gateway-fabric/resources/gateways.yaml to ensure the Gateway classes match your network needs.
(Optional) Install external-dns and cert-manager for automated DNS and SSL.
This template uses local-path-provisioner to utilize node disk space.
- Check
infrastructure/local-path-provisioner/resources/values.yamlif you need to customize the storage path (defaults to/opt/local-path-provisioner).
Once configured, commit and push your changes:
git add .
git commit -m "chore: configure cluster infrastructure"
git push origin main- Check Sync:
flux get kustomizations --watch - Test App: After a few minutes, the demo
echo-servershould be running.- Get the IP:
kubectl get svc -n echo-server-namespace - Access the IP in your browser.
- Get the IP:
- Repo Privacy: Keep this repository Private.
- Secret Management:
- Never commit raw secrets.
- Encrypt before pushing:
sops --encrypt --in-place path/to/secret.yaml - To edit an encrypted file:
sops path/to/secret.yaml(it handles decryption/encryption automatically).
Note: The code, templates, and configuration logic in this repository were manually crafted and tested. Only this README file was generated/enhanced with the assistance of AI.