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
fluxCLI.
Flux Toolkit Components
Flux v2 is composed of several independent controllers installed in the flux-system namespace:
- source-controller — Watches
GitRepository,HelmRepository,HelmChart,OCIRepository, andBucketsources. Fetches artifacts and makes them available to other controllers. - kustomize-controller — Watches
Kustomizationresources. Applies Kustomize overlays (or plain YAML) from source artifacts to the cluster. - helm-controller — Watches
HelmReleaseresources. Reconciles Helm releases against the cluster, using charts from source-controller. - notification-controller — Watches
AlertandReceiverresources. Sends notifications to Slack, Teams, PagerDuty, and others. Also receives incoming webhooks. - image-reflector-controller — Scans container registries and reflects image tags into
ImageRepositoryandImagePolicyresources. - 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
./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
flux-system namespace. Check the bootstrap repo was created with the gotk-components and gotk-sync files.
kubectl — observe Flux restoring it within one interval.
Key Takeaways & Next Steps
- Flux is a set of Kubernetes controllers, not a server — every configuration is a Kubernetes CRD
flux bootstrapinstalls Flux AND sets it up to watch your repo for cluster config- Sources (GitRepository, HelmRepository, OCIRepository) fetch artifacts; other controllers consume them
prune: trueon a Kustomization makes Flux delete resources removed from Git — enable this in productionflux reconciletriggers 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.