Meet Unleash at one of the events we're attending this year➩ See where we'll be

Watch "Implementing a Kill Switch for AI"

Events

Join the Unleash team to learn how to integrate runtime control in your AI strategy.

GitOps Deploys Your Code. What Deploys Your Decisions?

Wojtek Gawroński

Wojtek Gawroński

Developer Advocate

August 28, 2026

If you’ve adopted GitOps, you’ve made a clear architectural commitment: Git is the source of truth. The reconciliation loop ensures the cluster converges toward the declared state. Drift gets detected and corrected. Every change has a commit hash, ideally a PR review, or at least merge approval.

That commitment beautifully covers deployment and infrastructure concepts (that we named Layer 2 in the series). The artifact is deployed, and all infrastructure elements are configured: replica count, service mesh rules, and similar. The desired state in source control is the running state in the cluster.

However, GitOps can’t answer release questions like: “should this feature be active for this user on this request?” That’s not a deployment- or infrastructure-related question. It also does not look like a gap, since GitOps wasn’t designed to handle those. However, to have a complete picture of your system, you need to have those answers – and FeatureOps provides them by decoupling deployment from release.

Deployment ≠ Release

Conflating the two is so common that it practically goes unnoticed. You may say “we released the new checkout” when you really meant “we deployed the artifact that contains the new checkout code”. Deployment and release are different operations with different failure modes, different reversal costs, and different decision triggers. It is not only a discussion about semantics – by merging them, you are also unintentionally losing a layer that gives you a very flexible decision point.

Deployment (Layer 2) answers: is the right artifact running with the right infrastructure configuration? GitOps handles this by reconciliation, drift correction, and rollback via git revert.

Release (Layer 3) answers: is this feature active for the right users at the right time? Feature flags and targeting handle this through contextual evaluation, gradual rollout, or an instant kill switch.

How does the decoupling look in practice?

Let’s look at a real example – GitOps deploys a service from a following definition:


apiVersion: apps/v1
kind: Deployment
metadata:
  name: checkout-service
spec:
  template:
    spec:
      containers:
        # Contains new payment flow code.
        - name: checkout
          image: registry.internal/checkout:v2.15.0  

That version contains the following code:


# Released with a feature flag:
def process_payment(user, order):
  runtime_context = {"userId": user.id, "tenantId": user.tenant}

  if unleash.is_enabled("new-payment-flow", runtime_context):
    # New code: deployed but gated.
    return new_payment_processor(order)   
  else:
    # Fallback to the old path.
    return legacy_payment_processor(order) 

The artifact with the new payment flow is deployed to every pod. GitOps confirms it and the new flow is off: no user sees it until a product manager or engineer enables the flag, gradually, with precise targeting.

This is the decoupling. GitOps ensures the capability is deployed. Feature flags ensure the behavior is activated. The two layers work in symbiosis, not competition.

Benefits of GitOps and FeatureOps symbiosis

A more precise term is mutualism: a symbiotic relationship in which both parties benefit. In addition, each system handles reversibility at its own layer:

  • GitOps provides reversibility for infrastructure state. Wrong image tag? git revert, wait for reconciliation. Bad ConfigMap? Same path. The reversal cost is usually in minutes: the reconciliation interval plus the rolling update.
  • FeatureOps provides reversibility for runtime behavior. Wrong feature activated? Flag off. Bad rollout? Reduce percentage. Kill switch needed? Instant disable. The reversal cost is seconds: the SDK update interval.

Here is a side-by-side comparison:

Incident: new payment flow is causing timeouts
GitOps path FeatureOps path
1. Engineer opens PR to revert image. 1. Engineer toggles flag off.
2. Peer review (hopefully fast). 2. SDK picks up change (~seconds).
3. Merge to main. 3. The next request uses the old path.
4. ArgoCD detects drift.
5. Rolling update begins.
6. Old pods drain.
Total: dozens of minutes (15 – 45 minutes) Total: seconds (at worst 1-2 minutes)

 

The point here is: they are not mutually exclusive. You use GitOps for deployment and FeatureOps for release. Together they give you full-stack reversibility. Any change, at every layer, can be undone at the layer’s speed. The runtime layer (FeatureOps path) is faster and more immediate for a specific mitigation, since the upper-bound constraint is how quickly you can toggle a flag off. The deployment layer (GitOps path) takes longer, but in most cases (excluding quick workarounds and hotfixes) introduces persistent, lasting solutions.

What if there is competition between GitOps and FeatureOps?

