Helm Charts: Standardizing Kubernetes Deployments
Back to Blog

Helm Charts: Standardizing Kubernetes Deployments

6 min read
Read in Deutsch

When kubectl Alone Is No Longer Enough

If you're deploying a single Spring Boot application to Kubernetes, a few YAML files and kubectl apply will get you surprisingly far. A Deployment, a Service, maybe a ConfigMap -- that's manageable. But in practice, it rarely stays at just one application.

In our projects, we now manage ten to twenty microservices per cluster, each with a Deployment, Service, Ingress, ConfigMap, and Secrets. Multiply that by three environments -- Development, Staging, and Production -- and you quickly end up with a hundred or more YAML files. And that's where the problems begin: values like image tags, replica counts, or resource limits differ per environment but are scattered across dozens of files. An image update requires manual changes in multiple places. Copy-paste errors creep in, and configurations drift apart.

What's missing is a mechanism that makes Kubernetes manifests parameterizable and reusable. That's exactly what Helm does.

What Helm Is -- and What Charts Are

Helm describes itself as a package manager for Kubernetes, and the analogy is fitting: what APT is for Debian or YUM for Red Hat, Helm is for Kubernetes. It bundles related Kubernetes resources into a defined format -- called Charts -- and makes them versionable, configurable, and shareable.

At its core, a Helm Chart is a directory with a fixed structure:

my-spring-app/
  Chart.yaml          # Metadata: name, version, description
  values.yaml         # Default configuration values
  templates/          # Kubernetes manifests as Go templates
    deployment.yaml
    service.yaml
    ingress.yaml
    configmap.yaml
  charts/             # Dependencies on other charts

Chart.yaml contains the chart's metadata -- name, version, and a description. values.yaml defines the default values used to render the templates. And in the templates/ directory, you'll find the actual Kubernetes manifests -- not as static YAML, but as Go templates with placeholders.

Parameterization: The Heart of Helm

Helm's real strength lies in the separation of structure and configuration. Instead of writing concrete values in every YAML file, you reference variables that are resolved from values.yaml at deployment time.

A typical values.yaml for a Spring Boot application looks like this in our setup:

replicaCount: 2

image:
  repository: registry.example.com/my-spring-app
  tag: "1.3.0"
  pullPolicy: IfNotPresent

service:
  type: ClusterIP
  port: 80

resources:
  requests:
    memory: "256Mi"
    cpu: "250m"
  limits:
    memory: "512Mi"
    cpu: "500m"

spring:
  profile: "default"

The corresponding Deployment template accesses these values:

apiVersion: apps/v1beta1
kind: Deployment
metadata:
  name: {{ .Release.Name }}-{{ .Chart.Name }}
  labels:
    app: {{ .Chart.Name }}
    release: {{ .Release.Name }}
spec:
  replicas: {{ .Values.replicaCount }}
  selector:
    matchLabels:
      app: {{ .Chart.Name }}
      release: {{ .Release.Name }}
  template:
    metadata:
      labels:
        app: {{ .Chart.Name }}
        release: {{ .Release.Name }}
    spec:
      containers:
        - name: {{ .Chart.Name }}
          image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
          imagePullPolicy: {{ .Values.image.pullPolicy }}
          ports:
            - containerPort: 8080
          resources:
            requests:
              memory: {{ .Values.resources.requests.memory }}
              cpu: {{ .Values.resources.requests.cpu }}
            limits:
              memory: {{ .Values.resources.limits.memory }}
              cpu: {{ .Values.resources.limits.cpu }}
          env:
            - name: SPRING_PROFILES_ACTIVE
              value: {{ .Values.spring.profile | quote }}

The double curly braces mark Go template expressions. .Values refers to values.yaml, .Release contains information about the current release, and .Chart holds the metadata from Chart.yaml. The quote filter ensures that values are properly enclosed in quotation marks.

Environment-Specific Configurations

The decisive advantage becomes apparent when you need to serve different environments. Instead of maintaining separate YAML files per environment, you create additional values files:

# values-dev.yaml
replicaCount: 1
image:
  tag: "latest"
resources:
  requests:
    memory: "128Mi"
    cpu: "100m"
  limits:
    memory: "256Mi"
    cpu: "250m"
