Objective
Flux v2 is a set of controllers that implement GitOps for Kubernetes. After bootstrapping, the cluster continuously reconciles its state against Git — you never run kubectl apply manually again. This exercise sets up the complete GitOps loop: a GitHub repo as the source of truth, Flux watching for changes, and a real application deployment managed entirely through Git commits.
Prerequisites
- Kubernetes cluster (local kind/minikube or cloud) with kubectl access
- GitHub account and a new empty repository created for this exercise
- GitHub Personal Access Token (PAT) with repo permissions
- Flux CLI installed:
curl -s https://fluxcd.io/install.sh | sudo bash
Steps
Pre-flight checks
# Verify Flux CLI is installed flux version # Verify cluster meets Flux requirements flux check --pre # All checks should pass before proceeding # Set environment variables export GITHUB_TOKEN=ghp_yourpersonalaccesstoken export GITHUB_USER=yourgithubusername export GITHUB_REPO=flux-gitops-demo
Bootstrap Flux onto the cluster
The bootstrap command installs Flux controllers, creates a deploy key on the GitHub repo, and pushes the Flux manifests to the repo. Flux then manages itself via GitOps.
# Bootstrap Flux — this command is idempotent and safe to re-run flux bootstrap github \ --owner=$GITHUB_USER \ --repository=$GITHUB_REPO \ --branch=main \ --path=clusters/my-cluster \ --personal \ --private=false # Watch Flux controllers start kubectl get pods -n flux-system -w # Expected pods: # helm-controller-* Running # kustomize-controller-* Running # notification-controller-* Running # source-controller-* Running # Verify Flux installed correctly flux check
flux-system directory to your repo with all Flux CRDs and controller deployments. Flux then reconciles itself — changes to these files in Git will be applied to the cluster automatically.Clone the repository and set up the app directory structure
# Clone the repo that Flux bootstrapped git clone https://github.com/$GITHUB_USER/$GITHUB_REPO cd $GITHUB_REPO # Create directory structure for the sample app mkdir -p apps/base/nginx mkdir -p apps/staging # Create the base Deployment cat > apps/base/nginx/deployment.yaml << 'EOF' apiVersion: apps/v1 kind: Deployment metadata: name: nginx-demo namespace: default spec: replicas: 2 selector: matchLabels: app: nginx-demo template: metadata: labels: app: nginx-demo spec: containers: - name: nginx image: nginx:1.25.3 # pinned tag ports: - containerPort: 80 resources: requests: {cpu: 50m, memory: 64Mi} limits: {cpu: 200m, memory: 128Mi} EOF # Create kustomization.yaml for the base cat > apps/base/nginx/kustomization.yaml << 'EOF' apiVersion: kustomize.config.k8s.io/v1beta1 kind: Kustomization resources: - deployment.yaml EOF
Create a Flux Kustomization to deploy the app
A Flux Kustomization (not to be confused with a Kustomize kustomization.yaml) tells Flux which path in the Git repo to reconcile and how often.
# Create Flux Kustomization resource cat > clusters/my-cluster/nginx-app.yaml << 'EOF' apiVersion: kustomize.toolkit.fluxcd.io/v1 kind: Kustomization metadata: name: nginx-app namespace: flux-system spec: interval: 1m0s # Reconcile every minute path: ./apps/base/nginx prune: true # Delete resources removed from Git sourceRef: kind: GitRepository name: flux-system # Points to the repo Flux bootstrapped targetNamespace: default healthChecks: - apiVersion: apps/v1 kind: Deployment name: nginx-demo namespace: default EOF # Commit and push to trigger Flux git add -A git commit -m "feat: add nginx demo application" git push origin main
Watch the reconciliation loop
Flux will detect the new commit within 60 seconds and apply the changes. Watch the reconciliation in real time.
# Watch Flux reconcile (the GitOps loop) flux get kustomizations --watch # Expected output progression: # NAME READY MESSAGE # flux-system True Applied revision: main@sha1:abc123 # nginx-app False Reconciliation in progress # nginx-app True Applied revision: main@sha1:abc123 # Verify the deployment was created kubectl get deployment nginx-demo kubectl get pods -l app=nginx-demo # Force immediate reconciliation (don't wait for interval) flux reconcile kustomization nginx-app --with-source
Make a change in Git and observe automatic sync
Change the replica count in Git and verify Flux detects and applies the change without any manual kubectl commands.
# Change replicas from 2 to 3 in Git sed -i 's/replicas: 2/replicas: 3/' apps/base/nginx/deployment.yaml git add -A git commit -m "scale: increase nginx replicas to 3" git push origin main # Watch Flux detect and apply the change flux get kustomizations --watch # In another terminal, watch the deployment scale kubectl get deployment nginx-demo -w # Verify final state matches Git kubectl get deployment nginx-demo \ -o jsonpath='{.spec.replicas}' # Expected: 3 # View the full GitOps event history flux events --for Kustomization/nginx-app
Understand the prune behaviour
With prune: true, deleting a resource from Git will delete it from the cluster. This is a critical GitOps feature — the cluster always matches Git exactly.
# Remove the deployment from Git rm apps/base/nginx/deployment.yaml # Update kustomization.yaml to remove the reference cat > apps/base/nginx/kustomization.yaml << 'EOF' apiVersion: kustomize.config.k8s.io/v1beta1 kind: Kustomization resources: [] EOF git add -A git commit -m "test: remove nginx deployment (prune test)" git push flux reconcile kustomization nginx-app --with-source kubectl get deployment nginx-demo # Expected: Error from server (NotFound) # Flux pruned the resource because it was removed from Git # Restore the deployment git revert HEAD --no-edit git push flux reconcile kustomization nginx-app --with-source
Success Criteria
Further Reading
- Flux v2 documentation — fluxcd.io/flux/get-started
- Flux Bootstrap — fluxcd.io/flux/installation/bootstrap/github
- Flux Kustomization API — fluxcd.io/flux/components/kustomize/kustomizations
- GitOps principles — opengitops.dev