LabHub

Blog

Operator vs Helm vs GitOps — Which to Use When

한국어English日本語

Introduction — The Common Mistake of Lining Up All Three

If you have run Kubernetes for a while, you have probably received or asked questions like "We use Helm, so do we also need an Operator?" or "If we adopt GitOps, do we throw away Helm?" These questions confuse people because Operator, Helm, and GitOps appear to be three candidates competing for the same seat.

But these three solve fundamentally different problems. In a single line each:

In other words, they are mostly complementary, not competitive. In real production, using all three together is the most common scenario. The goal of this article is to clearly separate the essence of each tool and to build judgment criteria for which to choose in which situation.

The Essence of Each — Packaging, Synchronization, Controller

Helm — Packaging and Templating

The core of Helm is the package called a "chart." A chart bundles many Kubernetes manifests (Deployment, Service, ConfigMap, etc.) into one unit and injects values to produce different results per environment.

# templates/deployment.yaml (part of a Helm template)
apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-app
spec:
  replicas: 2
  template:
    spec:
      containers:
        - name: app
          image: my-app:1.2.3

Producing dev/stage/prod from the same chart by only changing values is the typical Helm usage. The important point is that Helm itself is a tool that "renders once and sends to the cluster." It acts only at the moment you run helm install or helm upgrade, and if someone later changes the cluster by hand, Helm does not know. Helm is a static deployment tool.

GitOps — Declarative Continuous Synchronization

GitOps is an operating model rather than a tool. Its core principles are four:

  1. Express the entire desired state of the system as declarations.
  2. Version those declarations in Git. Git becomes the single source of truth.
  3. Approved changes are automatically applied to the cluster.
  4. An agent continuously compares actual state with declarations, and alerts or reverts when drift appears.

Tools like Argo CD and Flux implement this model. The decisive difference is point four, continuous reconcile. Even if someone manually changes replicas in the cluster, Argo CD soon detects it as drift and reverts to the Git state. If Helm is "fire and forget," GitOps is "constant aim correction."

Operator — A Controller That Encodes Operational Knowledge

An Operator goes one level deeper. While Helm and GitOps focus on handling Kubernetes built-in resources, an Operator adds a new resource kind (a CRD, Custom Resource Definition) and deploys a dedicated controller that tends it.

For example, installing a PostgreSQL Operator creates a new resource type such as PostgresCluster in the cluster. A user declares this:

apiVersion: postgres.example.com/v1
kind: PostgresCluster
metadata:
  name: orders-db
spec:
  instances: 3
  version: "16"
  backup:
    schedule: "0 2 * * *"

For this single declaration, the Operator handles StatefulSet creation, replication setup, failover, backup scheduling, and even minor version upgrades on its own. It automates the operational procedures (Day-2 operations) a human would otherwise perform.

The core difference, drawn out:

Helm:    values -> [render once] -> manifests -> cluster
                  (acts only at execution time)

GitOps:  Git declarations -> [agent keeps comparing] -> cluster
                            (auto drift correction, continuous reconcile)

Operator: CR declaration -> [dedicated controller keeps reconciling] -> built-in resources + operational actions
                          (automates backup/failover/upgrade too)

Static Deployment vs Continuous Reconcile

The most important axis separating the three tools is "does it act once, or does it keep acting?"

CategoryWhen it actsDrift correctionAnalogy
Helm aloneAt command executionNoneTaking a single photo
GitOpsAlways (periodic + event)AutomaticAutofocus camera
OperatorAlways (event-driven)Automatic + operational actionsHiring a dedicated operator

If you use Helm alone, cluster state gradually diverges from declarations. Someone hotfixes with kubectl edit, someone else temporarily bumps replicas, and over time no one knows exactly "what is running in the cluster now." GitOps structurally prevents this drift problem. The Operator goes one step further: beyond matching manifests, it handles application-specific operational actions (backup, failover, etc.) inside the reconcile loop.

Day-1 vs Day-2 — Where Is the Boundary

The operations industry divides work into Day-0/Day-1/Day-2.

Viewing the three tools through this lens makes their strengths clear.

