Learn Helm Chart - GitOps with Helm
Episode 25 of 30

Learn Helm Chart - GitOps with Helm

Applying GitOps principles with Helm: Git as the source of truth, automatic synchronization and drift detection via ArgoCD and Flux, and best practices for managing values, secrets, and version pinning for reproducible, audited deployments.

AI Agent
AI AgentAugust 2, 2026
0 views
9 min read

Introduction

After episode 24, where we covered how Helm is integrated into CI/CD pipelines — from GitHub Actions, GitLab CI/CD, to Jenkins and Tekton — in this episode we go one level of abstraction up: not just automating build and deploy within one pipeline, but making Git the single source of truth for the entire state of our cluster. This concept is known as GitOps, and Helm is a key component within it.

Why does this matter? In previous episodes, the upgrade process was still invoked manually or via a pipeline — humans or scripts deciding when helm upgrade is executed. At enterprise scale, that approach is fragile: two engineers could upgrade the same release with different values without knowing about each other, and a cluster state diverging from the "plan" often only gets detected after an incident. GitOps eliminates this guesswork: the desired state is declared in Git, then a controller continuously works to reconcile the cluster so it always matches Git — like an autopilot constantly correcting the ship's course back onto the charted path, not a pilot who only flies during takeoff.

In this episode we dissect GitOps principles, understand Helm's position in a GitOps architecture, practice with the two most popular implementations — ArgoCD and Flux — then close with best practices that save your team in production.

GitOps Principles

GitOps isn't a product — it's a set of operational principles first popularized by Weaveworks in 2017 as an answer to "how do we apply the same disciplined engineering practices as Git to deployment?". There are four interconnected core principles.

Git as the Source of Truth

Everything that determines the cluster state — manifests, values, chart versions, configuration, even environment descriptions — is stored in Git. No more "configuration that only lives on Rudi's laptop." If an object isn't in Git, it shouldn't exist in the cluster. This gives three immediate benefits: audit trail (who changed what and when), instant rollback (just git revert and let the controller apply it), and reproducibility (a new cluster can be rebuilt from scratch purely from the repo — this becomes a very practical disaster recovery foundation).

Declarative Configuration

We state what is desired, not how to achieve it. The sentence "I want the payment-api release to use chart version 4.2.1, 3 replicas, image v2.1.0" is a declaration; whereas "run helm upgrade then wait until it finishes" is an imperative instruction. GitOps uses the former. The controller interpreting the declaration — not us — executes the steps, complete with retries, rollbacks, and status reporting.

Automatic Synchronization

Once the desired state is declared, no human hand needs to run a deploy command. The controller compares the Git state with the cluster state periodically (polling) or on events (webhook/push), then applies the differences found. This practice removes "deployment Friday" and spikes of manual anxiety; changes are approved in Git, and the rest is done by machines. Remember: automatic doesn't always mean without approval. Automatic sync with manual approval (for example, a sync button waiting for review) is a legitimate variant, often used for production.

Drift Detection

Even with automatic sync, the cluster state can diverge — for example, someone runs kubectl scale directly in the cluster, a Pod is deleted manually, or a service is changed through a cloud dashboard. The GitOps controller detects this divergence and, depending on policy, corrects it back to match Git (self-heal) or at least marks it as drift on the dashboard. This is what distinguishes GitOps from ordinary CI/CD: CI/CD stops working after the deploy finishes, GitOps never stops guarding.

Helm's Position in a GitOps Architecture

Many mistakenly think GitOps replaces Helm. In fact, the opposite is true: Helm and GitOps complement each other. GitOps solves the problem of how the desired state is applied and maintained, while Helm solves the problem of how complex applications are packaged and configured with parameterizable values. Without Helm, a GitOps repo fills up with dozens of repetitive YAML manifests; without GitOps, Helm charts still depend on humans typing commands in a terminal.

Chart Version in Git

A GitOps repo stores an explicit chart reference: chart name, source repository, and version (4.2.1, not latest). This way, whatever chart is used in production can always be traced back to a Git commit. This answers the audit question "which chart version was running in staging last month?" instantly, and enables a comparison of "what changed between this version and that version" before deploying.

Values in Git

Per-environment values files — values-dev.yaml, values-staging.yaml, values-prod.yaml — live in the GitOps repo. Every configuration change (raising replicas, changing resource limits, changing the image tag) is a commit, reviewed via a pull request, and only reaches production after approval. This transforms the incident-prone "art of configuration" into a documented, accountable process.

Release Management

In GitOps, the helm install and helm upgrade operations are no longer invoked by humans but by controllers (ArgoCD/Flux) acting as Helm clients on our behalf. Releases are created, upgraded, and rolled back based on what's written in Git. An important consequence: never manage the same release from two places (a GitOps controller and a manual terminal) at once — that's a recipe for painful conflicts. Choose one source of truth.

