GitOps
Flux, CI and keeping cluster state in Git.
Posts
A correction to my NAS maintenance notes: when a parent Kustomization applies the child, the next sync clears a manual suspend. Annotating the one object with reconcile=disabled holds, but it also stops Flux from pruning it.
Notes
The differences that actually bit me when I moved a workflow from GitHub Actions to a self-hosted act_runner on Gitea.
flux bootstrap github offers token auth or SSH deploy keys. Both need a personal access token at bootstrap; they differ in what the cluster keeps afterwards, and that is what to choose on.
When the thing you want declared lives in an application database behind an API, a CronJob that writes it every few minutes can do the operator's job. What went wrong for me was in the shell, in the API verbs, and in triggering the job by hand under Flux.
If the Flux Kustomization that applies a *.sops.yaml Secret has no decryption block, Flux applies the encrypted text as the value and reports success. The app then fails with a baffling auth error. How to spot it without reading the secret.
Handing a kubectl-applied add-on or Secret to Flux should change nothing. Two read-only checks prove it: kubectl diff and a server-side dry run under Flux's own field manager. And why you must never print the diff of a Secret.
Production applies these fine because the CRDs already exist. On a cluster rebuilt from nothing, a Kustomization with a HelmRelease and objects of the kinds that chart installs fails its dry run. Monitors for a Prometheus operator that is not there fail the same way. Split it into two stages.
A Longhorn volume showed up as detached and unused because Helm never deletes PVCs in the namespace a release moved away from. How to spot the orphan by comparing volumes with running pods, and why the reclaim policy decides how much a delete takes with it.
Gitea Actions jobs sat queued because act_runner was registering as ephemeral. The image only checks that GITEA_RUNNER_EPHEMERAL is non-empty, so setting it to "0" enables it, and the pod looks like a crash loop.
An unquoted Discord or Telegram user ID in stringData becomes a YAML number. The apply fails, and a long ID has already lost precision by the time you read the error.
A Kustomization that keeps failing flips its Ready condition between False and Unknown on every reconcile, and lastTransitionTime moves with it. An alert for "not ready for ten minutes" built on that field almost never fires. Keep your own first-seen time.
Gitea pull mirrors created through the migrate API: the request body, why private repos store a GitHub token in Gitea, what breaks when that token rotates, and how to prove each mirror is complete.