In 2017, a small team at Weaveworks coined a term that would quietly reshape how organizations deploy software: GitOps. The idea was simple but radical. Instead of running deployment scripts from a laptop or a CI server, you let a Git repository hold the desired state of your entire system, and an automated agent continuously makes reality match it. Within a few years, two open-source projects built around this model, Argo CD and Flux, had become among the most widely adopted tools in the cloud-native ecosystem. (Cloud Native Computing Foundation)
That origin story matters because it explains why GitOps exists. Teams were tired of “it works on my machine” deployments, secret credentials scattered across CI pipelines, and the sinking feeling of not knowing who changed production at 2 a.m. GitOps answers all three. This guide walks through how to actually implement it, from repository structure to the mistakes that trip up even experienced platform teams.
What GitOps Really Means in Practice
GitOps rests on four principles that the community has since formalized: the system is declarative, the desired state is versioned and immutable in Git, changes are pulled automatically by an agent inside the cluster, and software agents continuously reconcile actual state against desired state.
The practical consequence is a shift in how deployments happen. In a traditional pipeline, a CI job builds an image and then pushes changes to the cluster using credentials like `kubectl apply`. In GitOps, that same CI job builds the image, updates a manifest in Git, and stops there. A controller running inside the cluster notices the new commit and pulls it in. The cluster’s credentials never leave the cluster, which improves security.
It helps to see the contrast clearly:
| Aspect | Traditional CI/CD push | GitOps pull model |
|---|---|---|
| Who applies changes | CI server | In-cluster agent |
| Source of truth | Pipeline scripts | Git repository |
| Cluster credentials | Stored in CI | Stay in cluster |
| Drift detection | Manual or absent | Continuous and automatic |
| Rollback | Re-run pipeline | Revert a commit |
| Audit trail | CI logs | Git history |
That last row is underrated. When every change is a commit, your audit log is free, readable, and already familiar to every engineer on the team.
Choosing Your Controller: Argo CD vs. Flux

