RSS Amplifier

Alex Fadeev · Jun 12, 2026

Production GitOps Without the Demo-Only Traps

0
Sign in to vote or save

Alex Fadeev · Alex Fadeev

Getting GitOps running is rarely the hard part. A team can install Argo CD or Flux, point it at a repository, watch a manifest sync, and feel like the deployment problem is solved. The difficulty starts when that same model has to support many teams, dozens of clusters, multiple environments, regional constraints, secret handling, policy enforcement, and developers who still know how to run kubectl.

For production platform engineering, GitOps is less about “YAML in Git” and more about operating contracts. Product teams declare intent. Platform systems reconcile that intent into live infrastructure. The architecture must make drift visible, releases traceable, failures recoverable, and workflow bypasses difficult.

This guide walks through the major GitOps decisions that matter once you move past small demos: state storage, repository layout, promotion models, fleet patterns, security, secrets, policy enforcement, adoption metrics, and the common failure modes that can quietly break trust in the system. 🚀

The well-known GitOps principles are not just slogans. They are design constraints that should influence how you build the platform.

Declarative configuration means Git stores the target condition of the system, not an imperative runbook for reaching it. You write the desired state, for example:

replicas: 3

You do not encode the operational step as the contract:

kubectl scale deployment web-api --replicas=3

The controller decides how to move the cluster toward the declared state. That split between intent and execution is what makes reconciliation possible.

Versioned, immutable artifacts mean every deployed unit has a durable identity, such as a Git commit SHA or OCI digest. Once that unit is released, it should not be rewritten in place. This is why latest is a poor production tag: it destroys the ability to prove what is actually running.

Pull-based delivery changes the security model. Instead of a pipeline pushing into clusters, an in-cluster agent watches the source of desired state and pulls changes. That removes the need to keep cluster credentials inside CI, which is a meaningful security improvement.

Continuous reconciliation means the platform repeatedly compares live cluster state with the desired configuration. If someone deletes a pod manually, the reconciler recreates it. The operating model becomes “the system is being kept in the declared state,” not “we hope the last deploy still holds.”

📌 GitOps does not replace CI, nor does it replace platform engineering. CI still builds, tests, scans, and packages application code. GitOps answers a different question: how do you deploy consistently across a large fleet without turning every cluster into a special case?

A useful distinction here is Infrastructure as Code versus Infrastructure as Data. IaC tools such as Terraform execute logic to provision resources. GitOps works best when the repository contains rendered declarative data and controllers own the execution logic. If the repository becomes a maze of templates, functions, and conditionals, it gets harder to know what will actually reach the cluster.

Git is the default GitOps state store for many teams, but it is not always the best runtime source of desired state at scale.

Git works well because teams already understand commits, pull requests, review flows, and history. For many organizations, that familiarity matters more than theoretical purity.

The trade-off appears when the repository grows into thousands of YAML files. Controllers need to detect changes and retrieve content, and that becomes expensive when many applications and clusters share the same repo. Git is excellent version control, but it is not a purpose-built state database. In practice, it often acts as a protocol for retrieving target files such as manifests.

OCI-based GitOps packages Kubernetes manifests into artifacts and stores them in a container registry, using the same general artifact model as container images.

This separates source from release:

  • Git remains where developers collaborate and review changes.

  • The registry becomes where deployable manifest artifacts live.

  • GitOps controllers pull immutable artifacts instead of cloning repository history.

A key nuance: OCI stores a versioned template of desired state, not the live state of the application. You do not mutate an existing artifact to change replicas from 2 to 3. You produce a new artifact version.

This model has several advantages:

  • Quicker synchronization: a controller downloads one compressed artifact instead of traversing Git history.

  • Immutable release bundles: manifests can be signed and treated as one logical deployable package.

  • Cleaner workflow boundaries: CI renders manifests, creates OCI artifacts, and pushes them to a registry; GitOps agents deploy from that registry.

This is often called Gitless GitOps. Git still exists for source control, but the runtime state source becomes OCI. The pattern is especially useful when manifests and container images need to be versioned together as one release.