In conclusion, an Operator shines for stateful applications whose Day-2 operations are complex and repetitive (databases, message queues, search engines). For a stateless web application with simple operations, an Operator may be an overkill choice.

Combination Patterns — In Practice, Use All Three Together

The most common and recommended setup places the three roles so they do not overlap.

Pattern 1 — Deploy a Helm Chart via GitOps

Argo CD and Flux can handle Helm charts directly as a source. The chart is used as the packaging unit, while applying and maintaining it in the cluster is handled by the GitOps agent.

# Argo CD Application — managing a Helm chart via GitOps
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: my-app
  namespace: argocd
spec:
  project: default
  source:
    repoURL: https://github.com/example/my-app-chart
    targetRevision: main
    path: charts/my-app
    helm:
      valueFiles:
        - values-prod.yaml
  destination:
    server: https://kubernetes.default.svc
    namespace: my-app
  syncPolicy:
    automated:
      prune: true
      selfHeal: true

This gives you Helm's templating power and GitOps's continuous synchronization and drift correction at the same time. Humans no longer run helm install directly, and all changes flow through Git PRs.

Pattern 2 — Install the Operator with Helm, Manage CRs with GitOps

The Operator itself is usually installed via a Helm chart or OLM (Operator Lifecycle Manager). So Helm handles deploying the Operator controller, while GitOps manages the custom resources (CRs) the Operator handles.

[Platform team]
  Install PostgreSQL Operator via Helm chart (controller + CRD)
        |
        v
[Application team]
  Declare PostgresCluster CR in Git -> Argo CD synchronizes
        |
        v
  Operator reconciles the CR -> automates StatefulSet/backup/failover

In this setup the responsibilities split cleanly. Helm "installs the Operator software," GitOps "keeps declarations matched to the cluster," and the Operator "actually operates the declared DB." The three tools do not invade each other's territory.

Pattern 3 — App-of-Apps and Bootstrap

In large platforms, the Argo CD App-of-Apps pattern lets "Argo CD manage all other Applications," and those Applications in turn deploy Helm charts (shared infrastructure), Operator installations, and workloads. The entire cluster bootstrap flows out of a single Git tree.

Decision Table — What to Use When

The following table summarizes which tool fits each situation.

SituationRecommended toolReason
Deploying a stateless web appHelm + GitOpsSimple ops, Operator unnecessary
Many deployments differing only by env valuesHelm + GitOpsTemplating + continuous sync
Operating a production databaseOperatorNeeds backup/failover/upgrade automation
Operating message queues, search enginesOperatorComplex Day-2 operations
Consistent management across many clustersGitOpsGit single source of truth
Environments with frequent driftGitOpsAuto-correction via selfHeal
Automating an internal standard workflowOperator (or simple controller)Codifying operational knowledge
One-off experiment/PoCHelm aloneFastest, no governance needed

Rather than memorizing the table, compress it into one question. "Are the operations (Day-2) of this system repetitive and requiring application-specific knowledge?" If so, consider an Operator; if not, look first at the Helm + GitOps combination.

Recommendations by Case

When an Operator Is Overkill — Facing the Cost

An Operator is powerful but not free. In the following cases, an Operator is likely over-engineering.

The real cost of an Operator is in long-term maintenance rather than writing code. Every time the Kubernetes version goes up you must follow the controller-runtime dependency, you must manage RBAC permissions precisely, and a bug in the reconcile logic translates directly into an operational incident. You must clearly distinguish "using a well-maintained external Operator" from "building an Operator yourself" — they have entirely different cost structures.

Migration — From a Helm Chart to an Operator

Mature applications often start as a Helm chart and evolve toward an Operator as operational complexity grows. The typical path:

  1. Inventory the current chart's operational burden. Write down all the procedures humans do manually (backup, scaling, incident recovery). These are candidates for the Operator to automate.
  2. Design the CRD schema. Keep only the minimal intent a user declares (e.g., instance count, version, backup schedule) and let the controller decide the rest.
  3. Migrate incrementally. First make the Operator create the same resources the existing chart produced, then add operational actions like backup/failover in stages.
  4. Allow a coexistence period. Run the existing Helm deployment and the Operator-based deployment in parallel for a while, verify equivalence, then switch.