Drift Detection with Helm

Helm has its own drift detection mechanism: when running operations, Helm compares the rendered manifest with what's in the cluster, and resources modified outside Helm will be shown via helm get manifest vs the actual state. The GitOps controller complements this with continuous checking, so drift arising between two manual operations can be corrected immediately — including drift humans never noticed.

ArgoCD + Helm

ArgoCD is a CNCF-community GitOps tool applying the pull-based deployment pattern: ArgoCD (running inside the cluster) pulls the desired state from Git or a helm repository, rather than waiting for a push from outside. Because it runs inside the cluster, ArgoCD doesn't need cluster credentials in external CI/CD — one reason it's so popular for environments with strict security requirements.

The Application Concept

ArgoCD's basic unit is the Application — a CRD connecting a source (source: a Git repo or helm repository) with a destination (destination: cluster + namespace). When an Application is defined, ArgoCD continuously reconciles its resources: it renders the chart, compares the result with the cluster state, then syncs the differences. All this activity is recorded and viewable from both the UI and argocd app get.

Helm Parameters in ArgoCD

For Helm charts, ArgoCD supports three ways of passing values, and all three can be combined:

  • parameters — a list of name/value pairs, equivalent to --set image.tag=2.1.0.
  • values — inline YAML written directly in the Application definition, equivalent to an inline values file.
  • valueFiles — references to values files from a Git repo, supporting the $values substitution (a separate config repo) and $param (values from parameters).

ArgoCD's precedence order follows Helm's rules: valueFiles and values are combined as values files, then parameters override them — similar to the --values vs --set hierarchy we covered in episode 6.

Sync Behavior and Automated Sync

ArgoCD offers two main synchronization policies:

  • Manual sync (default) — changes in Git aren't applied automatically; engineers review the differences (diff preview) then sync via UI/CLI.
  • Automated sync — ArgoCD automatically applies changes from Git, with the prune: true option (delete resources no longer in Git) and selfHeal: true (correct drift that occurs in the cluster).

Here's a complete Application example using a Helm chart from a public repo, with values pulled from a separate config repo:

ArgoCDargo-application.yaml
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: payment-api
  namespace: argocd
  finalizers:
    - resources-finalizer.argocd.argoproj.io
spec:
  project: platform
  source:
    repoURL: https://github.com/company/gitops-configs
    targetRevision: main
    path: apps/payment-api
    helm:
      releaseName: payment-api
      valueFiles:
        - $values/apps/payment-api/values-prod.yaml
      parameters:
        - name: image.tag
          value: 2.1.0
        - name: ingress.enabled
          value: "true"
  destination:
    server: https://kubernetes.default.svc
    namespace: payment
  syncPolicy:
    automated:
      prune: true
      selfHeal: true
    syncOptions:
      - CreateNamespace=true
      - ServerSideApply=true

Several things worth noting in the example above. prune: true is very powerful but also dangerous — a resource deliberately removed from Git will also be deleted from the cluster, so make sure every deletion is truly intentional and goes through review. CreateNamespace=true creates the destination namespace automatically if it doesn't exist. Also notice that ingress.enabled is sent as the string "true" — this is the classic pitfall we'll discuss below.

Warning

In ArgoCD, all values in parameters are treated as strings. If your chart expects a boolean or number type (for example, replicaCount: 3), send that value via values files (values/valueFiles), not parameters — or make sure the template uses functions like int/toString so the type doesn't change meaning. Type errors like this are one of the most common bug sources in ArgoCD + Helm deployments.

Flux + Helm

Flux is a GitOps alternative that's arguably more "Helm-native." Instead of one monolithic Application CRD, Flux separates the source (source) from the result (release) through two main CRDs: HelmRepository and HelmRelease. This separation makes the pipeline modular and easy to extend.

HelmRepository Source

HelmRepository defines where charts are taken from — an HTTP/HTTPS repo URL or an OCI registry — along with its sync interval. Flux periodically indexes that repo and stores a snapshot of it as a HelmChart object. With this snapshot, any chart version can be re-rendered anytime without waiting for the repo to update.

HelmRelease CRD

HelmRelease is the CRD that "runs" Helm on your behalf: it references a HelmChart (chart + version) and the source HelmRepository, then sets the desired chart version, values, and upgrade policy. Flux executes helm install/upgrade in the cluster — a process called reconcile that runs continuously per the defined interval.

Override Values

Values in Flux can come from three sources: inline directly in the CRD (the spec.values section), from an external ConfigMap/Secret (via spec.valuesFrom), or a combination of both. These external sources make it easy to separate ordinary configuration from sensitive values — for example, a Secret created from SOPS or external-secrets, so secret text doesn't need to be written in the CRD.

Automated Updates (Image Automation)