Another emerging direction is storing fully rendered manifests in a structured data backend rather than relying on file trees and overlay chains. The goal is WYSIWYG configuration: the stored object is exactly what the cluster receives.

This reduces configuration sprawl because operators no longer need to mentally evaluate multiple layers of templates before understanding the final resource. Strictly speaking, this kind of system behaves more like a true state store. Git and OCI are better understood as ways to store desired-state inputs or release artifacts.

Start with Git when the system is still modest: fewer than about 10 clusters or 50 applications is a reasonable threshold. The simplicity usually wins.

Consider OCI when these conditions appear:

  • Repositories pass roughly 1,000 files and sync performance starts to degrade.

  • Manifest versions must be tied tightly to container image versions.

  • Artifact signing and verification are required for supply chain controls.

  • Multi-region deployments suffer from Git network latency.

The migration does not need to be a hard cutover. Many teams keep Git-based flows for development and test while using OCI artifacts for production release paths.

Repository structure directly affects team autonomy, access control, deployment speed, and operational clarity.

A mono-repo keeps services, charts, and manifests together. GitOps controllers then target folders inside that repository. This improves visibility because the full system state is easier to inspect in one place. Shared templates are also simpler to maintain. It fits smaller teams or organizations that prefer centralized control.

A multi-repo setup gives each team or service its own repository. Controllers manage many application definitions, each pointing at a separate repo. This favors isolation and ownership: one team is less likely to break another team’s configuration accidentally. It is often the better fit for large organizations with independent teams and strict access boundaries.

Neither model is universally correct. The right choice follows your operating model. Stream-aligned teams often prefer multi-repo ownership, while platform teams may prefer centralized repositories for shared infrastructure and fleet-level components.

Using Git branches as environments, such as dev, staging, and production, is usually a mistake. It creates awkward merges, obscures what differs between environments, and couples deployment promotion to branch management.

A more practical model is a single mainline branch with environment directories.

For a small team or single-region application:

/envs
/dev
/staging
/prod

Promotion becomes a controlled update or copy between folders. Anyone can inspect the directory to see the declared state for that environment.

For global applications with residency or regional rules:

/envs
/dev
/staging-eu
/staging-us
/prod-eu
/prod-us

This layout supports region-specific configuration such as database endpoints, compliance policies, or cluster selectors.

For workloads that depend on hardware profile:

/envs
/dev
/staging-cpu
/staging-gpu
/prod-cpu
/prod-gpu

That structure is useful for AI/ML systems, performance testing, and environments where cost or hardware capabilities differ materially.

In a trunk-based model, short-lived feature branches merge into a shared mainline. The GitOps controller watches that main branch, and deployment follows merge and promotion policy.

Promotion is handled by updating artifact references, not by merging environment branches. For example, production can point to a different image.tag than staging while both live on the same branch. The branch is shared; the artifact version changes.

Branch-based promotion puts each environment on its own branch. Staging follows one branch, production follows another, and promotion is a Git merge. That can be useful in narrow regulatory contexts where manual sign-off and audit boundaries are mandatory, but it is an anti-pattern for most teams. It encourages merge conflicts, makes environment diffs harder to reason about, and ties release management to Git branch choreography.

Tools such as Kargo provide a middle path. They support controlled promotion across stages, for example Dev -> Staging -> Prod, while keeping the mainline development model. Instead of manually editing YAML or merging branches, the system promotes a bundle of Git commits, images, and Helm charts as a unit.

GitOps for one cluster is straightforward. GitOps across 50 clusters, several regions, multiple clouds, and different compliance zones needs explicit architecture.

The hub-and-spoke pattern uses one central GitOps instance to manage many clusters through their Kubernetes APIs. It is common for fleet management.

Strengths:

  • One place to view clusters and applications.

  • SSO, RBAC, and repository credentials are configured centrally.

  • ApplicationSets and fleet add-ons are easier to manage.