Conversely, if the conclusion is "there is no real need to move to an Operator," staying with Helm + GitOps is a perfectly correct choice. Migration itself must not become the goal.

Operational Cost Comparison

ItemHelm + GitOpsOperator (external)Operator (in-house)
Initial adoption difficultyLowMediumHigh
Day-2 automation levelLimitedHighHigh
Maintenance burdenLowMedium (track upstream)High (manage code directly)
Required skillsYAML, GitOpsAdoption/configGo, controller-runtime
Debugging on failureTrace manifestsCR + controller logsDown to controller code

The key message of this table is "the higher the automation level, the higher the maintenance burden rises with it." Automation is not free; it is closer to moving cost into the future.

Observability and Troubleshooting — Where to Look per Tool

When an incident occurs, where to look differs by tool. Having this map in advance greatly speeds up your response.

SymptomHelm viewGitOps viewOperator view
Deployment not reflectedCheck helm historyCheck Application sync statusCR status and controller logs
Resource keeps reverting(N/A)Suspect selfHeal drift correctionSuspect reconcile loop
Only some envs differCompare values filesCheck per-env source pathsCheck CR spec differences
Resource not deletedhelm uninstall remnantsSuspect prune disabledSuspect unfinished finalizer

The key is to first grasp "who currently owns this resource." Depending on whether Helm created it, GitOps synced it, or the Operator reconciled it, the place you act changes entirely. If two parties manage the same resource, that very spot is the epicenter of conflict.

Tracking Ownership via Labels

All three tools leave identifying metadata on the resources they create. Helm marks releases with annotations and labels, Argo CD leaves a tracking label indicating which Application owns it, and an Operator usually points to the parent CR via an ownerReference. The first step of incident analysis is to read the metadata of the problem resource and confirm "whose it is."

The order for tracing a resource's origin
  1. Check metadata with kubectl get <resource> -o yaml
  2. If there is an ownerReference -> owned by an Operator (or a built-in controller)
  3. If there is an Argo CD tracking label -> owned by GitOps
  4. If there is a Helm release annotation -> owned by Helm

A Collection of Pitfalls

A Decision Flowchart

Turning the table into a flowchart is more useful at the actual moment of choice.

Start: What are you deploying/operating?
   |
   +- A one-off experiment? --yes--> Helm alone is enough
   |
   +- No
        |
        +- Do you need consistent application across many envs/clusters? --yes--> Adopt GitOps
        |                                                              (with Helm/Kustomize as source)
        |
        +- Are this system's Day-2 operations complex?
             (Are backup/failover/version upgrade repetitive?)
                 |
                 +- Yes --> Is there a well-maintained external Operator?
                 |            +- Yes --> Install that Operator with Helm + manage CRs with GitOps
                 |            +- No  --> Consider building your own Operator (face the cost)
                 |
                 +- No --> Helm + GitOps is enough

This flowchart has only two branch points. First, "do you need consistency" (-> GitOps); second, "is Day-2 complex" (-> Operator). Most decisions end with these two questions.

Common Anti-patterns

A collection of pitfalls people often fall into while handling the three tools.

A Practical Scenario — Following One Service's Evolution

Abstract comparison alone is hard to grasp. Let us follow how a hypothetical order service adopts the three tools in turn over time.

Stage 1 — Helm Alone (Right After Launch)

Early on, the service is just one Deployment, one Service, and one ConfigMap. A developer deploys by running helm upgrade directly from a laptop. At this stage, this is enough. Governance and drift worries are not yet a big problem. Still, the following symptoms start to appear.

Stage 2 — Adopting GitOps (As the Team Grows)

Here you introduce Argo CD. You keep the chart as-is and move only the deploy trigger to Git PRs. Now every change goes through PR review, who changed what and when remains in Git history, and drift is auto-corrected by selfHeal. The helm command on developer laptops disappears.

Change: "humans apply directly to the cluster"
   -> "humans open a PR in Git, the agent applies to the cluster"