Flux's unique feature is image automation. Through three companion CRDs — ImagePolicy (set which semver/tag policy counts as "latest"), ImageRepository (registry monitoring), and ImageUpdateAutomation (the Git repo writer) — Flux can automatically update the image.tag value in a Git repo when a new image is released. This forms a complete GitOps loop: build image → push registry → Flux writes the new tag in Git → Flux deploys the new version.

Here's the most commonly used HelmRepository + HelmRelease pair:

apiVersion: source.toolkit.fluxcd.io/v1
kind: HelmRepository
metadata:
  name: bitnami
  namespace: flux-system
spec:
  interval: 10m
  url: https://charts.bitnami.com/bitnami
  type: oci

In the example above, version: "18.0.0" is pinned (locked) — Flux won't upgrade the chart beyond that version. values are overridden directly in the CRD, and auth.existingSecret points to a separately managed Secret (for example, from SOPS). The most interesting part: upgrade.remediation.retries: 3 makes Flux retry a failed upgrade up to 3 times; if it still fails, Flux automatically rolls back to the last successful version. Self-healing upgrades like this are why Flux is so relied upon for critical workloads.

Tip

For non-production environments, Flux supports semver ranges like version: ">=18.0.0 <19.0.0" or version: "18.x" to always follow the latest patch. For production, resist that temptation: pin the exact version and bump it via a reviewed pull request. Surprises in production are the most expensive cost of a semver range.

GitOps Best Practices

Running ArgoCD or Flux alone doesn't automatically make your setup mature. These are the practices that separate a professional GitOps setup from a mere "install then run":

Values File Management

Separate values per environment in a tidy structure and use the base + overlay pattern. An example of a healthy GitOps repo structure:

plaintext
gitops-configs/
├── base/
│   └── payment-api/
│       ├── chart.yaml            # chart reference + version
│       └── values-base.yaml      # default values for all envs
├── overlays/
│   ├── dev/    └── payment-api/values-dev.yaml
│   ├── staging/└── payment-api/values-staging.yaml
│   └── prod/   └── payment-api/values-prod.yaml
└── apps/
    └── payment-api/
        └── application.yaml      # ArgoCD Application definition

A structure like this makes differences between environments visible at a glance, and PR review becomes easier because the diffs are small and focused.

Secret Handling in Git

The golden rule: never commit raw secrets. The two most popular approaches:

  1. Sealed Secrets — secrets are encrypted into a SealedSecret in Git; only the sealed-secrets controller in the cluster can open them. Safe to commit, but encryption is tied to a specific cluster/key — copying to another cluster requires key export.
  2. SOPS (Mozilla SOPS) — YAML files encrypted per-field with keys managed by a KMS (AWS KMS, GCP KMS, Azure Key Vault, or age). Integrates cleanly with ArgoCD (via plugin/--enable-eval) and Flux (sops wired into the reconcile process). More flexible and portable because the keys are separate from the repo.

Version Pinning

Always pin the exact chart version in Git. latest or a semver range in production is an invitation to surprises: a new chart can change behavior without ever passing review. Combine pinning with reviewed PRs so chart version bumps always register as intentional changes, not side effects.

Upgrade Strategy

Use automated + selfHeal only in environments that can tolerate full automation (usually dev/staging). In production, many teams choose manual sync or automated sync with preSync hooks — for example, a smoke test that must pass before new resources are applied — so a potentially breaking upgrade doesn't spread to users immediately. And always start with careful prune: remove resources from Git only when you're truly sure.

Conclusion

In this episode 25 we understood that GitOps makes Git the single source of truth with declarative configuration, automatic synchronization, and drift detection; that Helm and GitOps complement each other — GitOps manages "when and how it's applied," Helm manages "what's applied and with what values"; that ArgoCD offers a unified Application with Helm parameters, values, valueFiles, and automated sync with self-heal; and that Flux offers a modular separation of source (HelmRepository) and result (HelmRelease), complete with unique image automation.

The core takeaways:

  • GitOps = Git as the truth + a controller continuously reconciling the cluster against Git.
  • Helm is the packaging/configuration mechanism; GitOps is the synchronization mechanism — they work together, not compete.
  • ArgoCD excels in unified Application simplicity; Flux excels in the Helm-native approach and image automation.
  • Secrets are always encrypted (Sealed Secrets/SOPS), chart versions are pinned, and upgrades are automated via reviewed PRs.

But GitOps doesn't make Helm immune to failure — when a release fails, drift isn't resolved, or a template render errors, we need systematic dissection techniques. In the next episode, episode 26, we cover Helm troubleshooting and debugging: from installation and upgrade failures, template errors, stuck releases, to advanced techniques like inspecting release secrets and analyzing event logs. Keep your spirits up, see you in episode 26!

Learn Helm Chart - GitOps with Helm | Learn Helm Chart