GitOps with Flux: Practical Implementation Guide

GitOps treats Git as the single source of truth for infrastructure and application state. Flux watches a Git repository and automatically applies changes to a Kubernetes cluster, replacing manual kube

Introduction#

GitOps treats Git as the single source of truth for infrastructure and application state. Flux watches a Git repository and automatically applies changes to a Kubernetes cluster, replacing manual kubectl apply or helm upgrade commands. This post covers Flux v2 setup and practical patterns.

Core Concepts#

Source Controller: watches Git repositories, Helm repositories, and OCI registries for changes.

Kustomize Controller: applies Kustomize configurations from sources.

Helm Controller: applies HelmReleases from Helm charts in sources.

Notification Controller: sends alerts to Slack, Teams, or webhooks.

Image Automation Controller: automatically updates image tags in Git when new images are pushed.

Installing Flux#

1
2
3
4
5
6
7
8
9
10
11
12
13
# Install Flux CLI
curl -s https://fluxcd.io/install.sh | sudo bash

# Bootstrap: installs Flux into the cluster and creates a Git repository
flux bootstrap github \
  --owner=my-org \
  --repository=my-fleet \
  --branch=main \
  --path=clusters/production \
  --personal  # use personal access token

# Flux commits its own manifests to the repo and installs them
# The cluster is now managed by GitOps

Repository Structure#

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
my-fleet/
├── clusters/
│   ├── production/
│   │   ├── flux-system/       # Flux's own manifests (auto-generated)
│   │   ├── sources.yaml       # GitRepository, HelmRepository sources
│   │   └── apps.yaml          # Kustomization pointing to apps/
│   └── staging/
├── infrastructure/
│   ├── cert-manager/
│   ├── nginx-ingress/
│   └── monitoring/
└── apps/
    ├── production/
    │   ├── api/
    │   └── worker/
    └── staging/

Defining a Source#

1
2
3
4
5
6
7
8
9
10
11
12
13
# clusters/production/sources.yaml
apiVersion: source.toolkit.fluxcd.io/v1
kind: GitRepository
metadata:
  name: my-apps
  namespace: flux-system
spec:
  interval: 1m       # poll interval
  url: https://github.com/my-org/my-apps
  ref:
    branch: main
  secretRef:
    name: github-deploy-key

Deploying with Kustomize#

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# clusters/production/apps.yaml
apiVersion: kustomize.toolkit.fluxcd.io/v1
kind: Kustomization
metadata:
  name: apps
  namespace: flux-system
spec:
  interval: 10m
  sourceRef:
    kind: GitRepository
    name: my-apps
  path: ./apps/production
  prune: true          # delete resources removed from Git
  wait: true           # wait for resources to become ready
  timeout: 5m
  healthChecks:
  - apiVersion: apps/v1
    kind: Deployment
    name: api
    namespace: production
1
2
3
4
5
6
7
8
9
10
# apps/production/api/kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- deployment.yaml
- service.yaml
- ingress.yaml
images:
- name: my-registry/api
  newTag: "2.3.1"    # image tag updated by image automation

Deploying Helm Charts with Flux#

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# apps/production/nginx-ingress/helmrelease.yaml
apiVersion: helm.toolkit.fluxcd.io/v2
kind: HelmRelease
metadata:
  name: nginx-ingress
  namespace: flux-system
spec:
  interval: 1h
  chart:
    spec:
      chart: ingress-nginx
      version: "4.9.0"
      sourceRef:
        kind: HelmRepository
        name: ingress-nginx
        namespace: flux-system
  targetNamespace: ingress-nginx
  install:
    createNamespace: true
  values:
    controller:
      replicaCount: 2
      resources:
        requests:
          cpu: 100m
          memory: 128Mi

Image Automation#

Flux can automatically update image tags in Git when new images are pushed to a registry.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
# Image policy: track semver tags
apiVersion: image.toolkit.fluxcd.io/v1beta2
kind: ImageRepository
metadata:
  name: api
  namespace: flux-system
spec:
  image: my-registry.example.com/api
  interval: 1m
---
apiVersion: image.toolkit.fluxcd.io/v1beta2
kind: ImagePolicy
metadata:
  name: api
  namespace: flux-system
spec:
  imageRepositoryRef:
    name: api
  policy:
    semver:
      range: ">=2.0.0 <3.0.0"  # track 2.x.x releases
---
# Automation: commit image tag updates to Git
apiVersion: image.toolkit.fluxcd.io/v1beta2
kind: ImageUpdateAutomation
metadata:
  name: flux-system
  namespace: flux-system
spec:
  interval: 5m
  sourceRef:
    kind: GitRepository
    name: my-apps
  git:
    checkout:
      ref:
        branch: main
    commit:
      author:
        email: fluxbot@example.com
        name: Flux
      messageTemplate: "Auto-update images to latest"
    push:
      branch: main
  update:
    path: ./apps
    strategy: Setters
1
2
3
4
# Marker in kustomization.yaml for image automation
images:
- name: my-registry/api
  newTag: "2.3.1" # {"$imagepolicy": "flux-system:api:tag"}

Notifications#

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# Send Slack alert on failed reconciliation
apiVersion: notification.toolkit.fluxcd.io/v1
kind: Alert
metadata:
  name: on-call-slack
  namespace: flux-system
spec:
  providerRef:
    name: slack
  eventSeverity: error
  eventSources:
  - kind: Kustomization
    name: "*"
  - kind: HelmRelease
    name: "*"
---
apiVersion: notification.toolkit.fluxcd.io/v1
kind: Provider
metadata:
  name: slack
  namespace: flux-system
spec:
  type: slack
  channel: "#alerts-production"
  secretRef:
    name: slack-webhook-url

Monitoring Flux#

1
2
3
4
5
6
7
8
9
10
11
12
# View reconciliation status
flux get all -n flux-system

# View recent events
flux events --watch

# Force reconciliation (don't wait for interval)
flux reconcile source git my-apps
flux reconcile kustomization apps

# View diff before Flux applies
flux diff kustomization apps

Conclusion#

Flux turns cluster state management from an imperative process (running commands) to a declarative one (updating Git). Prune enables automatic cleanup of removed resources. Image automation closes the loop from CI (build and push image) to CD (update cluster). Start with Kustomize-based deployments, add HelmReleases for third-party charts, and enable image automation once the basic GitOps flow is stable.

Contents