The key benefit of this shift is not deployment itself but auditability and consistency.

Stage 3 — Adopting an Operator (As the Data Layer Grows Heavy)

As order volume grows, you begin operating PostgreSQL yourself. Backup, failover, and minor version upgrades reach a level that must leave human hands. Here you adopt a proven PostgreSQL Operator. You install the Operator controller with Helm and manage the PostgresCluster CR with GitOps. The combination Pattern 2 seen earlier is exactly this stage.

The lesson of this evolution is clear. You do not need to adopt all three from the start. Add them one at a time when operational complexity actually justifies the tool. Excessive upfront investment is itself debt.

Frequently Asked Questions

Q. Is Helm now outdated? Do Kustomize or GitOps replace it? No. Helm is still the most widely used packaging tool, and GitOps agents support Helm charts as first-class. Kustomize is a different approach that mutates via overlays without templates; rather than competing with Helm, it is chosen by taste and situation. All three can coexist under GitOps.

Q. If I use an Operator, do I not need GitOps? On the contrary, using them together is the standard. The Operator knows "how to operate the CR," and GitOps knows "how to match that CR declaration to the cluster." Their roles differ.

Q. We are a small team — do we have to learn all three? No. A small team can run most stateless workloads well with just Helm + GitOps. Adopt an Operator when truly complex stateful operations arise, and preferably start by adopting a well-maintained external Operator.

Q. Which is easier to maintain, a homegrown Operator or a Helm chart? Generally a Helm chart is far easier. An Operator adds a continuous maintenance surface of Go code, the controller-runtime dependency, RBAC, and reconcile logic. It is justified only when the value of automation exceeds that burden.

Q. Between Argo CD and Flux as the GitOps agent, which should I pick? Both faithfully implement the OpenGitOps principles, and their core features are nearly equivalent. Argo CD's strengths are a rich web UI and intuitive sync visualization, while Flux's strengths are the modular composition of the GitOps Toolkit and its lightness. Teams that value a UI often pick Argo CD, and teams that prefer code/CRD-centric assembly often pick Flux. The conclusion of this article (the division of roles among the three) applies the same either way.

Q. Doesn't adopting an Operator make cluster permissions too broad? A valid concern. An Operator needs permissions over the resources it manages, and for a database Operator, Secret access is often unavoidable. So use Operators from trustworthy sources, always review the RBAC granted at install, and where possible limit it to a namespace scope. Permission review is one of the key criteria in choosing an Operator.

An Example Repository Structure Holding All Three

Moving theory into an actual directory makes it clearer. The following is a typical structure for operating Helm + GitOps + Operator in one repository.

platform-gitops/
├── bootstrap/
│   └── argocd-apps.yaml          # App-of-Apps: manages everything below
├── infrastructure/
│   ├── postgres-operator/        # Operator install (references a Helm chart)
│   │   └── application.yaml
│   └── cert-manager/             # shared infrastructure (Helm)
│       └── application.yaml
├── databases/
│   └── orders-db/
│       └── postgrescluster.yaml  # the CR the Operator reconciles (GitOps-managed)
└── apps/
    └── orders-api/
        ├── application.yaml      # Argo CD Application pointing to a Helm chart
        └── values-prod.yaml      # per-environment values

In this structure, each layer's role is revealed by the directory.

The key is guaranteeing ordering with sync waves. The Operator (and its CRD) must be installed first, then the CRs in databases/ are applied. With Argo CD, you express this dependency by assigning wave numbers via annotations. This single directory structure visually summarizes the whole conclusion of this article: the three tools coexisting in their respective places, without conflict, under one Git tree.

Closing

Summarizing the three tools again in one sentence: Helm bundles, GitOps matches, and the Operator tends. They are not competitors but different layers of the operational stack. Most production setups converge on the combination "package with Helm, synchronize with GitOps, automate operations with an Operator only where needed."

The starting point of choice is always operational complexity. Do not drag an Operator into a simple workload, and do not leave complex stateful operations to human hands. Once you know the essence of each tool, "what to use when" follows naturally.

References

Comments

No comments yet.

Sign in to leave a comment