GitOps, when done right, means your Git repository isn’t just a place to store code; it’s the single source of truth for your entire infrastructure and application state.
Let’s see it in action. Imagine you’re managing a Kubernetes cluster. Your Git repo might look something like this:
.
├── apps/
│ ├── frontend/
│ │ ├── base/
│ │ │ ├── deployment.yaml
│ │ │ └── service.yaml
│ │ └── overlays/
│ │ └── production/
│ │ ├── kustomization.yaml
│ │ └── ingress.yaml
│ └── backend/
│ ├── base/
│ │ ├── deployment.yaml
│ │ └── service.yaml
│ └── overlays/
│ └── staging/
│ ├── kustomization.yaml
│ └── configmap.yaml
├── clusters/
│ └── production/
│ ├── cluster-config.yaml
│ └── flux-system/
│ └── gotk-sync.yaml
└── infrastructure/
├── cert-manager/
│ ├── base/
│ │ └── helmrelease.yaml
│ └── overlays/
│ └── production/
│ ├── kustomization.yaml
│ └── values.yaml
└── nginx-ingress/
├── base/
│ └── helmrelease.yaml
└── overlays/
└── staging/
├── kustomization.yaml
└── values.yaml
This structure, often called a "monorepo" for GitOps, separates concerns: apps for deployable workloads, clusters for cluster-specific configurations (like connecting Flux to the cluster), and infrastructure for foundational services. Within apps and infrastructure, you might see a base directory for common Kubernetes manifests and an overlays directory for environment-specific customizations using tools like Kustomize or Helm.
The core problem GitOps solves is the disconnect between the desired state of your systems and their actual state. Manually updating deployments, configurations, and infrastructure leads to configuration drift, inconsistencies, and a lack of auditability. GitOps brings these declarative configurations into Git, and an automated process ensures the cluster matches what’s in Git.
The magic happens with a GitOps agent running in your cluster, most commonly Flux or Argo CD. This agent continuously watches your Git repository. When it detects a change (a new commit), it pulls that change and applies it to the cluster. Your clusters/production/flux-system/gotk-sync.yaml might look like this:
apiVersion: source.toolkit.fluxcd.io/v1beta2
kind: GitRepository
metadata:
name: my-gitops-repo
namespace: flux-system
spec:
interval: 1m
url: ssh://git@github.com/your-org/your-gitops-repo.git
ref:
branch: main
secretRef:
name: git-ssh-key
---
apiVersion: kustomize.toolkit.fluxcd.io/v1
kind: Kustomization
metadata:
name: apps-production
namespace: flux-system
spec:
interval: 10m
path: ./apps/overlays/production
sourceRef:
kind: GitRepository
name: my-gitops-repo
prune: true
wait: true
This Flux GitRepository resource tells Flux where to find your Git repo, and the Kustomization resource tells it what to apply from that repo (./apps/overlays/production) and how often to check for updates (interval: 10m). prune: true is critical; it means any resources defined in Git but no longer present will be deleted from the cluster, ensuring perfect state reconciliation. wait: true ensures that the reconciliation process waits for Kubernetes resources to become ready before marking the Kustomization as healthy.
Sync policies dictate how often the GitOps agent polls the Git repository for changes. Common intervals range from 1m to 10m. Shorter intervals mean faster propagation of changes but higher load on the Git provider and the agent. Longer intervals reduce load but increase the latency between a commit and its application. The choice depends on your blast radius tolerance for outdated configurations.
Drift detection is the process of identifying when the actual state of the cluster deviates from the desired state defined in Git. Your GitOps agent does this automatically as part of its reconciliation loop. If a change is made to the cluster directly (e.g., kubectl edit deployment frontend-deployment), the agent will detect this mismatch during its next sync and revert the change to match the Git definition. For more advanced drift detection and reporting, tools like flux diff or specific features within Argo CD can be used to preview changes or highlight deviations without automatic reconciliation.
One aspect often overlooked is how to handle secrets. Storing plain-text secrets in Git is a non-starter. Solutions like Mozilla SOPS, Sealed Secrets, or external secret management tools integrated with your GitOps workflow are essential. For example, with SOPS, you encrypt your secrets file in Git, and the GitOps agent, with the appropriate decryption key, decrypts them just before applying them to the cluster. This maintains the GitOps principle of Git being the source of truth while protecting sensitive information.
The next logical step is exploring how to manage complex application lifecycles, including progressive rollouts and automated rollbacks based on health metrics.