Deployment pipelines are optimised to answer whether code shipped. Compliance teams, security auditors, and incident responders ask a different set of questions entirely: who approved this change, what did the system look like before and after, and can you prove that the running state matches the declared intent? Traditional CI/CD tooling treats those as afterthoughts, bolting on audit logs and approval gates as separate systems that inevitably drift from the deployment reality they’re supposed to describe.
GitOps, when implemented with discipline, collapses the deployment record and the audit trail into the same artifact. The git log becomes a tamper-evident, cryptographically-linked chain of every infrastructure change (its integrity depending on branch protection rules, signed commits, and hosting platform configuration): who authored it, who reviewed it, what the diff contained, and when it merged. That convergence changes how I think about deployment architecture for any team operating under compliance constraints.
Declarative State and the Reconciliation Loop
The core mechanic of GitOps fits in one sentence. You declare the desired state of your infrastructure in version-controlled manifests, and a controller running inside your cluster continuously reconciles the actual state against that declaration. The controller doesn’t execute a pipeline and walk away; it watches, compares, and corrects on an ongoing loop.
This is a fundamentally different posture from imperative CI/CD, where a pipeline runs a sequence of commands and the system’s state after execution is whatever those commands happened to produce. In a declarative model, the desired state is explicit and versioned, and deviations from it are treated as problems to fix rather than realities to accept. (Other declarative tools like Terraform share this drift-detecting philosophy; GitOps extends it by coupling the declaration directly to the deployment mechanism and its audit trail.)
ArgoCD and Flux both implement this pattern, albeit with meaningfully different philosophies. ArgoCD provides a centralized control plane with a UI, making it easier to manage application sets across multiple clusters from a single dashboard. Flux takes a more decoupled approach, running controllers directly in each cluster and relying on Kustomize overlays and Helm for composition. For multi-cluster environments, ArgoCD’s ApplicationSets generator can template deployments across dozens of clusters from a single manifest, whilst Flux achieves similar results through its Kustomization controller and source references. Progressive sync strategies, like canary deployments gated on Prometheus metrics, layer on top of either tool.
The Audit Trail Argument
Every merge to the infrastructure repo creates an immutable record: the author’s identity (tied to their SSH key or GPG signature), the timestamp, the reviewer who approved the pull request, and the complete diff showing exactly what changed. This record addresses several core requirements of compliance frameworks and provides a strong foundation for demonstrating change control. SOC 2 wants evidence of change management processes, separation of duties, and an audit trail for production changes. A properly configured GitOps workflow, with branch protection, required reviews, and signed commits, generates that evidence as a byproduct of the deployment process itself.
When the deployment mechanism and the audit trail are the same artifact, compliance becomes a property of the workflow rather than a separate reporting burden.
The contrast with traditional approaches is stark. In a typical CI/CD setup, the deployment pipeline has its own logs, the approval process lives in a ticketing system, and the actual state of production lives in a cloud provider’s API. Keeping those three sources of truth synchronized requires constant vigilance and custom integration work. GitOps doesn’t eliminate the need for observability, but it does give you a single, verifiable timeline of intended state changes that you can present to an auditor alongside the cluster state to demonstrate conformance.
Drift Detection as a Security Control
Drift detection functions as a security control in its own right. The controller continuously compares declared state against running state, and when the two diverge, either something broke or someone bypassed the process.
Both scenarios demand investigation. In the first case, you have an operational issue to triage. In the second, you have a potential security event, because an operator with cluster access made a change that didn’t go through the reviewed, approved, signed process that every other change follows. The GitOps controller flags the drift, and your response determines whether you operate in a “notify” mode (alert on drift but allow it) or “enforce” mode (automatically revert drift to match the declared state).
The structural parallel to blockchain is instructive. Git history and blockchain ledgers solve the same fundamental trust problem: how do you prove that a sequence of events occurred in a specific order, authored by specific actors, without tampering? Git uses SHA-1 hash chains (or SHA-256 in newer versions) linking each commit to its parent; blockchains use cryptographic hash chains linking each block to its predecessor. The tradeoffs differ, as git relies on access controls and signed commits for integrity whilst blockchains distribute trust across a consensus network, but the underlying mechanism of tamper-evident, append-only records serves the same audit function.
Failure Modes Worth Designing For
GitOps has real failure modes, and the teams that adopt it without accounting for them end up with a different class of outage.
Secrets in git is the most common pitfall. Declaring all infrastructure state in git sounds clean until you realize that state includes database credentials, API keys, and TLS certificates. Committing secrets to git, even a private repo, creates a permanent record that’s extraordinarily difficult to fully expunge (git’s reflog and any forks or mirrors retain the history). The mature pattern here is external secrets management: tools like Sealed Secrets, SOPS, or Vault-backed operators that store only encrypted references in git, with decryption happening inside the cluster at reconciliation time.
Reconciliation storms emerge when the controller detects drift and attempts to correct it, but the correction itself triggers further drift detection, creating a feedback loop. This typically happens with resources that have fields updated by both the controller and external systems (think Kubernetes HPA modifying replica counts that the GitOps controller also manages). Exclude fields or entire resources from reconciliation scope to prevent this.
When GitOps Doesn’t Fit
GitOps works best for stateless, declaratively-managed workloads. Stateful systems like databases, message queues, and anything with operator-managed lifecycle hooks introduce complexities that a simple “reconcile to declared state” model handles poorly. Declaring a database’s desired state in git doesn’t capture the migration ordering, data backfill steps, or rollback procedures that stateful changes require. You can wrap these in operators that expose a declarative interface, but the operational complexity shifts rather than disappears.
Emergency hotfixes present the other challenge. When production is down and the fix requires a manual change that would take twenty minutes to push through a PR, get a review, merge, and wait for reconciliation, the team will bypass the process. The right response is designing an escape hatch: a documented, audited path for emergency changes that feeds back into the git record after the fact. A “break glass” procedure with a post-incident commit that captures the manual change preserves the audit trail whilst acknowledging that process purity and incident response sometimes conflict.
The Broader Bet
GitOps works because it aligns the deployment model with the compliance model. Most approaches I’ve used before GitOps required maintaining two parallel records of what happened: the operational record and the auditable record, always slightly out of sync. The teams I’ve seen adopt GitOps effectively treat it as an infrastructure decision with security implications, choosing the reconciliation mode, the secrets management strategy, and the escape hatch design with the same rigour they’d apply to choosing an authentication provider. The git log becomes the spine of the deployment story, and for teams operating under regulatory scrutiny, that story being verifiable, tamper-evident, and complete changes what’s possible.