The tension shows up when the dynamic runtime state meets the reconciliation loop. GitOps wants everything in the cluster to match Git. But some things shouldn’t be reconciled from a static manifest.

You’ve seen this if you use ArgoCD with the Horizontal Pod Autoscaler (HPA):


# ArgoCD Application:
spec:
  ignoreDifferences:
    - group: apps
      kind: Deployment
      jsonPointers:
        # Don't fight the autoscaler!
        - /spec/replicas  

It’s just one example. The HPA sets replicas based on real-time CPU and memory metrics. The GitOps manifest says replicas: 3. If ArgoCD reconciles the static value, it fights the autoscaler in a loop. So you tell ArgoCD to ignore it. I think you will agree that if it quacks like a workaround, it is a workaround.

Feature flag states live in the same category. The flag evaluation engine returns values based on runtime context: user identity, tenant, region, rollout percentage. This state has no business being in source control. In many cases, it is even impossible to represent properly at that level. And it is not only about cadence (how frequently it changes: seconds vs. minutes), but where it truly lives: bound to runtime evaluation at request time.

You might decide to ignore that and forcefully manage feature flags through GitOps. You would represent the flag state as YAML in a repo with ArgoCD, syncing it to a ConfigMap or similar. And you will end up rebuilding a worse version of what a FeatureOps platform provides. Why worse? Here’s what you would lose:

  • You lose per-request context, e.g., ConfigMaps are pod-scoped, with infrastructure-level concepts only.
  • You slow reaction time to apply changes and roll back because of cadence differences.
  • You lose instant propagation, as reconciliation takes minutes at best.
  • You limit governance techniques to commits and discussion within a PR, leaving options like approval workflows, audit logs, and role-based access control on the table.

It doesn’t have to work like that, because the boundary is clear: GitOps manages the infrastructure that runs the flag evaluation engine. Together with the artifact, it deploys the SDK, configures the connection to the flag service, and manages the pods that consume flag values. But the flag state – which flags are on, for whom, with what targeting rules – lives in the FeatureOps control plane, not in the source control.

Beware the governance gap

The urge to handle runtime control decisions via GitOps may stem from a justified effort to close a governance gap.

When GitOps handles deployments, and feature flags steer releases, they produce two separate audit trails. And for a while, that works. Then, the gap becomes an immediate pain when an auditor asks: walk me through the full change trail for this production behavior change.

For a deployment, you have a complete trail – from commit hash, through PR review and merge approval, to the ArgoCD sync event causing a pod rollout. Source control is the audit log. The auditor is happy.

For a flag toggle? Things can get hairy here if you don’t do it right. Scattered documentation across wikis or communication tools. Was it a Slack message from the product manager or the friendly marketer wiki page with the experiment details? If you find it – great. It’s worse if you get nothing.

The immediate instinct of infrastructure and platform engineers is to close that gap with the tools they know, but it leads to suboptimal results, as highlighted above. The good news is that Unleash closes the gap by telling you what was activated, for whom, by whom, and why.

If you already value GitOps for auditability and deployment change control, then you need the same for runtime changes. That’s what FeatureOps Zero-Trust Governance pillar delivers: role-based access control (RBAC) for who can change flags in which environments, change request workflows with approval gates for production, and a comprehensive audit log that records every flag state change with who, what, when, and context.

It is not a replacement for Git-based audit. It is a mutualistic relationship that handles the runtime dimension source control can’t see. Together they provide continuous audit coverage from commit to runtime. True full-stack traceability.

Embrace both GitOps and FeatureOps with Unleash

Here’s how this works in practice for the mature engineering organization:

FeatureOps platform contract between platform engineering and application teams

Provided Once. Consumed Everywhere – the FeatureOps platform contract between platform engineering and application teams.

The platform team provides the infrastructure for runtime control: the FeatureOps platform, pipelines, policies (e.g., role-based access control, release templates), and skeletons for SDK usage or other subsystem integrations (e.g., monitoring, observability). The platform team doesn’t manage individual flags – that’s the application team’s domain. Application teams follow a paved path by consuming provided scaffolding, adhering to governance rules, and introducing flags at specific points in the code.

GitOps + FeatureOps isn’t redundant. Together they provide traceability and reversibility as first-class platform principles: the shared capabilities that every application team can rely on. Deployment (Layer 2) and runtime (Layer 3) work in mutualism to support your business and your customers.

This is Post 4 of “The Runtime Control Layer” – a series on FeatureOps for infrastructure engineers.

Previously: Static Config in Your Artifacts
Next: Runtime Doesn’t Mean Instant: You Control “When”