A cluster’s lifecycle, from creation to destruction, can be fully automated using Flux, turning your Kubernetes infrastructure into a declarative GitOps workflow.

Let’s see what that looks like in practice. Imagine we have a Git repository with a cluster-config directory. Inside, we have a clusters/dev directory, which contains manifests for a development cluster.

# clusters/dev/namespace.yaml
apiVersion: v1
kind: Namespace
metadata:
  name: my-app
# clusters/dev/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-app-deployment
  namespace: my-app
spec:
  replicas: 2
  selector:
    matchLabels:
      app: my-app
  template:
    metadata:
      labels:
        app: my-app
    spec:
      containers:
      - name: my-app-container
        image: nginx:latest
        ports:
        - containerPort: 80

Now, we configure Flux to watch this Git repository and apply these manifests to our dev cluster.

First, we install Flux into the cluster. This typically involves running a command like:

flux bootstrap git \
  --url=ssh://git@github.com/your-org/your-repo.git \
  --branch=main \
  --path=./clusters/dev \
  --personal

This command does a few key things:

  1. Installs Flux controllers: It deploys the necessary Flux components (Source Controller, Kustomize Controller, Helm Controller, Notification Controller) into your cluster.
  2. Configures Git access: It sets up authentication (SSH in this case) for Flux to pull from your Git repository.
  3. Defines a GitRepository resource: This tells Flux where to look for manifests.
  4. Defines a Kustomization resource: This tells Flux what to apply from the repository and where (which path, which namespace).

The GitRepository resource Flux creates might look something like this internally:

apiVersion: source.toolkit.fluxcd.io/v1beta2
kind: GitRepository
metadata:
  name: cluster-config
  namespace: flux-system # Flux's namespace
spec:
  interval: 1m0s
  url: ssh://git@github.com/your-org/your-repo.git
  ref:
    branch: main
  secretRef:
    name: flux-system-git-auth # The secret Flux created for SSH keys

And the Kustomization resource, which points to the manifests:

apiVersion: kustomize.toolkit.fluxcd.io/v1beta2
kind: Kustomization
metadata:
  name: dev-cluster-apps
  namespace: flux-system
spec:
  interval: 5m0s
  path: ./clusters/dev # Path within the Git repository
  prune: true         # Automatically delete resources removed from Git
  sourceRef:
    kind: GitRepository
    name: cluster-config # Refers to the GitRepository above
  targetNamespace: dev # Apply resources in this namespace on the cluster (optional, if not specified, defaults to Git path)

Once these are in place, Flux’s Source Controller continuously watches the specified Git repository. When it detects a change (a new commit), it pulls the latest manifests. The Kustomize Controller then compares these desired states with the actual state in the cluster and applies any necessary changes (creation, updates, or deletions).

This system elegantly solves the problem of drift. Because Flux is constantly reconciling the cluster’s state with Git, any manual changes made directly to the cluster (e.g., via kubectl apply) will be automatically reverted by Flux on its next reconciliation cycle.

You can also manage more complex scenarios. For instance, you might have a base set of manifests in ./clusters/base and then create specific Kustomization resources for different environments that overlay or extend the base.

# clusters/prod/kustomization.yaml
apiVersion: kustomize.toolkit.fluxcd.io/v1beta2
kind: Kustomization
metadata:
  name: prod-cluster-apps
  namespace: flux-system
spec:
  interval: 5m0s
  path: ./clusters/prod # Path for production specific manifests
  prune: true
  sourceRef:
    kind: GitRepository
    name: cluster-config
  patches: # Apply patches to the base configuration
    - patch: |-
        apiVersion: apps/v1
        kind: Deployment
        metadata:
          name: my-app-deployment
        spec:
          replicas: 10 # Scale up for production
      target:
        kind: Deployment
        name: my-app-deployment
        namespace: my-app

The truly surprising part is how seamlessly Flux integrates with Git hooks and CI/CD pipelines. You can set up a pipeline that, upon merging to your Git repository’s main branch, triggers a commit to your cluster-config repository. Flux then picks up this commit, and your entire cluster infrastructure is updated without any direct human intervention on the cluster itself.

The core components of Flux (Source, Kustomize, Helm, Notification Controllers) work in concert. The Source Controller fetches artifacts from external sources (Git, OCI registries, Helm repositories). The Kustomize Controller applies Kubernetes manifests defined in Kustomizations. The Helm Controller manages Helm releases. The Notification Controller handles events and alerts. Together, they provide a robust, event-driven GitOps engine for your cluster.

When you’re managing multiple clusters, you’ll often create a top-level GitRepository pointing to your main configuration repo, and then have individual Kustomization resources for each cluster (clusters/dev, clusters/staging, clusters/prod). Each of these Kustomization resources will then reference the appropriate path within that single GitRepository. This allows for a single source of truth for all your cluster configurations, with environment-specific variations managed through path segregation or Kustomize overlays.

Managing secrets across environments requires careful consideration. While you can store secrets directly in Git, it’s generally discouraged for anything sensitive. Flux integrates well with tools like External Secrets Operator or Vault to fetch secrets from external stores at runtime, rather than committing them.

The next logical step is to explore how to integrate Flux with CI/CD pipelines to automate the Git commit process itself.

Want structured learning?

Take the full Flux course →