GitOps
GitOps means driving Kubernetes changes from Git, using automation to reconcile the cluster to whatever is described in the repo. You review and approve changes in Git; controllers do the rest.
1. What is GitOps
GitOps is an operating model where:
- Git is the source of truth for Kubernetes manifests, config and policies.
- Automation watches Git and reconciles the cluster to match the desired state.
- Changes go via merge requests, not
kubectl applyfrom a laptop.
In practice:
- You treat manifests, Helm charts, Kustomize overlays and policies as code.
- You use a pull-based controller in the cluster (for example Flux) that:
- Pulls from Git.
- Applies changes.
- Heals drift when something in the cluster diverges from Git.
This gives you:
- Audit trail – every change is a commit with an author and review history.
- Repeatability – you can recreate the same state in another cluster from Git.
- Safety – you can roll back by reverting a commit and letting GitOps reconcile.
2. Core GitOps principles
-
Declarative configuration
- Everything that matters (apps, networking, ingress, security, policies) is defined declaratively.
- The cluster is expected to converge to this declared state.
-
Versioned and immutable
- All config is stored in Git with history and tags.
- You store only the desired state, not imperative scripts.
-
Automated reconciliation
- An operator (Flux) continuously compares the cluster with Git.
- If the cluster drifts, Flux attempts to bring it back to the Git state.
-
Operations via pull requests
- All changes go through GitLab merge requests.
- Reviews, approvals and CI pipelines happen before anything reaches the cluster.
-
Git is the control plane for config
- You do not treat
kubectlas the source of truth. - If it is not in Git, it is either temporary or a bug.
- You do not treat
3. How I implement GitOps
This section describes the approach used for Muppit clusters and the Blaster game.
3.1 Repositories
- App repo – for example
games/blaster:- Application code (Next.js, Phaser).
- Dockerfile.
- Kubernetes manifests under
k8s/(dev and prod overlays).
- Flux config repo – for example
flux-config:- Cluster definitions under
clusters/my-cluster/.... GitRepository,Kustomization,ImageRepository,ImagePolicyandImageUpdateAutomationobjects.- Shared infrastructure components (Cloudflare tunnel, Ingress-NGINX, cert-manager, etc).
- Cluster definitions under
This separation lets you:
- Evolve the app independently of the cluster plumbing.
- Reuse the Flux config across multiple applications.
3.2 CI and image build flow
GitLab CI builds and publishes container images; Flux deploys them.
- GitLab Runner using the Kubernetes executor runs pipelines in the
gitlabnamespace. - Kaniko builds images and pushes them to
registry.reids.net.au.
For Blaster:
- Commits to
developproduce images tagged like
registry.reids.net.au/games/blaster:dev-YYYYMMDD.IID. - Commits to
mainproduce images tagged like
registry.reids.net.au/games/blaster:prod-YYYYMMDD.IID. - Pipelines are marked
[skip ci]when Flux image automation commits back to Git.
GitLab CI does not talk to the cluster directly. It only:
- Builds images.
- Updates image references in the app repo (via Flux image automation).
- Lets Flux reconcile the running workloads.
3.3 Flux and reconciliation
Flux runs inside the cluster and owns the reconciliation loop.
Key objects:
GitRepository– tells Flux which repo and branch to watch.Kustomization– points at a path inside the repo and controls:- Sync interval.
- Pruning.
- Health checks.
ImageRepositoryandImagePolicy– tell Flux how to discover and select image tags.ImageUpdateAutomation– commits new image tags back to the app repo.
Flow:
- GitLab CI pushes a new
dev-YYYYMMDD.IIDorprod-YYYYMMDD.IIDimage tag. - Flux
ImageRepositorysees the new tag in the registry. ImagePolicyselects the latest acceptable tag (for example by timestamp).ImageUpdateAutomationupdates the image field in the deployment manifest and commits to Git.- The relevant
Kustomizationsees the commit, applies the change and waits for the rollout to become healthy.
At every point, the cluster state is driven from Git.
3.4 Environments and overlays
Environments are split by directory and Flux Kustomization objects.
For example, in the app repo:
k8s/dev/...– manifests for the internal dev environment:- Namespace
blaster. - Internal DNS such as
blaster.reids.net.au. - Less aggressive resource limits.
- Namespace
k8s/prod/...– manifests for the public production environment:- Namespace
blaster. - Public DNS such as
blaster.muppit.au. - Production resource requests, limits, probes and
NetworkPolicy.
- Namespace
In the Flux config repo:
clusters/my-cluster/blaster/10-blaster-kustomization.yamlpoints at the dev path.clusters/my-cluster/blaster-prod/10-blaster-kustomization.yamlpoints at the prod path.
This gives:
- A single app codebase.
- Clear separation of dev and prod operational concerns.
- Git as the place where you can diff and review environment changes.
3.5 Secrets and SOPS
Secrets are stored in Git in encrypted form using SOPS with age keys.
Pattern:
- Plaintext
Secretmanifests never live in Git. - Instead you keep
secret.enc.yamlencrypted with SOPS. - Flux integrates with SOPS so it can decrypt at apply time using an age key stored in the cluster.
Benefits:
- Secrets are versioned and reviewed like everything else.
- You keep compliance and auditability without leaking credentials.
3.6 Branching and GitOps
Branching interacts with GitOps as follows:
main– reflects what is in production. Only updated via MR fromdevelop.develop– integration branch used by the dev environment.- Feature branches – short-lived branches for specific changes that merge into
develop.
Flux is wired to:
- Track
developfor dev workloads. - Track
mainfor prod workloads.
When you merge a feature branch into develop:
- CI builds a new dev image.
- Flux updates the dev deployment automatically.
When you later merge develop into main:
- CI builds a new prod image.
- Flux updates the prod deployment automatically.
The Kubernetes cluster never depends on someone manually applying YAML.
4. When GitOps is and is not used
GitOps is used for:
- Long-lived Kubernetes workloads (apps, databases, ingress, tunnels).
- Cluster add-ons and infrastructure.
- Policies and security objects.
GitOps is not used for:
- One-off debug commands (
kubectl exec,kubectl logs). - Temporary exploratory resources created in a live debugging session (these should either be codified later or removed).
- Local-only development using
next devor similar.
Anything that needs to be recreated, audited or promoted between environments should be brought under GitOps.
5. Verification checklist
- All Kubernetes manifests for long-lived workloads live in Git.
- Flux (or another GitOps controller) is installed in the cluster and healthy.
- App repo and Flux config repo boundaries are clear and documented.
- GitLab CI builds images and pushes to
registry.reids.net.au. - Flux image automation updates manifests with new image tags.
- Environments (
dev,prod) are separated by directories and FluxKustomizationobjects. - Secrets are stored in Git only in SOPS-encrypted form.
-
mainreflects production;developreflects integration; feature branches are short-lived and merge intodevelop.