← Back to Guide
GitOps & CI/CD L1 · INTRO ~45 min

Bootstrap Flux v2 with GitHub Repository

Bootstrap Flux v2 onto a Kubernetes cluster pointing at a personal GitHub repository. Deploy a sample application via a Kustomization resource. Make a change in Git and watch the cluster reconcile automatically.

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

Steps

01

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
02

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
The bootstrap command pushes a 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.
03

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
04

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
05

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
06

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
07

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