Two tools dominate the conversation, and both are mature, CNCF-graduated projects. Picking between them is less about which is “better” and more about how your team works.
Argo CD ships with a rich web UI that visualizes the live state of every resource against its desired state. That UI is a genuine adoption accelerator, especially for teams new to Kubernetes. It also offers an “app of apps” pattern and ApplicationSets for managing many clusters from one place.
Flux is more composable and CLI-first. It leans into the Unix philosophy, exposing separate controllers for sources, Kustomize, Helm, and notifications. Teams that prefer everything as code, including their tooling configuration, often gravitate toward it.
A rough decision guide:
- Choose Argo CD if you want visibility for a broad audience, a strong UI, and multi-cluster management out of the box.
- Choose Flux if you want minimal footprint, tight integration with Helm and Kustomize, and a fully declarative setup with no UI dependency.
- Either works at scale. The bigger risk is picking one and never standardizing how teams use it.
Structuring Repositories So They Scale
Repository layout is where most implementations either succeed or quietly fall apart. The classic debate is monorepo versus polyrepo. A single repository holding all environments and applications is easier to reason about early on. Separate repositories per team reduce blast radius and permission headaches later. (DevOps)
The pattern that tends to age well is environment separation through overlays. Keep a base configuration for each application and layer environment-specific patches on top using Kustomize or Helm values files. This avoids copy-pasting manifests across dev, staging, and production, which is a classic source of drift.
Practical tips that save pain:
- Keep application source code and deployment manifests in separate repositories. This prevents a code commit from accidentally triggering a production deploy before tests pass.
- Never store plaintext secrets in Git. Use SOPS, Sealed Secrets, or an external secrets operator that syncs from a vault.
- Tag your manifests by environment and team so ownership is obvious at a glance.
- Pin image tags to immutable digests, not floating tags like `latest`. A moving tag defeats reproducibility.
A common mistake: teams put everything in one giant repository, then discover that a single misconfigured pull request can affect every environment at once. Start with clear boundaries even if you keep things in one repo initially.
Wiring the Pipeline: From Commit to Cluster
The full loop has two halves. The CI half builds, tests, and produces an artifact. The GitOps half reconciles that artifact into the cluster.
A typical flow looks like this:
- A developer pushes code to the application repository.
- CI runs tests and builds a container image, tagging it with the commit SHA.
- CI pushes the image to a registry.
- CI opens a pull request against the deployment repository, updating the image tag.
- A reviewer approves the change (this is your deployment gate).
- The GitOps controller detects the new commit and applies it.
- Health checks and sync status confirm the rollout.
That pull request in step four is the heart of the model. It gives you a reviewable, revertible record of every deployment, and it lets policy tools like automated approval bots or security scanners run before anything reaches production.
For progressive delivery, pair the controller with a rollout tool such as Argo Rollouts or Flagger. These let you shift traffic gradually to a canary, watch error rates, and roll back automatically if metrics degrade. GitOps handles the “what,” and progressive delivery handles the “how fast.”
Security and Secrets: The Part Everyone Underestimates
The pull model’s biggest security advantage is that cluster credentials stay inside the cluster. But that advantage disappears if you leave secrets lying around in Git.
Three approaches dominate:
- SOPS encrypts values in place, so encrypted secrets live alongside manifests and decrypt at reconcile time.
- Sealed Secrets encrypts a secret so only the target cluster can decrypt it.
- External Secrets Operator pulls secrets from a managed vault at runtime, keeping them out of Git entirely.
Each has tradeoffs. SOPS requires key management discipline. Sealed Secrets ties secrets to a specific cluster. External Secrets adds a dependency on an external system but gives you the cleanest separation. For regulated environments, the external approach is often the safest default.
Also lock down your repository permissions. If anyone can merge to the production branch, GitOps just gives bad actors a faster path to production. Branch protection, required reviews, and signed commits are not optional extras.
Common Mistakes and What Actually Works
Having watched many teams adopt GitOps, a few failure patterns repeat.
Treating GitOps as a tool install. Installing Argo CD is a day of work. Changing how a team deploys is a quarter of work. The tool is the easy part.
Ignoring drift. The whole point is continuous reconciliation, but teams sometimes disable auto-sync to “play it safe.” That turns GitOps into a fancy dashboard and loses the drift-correction benefit. If you fear auto-sync, fix your testing and review process instead.
Over-engineering the repo structure on day one. Start simple. One repository, clear folders, environment overlays. Refactor when the pain is real, not theoretical.
Forgetting the developer experience. If updating an image tag requires hand-editing YAML, developers will resent the system. Automate the pull request creation and keep the loop fast.
Skipping observability. Sync status and health checks are not enough. You need alerts when reconciliation fails, when a sync drifts, and when a rollout stalls.
What actually works is boring and consistent: small commits, mandatory reviews, automated image updates, secrets managed outside plaintext Git, and a platform team that treats the deployment repository as a product with real users.
Conclusion
GitOps is not a product you buy or a switch you flip. It is an operating model built on a simple promise: the state of your system lives in Git, and software keeps reality aligned with it. The tools, whether Argo CD, Flux, or something else, are mature and well documented. The hard work is organizational. Standardize your repository layout, separate code from configuration, keep secrets out of plaintext, and resist the urge to disable the reconciliation that makes the whole thing valuable. Teams that get these fundamentals right end up with something rare in infrastructure work: deployments that are boring, reviewable, and reversible. That is exactly the point.
The teams that struggle most are often those that adopt GitOps for its technical merits alone, without addressing the human side. A deployment repository with no clear owner becomes a dumping ground. Assign explicit ownership—via `CODEOWNERS` files or team-level conventions—so that every manifest has someone accountable for it. When reviewers know which changes affect them, the pull request gate remains a real check instead of becoming a rubber stamp.
It also pays to think about the lifecycle of environments themselves. Ephemeral preview environments, spun up per pull request and torn down on merge, fit naturally into a GitOps model because the desired state of a preview cluster is just another commit. This shortens feedback loops dramatically and lets reviewers see a change running before it merges. The same declarative approach extends to cluster add-ons: ingress controllers, certificate managers, and monitoring stacks can all be reconciled from Git, so a new cluster is bootstrapped rather than hand-configured.
Finally, measure adoption rather than assume it. Track how often deployments happen, how long the commit-to-cluster loop takes, how frequently reconciliation fails, and how many changes bypass the repository entirely. Those metrics reveal whether GitOps is genuinely the operating model or merely a sidecar to the old way of working. When the numbers improve—faster rollouts, fewer manual interventions, cleaner audit trails—the case for the model makes itself. Start small, standardize early, and let the reconciliation loop do the rest.
Track those metrics from day one, even in a spreadsheet. The commit-to-cluster time is the single best proxy for how healthy your delivery pipeline is, and reconciliation failure rate tells you whether your manifests are honest about the real world. If a metric looks bad, resist the temptation to add process; usually the fix is simpler manifests, faster tests, or fewer manual gates. Review the numbers with the team monthly so the model stays visible rather than fading into background infrastructure nobody thinks about.
One last piece of advice: document your conventions. Write down the repository layout, the naming rules, the secret-handling policy, and the promotion path from staging to production. New engineers should be able to read that document and ship a change on their first day. Conventions that live only in senior engineers’ heads are the quiet cause of most GitOps drift, because people guess and guesses diverge. A short, maintained README in the deployment repository is worth more than any amount of tooling sophistication.
With those pieces in place—a clear controller choice, a scalable repository structure, an automated commit-to-cluster loop, secrets kept out of plaintext, and ownership that is explicit—you have a system that rewards discipline instead of fighting it. The tools will keep evolving, but the underlying promise stays the same: Git holds the truth, and the cluster follows.


