The problem GitOps removes
Without it, the real state of a cluster lives in the memory of whoever last ran kubectl apply. Staging and production drift apart. A hotfix applied during an incident is never committed. A pipeline holds cluster-admin credentials, so a compromised CI runner is a compromised cluster.
GitOps deployment on Kubernetes fixes this by inverting the direction. Instead of CI pushing changes into the cluster, a controller inside the cluster pulls from Git and reconciles. The GitOps principle is simple, and doing it well takes decisions about structure, secrets and promotion. We make these as part of our Kubernetes consulting work, with the wider approach in our GitOps, Helm, Argo CD and Flux service.
What good looks like
- Every deployed resource is traceable to a commit and a reviewer.
- The cluster cannot silently differ from Git. Drift is corrected or alerted.
- Rolling back is a revert, and it works on a bad day.
- CI has no standing credentials for production clusters.
- Adding a service or environment follows a documented pattern, not tribal knowledge.
Argo CD or Flux
Both reconcile Git to a cluster, and both are widely used in production. The differences are of style.
| Aspect | Argo CD | Flux |
|---|---|---|
| Interface | Web UI, CLI | CLI and CRDs, optional UIs |
| Core model | Application and ApplicationSet | GitRepository, Kustomization, HelmRelease |
| Multi-cluster | One instance can manage many | Usually a controller per cluster |
| Image automation | Via separate tooling | Built-in image automation controllers |
| Learning curve | Gentle, visual | Steeper but consistent |
If your developers want to see sync status and logs in a browser, Argo CD is often the more welcoming choice. If you want minimal footprint and everything as Kubernetes objects, Flux fits. Our GitOps with Argo CD on EKS tutorial walks through one implementation.
Repository structure that survives growth
Structure matters more than the tool. We usually separate application source from deployment configuration, then organise the config repository by environment with a shared base:
platform-config/
apps/
api/
base/ # Deployment, Service, config
overlays/
dev/
staging/
production/
clusters/
dev/ # Which apps run in dev cluster
production/
infrastructure/ # ingress, cert-manager, monitoring
An Argo CD application pointing at one overlay looks like:
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: api-production
namespace: argocd
spec:
project: production
source:
repoURL: https://github.com/example-org/platform-config.git
targetRevision: main
path: apps/api/overlays/production
destination:
server: https://kubernetes.default.svc
namespace: api
syncPolicy:
automated:
prune: true
selfHeal: true
syncOptions:
- CreateNamespace=true
Automated sync with selfHeal makes Git authoritative. Many teams keep production on manual sync approval at first and move to automation as confidence grows. The environment overlay model is the same one we use to keep dev, staging and production consistent.
Promotion and CI integration
The healthy flow: CI builds an image tagged with the commit and scans it; a pipeline step opens a pull request (or commits) that updates the image reference in the dev overlay; the controller deploys it; promotion to staging and production is another reviewed change that moves the same image digest. Environment differences live in overlays, never in rebuilt images. See our CI/CD pipeline work and the pipeline best practices guide for the build side.
Migrating an existing cluster without a big bang
Most teams adopt GitOps on clusters that already run production traffic, so the migration order matters. We export the live manifests of one low-risk service, clean out generated fields such as status and managed metadata, and commit them to the config repository. The controller is then pointed at that path in a manual-sync mode, and we compare the diff it reports. A clean diff means Git now describes reality.
Only then do we switch on automated sync for that service. Repeating this service by service is slower than a single cutover, but a failed sync affects one workload, not the whole platform. Services with unusual lifecycles, such as jobs and operators that own their own resources, are left until last and often need ignore rules for fields that other controllers legitimately change.
Secrets
The first question every GitOps design must answer. Options:
- Sealed Secrets: encrypt with a cluster key, commit the sealed object. Simple, but tied to that cluster’s key.
- SOPS: encrypt files in Git with a cloud KMS key. Flexible, good for Flux and Argo CD plugins.
- External Secrets Operator: reference secrets stored in a cloud secrets manager. Best when rotation and central storage matter.
We choose one pattern per organisation. Mixed approaches become hard to audit.
Trade-offs and when GitOps is not enough
- Progressive delivery: a GitOps controller applies manifests. Canary and blue-green analysis need Argo Rollouts or Flagger on top.
- Stateful migrations: database schema changes do not revert with Git. Plan them as separate, backwards compatible steps.
- Small teams: for one service on one cluster, a simple pipeline may be enough. GitOps pays back as environments and teams multiply.
- Emergency access: you still need a break-glass path, and a rule that anything changed by hand is committed afterwards.
- Controller as critical infrastructure: if the controller breaks, deployments stop. Back it up, monitor it and know how to bootstrap it again from Terraform.
Common failure modes
- Putting generated manifests and hand-written values in the same files, causing merge conflicts.
- A single giant Application, so one error blocks every service.
- Allowing
prunewithout understanding it, then deleting a shared resource. - Controllers with cluster-admin used by every team, bypassing RBAC design. Use projects or tenants to scope what each team can deploy.
- Ignoring sync failures because nobody is alerted.
Delivery and how to tell it works
We hand over the bootstrapped controller as code, the repository layout with templates for new services, a secrets pattern, alerts on failed or out-of-sync applications, and a rehearsed rollback. You will know it works when a deploy is a merged pull request, a manual change to the cluster is flagged or reverted within minutes, and a new engineer can ship a change on their first week. For help designing yours, book a consultation.