•6 min read

GitOps with Flux: Declarative Infrastructure and CD for Kubernetes

GitOps with Flux: Declarative Infrastructure and CD for Kubernetes

GitOps is the operational model where Git is the single source of truth for your infrastructure and application desired state, and automated controllers inside your Kubernetes cluster continuously reconcile real-world state to match Git.

While ArgoCD provides a popular centralized Web UI, Flux (v2) is built on the GitOps Toolkit (GOTK): a modular set of Kubernetes Custom Resource Definitions (CRDs) and specialized controllers that run natively in-cluster. Flux excels at multi-tenant environments, automated image updating, and minimal resource footprint.

This guide walks through setting up production-grade GitOps with Flux v2, including Kustomize reconciliation, Helm releases, SOPS secrets encryption, and automated container image PRs.


Audio Briefing
0:00 / 0:00

1. Flux Architecture: The GitOps Toolkit

Flux breaks GitOps down into dedicated single-responsibility controllers:

                  Git Repository (GitHub / GitLab)
                                │
                                ▼ (Pulls Git commit / tag / branch)
                     [ source-controller ]
                                │
         ┌──────────────────────┴──────────────────────┐
         ▼ (Raw manifests & Kustomize)                 ▼ (Helm Charts)
[ kustomize-controller ]                      [ helm-controller ]
         │                                             │
         ▼ (Applies with Prune & Health Checks)        ▼ (Executes Helm engine)
   Kubernetes API                                Kubernetes API
         ▲                                             ▲
         └──────────────────────┬──────────────────────┘
                                │ (Watches OCI Registry & commits new tags to Git)
                 [ image-automation-controller ]
  • source-controller: Fetches Git repos, Helm charts, OCI artifacts, and S3 buckets into cluster storage.
  • kustomize-controller: Runs Kustomize overlays, decrypts SOPS secrets, applies manifests to the API server, and automatically prunes deleted resources.
  • helm-controller: Declaratively manages the lifecycle of Helm chart releases.
  • notification-controller: Emits events to Slack, Discord, or webhooks, and receives incoming webhooks from Git/OCI registries to trigger instant reconciliations.
  • image-automation-controller: Detects new container tags in Docker registries and automatically commits updated image tags back to your Git repo.

Advertisement

2. Production Monorepo Repository Structure

A resilient multi-environment repository layout cleanly separates base infrastructure from tenant applications:

fleet-infra/
├── clusters/
│   ├── staging/
│   │   ├── flux-system/        # Bootstrap manifests
│   │   ├── infrastructure.yaml # Reconciles /infrastructure/staging
│   │   └── apps.yaml           # Reconciles /apps/staging
│   └── production/
│       ├── flux-system/
│       ├── infrastructure.yaml
│       └── apps.yaml
├── infrastructure/
│   ├── base/
│   │   ├── ingress-nginx/
│   │   └── cert-manager/
│   └── staging/
│       └── kustomization.yaml
└── apps/
    ├── base/
    │   └── payments-api/
    └── staging/
        ├── kustomization.yaml
        └── patch-replicas.yaml

3. Core Manifests: GitRepository & Kustomization

Defining the Source: GitRepository

# clusters/staging/fleet-source.yaml
apiVersion: source.toolkit.fluxcd.io/v1
kind: GitRepository
metadata:
  name: fleet-infra
  namespace: flux-system