spring:
  profile: "dev"
# values-prod.yaml
replicaCount: 3
image:
  tag: "1.3.0"
resources:
  requests:
    memory: "512Mi"
    cpu: "500m"
  limits:
    memory: "1Gi"
    cpu: "1000m"
spring:
  profile: "production"

During deployment, you simply specify the appropriate values file. The templates remain identical -- only the configuration changes. No duplicating manifests, no risk of environments drifting apart structurally.

Helm in Practice: Installation, Upgrades, and Rollbacks

Before you can use Helm, it needs to be initialized in the cluster. Helm 2 uses a client-server architecture: the helm client runs locally, and on the cluster side runs Tiller -- a server process that executes the actual deployments.

# Helm initialisieren (installiert Tiller im Cluster)
helm init

# Chart installieren -- Development-Umgebung
helm install --name my-app-dev -f values-dev.yaml ./my-spring-app

# Chart installieren -- Production-Umgebung
helm install --name my-app-prod -f values-prod.yaml ./my-spring-app

# Laufendes Release aktualisieren (z.B. neues Image-Tag)
helm upgrade my-app-prod -f values-prod.yaml --set image.tag="1.4.0" ./my-spring-app

# Rollback auf die vorherige Version
helm rollback my-app-prod 1

# Alle Releases anzeigen
helm list

# Templates lokal rendern, ohne zu deployen (zum Prüfen)
helm template -f values-prod.yaml ./my-spring-app

helm template in particular has proven indispensable in our workflow. The command renders the templates locally and outputs the finished YAML without changing anything in the cluster. This way, you can verify that the generated manifests are correct before every deployment -- ideal for code reviews and CI pipelines as well.

helm upgrade performs a rolling update and versions each release in the process. If something goes wrong, helm rollback restores the previous state. Helm manages the release history internally, so you can always revert to an earlier revision.

A Word on Tiller

Tiller is Helm's server-side component and one of its most controversially discussed aspects. Tiller runs as a pod in the cluster and requires extensive permissions to create and manage resources. In the default configuration, Tiller has cluster-admin rights -- which is a serious security concern in shared cluster environments.

For production environments, we recommend running Tiller with restricted RBAC permissions and securing access via TLS. The configuration is not trivial, but it is necessary:

# Tiller mit Service-Account und eingeschränktem Namespace installieren
helm init --service-account tiller --tiller-namespace my-team

The community is actively discussing the future of Tiller. There are efforts to reduce or entirely eliminate the server-side component. Until that happens, you should be aware of the security implications and take appropriate measures.

Reusing and Sharing Charts

Beyond your own charts, there is a growing ecosystem of public charts. The official Stable repository contains charts for common software like PostgreSQL, Redis, Prometheus, or Nginx Ingress. A helm search shows the available charts, and with helm install stable/postgresql, you have a database running in your cluster within seconds.

For your own charts, you can set up a private chart repository -- in the simplest case, an HTTP server that serves an index file and the packaged chart archives. In our teams, we have an internal repository with base charts for Spring Boot services that new projects use as a starting point. This saves considerable setup time for every new service.

Conclusion

Helm solves a real problem that every team running more than a handful of services in Kubernetes knows: managing configuration across environments. The separation of template structure and configuration values is a simple yet powerful concept. Instead of manually maintaining hundreds of YAML files, you have parameterized charts that can be used equally for Development, Staging, and Production.

Helm is increasingly becoming the de facto standard for Kubernetes deployments. More and more projects and tools are adopting the chart format, and the community is growing steadily. Anyone who uses Kubernetes seriously can hardly avoid Helm.

Our advice: start with a single service. Create a chart, parameterize the most important values, and deploy it to two environments. The initial effort pays off quickly -- at the latest when the next service can use the same chart as a template.

Patrick Hütter

Written by

Patrick Hütter

Founder & Software Architect

Software architect, engineer and entrepreneur. Patrick has been building products and platforms for over a decade — from enterprise backends and cloud-native infrastructure to AI-powered applications. As founder of encircle360, he combines deep technical expertise with entrepreneurial vision, driving open source projects that create real impact.