Infrastructure · GitOps

What GitOps actually is (and what it isn't)

GitOps is not "CI/CD with a new name." It's a specific architectural pattern in which a Git repository becomes the single source of truth for what runs in production. Here's how it works, where it breaks, and when it's the wrong choice for your team.

12 min read Infrastructure Updated 2026

The definition that matters

GitOps is a way of managing infrastructure and application deployment where Git is the single source of truth for what runs in a cluster. Everything else follows from that one sentence.

In a properly implemented GitOps workflow:

That last point is what separates GitOps from "we store our configs in Git." The reconciliation loop is the defining feature. Without it, you have Git-stored configs. With it, you have GitOps.

How it actually works

There are four components in any GitOps system:

1. The Git repository

A repository (or set of repositories) containing declarative descriptions of the desired state. In Kubernetes terms, that means YAML manifests, Helm charts, or Kustomize overlays. In infrastructure terms, that means Terraform, Pulumi, or CloudFormation templates. The format doesn't matter as long as it describes what should exist, not what steps to run.

2. The operator

A software agent running inside the cluster (or as a separate service) that watches the Git repository. When it sees a change, it applies that change to the cluster. When it notices a difference between Git and the cluster, it corrects the cluster to match Git.

ArgoCD and Flux are the two dominant operators. Both run as Kubernetes controllers.

3. The reconciliation loop

Every few seconds, the operator compares the desired state (from Git) to the observed state (from the Kubernetes API). If they differ, the operator acts. This is what makes GitOps self-healing.

# Simplified reconciliation pseudocode
while true:
    desired = git.fetch(repo, branch)
    observed = kubernetes.get(all_resources)

    if desired != observed:
        kubernetes.apply(desired)

    sleep(30)  # check again in 30 seconds

4. The Git history as audit log

Because every change is a commit, the Git log is the audit trail. Who changed what, when, and who reviewed it. This is a significant benefit for compliance-heavy environments, and it's a byproduct of the workflow rather than a separate tool you have to run.

GitOps vs CI/CD

This is where most confusion lives. GitOps is often described as "CI/CD for Kubernetes," which is misleading.

Traditional CI/CD
GitOps
Pipeline pushes changes to the cluster
Cluster pulls changes from Git
CI system needs cluster credentials
CI system never touches the cluster
State lives in the cluster and the CI logs
State lives in Git
Deploy = run pipeline
Deploy = merge pull request
Rollback = re-run a previous pipeline
Rollback = revert a commit
Drift goes undetected until something breaks
Drift is detected and corrected automatically

The key architectural difference is direction of trust. In traditional CI/CD, the pipeline has credentials to the cluster and pushes changes. In GitOps, the cluster has credentials to Git and pulls changes. That inverts the attack surface. A compromised CI system in a GitOps model can propose changes but can't directly apply them.

You can run GitOps and still have CI. CI runs your tests, builds your container images, and pushes them to a registry. But CI doesn't deploy. Deployment happens because someone merged a pull request that changed the desired state in Git.

Drift detection and reconciliation

The reconciliation loop is the most important operational feature of GitOps, and it's the one most teams under-appreciate until they've lived with it.

Consider what happens without it. An engineer is debugging a production issue at 2am. They open the cloud console, change a security group rule, and solve the problem. Six months later, someone reads the Terraform code and sees a rule that doesn't match what's actually running. Nobody knows why. Changing the code might break production. Deleting the rule might break something else.

That's drift. It accumulates silently. Every manual change is a decision that isn't in code, and every decision that isn't in code is a decision that will surprise someone later.

In a GitOps model, that 2am security group change gets reverted. The engineer has to update the code and merge a PR to make the change stick. That feels like friction in the moment. It's the friction that keeps your infrastructure honest.

Why this matters for compliance. SOC 2, HIPAA, PCI-DSS, and similar frameworks all require demonstrating that changes to production are authorized, documented, and tested. In a GitOps model, that evidence is already there. Every change is a pull request with a reviewer, a description, and a diff. Auditors accept it. Without GitOps, teams spend weeks reconstructing that evidence from CloudTrail and Slack.

ArgoCD and Flux

Two operators dominate the space. Both are CNCF-graduated and production-ready. The choice between them is mostly a matter of team preference.

ArgoCD

ArgoCD ships with a web UI. You can see every application, its sync status, and its health at a glance. The UI matters less for daily operations than you'd think — you shouldn't be clicking deploy in it — but it's useful for demos, incident review, and helping non-Kubernetes engineers understand what's running.

ArgoCD uses a concept called "Applications." Each Application points at a Git path and a cluster target. The ApplicationSet controller extends this to generate Applications programmatically, which matters for multi-cluster and multi-tenant setups.

# Typical ArgoCD Application
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: api-server
  namespace: argocd
spec:
  project: default
  source:
    repoURL: https://github.com/example/gitops
    targetRevision: main
    path: apps/api-server/overlays/production
  destination:
    server: https://kubernetes.default.svc
    namespace: api
  syncPolicy:
    automated:
      prune: true
      selfHeal: true

Flux

Flux has no UI. Everything is CLI and YAML. For teams that live in the terminal, this is a feature, not a limitation. There's less surface area, fewer moving parts, and a cleaner mental model: the whole system is a set of controllers watching a set of sources.

Flux is composed of several controllers — source-controller, kustomize-controller, helm-controller, notification-controller — each with a narrow responsibility. You compose them to fit your workflow.

# Typical Flux GitRepository + Kustomization
apiVersion: source.toolkit.fluxcd.io/v1
kind: GitRepository
metadata:
  name: gitops
  namespace: flux-system
spec:
  interval: 1m
  url: https://github.com/example/gitops
  ref:
    branch: main
---
apiVersion: kustomize.toolkit.fluxcd.io/v1
kind: Kustomization
metadata:
  name: api-server
  namespace: flux-system
spec:
  interval: 10m
  path: ./apps/api-server/overlays/production
  prune: true
  sourceRef:
    kind: GitRepository
    name: gitops

Which should you pick? If you have a mixed-skill team and want visibility, ArgoCD. If you have engineers who live in the terminal and want the minimum number of abstractions, Flux. Both solve the same problem.

Where GitOps breaks

GitOps is not a silver bullet. It has specific failure modes that teams discover the hard way.

Secrets

You cannot store plaintext secrets in Git. The moment you do, they're in every developer's clone, every CI log, and eventually in a breach disclosure. The common solutions are Sealed Secrets, External Secrets Operator, and SOPS. Each has trade-offs. Sealed Secrets encrypt with a key stored in the cluster, so you can't decrypt outside it. External Secrets pulls from a real secret manager like Vault or AWS Secrets Manager but requires that manager to exist. SOPS encrypts files in Git with a KMS key, which means your developers can't read the secret without KMS access.

Pick one and standardize. Mixing approaches causes more confusion than it solves.

Stateful workloads

GitOps works beautifully for stateless deployments. It's awkward for databases. You can manage a Postgres StatefulSet with GitOps, but the actual data isn't in Git, and losing the cluster means restoring from backup, not from Git. GitOps doesn't solve state. It solves configuration.

Cluster bootstrapping

The chicken-and-egg problem: to run ArgoCD or Flux, you need a cluster, and to manage the cluster with GitOps, you need ArgoCD or Flux already installed. The standard answer is a small bootstrap script that installs the operator, which then installs everything else. Tools like flux bootstrap and argocd-autopilot automate this. But it's still a piece of the puzzle that isn't GitOps itself.

Large monorepos

A single repository containing every application and every environment works for small teams. Past a certain size, it becomes slow to clone, difficult to review, and impossible to reason about. The common pattern is to split into multiple repositories: one per application, one for cluster-level add-ons, one for environment overlays. That's more repos to manage, and it requires discipline around cross-repo references.

When GitOps is the wrong choice

GitOps is right for most teams running Kubernetes. It's wrong for some.

Adopting it without stopping work

The biggest mistake teams make with GitOps is trying to adopt it all at once. A full migration — every workload, every cluster, every config — takes months and stalls feature work the whole time.

A better path:

  1. Start with one non-critical application. Pick something small, deploy it through ArgoCD or Flux, and let it run for a week. Watch the reconciliation loop. Fix the small things that come up.
  2. Add your next application. Once you trust the first one, add a second. The pattern repeats. Your repo structure emerges from what you learn.
  3. Bring cluster add-ons under GitOps. Once applications work, do the same for ingress controllers, certificate managers, monitoring stacks, and policy engines. These are usually the last things to migrate, because they're harder to roll back if something breaks.
  4. Turn on automated sync. The first month, run in manual sync mode. You review every change before it applies. Once you're confident, turn on automated sync and let the reconciliation loop do its job.
  5. Write down what you learned. Your team will have questions. The repo should document its own conventions — folder structure, naming, where secrets live, how to add a new application. That documentation is worth more than the code.

Six months in, you'll wonder how you ran infrastructure any other way. But the path there is incremental, not a big-bang rewrite.

The honest summary. GitOps is the right pattern for teams running Kubernetes at any meaningful scale. It makes audit trails free, makes rollbacks trivial, and makes drift visible. It has real costs — operator setup, secret management complexity, a learning curve — and it's not the right fit for every team. But for teams that have crossed the threshold where manual kubectl and cloud console changes are the norm, it's usually the right answer.

Related service

Managed Kubernetes & GitOps

We design, build, and operate GitOps pipelines for teams running Kubernetes in production. ArgoCD or Flux, your choice of Git provider, with the audit trail and rollback you need.

View the service WhatsApp