A Flux GitOps repository for my personal homelab Kubernetes cluster.
This repository manages the desired state of my personal homelab production cluster.
It runs Flux on K3s and uses the manifests under apps and infrastructure to declaratively manage applications, networking, certificates, storage, and observability.
clusters/productionis the Flux bootstrap path.infrastructure/overlays/productioncontains the cluster foundation.apps/overlays/productioncontains homelab application configuration.apps/base/*andinfrastructure/base/*hold the workload manifests.- Secrets are encrypted with Sealed Secrets before they are committed.
ansible/prepares the replacement machine used for cluster migration.
Recovery uses the normal production path. There is no separate recovery Flux installation or recovery overlay.
Before bootstrapping a replacement cluster, temporarily remove the application entry point from Flux and push that change:
mv clusters/production/apps.yaml clusters/production/apps.yaml.bak
git add clusters/production/apps.yaml clusters/production/apps.yaml.bak
git commit -m "chore: pause applications for cluster recovery"
git pushUse ansible/ to install K3s, bootstrap Cilium, and restore the Sealed Secrets
private key before bootstrapping Flux from clusters/production. The bootstrap
uses the same cilium Helm release and values that Flux adopts later. Wait until
the infrastructure and Longhorn backup target are ready. Access Longhorn without
ingress if necessary:
kubectl -n longhorn-system port-forward service/longhorn-frontend 8000:80In the Longhorn UI, restore the latest Ready system backup created by the same
Longhorn minor version. The system backup restores application volumes from
their latest volume backups.
PostgreSQL is restored by CloudNativePG from Barman S3 backups, not from Longhorn volume backups. Before enabling applications, remove any CNPG data PVCs restored by an old Longhorn backup and confirm their PVs and Longhorn volumes are gone:
kubectl get pvc -A -l cnpg.io/pvcRole=PG_DATA
kubectl delete pvc -A -l cnpg.io/pvcRole=PG_DATA
kubectl get pvEnable applications again and push the change:
mv clusters/production/apps.yaml.bak clusters/production/apps.yaml
git add clusters/production/apps.yaml clusters/production/apps.yaml.bak
git commit -m "chore: resume applications after cluster recovery"
git pushAll seven CNPG manifests use bootstrap.recovery; they recreate their databases
from S3 when Flux applies the application manifests. Verify CNPG recovery before
allowing external traffic, then check Flux, certificates, ingress, storage, and
core application data.
Store the become password and Sealed Secrets TLS certificate/private key in the Ansible Vault, then let Ansible prepare everything up to the Flux boundary:
cd ansible
make vault-edit
make preflight
make check
make apply
make apply
make verify
cd ..Do not bootstrap Flux until make verify confirms that Cilium is healthy and
that the recovery key in the new cluster matches the Vault values. K3s starts
with Flannel, its network-policy controller, and kube-proxy disabled; Cilium must
therefore be running before Flux controllers and workloads can start.
flux bootstrap github \
--repository=homelab \
--branch=main \
--path=./clusters/production \
--owner=tinyrack-netFlux reconciles the cilium HelmRelease in kube-system and takes over the
release installed by Ansible. Multus remains the first CNI configuration and
delegates the primary Pod network to Cilium while continuing to provide the
wg-easy macvlan attachment.
- Keep a HelmRelease focused on chart lifecycle and load chart configuration
from a sibling
<component>.values.yamlthroughspec.valuesFrom. - Generate Helm values ConfigMaps with a stable name, the
reconcile.fluxcd.io/watch: Enabledlabel, andvalues.yamlas the data key. - Keep application-native YAML, TOML, JSON, ENV, and Alloy River configuration in files named for the owning component. Use Kustomize's default name hash when a Pod directly mounts or imports a ConfigMap so changes roll the Pod.
- Disable the name hash only when the consumer requires a stable name, such as Helm values and Alloy's externally managed, dynamically reloaded ConfigMap.
- Keep credentials out of values and configuration files. Secrets remain encrypted SealedSecret manifests or references to existing Secrets.
The production cluster was migrated from Flannel and kube-proxy to Cilium. The one-time migration and rollback playbooks were removed after verification.
- Keep only shared entrypoint, Kubernetes API, and telemetry access in the Traefik infrastructure policies.
- Put route-specific access beside its owning application or proxy in a
<owner>.traefik.cilium-network-policy.yamlfile. - Create the policy in
traefik-systemortraefik-external-system, name itroute-<owner>, and add thenetworking.tinyrack.net/ownerlabel andnetworking.tinyrack.net/hostsannotation. - Use
toServiceswith the backend target port for Kubernetes Services andtoCIDRwith explicit ports for LAN or ExternalName backends. - Group routes that share a backend into one owner policy. Host annotations are documentation and do not enable Cilium L7 HTTP filtering.
kubectl create secret generic some-secret \
--namespace some-namespace \
--dry-run=client \
--from-literal=SOME_SECRET_KEY=SOME_SECRET_VALUE \
-o yaml | \
kubeseal --cert ./tinyrack-homelab-secret-key.crt \
> ./some.secret.yaml