spec:
  interval: 1m
  url: https://github.com/my-org/fleet-infra.git
  ref:
    branch: main
  secretRef:
    name: github-deploy-token
  ignore: |
    # Exclude non-manifest directories from checksums
    /*
    !/apps
    !/infrastructure

Reconciling with Health Checks and Pruning: Kustomization

# clusters/staging/apps.yaml
apiVersion: kustomize.toolkit.fluxcd.io/v1
kind: Kustomization
metadata:
  name: staging-apps
  namespace: flux-system
spec:
  interval: 5m
  path: ./apps/staging
  prune: true               # Automatically deletes removed k8s resources
  wait: true                # Blocks until all Pods pass readiness probes
  timeout: 3m
  sourceRef:
    kind: GitRepository
    name: fleet-infra
  dependsOn:
    - name: staging-infrastructure  # Ensures ingress/CRDs exist before apps deploy
  postBuild:
    substitute:
      ENVIRONMENT: "staging"
      CLUSTER_DOMAIN: "stage.internal.net"

4. Managing Helm Releases with Drift Detection

Flux manages Helm charts natively without needing the Helm CLI installed locally:

# infrastructure/base/ingress-nginx/helm-release.yaml
apiVersion: source.toolkit.fluxcd.io/v1
kind: HelmRepository
metadata:
  name: ingress-nginx
  namespace: flux-system
spec:
  interval: 2h
  url: https://kubernetes.github.io/ingress-nginx
---
apiVersion: helm.toolkit.fluxcd.io/v2
kind: HelmRelease
metadata:
  name: ingress-nginx
  namespace: ingress-nginx
spec:
  interval: 15m
  chart:
    spec:
      chart: ingress-nginx
      version: "4.11.x"
      sourceRef:
        kind: HelmRepository
        name: ingress-nginx
        namespace: flux-system
  install:
    remediation:
      retries: 3
  upgrade:
    remediation:
      retries: 3
  driftDetection:
    mode: enabled          # Automatically reverts manual edits back to chart values
  values:
    controller:
      replicaCount: 3
      resources:
        requests:
          cpu: 100m
          memory: 128Mi

Advertisement

5. Secret Management: Encrypted In-Git with SOPS and Age

Never store plaintext secrets in Git. Flux natively decrypts SOPS encrypted YAML before applying it to the cluster:

Step 1: Encrypt the Secret with age

# Generate age private key for the cluster
age-keygen -o age.agekey

# Create in-cluster secret with the private key
kubectl create secret generic sops-age \
  --namespace=flux-system \
  --from-file=age.agekey

# Encrypt local Kubernetes secret
sops --encrypt --age $(cat age.agekey | grep public | cut -d: -f2 | xargs) \
  --encrypted-regex '^(data|stringData)$' \
  secret.yaml > apps/staging/secret.enc.yaml

Step 2: Configure Flux to Decrypt at Runtime

# In apps/staging/kustomization.yaml or root Kustomization CRD
apiVersion: kustomize.toolkit.fluxcd.io/v1
kind: Kustomization
metadata:
  name: staging-secrets
  namespace: flux-system
spec:
  interval: 10m
  path: ./apps/staging
  prune: true
  sourceRef:
    kind: GitRepository
    name: fleet-infra
  decryption:
    provider: sops
    secretRef:
      name: sops-age        # Matches private key stored in flux-system

6. Automated Image Updates (Continuous Deployment)

Flux can monitor your container registry and automatically commit new image tags directly to Git:

# apps/base/payments-api/image-policy.yaml
apiVersion: image.toolkit.fluxcd.io/v1beta2
kind: ImageRepository
metadata:
  name: payments-api
  namespace: flux-system
spec:
  image: ghcr.io/my-org/payments-api
  interval: 1m
---
apiVersion: image.toolkit.fluxcd.io/v1beta2
kind: ImagePolicy
metadata:
  name: payments-api
  namespace: flux-system
spec:
  imageRepositoryRef:
    name: payments-api
  policy:
    semver:
      range: '^1.x'        # Auto-promotes minor and patch versions
---
apiVersion: image.toolkit.fluxcd.io/v1beta1
kind: ImageUpdateAutomation
metadata:
  name: flux-system
  namespace: flux-system
spec:
  interval: 1m
  sourceRef:
    kind: GitRepository
    name: fleet-infra
  git:
    checkout:
      ref:
        branch: main
    commit:
      author:
        email: fluxcdbot@users.noreply.github.com
        name: fluxcdbot
      messageTemplate: 'chore(cd): update payments-api to {{range .Updated.Images}}{{println .}}{{end}}'
    push:
      branch: main
  update:
    path: ./apps/staging
    strategy: Setters

In your deployment YAML, add a comment setter:

spec:
  template:
    spec:
      containers:
        - name: api
          image: ghcr.io/my-org/payments-api:1.4.2 # {"$imagepolicy": "flux-system:payments-api"}

When CI pushes ghcr.io/my-org/payments-api:1.4.3, Flux detects the tag, updates this exact line in Git, commits with chore(cd)..., pushes to main, and reconciles the new Pod in staging.


Flux v2 vs ArgoCD: Production Tradeoffs

FeatureFlux v2ArgoCD
ArchitectureNative Kubernetes controllers (GOTK)Application CRD + Centralized Web Server
Web UIMinimal / Weave GitOps (optional)Rich interactive Dashboard built-in
Secret DecryptionNative SOPS & Age / KMS in controllerRequires ArgoCD plugin / external operator
Multi-ClusterLightweight agent pull per clusterCentral pull or push to remote clusters
Drift CorrectionContinuous background self-healingContinuous sync or manual sync buttons
Image AutomationNative in-repo auto-commit controllerRequires ArgoCD Image Updater (separate)

You Might Also Like

Share this article:

Stay Updated

Get the latest posts delivered straight to your inbox.

Free Developer Utilities

Free In-Browser Developer Tools

Clean AI CLI logs, build cron expressions, decode JWTs, and calculate chmod permissions offline.

Explore Tools
Advertisement