Back to Distributed Systems & Kubernetes Series

Flux Track Part 1: Bootstrap & Sources

June 6, 2026 Wasil Zafar 35 min read

Flux v2 takes a fundamentally different approach to GitOps from Argo CD: instead of a server you push to, it's a set of Kubernetes controllers that pull from Git. Bootstrap it once, point it at a repo, and the cluster permanently converges to whatever is in that repo.

Table of Contents

  1. Flux vs Argo CD
  2. Bootstrapping Flux
  3. Source Types
  4. Deploying Your First App
  5. flux CLI Essentials
  6. Exercises
  7. Key Takeaways & Next Steps

Flux vs Argo CD

The Pull Model

Both Flux and Argo CD implement GitOps — Git is the source of truth and the cluster converges to it. The key difference is architecture:

  • Argo CD is a server application with a web UI, a gRPC API, and CLI. You connect to Argo CD and tell it to sync. It pushes changes to clusters.
  • Flux is a set of Kubernetes controllers. There is no central server. Each controller watches a specific resource type (GitRepository, Kustomization, HelmRelease) and reconciles continuously. No UI by default; you manage it via YAML and flux CLI.
When to choose Flux over Argo CD: Prefer Flux when you want a pure Kubernetes-native operator model with no external server to manage, when you want tight Kustomize integration (Flux's Kustomization controller understands Kustomize natively), or when all configuration should be expressed as Kubernetes CRDs committed to Git.

Flux Toolkit Components

Flux v2 is composed of several independent controllers installed in the flux-system namespace:

  • source-controller — Watches GitRepository, HelmRepository, HelmChart, OCIRepository, and Bucket sources. Fetches artifacts and makes them available to other controllers.
  • kustomize-controller — Watches Kustomization resources. Applies Kustomize overlays (or plain YAML) from source artifacts to the cluster.
  • helm-controller — Watches HelmRelease resources. Reconciles Helm releases against the cluster, using charts from source-controller.
  • notification-controller — Watches Alert and Receiver resources. Sends notifications to Slack, Teams, PagerDuty, and others. Also receives incoming webhooks.
  • image-reflector-controller — Scans container registries and reflects image tags into ImageRepository and ImagePolicy resources.
  • image-automation-controller — Updates YAML in Git when a new image tag matches an ImagePolicy.

Bootstrapping Flux

Prerequisites

# Install the flux CLI
curl -s https://fluxcd.io/install.sh | sudo bash
# or on macOS
brew install fluxcd/tap/flux

# Verify the CLI
flux --version
# flux version 2.3.0

# Check cluster compatibility
flux check --pre
# ► checking prerequisites
# ✔ Kubernetes 1.29.0 >=1.26.0-0
# ✔ prerequisites checks passed

# You need a GitHub personal access token with repo scope
export GITHUB_TOKEN=ghp_...
export GITHUB_USER=your-github-username

GitHub Bootstrap

# Bootstrap Flux onto your cluster, pointing it at a GitHub repo.
# If the repo doesn't exist, Flux creates it.
# If it does exist, Flux adds its manifests to it.

flux bootstrap github \
  --owner=$GITHUB_USER \
  --repository=grade-api-gitops \
  --branch=main \
  --path=./clusters/my-cluster \
  --personal \
  --private=false

# Output:
# ► connecting to github.com
# ✔ repository "grade-api-gitops" created
# ► cloning branch "main" from Git repository "https://github.com/your-user/grade-api-gitops.git"
# ✔ cloned repository
# ► generating component manifests
# ✔ generated component manifests
# ► committing component manifests to "main" ("clusters/my-cluster")
# ✔ committed component manifests ("clusters/my-cluster")
# ► pushing component manifests to "https://github.com/your-user/grade-api-gitops.git"
# ✔ pushed component manifests
# ► applying component manifests
# ✔ applied component manifests
# ◎ waiting for Flux components to be ready
# ✔ all components are healthy
# ✔ bootstrap finished
# For GitLab, Bitbucket, or self-hosted Gitea:
flux bootstrap gitlab \
  --owner=your-group \
  --repository=grade-api-gitops \
  --branch=main \
  --path=clusters/my-cluster \
  --token-auth

# For generic Git with SSH (no provider integration):
flux bootstrap git \
  --url=ssh://git@github.com/your-org/grade-api-gitops \
  --branch=main \
  --path=clusters/my-cluster \
  --ssh-key-algorithm=ecdsa

What Bootstrap Does

After bootstrap, your Git repo contains:

grade-api-gitops/
└── clusters/
    └── my-cluster/
        └── flux-system/
            ├── gotk-components.yaml   # All Flux controllers as Deployments
            ├── gotk-sync.yaml         # The Flux GitRepository + Kustomization pointing back at this repo
            └── kustomization.yaml     # Standard Kustomize file referencing the two above
# gotk-sync.yaml — this is Flux watching itself
---
apiVersion: source.toolkit.fluxcd.io/v1
kind: GitRepository
metadata:
  name: flux-system
  namespace: flux-system
spec:
  interval: 1m0s
  ref:
    branch: main
  secretRef:
    name: flux-system   # SSH key or PAT created during bootstrap
  url: ssh://git@github.com/your-user/grade-api-gitops
---
apiVersion: kustomize.toolkit.fluxcd.io/v1
kind: Kustomization
metadata:
  name: flux-system
  namespace: flux-system
spec:
  interval: 10m0s
  path: ./clusters/my-cluster
  prune: true
  sourceRef:
    kind: GitRepository
    name: flux-system
Key concept — self-reconciliation: The bootstrap Kustomization points at ./clusters/my-cluster in your repo. Flux watches that path and applies everything it finds there. You add apps by creating YAML files in that path and pushing to Git. Flux pulls and applies automatically.

Source Types

GitRepository

# A GitRepository tells source-controller where to fetch manifests from.
# It does NOT apply anything — it just fetches and stores artifacts.
apiVersion: source.toolkit.fluxcd.io/v1
kind: GitRepository
metadata:
  name: grade-api
  namespace: flux-system
spec:
  interval: 1m          # How often to check for new commits
  url: https://github.com/your-org/grade-api
  ref:
    branch: main
    # Or target a specific tag:
    # tag: v1.2.3
    # Or a semver range:
    # semver: ">=1.0.0 <2.0.0"
    # Or a specific commit:
    # commit: abc1234

  # For private repos, reference a Secret with credentials
  secretRef:
    name: grade-api-credentials

  # Ignore certain paths (reduces reconciliation noise)
  ignore: |
    /*.md
    /docs/
    /tests/

  # Timeout for cloning
  timeout: 60s
# Create a secret for private HTTPS repos
kubectl create secret generic grade-api-credentials \
  --namespace=flux-system \
  --from-literal=username=git \
  --from-literal=password=$GITHUB_TOKEN

# For SSH repos
flux create secret git grade-api-ssh \
  --url=ssh://git@github.com/your-org/grade-api \
  --namespace=flux-system
# Then add the public key as a deploy key on GitHub
# Check source status
flux get sources git
# NAME       REVISION          SUSPENDED  READY  MESSAGE
# flux-system main/abc1234     False      True   stored artifact for revision 'main/abc1234'
# grade-api  main/def5678      False      True   stored artifact for revision 'main/def5678'

# Describe a specific source
flux describe source git grade-api

HelmRepository

# A HelmRepository indexes a Helm chart repository so helm-controller
# can pull charts from it.
apiVersion: source.toolkit.fluxcd.io/v1
kind: HelmRepository
metadata:
  name: bitnami
  namespace: flux-system
spec:
  interval: 1h          # How often to re-index the chart repository
  url: https://charts.bitnami.com/bitnami

---
# For OCI-based Helm repositories (Part 3)
apiVersion: source.toolkit.fluxcd.io/v1beta2
kind: HelmRepository
metadata:
  name: podinfo
  namespace: flux-system
spec:
  type: oci
  interval: 5m
  url: oci://ghcr.io/stefanprodan/charts
# List Helm repositories
flux get sources helm
# NAME     REVISION   SUSPENDED  READY  MESSAGE
# bitnami       False      True   stored artifact: revision 'sha256:...'

OCIRepository

# An OCIRepository fetches artifacts directly from an OCI registry.
# Useful for distributing Kustomize bundles or plain YAML as OCI images.
apiVersion: source.toolkit.fluxcd.io/v1beta2
kind: OCIRepository
metadata:
  name: grade-api-manifests
  namespace: flux-system
spec:
  interval: 5m
  url: oci://ghcr.io/your-org/grade-api-manifests
  ref:
    tag: latest
    # Or digest for immutable references:
    # digest: sha256:abc123...
  # For private OCI registries
  secretRef:
    name: ghcr-credentials
# Create OCI registry credentials
kubectl create secret docker-registry ghcr-credentials \
  --namespace=flux-system \
  --docker-server=ghcr.io \
  --docker-username=$GITHUB_USER \
  --docker-password=$GITHUB_TOKEN

Deploying Your First App

Add these files to your Git repo at clusters/my-cluster/apps/grade-api/ and push:

# clusters/my-cluster/apps/grade-api/namespace.yaml
apiVersion: v1
kind: Namespace
metadata:
  name: grade-api
# clusters/my-cluster/apps/grade-api/source.yaml
apiVersion: source.toolkit.fluxcd.io/v1
kind: GitRepository
metadata:
  name: grade-api
  namespace: flux-system
spec:
  interval: 1m
  url: https://github.com/your-org/grade-api
  ref:
    branch: main
# clusters/my-cluster/apps/grade-api/kustomization.yaml (Flux Kustomization, not a kustomize file)
apiVersion: kustomize.toolkit.fluxcd.io/v1
kind: Kustomization
metadata:
  name: grade-api
  namespace: flux-system
spec:
  interval: 5m
  sourceRef:
    kind: GitRepository
    name: grade-api
  path: ./deploy/k8s        # Path within the grade-api repo
  prune: true               # Delete resources removed from Git
  targetNamespace: grade-api
  healthChecks:
    - apiVersion: apps/v1
      kind: Deployment
      name: grade-api
      namespace: grade-api
  timeout: 2m
# Push to Git — Flux will detect and apply within 1 minute
git add clusters/my-cluster/apps/grade-api/
git commit -m "feat: add grade-api application"
git push

# Force reconciliation immediately (don't wait for poll interval)
flux reconcile source git flux-system
flux reconcile kustomization flux-system

# Watch Kustomization status
flux get kustomizations --watch
# NAME       REVISION          SUSPENDED  READY  MESSAGE
# flux-system main/abc1234     False      True   Applied revision: main/abc1234
# grade-api  main/def5678      False      True   Applied revision: main/def5678

# Check all Flux resources
flux get all

flux CLI Essentials

# ── STATUS ────────────────────────────────────────────────────────
flux check                     # Verify Flux installation health
flux get all                   # All Flux resources and their status
flux get kustomizations        # Kustomization status
flux get helmreleases          # HelmRelease status
flux get sources all           # All source statuses

# ── RECONCILIATION ────────────────────────────────────────────────
flux reconcile source git flux-system          # Force Git pull
flux reconcile kustomization grade-api         # Force apply
flux reconcile helmrelease grade-api -n apps   # Force Helm sync

# ── SUSPEND / RESUME ─────────────────────────────────────────────
flux suspend kustomization grade-api   # Pause reconciliation
flux resume kustomization grade-api    # Resume reconciliation

# ── EVENTS & DEBUGGING ────────────────────────────────────────────
flux events                            # Recent Flux events
flux logs                              # Controller logs (all controllers)
flux logs --kind=Kustomization         # Logs for a specific kind
flux logs --follow                     # Tail logs

# ── DIFFING (requires flux 2.x) ──────────────────────────────────
flux diff kustomization grade-api      # What would change on next reconcile

# ── EXPORTING ────────────────────────────────────────────────────
flux export source git grade-api       # Export resource as YAML
flux export kustomization grade-api

Exercises

Exercise 1 — Bootstrap: Bootstrap Flux onto a Kind or Minikube cluster pointing at a new GitHub repo. Verify all Flux components are running in the flux-system namespace. Check the bootstrap repo was created with the gotk-components and gotk-sync files.
Exercise 2 — First App: Create a simple Deployment + Service in a separate repo. Create a GitRepository source pointing at it and a Kustomization to apply it. Push to Git and watch Flux reconcile it. Delete a resource manually from kubectl — observe Flux restoring it within one interval.
Exercise 3 — Suspend and Drift: Suspend the grade-api Kustomization. Scale its Deployment to 0 replicas manually. Resume the Kustomization and watch Flux restore the replica count. This demonstrates self-healing.

Key Takeaways & Next Steps

Key Takeaways:
  • Flux is a set of Kubernetes controllers, not a server — every configuration is a Kubernetes CRD
  • flux bootstrap installs Flux AND sets it up to watch your repo for cluster config
  • Sources (GitRepository, HelmRepository, OCIRepository) fetch artifacts; other controllers consume them
  • prune: true on a Kustomization makes Flux delete resources removed from Git — enable this in production
  • flux reconcile triggers immediate sync without waiting for poll interval
  • Everything in Flux is expressed as Kubernetes resources committed to Git

Next in This Track

In Part 2: Kustomization & Reconciliation, we go deep on the Flux Kustomization resource — health checks, dependencies between Kustomizations, cross-namespace references, and managing complex multi-environment deployments with Flux.