Weaknesses:

  • A hub outage can affect all managed clusters.

  • The hub may hold powerful credentials for every target cluster.

  • The hub needs network access to all cluster APIs.

This model fits centralized operations, reliable connectivity, and relatively uniform clusters.

The standalone model runs a GitOps controller in every cluster. Each instance manages local workloads.

Strengths:

  • Failures are isolated to a single cluster.

  • Credentials are not centralized.

  • Edge, isolated, and air-gapped environments are easier to support.

Weaknesses:

  • Every controller needs patching, configuration, and lifecycle management.

  • Visibility is fragmented across many UIs.

  • Keeping controller behavior consistent across the fleet becomes harder.

Use this model when regulatory boundaries, firewall restrictions, or edge constraints make central management impractical.

A hybrid architecture keeps central coordination while letting local agents perform reconciliation inside each target cluster.

The hub prepares and distributes configuration. Local controllers pull that configuration and apply it locally. If the hub becomes unavailable, local agents can continue maintaining declared state.

The cost is operational overhead. You run controllers in every cluster and still maintain the hub layer, so the architecture is more complex than either extreme.

The technical architecture matters, but team behavior can sink GitOps faster than a bad folder layout.

Installing a controller is the easy part. The hard part is getting engineers to stop running direct commands such as kubectl edit or kubectl apply against live clusters.

Every manual change creates drift. The reconciler may overwrite it, which leads to confusion and sometimes lost work. The platform contract must be explicit:

  • No production fixes applied only to the cluster.

  • No temporary changes outside the GitOps workflow.

  • No debugging strategy that depends on mutating live resources.

The tooling side is maybe 20% of the work: admission controllers such as Kyverno can block or flag manual changes. The larger 80% is discipline: teams must build the habit of changing Git first.

Configuration sprawl appears when too many rendering layers are stacked:

  • A third-party Helm chart, such as cert-manager.

  • An umbrella chart with global values and overrides.

  • Per-cluster Kustomize overlays.

Each layer can transform the final manifest. When the result only becomes visible inside the GitOps controller after rendering, debugging becomes a scavenger hunt across repositories, directories, and overlays.

A better pattern is to render manifests in CI. Developers can still use Helm and Kustomize, but CI produces final YAML and commits or publishes that output. The GitOps controller then syncs rendered manifests rather than templates. The practical benefit is simple: what the controller sees is what runs in the cluster. ✅

Using latest image tags or mutable branch references breaks the immutable-version principle. You lose precise traceability and make rollback behavior uncertain.

Production references should be immutable:

  • Container images should use SHA digests such as image@sha256:abc123, or pinned semantic versions such as v1.2.3.

  • Git references should use commit SHAs when immutability is required.

  • Helm charts should be pinned to explicit versions in Chart.yaml.

For production systems, this should be treated as a hard requirement.

GitOps introduces a natural tension: you want everything declared in Git, but plaintext secrets cannot live there.

Sealed Secrets uses asymmetric encryption. You encrypt secret material with a public key and commit the resulting SealedSecret resource. The private key stays with the controller inside the cluster, which decrypts the value at runtime.

Strengths:

  • Secrets stay in the same Git workflow as manifests.

  • No external Vault or cloud secret manager is required.

  • The setup is relatively simple.

Weaknesses:

  • Losing the cluster private key can make encrypted secrets unrecoverable.

  • Secret changes require re-encryption.

  • Rotation remains a manual process.

This approach works well for teams that want Git-native workflows and do not already operate a central secret platform.

External Secrets Operator acts as a bridge between Kubernetes and systems such as AWS Secrets Manager, HashiCorp Vault, or Azure Key Vault. Git stores an ExternalSecret reference. At runtime, the operator fetches the real value and creates a Kubernetes Secret.

Strengths:

  • Sensitive values never enter Git.

  • Secret changes can synchronize automatically.

  • Multiple clusters can share centralized secret governance.

Weaknesses:

  • The cluster depends on external infrastructure.

  • Authentication, SecretStore objects, and IAM rules add complexity.

  • There are more moving parts to operate.

