> Markdown version of https://www.getunleash.io//blog/gitops-deploys-your-code-what-deploys-your-decisions
> For clean Markdown of any blog post, append .md to its URL.
> For a site index, see https://www.getunleash.io//llms.txt.

# GitOps Deploys Your Code. What Deploys Your Decisions?

_Published 2026-08-28 by Wojtek Gawronski in Industry Insights._

If you’ve adopted [GitOps](https://glossary.cncf.io/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](https://www.getunleash.io/blog/where-configuration-decisions-actually-belong#layer-2-infrastructure-deploy-time) 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](https://featureops.io/) 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](https://www.getunleash.io/blog/how-do-i-reduce-risk-associated-with-releasing-new-features) 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](https://www.getunleash.io/blog/where-configuration-decisions-actually-belong).

**Deployment** ([_Layer 2_](https://www.getunleash.io/blog/where-configuration-decisions-actually-belong#layer-2-infrastructure-deploy-time)) 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_](https://www.getunleash.io/blog/where-configuration-decisions-actually-belong#layer-3-runtime-request-time)) 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:

```yaml
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:

```python
# 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_](https://en.wikipedia.org/wiki/Mutualism_\(biology\)): 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:

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

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](https://www.getunleash.io/blog/static-config-vs-runtime-control#the-reaction-time-ceiling). 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):

```yaml
# 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](https://www.getunleash.io/blog/platform-engineering-best-practices-a-guide-for-modern-teams) 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](https://featureops.io/) 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](https://cdn.getunleash.io/uploads/2026/08/preview-1024x576.png)

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](https://docs.getunleash.io/concepts/rbac), [release templates](https://docs.getunleash.io/concepts/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_](https://www.getunleash.io/blog/where-configuration-decisions-actually-belong#layer-2-infrastructure-deploy-time)) and runtime ([_Layer 3_](https://www.getunleash.io/blog/where-configuration-decisions-actually-belong#layer-3-runtime-request-time)) 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_](https://www.getunleash.io/blog/static-config-vs-runtime-control)_Next: Runtime Doesn’t Mean Instant: You Control “When”_
