Helm Charts: Standardizing Kubernetes Deployments
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.
Written by
Patrick HütterFounder & 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.
You might also like
CI Runners in a microVM: Docker Builds with Kata Containers on Kubernetes
Aug 31, 2026 · 10 min read
GitOps with Helmfile and Kyverno: Our Deployment Workflow
Mar 10, 2025 · 7 min read
K3s and KubeVirt: Converged Infrastructure on Bare Metal
Jan 20, 2025 · 6 min read