Use this pattern when a secrets platform already exists or policy forbids encrypted secrets in Git.

Kyverno is a Kubernetes-native policy engine that uses declarative YAML policies. It provides the guardrails that keep noncompliant resources out of clusters.

Important capabilities include:

  • Validation: reject workloads that violate requirements, such as missing resource limits or running as root.

  • Mutation: patch resources automatically, for example by adding required labels.

  • Generation: create resources from triggers, such as producing a NetworkPolicy for each new namespace.

Kyverno fits GitOps well because policies themselves can be stored, reviewed, versioned, and synchronized through the same flow as applications. It also supports shift-left checks: validate manifests during CI or pull request review before they ever reach a cluster. ⚠️

GitOps is expanding beyond “sync Kubernetes YAML from a repository.” It is becoming part of a broader platform engineering control plane.

GitOps creates structured desired-state data. Observability platforms provide live state, logs, metrics, and traces. AI tooling can connect those two worlds.

For example, when a developer asks why an application is unhealthy, an agent can inspect the declared manifest, query the Kubernetes API for live state, review logs, and explain the difference between expected and observed behavior.

This works better than generic alert interpretation because GitOps gives the system versioned, structured context about what should be deployed.

In modern internal developer platforms, GitOps often becomes the operational backbone.

A typical flow looks like this:

Developer portal -> generated manifests -> Git or OCI artifact -> GitOps controller -> cluster state -> observability feedback

Developers use a portal such as Backstage, Port, or a custom UI. The portal creates or updates manifests from templates. GitOps controllers apply the changes to target clusters. Observability data returns to the portal so developers can see deployment status.

The developer never needs to touch YAML or run kubectl, but the system still preserves auditability, consistency, and review workflows.

Track adoption and impact with operational and GitOps-specific metrics.

DORA-style operational metrics:

  • Deployment frequency: how often changes reach environments.

  • Lead time for changes: how long it takes a commit to reach production.

  • Mean time to recovery: how quickly rollback or remediation happens.

  • Change failure rate: how often deployments trigger incidents.

GitOps-focused metrics:

  • Percentage of deployments performed through GitOps rather than manual kubectl.

  • Time to detect and correct drift.

  • Count of manual cluster changes per week.

  • Policy violation rate.

The goal is not to drive every metric to zero. The point is to set baselines, identify constraints, and make informed decisions about where the platform needs improvement. 🛠️

Traditional CI/CD commonly pushes changes from a pipeline into a target environment. GitOps uses pull-based reconciliation: an agent inside the cluster observes the desired state and applies changes from the configured source.

In principle, yes. The model works with any system that supports declarative state and reconciliation. In practice, most mature tooling and patterns are centered on Kubernetes.

Use encrypted Git-native options such as Sealed Secrets when you want everything in the repository workflow. Use External Secrets Operator when secrets must stay in a dedicated backend such as Vault or a cloud secret manager.

For most teams, yes. A mainline branch with environment folders and pinned artifact versions is simpler to inspect and easier to operate. Branch-based promotion usually introduces merge pain and makes deployed state harder to understand. Highly regulated environments may still require it for audit reasons.

  • GitOps succeeds in production when it is treated as an operating contract, not just a YAML sync mechanism.

  • Git is a strong starting point, but OCI artifacts become attractive when repositories exceed about 1,000 files or release immutability and signing matter.

  • Prefer environment folders on a shared mainline branch over environment-specific branches.

  • For multi-cluster systems, choose between hub-and-spoke, standalone controllers, or a hybrid model based on blast radius, network boundaries, and operational overhead.

  • Avoid manual cluster changes, mutable references such as latest, and template stacks that hide the final manifest.

  • Use Sealed Secrets, External Secrets Operator, and Kyverno to make secrets and policy enforcement production-ready.

No posts

Read the original on afadeev.substack.com

Comments

Nothing yet. Say the first thing.

    Sign in to join the conversation.