# GitOps Is Not Deployment Automation: Where the Abstraction Actually Breaks


GitOps is often introduced as a clean operational model in which Git stores the desired state of a system and a controller continuously reconciles the running environment against that state. In Kubernetes environments, this model is most commonly associated with tools such as Argo CD and Flux, where application manifests, Helm values, and cluster configuration are versioned in a repository and applied automatically whenever the declared state changes. The appeal is obvious because the workflow is auditable, reproducible, and easier to review than a collection of manual deployment procedures.

The problem begins when GitOps is treated as if it were equivalent to deployment automation as a whole. A controller may be able to ensure that a Deployment uses a particular image tag or that a Service exposes a certain port, but a production system contains much more than those resources. Databases, secrets, DNS, cloud infrastructure, event schemas, external APIs, migration jobs, and operational interventions all form part of the same system, yet many of them do not fit naturally into a simple desired state reconciliation model. GitOps remains useful in such environments, but its boundaries become much more important than its basic mechanism.

## The Ideal GitOps Model

The simplest GitOps loop is easy to reason about because the relationship between intent and runtime state is direct.

![](https://cdn.hashnode.com/uploads/covers/68fb4c09020910660a830e8f/9eb4f0c5-8e36-4cde-a21b-b96e3a9deaa0.png align="center")

A developer changes a configuration in Git, the controller detects the difference, and the cluster is updated until the live state matches the repository. If the runtime state changes outside Git, the controller can detect that drift and restore the declared configuration. This works particularly well for Kubernetes resources because Kubernetes itself was designed around reconciliation.

For example, if the repository declares four replicas of an application running version `2.4.1`, the desired state is unambiguous.

```yaml
replicas: 4

image:
  repository: myapp
  tag: 2.4.1
```

The controller can compare the declared and live states without needing to understand how the application reached that point. The system either matches the declaration or it does not, which makes the reconciliation model reliable and predictable. The difficulty appears when the same assumption is extended to system components whose correctness depends not only on their final state, but also on timing, ordering, historical state, and side effects.

## Production Rarely Has One Source of Truth

A production platform usually contains several operational authorities. Terraform may manage networking, managed databases, IAM roles, storage, and DNS. Argo CD may control workloads inside Kubernetes. Secrets may live in AWS Secrets Manager or Vault. A database engine holds application state independently of Git, while observability systems maintain metrics, logs, alerts, and traces that are essential for understanding whether the service is actually healthy.

The architecture therefore looks closer to this:

![](https://cdn.hashnode.com/uploads/covers/68fb4c09020910660a830e8f/83cd7b27-43c0-4351-8405-08f04ede445f.png align="center")

Git remains an important source of declared intent, but it is no longer the complete representation of production reality. Terraform has its own state, the database contains persistent data that cannot be reconstructed from application manifests, and the secret manager stores values that are intentionally absent from the repository. The phrase "single source of truth" therefore becomes misleading unless it is qualified. Git may be the source of truth for configuration, while the operational state of the system remains distributed across several control planes.

This distinction becomes important during failures. A Kubernetes application can be perfectly synchronized with Git while failing because an IAM role has not been created, a database endpoint has changed, or an external API is unavailable. From the perspective of the GitOps controller, the environment may be healthy and synchronized even though the service is unusable.

## Declarative State Does Not Remove Procedural Operations

Database migrations are one of the clearest examples of where the abstraction starts to weaken. A migration file can easily be stored in Git, but the fact that its source is version controlled does not make the execution itself declarative.

Consider a simple schema change:

```sql
ALTER TABLE users
ADD COLUMN last_login_at TIMESTAMP;
```

The final schema is straightforward, yet the operational path to that schema may not be. The migration may need to run before the new application version becomes active, or it may require a compatibility period in which both old and new application versions can operate against the same database. A large table may require a long running migration or a staged backfill, and rollback may not be possible once new data has been written.

Git can describe that the migration exists, but it does not inherently describe whether the migration should run before deployment, after deployment, only once, or under a particular runtime condition. Additional orchestration is required because some production changes are transitions rather than continuously reconcilable states.

This is an important architectural distinction. A declarative system describes what should exist, while a procedural operation describes how a system must move from one state to another. Production environments usually contain both.

## The Terraform and Kubernetes Boundary

A similar problem appears when Terraform and Kubernetes controllers independently manage different parts of the same application lifecycle. Terraform may create an IAM role, database, or load balancer, while Argo CD deploys a workload that depends on those resources.

![](https://cdn.hashnode.com/uploads/covers/68fb4c09020910660a830e8f/07e4db82-5786-4671-9e3c-ae32addbe2db.png align="center")

Both systems are declarative, but the overall platform does not automatically become declarative simply because both tools use desired state models. Argo CD may reconcile the application before Terraform has completed the infrastructure it depends on, while Terraform may replace or delete a resource that Kubernetes still expects to exist.

The missing element is coordination between control loops. Each controller understands its own desired state, but neither necessarily understands the lifecycle of the other. As the number of independently managed systems increases, dependency sequencing and ownership become architectural concerns rather than implementation details.

## Secrets and the Limits of Git as an Authority

Secrets expose the same issue from another direction. Modern GitOps systems normally avoid storing raw production credentials in Git and instead reference an external secret store.

```yaml
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret

metadata:
  name: payment-api

spec:
  secretStoreRef:
    name: production-secrets

  data:
    - secretKey: api-key
      remoteRef:
        key: payment-api-key
```

The repository declares that a secret should be consumed, but the actual value exists elsewhere. The secret manager is authoritative for the credential, Git is authoritative for how that credential should be referenced, and Kubernetes may contain a synchronized runtime representation. None of these systems alone represents the complete operational truth.

This is not a weakness in GitOps. In fact, separating secret values from Git is usually the correct design. The important point is simply that GitOps works best when Git is treated as a source of intent rather than as a universal database for every form of runtime state.

## Rollback Is More Than Reverting a Commit

GitOps is also frequently associated with simple rollback because reverting a commit can cause the controller to restore a previous configuration. This works well for many stateless changes, but production history is often not as reversible as Git history.

A release may involve an application update, a database migration, and the publication of events using a new schema. If the application commit is later reverted, the database and downstream consumers may already have moved forward. Restoring the previous image does not necessarily restore the previous operational state.

For this reason, reliable rollback requires architecture beyond Git. Database changes may need expand and contract migration patterns, APIs may need backward compatibility, events may require versioning, and application releases may need staged rollout strategies. GitOps can participate in these workflows, but it cannot make irreversible operations reversible merely because the configuration that triggered them is version controlled.

## Reconciliation Does Not Mean Correctness

One of the most important limitations of GitOps is that configuration convergence does not guarantee service correctness. A controller can verify that the environment matches the repository, but it cannot determine whether the deployed system is achieving the intended operational outcome unless runtime signals are introduced into the process.

![](https://cdn.hashnode.com/uploads/covers/68fb4c09020910660a830e8f/27d9e864-b5b2-42e6-ba61-59104c63f265.png align="center")

A deployment can be synchronized while user requests are failing. Pods can be ready while database latency is increasing. A Service can exist exactly as declared while a third party dependency is unavailable. GitOps answers whether the environment matches the intended configuration, whereas observability answers whether that configuration is producing acceptable behaviour.

A mature platform therefore needs both reconciliation and feedback. The first controls configuration drift, while the second provides evidence about the consequences of that configuration.

## GitOps as One Control Loop Among Several

A more accurate way to understand GitOps is to treat it as one control loop within a larger operational system. Git stores desired configuration, controllers reconcile parts of the platform, observability systems report runtime behaviour, and humans or automated policies interpret those signals and decide what should happen next.

![](https://cdn.hashnode.com/uploads/covers/68fb4c09020910660a830e8f/5cb5d30e-5ce4-4041-a0bb-aaf049652aa6.png align="center")

This model is closer to how production systems actually operate because it accepts that different forms of state require different mechanisms of control. Kubernetes manages resource state, Terraform manages infrastructure state, databases manage persistent data, observability systems expose runtime behaviour, and operators handle conditions that cannot always be predicted in advance.

The architectural challenge is therefore not to force all of these systems into Git, but to define clear ownership between them and ensure that transitions across those boundaries are predictable.

## Toward Closed Loop GitOps

The natural evolution of GitOps is not simply adding more configuration to repositories. A more interesting direction is connecting deployment decisions with runtime outcomes.

Progressive delivery systems already move in this direction. A canary release can be deployed to a limited percentage of traffic while the platform evaluates latency, error rates, or other service indicators. If the metrics remain within acceptable limits, the rollout continues. If they deteriorate, the deployment can be stopped or reverted automatically.

![](https://cdn.hashnode.com/uploads/covers/68fb4c09020910660a830e8f/0c1454d4-fd76-4aea-9ab9-03a1cdcac387.png align="center")

This creates a more complete feedback loop because the system is no longer checking only whether the configuration was applied. It is also evaluating whether the applied change produced the intended operational result.

Such systems are more complex because they must define what healthy means, which metrics are meaningful, how long a release should be observed, and when automated remediation is appropriate. Nevertheless, this direction better reflects the reality of production engineering than treating synchronization alone as evidence of success.

## Conclusion

GitOps is a powerful operational pattern because it provides a controlled, auditable, and repeatable way to manage declarative infrastructure and application configuration. Its value is strongest when resources have a clear desired state and can be safely reconciled whenever drift occurs.

The abstraction becomes weaker when systems depend on irreversible transitions, persistent data, external control planes, runtime conditions, or coordination between independently managed components. Databases, secrets, Terraform managed resources, third party services, and operational interventions all expose areas where Git can describe intent without fully representing production reality.

The goal of a mature GitOps architecture should therefore not be to place every aspect of production inside Git. It should be to clearly define where Git is authoritative, where another system owns the state, and how changes move safely across those boundaries.

Seen in this way, GitOps is not simply deployment automation. It is a disciplined control mechanism within a broader operational architecture, and understanding where that control ends is just as important as understanding how it begins.
