You can manage multiple Kubernetes clusters from a single Flux repository by structuring your Git repository to represent your desired state across all clusters, and then letting Flux reconcile that state with each individual cluster.

Let’s see this in action. Imagine we have two clusters, prod-east and staging-west, and we want to deploy a common nginx application to both, but with different replica counts.

Our Git repository might look like this:

├── clusters
│   ├── prod-east
│   │   ├── flux-system
│   │   │   └── gotk-components.yaml
│   │   ├── apps
│   │   │   └── nginx.yaml
│   │   └── infra
│   │       └── monitoring.yaml
│   └── staging-west
│       ├── flux-system
│       │   └── gotk-components.yaml
│       ├── apps
│       │   └── nginx.yaml
│       └── infra
│           └── monitoring.yaml
└── infra
    └── monitoring
        └── prometheus-operator.yaml

In clusters/prod-east/apps/nginx.yaml:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx
  namespace: default
spec:
  replicas: 5 # High replica count for production
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: nginx:latest
        ports:
        - containerPort: 80

And in clusters/staging-west/apps/nginx.yaml:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx
  namespace: default
spec:
  replicas: 1 # Low replica count for staging
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: nginx:latest
        ports:
        - containerPort: 80

Notice how the nginx.yaml files are identical except for the spec.replicas field. This is where the power of GitOps and Flux comes in.

To achieve this, we’ll configure Flux on each cluster to point to a specific path within our monorepo.

On prod-east cluster, we’d have a Kustomization resource in Flux that targets clusters/prod-east. This Kustomization would be defined in the Git repository itself, perhaps in clusters/prod-east/flux-system/gotk-components.yaml (though typically this initial bootstrap is done via the flux bootstrap command). The key is that Flux, running within the prod-east cluster, is configured to watch the prod-east directory.

Similarly, on staging-west, another Flux instance would be configured to watch the staging-west directory.

The flux bootstrap command is crucial here. For prod-east, you’d run something like:

flux bootstrap git \
  --url=ssh://git@github.com/your-org/your-repo.git \
  --branch=main \
  --path=./clusters/prod-east \
  --ssh-key-file=/path/to/prod-east/deploy_key

And for staging-west:

flux bootstrap git \
  --url=ssh://git@github.com/your-org/your-repo.git \
  --branch=main \
  --path=./clusters/staging-west \
  --ssh-key-file=/path/to/staging-west/deploy_key

Each Flux instance is now an independent agent, but they all read from the same source of truth: your Git repository. When you push a change to clusters/prod-east/apps/nginx.yaml, Flux on prod-east will detect it and apply the updated deployment with 5 replicas. If you change clusters/staging-west/apps/nginx.yaml to 2 replicas, Flux on staging-west will make that change.

The infra directories are for shared infrastructure components. For example, infra/monitoring/prometheus-operator.yaml could be a common manifest that is applied to both clusters. To do this, you’d create a Kustomization in clusters/prod-east/infra/monitoring.yaml that points to the root infra/monitoring path in your Git repository.

# clusters/prod-east/infra/monitoring.yaml
apiVersion: kustomize.toolkit.fluxcd.io/v1
kind: Kustomization
metadata:
  name: monitoring
  namespace: flux-system
spec:
  interval: 5m
  sourceRef:
    kind: GitRepository
    name: flux-system # This refers to the GitRepository object that Flux created during bootstrap
  path: ./infra/monitoring # Points to the shared infrastructure path
  prune: true

You’d have a similar Kustomization in clusters/staging-west/infra/monitoring.yaml. This allows you to manage shared resources across clusters without duplication in the cluster-specific directories. Flux handles applying the same manifests to different environments based on their respective configurations.

The mental model here is that your Git repository is an immutable declaration of your desired state for each cluster. Flux agents running within each cluster are responsible for observing their designated directory within that repository and making the cluster’s reality match the declared state. This separation of concerns allows for a single source of truth for your entire fleet, while still enabling environment-specific configurations.

A common pattern to manage differing configurations without duplicating entire files is to use Kustomize’s kustomization.yaml to patch or overlay base manifests. For instance, you could have a base nginx deployment in a shared base/apps/nginx directory, and then use cluster-specific kustomization.yaml files to apply patches for replica counts or image tags.

# base/apps/nginx/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx
  namespace: default
spec:
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: nginx:latest
        ports:
        - containerPort: 80

# clusters/prod-east/apps/kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- ../../base/apps/nginx # Reference the base deployment

patchesStrategicMerge:
- nginx-patch.yaml

# clusters/prod-east/apps/nginx-patch.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx
spec:
  replicas: 5

# clusters/staging-west/apps/kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- ../../base/apps/nginx # Reference the base deployment

patchesStrategicMerge:
- nginx-patch.yaml

# clusters/staging-west/apps/nginx-patch.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx
spec:
  replicas: 1

This Kustomize-centric approach further reduces duplication and centralizes common configurations.

The next step in managing multiple clusters is to consider how to handle secrets and sensitive configurations across these environments.

Want structured learning?

Take the full Flux course →