Flux, the GitOps tool, can run with surprisingly few permissions and still do its job effectively.

Let’s see Flux in action, managing a Kubernetes deployment. Imagine we have a simple frontend-deployment.yaml file in a Git repository:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: frontend
  namespace: default
spec:
  replicas: 2
  selector:
    matchLabels:
      app: frontend
  template:
    metadata:
      labels:
        app: frontend
    spec:
      containers:
      - name: frontend
        image: nginx:latest
        ports:
        - containerPort: 80

And we want Flux to deploy this. First, we need to tell Flux where to look for this file. This is typically done via a Kustomization resource (or HelmRelease for Helm charts). Here’s a minimal Kustomization pointing to our Git repo:

apiVersion: kustomize.toolkit.fluxcd.io/v1beta2
kind: Kustomization
metadata:
  name: frontend-app
  namespace: flux-system # Flux typically runs in this namespace
spec:
  interval: 5m0s
  path: ./path/to/your/repo # The directory within your Git repo
  prune: true
  sourceRef:
    kind: GitRepository
    name: flux-gotk-system # A GitRepository resource we'll define
  validation: client

And the GitRepository resource:

apiVersion: source.toolkit.fluxcd.io/v1beta2
kind: GitRepository
metadata:
  name: flux-gotk-system
  namespace: flux-system
spec:
  interval: 1m0s
  url: ssh://git@github.com/your-org/your-repo.git # Your Git repo URL
  ref:
    branch: main

When Flux reconciles these resources, it will:

  1. Fetch the Git repository defined by flux-gotk-system.
  2. Look for the frontend-deployment.yaml at ./path/to/your/repo within that Git repo.
  3. Apply that manifest to the default namespace in your Kubernetes cluster.
  4. If prune: true, it will also remove any resources in the default namespace that are not defined in your Git repository.

The core problem Flux solves is bridging the gap between a desired state defined in Git and the actual state of your Kubernetes cluster, automating the reconciliation process. It does this by continuously observing both sources (Git and Kubernetes) and making changes to Kubernetes to match Git.

Flux achieves this through a set of controllers, each responsible for a specific reconciliation loop. The kustomize-controller (or helm-controller) is the one that actually applies your manifests. It watches Kustomization (or HelmRelease) resources, pulls the source code, and uses the Kubernetes API to create, update, or delete resources.

The key to least-privilege permissions lies in understanding what each controller actually needs to do. The kustomize-controller needs to create, get, list, watch, and update deployments, services, ingresses, configmaps, secrets, and other Kubernetes objects within specific namespaces. It doesn’t need to manage pods directly, nor does it need permissions cluster-wide unless you’re deploying to multiple namespaces.

The source-controller is responsible for fetching artifacts from Git, OCI registries, or Helm repositories. It needs permissions to create, get, list, watch, and delete gitrepositories, helmrepositories, and helmcharts resources, and crucially, it needs to be able to create artifacts in the flux-system namespace to store the fetched content.

The notification-controller handles events and alerts. It needs to create, get, list, and watch alerts and providers resources, and often needs to create events.

The helm-controller specifically manages Helm releases. It needs permissions to create, get, list, watch, and update helmreleases and helmcharts, and also needs to interact with the Kubernetes API to deploy Helm-generated resources.

The most surprising true thing about least-privilege for Flux is that the kustomize-controller (and helm-controller) can often manage resources in namespaces other than flux-system without needing to be deployed into those namespaces. You can grant it list, watch, and get permissions on deployments, pods, services, etc., in default, staging, and production, all from a single kustomize-controller instance running in flux-system.

Here’s how a minimal ClusterRole for the kustomize-controller might look:

apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: flux-kustomize-controller
rules:
- apiGroups: ["apps"]
  resources: ["deployments", "statefulsets", "daemonsets"]
  verbs: ["create", "get", "list", "watch", "update", "patch", "delete"]
- apiGroups: [""]
  resources: ["pods", "services", "configmaps", "secrets", "serviceaccounts", "persistentvolumeclaims"]
  verbs: ["create", "get", "list", "watch", "update", "patch", "delete"]
- apiGroups: ["networking.k8s.io"]
  resources: ["ingresses", "networkpolicies"]
  verbs: ["create", "get", "list", "watch", "update", "patch", "delete"]
# ... add other resource types as needed for your applications ...
- apiGroups: ["kustomize.toolkit.fluxcd.io"]
  resources: ["kustomizations"]
  verbs: ["create", "get", "list", "watch", "update", "patch", "delete"]
- apiGroups: ["source.toolkit.fluxcd.io"]
  resources: ["gitrepositories", "helmrepositories", "helmcharts", "buckets"]
  verbs: ["get", "list", "watch"]

And the corresponding ClusterRoleBinding to link this ClusterRole to the kustomize-controller’s ServiceAccount (which is typically flux-system in the flux-system namespace):

apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: flux-kustomize-controller
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: flux-kustomize-controller
subjects:
- kind: ServiceAccount
  name: kustomize-controller
  namespace: flux-system

Notice that the verbs for kustomizations and source.toolkit.fluxcd.io resources are full access, as the controller needs to manage its own configuration. However, for the application resources (deployments, services, etc.), it has full control. If you wanted to restrict Flux to only specific namespaces, you’d create Role and RoleBinding resources instead of ClusterRole and ClusterRoleBinding, and scope them to those namespaces.

The source-controller needs similar permissions, but primarily focused on fetching and storing artifacts. It needs to create, get, list, watch, and delete artifacts in the flux-system namespace.

One detail that often trips people up is that the source-controller needs permission to create artifacts resources in the flux-system namespace. These are custom resources Flux uses to store the fetched Git repository content. Without this, it can’t cache the repository state, and thus can’t provide it to the kustomize-controller.

The next concept to explore is how to integrate external secrets management systems like HashiCorp Vault or AWS Secrets Manager with Flux.

Want structured learning?

Take the full Flux course →