The core idea behind the cluster’s operational model is simple: Git is the source of truth, and nothing meaningful happens outside of it. If an application is not defined in the repository, it does not run. If a configuration change is not committed, it does not stick. Flux watches the repository continuously and reconciles the cluster back to whatever Git says it should be.
In practice this means almost all cluster interaction happens through pull requests rather than kubectl apply. The escape hatch exists — you can apply things manually — but anything done that way will get overwritten on the next reconciliation cycle, which is a useful forcing function.
The repository is structured around Flux Kustomization objects. Each application gets its own ks.yaml that defines where its manifests live, what it depends on, and what variables to inject at sync time. Most applications are defined as Helm releases, and most of those use the bjw-s app-template chart, which reduces a lot of boilerplate for single-container workloads.
The more interesting piece is image automation. Container images are pinned to SHA digests rather than mutable tags. When a new image is published, Flux’s image reflector detects it, image automation opens a commit updating the digest in Git, and then Flux reconciles the change into the cluster. The result is a full audit trail of every image update through the commit history, with no manual intervention required.
Renovate handles the Helm chart side — it runs hourly, detects new chart versions, and opens pull requests. Combined with image automation, the cluster stays current largely on its own.
The main operational friction is dependency ordering. Some applications depend on others being healthy before they can start, and expressing those dependencies correctly in ks.yaml takes some care. Getting it wrong tends to produce subtle reconciliation failures that are hard to trace without diving into Flux’s own event log.
There is also the question of what to do when you need to test a change quickly without waiting for reconciliation. The answer is usually task flux:apply, which applies a kustomization manually and skips the wait — but it is worth being conscious of when you are working outside the normal loop.