Helm Charts - Introduction to Advanced GitOps

If you are new to Kubernetes, you have likely realized that managing dozens of static YAML files for every deployment, service, and ingress quickly becomes a nightmare. Enter Helm, the package manager for Kubernetes.

Helm allows you to bundle your Kubernetes resources into a single logical unit called a “Chart,” making your infrastructure reusable, shareable, and dynamic. In this guide, we will start with the absolute basics of Helm’s architecture and work our way up to how Staff Engineers deploy Helm in modern GitOps environments.


1. The Helm Engine Room: Mastering values.yaml, templates/, and charts/

To understand Helm and adhere to the DRY (Don’t Repeat Yourself) principle across your staging, UAT, and production environments, you need to understand the relationship between three critical components.

1. templates/: The Blueprint

If you look inside a standard Kubernetes environment, you see static YAML. In a Helm chart, the templates/ directory holds the structural blueprints for those YAML files, supercharged with Go template syntax.

Instead of hardcoding a namespace or a Docker image tag, you use placeholders enclosed in double curly braces ``. When you run helm install, the Helm engine evaluates the logic, replaces the placeholders, and outputs the final Kubernetes manifests.

**Example: templates/service.yaml**

apiVersion: v1
kind: Service
metadata:
  name: 
spec:
  type: 
  ports:
    - port: 
      targetPort: http
      protocol: TCP

Notice how the structure remains pure Kubernetes YAML, but the data is completely dynamic.

2. values.yaml: The Control Panel

If templates/ is the blueprint, values.yaml is the control panel. It defines the default state of your application. When the Helm engine encounters `` in your template, it immediately looks inside values.yaml to resolve it.

**Example: values.yaml**

# Default configuration for sample-api
replicaCount: 3

service:
  type: ClusterIP
  port: 8080

💡 DevOps Best Practice: Value Overrides

The true power of values.yaml is that it acts as a base. You do not need to rewrite this file for different environments. Instead, you override specific values during deployment via the CLI:

helm upgrade --install prod-api ./sample-api \
  --set service.type=LoadBalancer \
  --set replicaCount=5

Or, even better, maintain separate environment files (like values-prod.yaml) and pass them in: helm install ... -f values-prod.yaml.

3. charts/: The Dependency Tree

Modern microservices rarely live in isolation. Your API might need a Redis cache or a PostgreSQL database. Instead of managing those separately, Helm allows you to bundle them using the charts/ directory (known as subcharts).

  • Self-Contained: Subcharts live inside your main chart’s charts/ directory, making your deployment entirely self-contained.
  • Global Values: You can configure a subchart from your parent chart’s values.yaml. For instance, set the PostgreSQL password globally, and Helm passes it down to the Postgres subchart.

2. Demystifying Chart.yaml: The Identity Card

Before a single template is rendered, Helm looks at the foundational blueprint of your package: the Chart.yaml. This file dictates API compatibility, manages dependencies, and governs your deployment lifecycle through strict versioning.

The Anatomy of a Chart.yaml

A production-ready Chart.yaml will look something like this:

apiVersion: v2
name: sample-api
description: A Helm chart for the internal Sample Web API
type: application
version: 1.2.0
appVersion: "2.1.4"
maintainers:
  - name: platform-team
    email: devops@yourcompany.com
dependencies:
  - name: redis
    version: 17.x.x
    repository: https://charts.bitnami.com/bitnami

Key Fields Explained

Field Requirement Description
apiVersion Required For Helm 3, this must be set to v2.
name Required The name of the chart. Must match the directory name.
version Required The version of the chart code itself (SemVer 2). Increment this when changing templates or values.yaml.
appVersion Optional The version of the software you are deploying (e.g., Docker image tag "2.1.4"). Wrap in quotes to avoid YAML parsing errors.
dependencies Optional A list of subcharts this chart relies on.

💡 DevOps Best Practice: version vs appVersion

Getting this wrong can wreak havoc on CI/CD pipelines. Strictly decouple your chart versions from your application versions.

  • Updating Application Code: If you update the NGINX image tag from v1 to v2, update the appVersion to "2.0" AND bump the chart version (e.g., 1.0.0 to 1.0.1) because the default config changed.
  • Updating Infrastructure: If you add a new HorizontalPodAutoscaler template but leave the app code alone, leave appVersion as-is, but bump the chart version (e.g., 1.0.1 to 1.1.0).

3. Advanced Context: Helm in a GitOps World

Now that you understand the mechanics of Helm, we must discuss how it is actually used in production today. In 2026, firing helm upgrade --install from your laptop or a Jenkins script is considered an anti-pattern. It leads to configuration drift and split-brain states where Git no longer reflects cluster reality.

Today, GitOps (powered by tools like ArgoCD and Flux) is the industry standard. This requires a mental shift: In GitOps, Helm is no longer your deployment orchestrator; it is strictly a packaging and templating format.

How GitOps Actually Handles Helm

  • The ArgoCD Paradigm (Helm as a Templater): ArgoCD does not create a Helm release. It executes a helm template command behind the scenes, takes the raw YAML output, and uses its own sync engine to apply it. Standard CLI commands like helm ls or helm rollback will not work.
  • The Flux Paradigm (Helm as a Native Controller): Flux utilizes a HelmRelease Custom Resource. It actually pulls the chart, merges your Git-stored values.yaml, and natively installs it, giving you standard Helm CLI compatibility.

The Modern GitOps Flow

To visualize how Helm fits into continuous delivery, look at how we separate application code from infrastructure configuration:

Kubernetes Cluster

CI Pipeline

Git

Push Code

Automated PR / Direct Commit

Continuous Pull / Watch

Inject Values & Render

Apply Desired State

App Code Repository

GitOps Config Repo
e.g., /envs/prod/values.yaml

Run Tests & Build Image

Push to OCI Registry

Commit new Image SHA to GitOps Repo

GitOps Controller
ArgoCD / Flux

Helm Templating Engine

Live K8s Resources

Architectural Takeaway: The CI pipeline does not touch the cluster. Its only job is to build an immutable artifact and update the values.yaml in the GitOps repository.

Implementation: Architectural Best Practices

  1. Embrace the Twin-Repo Strategy: Never store your Helm values.yaml files in the same repository as your application source code. Keep app code (Repo A) separate from declarative infrastructure state (Repo B).
  2. Standardize on OCI Artifacts: Package your Helm charts and push them to OCI-compliant registries (like AWS ECR or GitHub Container Registry) right alongside your Docker images. Both ArgoCD and Flux natively support pulling charts from OCI.
  3. Stop Putting Secrets in values.yaml: Never commit base64-encoded secrets into Git. Instead, your Helm chart should template out ExternalSecret resources to securely fetch credentials from AWS Secrets Manager or HashiCorp Vault at runtime.

Conclusion

By stripping Helm of its manual deployment responsibilities and using it strictly as a templating standard, we eliminate the flakiness of imperative rollouts. Let Helm build the puzzle pieces, and let GitOps put them together.