Skip to main content

Command Palette

Search for a command to run...

Helm Best Practices 2025: What Changed with Helm 4 and What You Should Know

Updated
11 min readView as Markdown
Helm Best Practices 2025: What Changed with Helm 4 and What You Should Know

Helm 4.0 just dropped at KubeCon — here’s everything DevOps engineers need to know about the biggest changes in 6 years.

After six years since Helm 3, the Kubernetes package manager just got its biggest update. Helm 4.0 was released at KubeCon North America 2025 (November 10–13), bringing significant architectural changes, new features, and updated best practices that every DevOps engineer needs to understand.

If you’re managing Helm charts in production, this isn’t just another minor update — it’s a fundamental shift in how Helm handles deployments. But don’t panic: the team has focused heavily on maintaining chart compatibility while modernizing the underlying architecture.

In this guide, I’ll walk you through what’s new, what’s changed, the best practices you need to adopt, and how to migrate safely.

What’s New in Helm 4.0

The Big Changes

Helm 4.0 represents the first major version bump since 2019. Here are the headline features:

1. Server-Side Apply (SSA) is Now Default

The biggest architectural change: Helm 4 ditches the three-way merge strategy in favor of Server-Side Apply, the same approach Kubernetes itself uses.

What this means:

  • Better conflict detection and handling

  • Clearer ownership of fields

  • More predictable upgrade behavior

  • Explicit errors instead of silent overwrites

2. Completely Redesigned Plugin System

The plugin architecture got a complete overhaul with support for:

  • CLI plugins (command extensions)

  • Getter plugins (custom download protocols)

  • Post-renderer plugins (template modifications)

  • WebAssembly (WASM) runtime for cross-platform plugins

3. Advanced Resource Status Monitoring

Helm now uses kstatus for intelligent resource watching:

  • Waits for actual readiness, not just pod creation

  • Better understanding of resource conditions

  • Smarter timeout handling

  • Improved debugging when things fail

4. OCI Install by Digest

Enhanced OCI registry support with digest-based installs for:

  • Immutable chart references

  • Better supply chain security

  • Precise version control

5. Chart v3 Support

New chart API version with:

  • Backwards compatibility for v2 charts

  • Better dependency management

  • Enhanced metadata support

Breaking Changes You Need to Know

1. Plugin Migration Required

All existing plugins must be updated to work with Helm 4. The HIP-0026 plugin redesign means:

# Old plugin structure (Helm 3)
my-plugin/
  ├── plugin.yaml
  └── plugin.sh

# New plugin structure (Helm 4)
my-plugin/
  ├── plugin.yaml
  ├── main.wasm (or binary)
  └── metadata.json

Action required:

  • Audit your plugin usage: helm plugin list

  • Check with plugin maintainers for Helm 4 compatibility

  • Test plugins in staging before upgrading production

2. CLI Flag Renaming

Several CLI flags have been renamed for consistency:

# Helm 3
helm install --wait-for-jobs

# Helm 4
helm install --wait

Action required:

  • Update CI/CD scripts

  • Search codebase for hardcoded Helm commands

  • Update documentation

3. Package Restructuring (SDK Users)

If you’re using Helm as a Go library, packages have been reorganized:

// Helm 3
import "helm.sh/helm/v3/pkg/chart"

// Helm 4
import "helm.sh/helm/v4/pkg/chart/v2"

Action required:

  • Update import paths

  • Test integrations thoroughly

  • Review API changes in documentation

4. Server-Side Apply Conflicts

With SSA as default, conflicts are now explicit errors rather than silent overwrites:

# This will now error if another controller owns the field
helm upgrade my-app ./chart

# Use --force-conflicts to override (use carefully!)
helm upgrade my-app ./chart --force-conflicts

Important limitations:

  • ❌ Multiple owners per manifest not supported

  • ❌ Field ownership transfer not supported

  • ✅ Backwards compatible with three-way merge charts (if K8s >= 1.22)

Helm 4 Best Practices: Updated for 2025

1. Embrace Server-Side Apply

Why it matters: SSA provides clearer semantics and better conflict handling.

Best Practice:

# In your chart values, be explicit about ownership
apiVersion: v1
kind: ConfigMap
metadata:
  name: my-config
  annotations:
    # Document who should own this resource
    meta.helm.sh/owner: "my-team"
data:
  config.yaml: |
    setting: value

What to avoid:

  • Don’t rely on undocumented merge behavior

  • SSA is more strict about field ownership

Migration tip: Test upgrades in staging with --dry-run first to catch conflicts early.

2. Use Digest-Based OCI Installs

Why it matters: Ensures immutable deployments and better security.

Best Practice:

# Pin to specific digest, not tag
helm install my-app oci://registry.example.com/charts/my-app@sha256:abc123...

# Avoid mutable tags in production
# BAD: helm install my-app oci://registry.example.com/charts/my-app:latest

In your CI/CD:

# GitHub Actions example
- name: Install chart by digest
  run: |
    DIGEST=$(helm show chart oci://registry/chart:${{ github.sha }} --output json | jq -r '.digest')
    helm install app oci://registry/chart@$DIGEST

3. Leverage Advanced Status Monitoring

Why it matters: Helm 4’s kstatus understands actual readiness.

Best Practice:

# Wait for real readiness, not just pod creation
helm install my-app ./chart \
  --wait \
  --timeout 10m

# Use specific status checks
helm install my-app ./chart \
  --wait \
  --wait-for-jobs \
  --atomic  # Rollback on failure

In your charts:

# Define readiness properly
apiVersion: apps/v1
kind: Deployment
spec:
  template:
    spec:
      containers:
      - name: app
        readinessProbe:
          httpGet:
            path: /health
            port: 8080
          initialDelaySeconds: 10
          periodSeconds: 5
        livenessProbe:
          httpGet:
            path: /health
            port: 8080
          initialDelaySeconds: 30
          periodSeconds: 10

4. Chart v3: Structure Your Charts Properly

Why it matters: Better organization and maintainability.

Best Practice:

my-chart/
├── Chart.yaml          # Use apiVersion: v3
├── values.yaml
├── values.schema.json  # JSON Schema validation
├── templates/
│   ├── _helpers.tpl    # Template functions
│   ├── deployment.yaml
│   ├── service.yaml
│   └── NOTES.txt       # User guidance
├── charts/             # Dependencies
└── crds/              # Custom Resource Definitions

Chart.yaml v3:

apiVersion: v3  # New in Helm 4
name: my-app
version: 1.0.0
dependencies:
  - name: postgresql
    version: "^12.0.0"
    repository: "https://charts.bitnami.com/bitnami"
    condition: postgresql.enabled

5. Use Multi-Document Values Files

Why it matters: Better organization of complex configurations.

Best Practice:

# values.yaml can now contain multiple documents
---
# Global configuration
global:
  domain: example.com
---
# Environment-specific overrides
env:
  production:
    replicas: 3
  staging:
    replicas: 1

Install with specific environment:

helm install my-app ./chart \
  --values values.yaml \
  --set env=production

6. Implement Proper Secret Management

Why it matters: Security is non-negotiable.

Best Practice — Use External Secrets Operator:

# Don't put secrets in values.yaml
# Use External Secrets Operator instead
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
  name: app-secrets
spec:
  refreshInterval: 1h
  secretStoreRef:
    name: vault-backend
    kind: SecretStore
  target:
    name: app-secrets
  data:
  - secretKey: db-password
    remoteRef:
      key: /secret/data/app
      property: db_password

Or use Sealed Secrets:

# Encrypt secrets before committing
kubeseal --format yaml < secret.yaml > sealed-secret.yaml

# Include sealed-secret.yaml in your chart

What to avoid:

# NEVER do this
apiVersion: v1
kind: Secret
data:
  password: cGFzc3dvcmQxMjM=  # Base64 is not encryption!

7. Validate Charts Before Deployment

Why it matters: Catch errors before they hit production.

Best Practice:

# 1. Lint the chart
helm lint ./my-chart

# 2. Template and validate
helm template my-app ./my-chart \
  --values values-prod.yaml \
  --validate

# 3. Dry-run install
helm install my-app ./my-chart \
  --values values-prod.yaml \
  --dry-run \
  --debug

# 4. Use external validators
helm plugin install https://github.com/instrumenta/helm-kubeval
helm kubeval ./my-chart

In your CI/CD:

# GitHub Actions example
- name: Validate Helm Chart
  run: |
    helm lint charts/*
    helm template test charts/my-app | kubeval --strict

8. Version Control Your Chart Dependencies

Why it matters: Reproducible builds.

Best Practice:

# Chart.yaml - Pin dependency versions
dependencies:
  - name: redis
    version: "17.11.3"  # Exact version, not range
    repository: "https://charts.bitnami.com/bitnami"

Lock dependencies:

# Generate Chart.lock
helm dependency update ./my-chart

# Commit Chart.lock to version control
git add Chart.lock
git commit -m "Lock chart dependencies"

What to avoid:

# Don't use version ranges in production
dependencies:
  - name: redis
    version: "^17.0.0"  # Could pull 17.11.x unexpectedly

9. Use Helm Test for Validation

Why it matters: Verify deployments actually work.

Best Practice:

# templates/tests/connection-test.yaml
apiVersion: v1
kind: Pod
metadata:
  name: "{{ include "my-app.fullname" . }}-test-connection"
  annotations:
    "helm.sh/hook": test
spec:
  containers:
  - name: wget
    image: busybox
    command: ['wget']
    args: ['{{ include "my-app.fullname" . }}:{{ .Values.service.port }}']
  restartPolicy: Never

Run tests:

# After installation
helm test my-app

# With verbose output
helm test my-app --logs

10. Document Your Charts Properly

Why it matters: Future you (and your team) will thank you.

Best Practice:

# Chart.yaml - Complete metadata
name: my-app
description: A production-ready application chart
type: application
version: 1.0.0
appVersion: "2.3.0"
keywords:
  - web
  - api
  - production
home: https://github.com/myorg/my-app
sources:
  - https://github.com/myorg/my-app
maintainers:
  - name: Your Name
    email: [email protected]

README.md template:

# My App Helm Chart

## Prerequisites
- Kubernetes 1.27+
- Helm 4.0+

## Installation
\`\`\`bash
helm install my-app ./my-app
\`\`\`

## Configuration
| Parameter | Description | Default |
|-----------|-------------|---------|
| `replicaCount` | Number of replicas | `1` |
| `image.repository` | Image repository | `myapp` |

## Examples
### Development
\`\`\`bash
helm install my-app ./my-app -f values-dev.yaml
\`\`\`
### Production
\`\`\`bash
helm install my-app ./my-app -f values-prod.yaml
\`\`\`

Migration Guide: Helm 3 to Helm 4

Step 1: Prepare Your Environment

# Install Helm 4 alongside Helm 3
curl https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-4 | bash

# Verify installation
helm version

# Check existing releases
helm list --all-namespaces

Step 2: Test Charts in Staging

# Template your charts with Helm 4
helm template my-app ./chart \
  --values values-staging.yaml \
  --debug

# Dry-run upgrade
helm upgrade my-app ./chart \
  --values values-staging.yaml \
  --dry-run \
  --debug

Step 3: Check for Conflicts

# Upgrade with conflict detection
helm upgrade my-app ./chart \
  --values values-staging.yaml

# If conflicts occur, inspect them
kubectl get <resource> <name> -o yaml --show-managed-fields

# Force if necessary (carefully!)
helm upgrade my-app ./chart \
  --force-conflicts

Step 4: Update Plugins

# List current plugins
helm plugin list

# Check for Helm 4 compatibility
# Visit plugin repos for updates
# Update plugins
helm plugin update <plugin-name>

Step 5: Update CI/CD Pipelines

# Before (Helm 3)
- name: Deploy with Helm
  run: |
    helm upgrade --install my-app ./chart \
      --wait-for-jobs \
      --timeout 5m

# After (Helm 4)
- name: Deploy with Helm
  run: |
    helm upgrade --install my-app ./chart \
      --wait \
      --timeout 5m

Step 6: Monitor the Migration

# Check release history
helm history my-app

# Verify resources
kubectl get all -l app=my-app
# Check for SSA annotations
kubectl get deployment my-app -o yaml | grep -A 5 "managedFields"

Troubleshooting Common Issues

Issue 1: Conflict Errors After Upgrade

Symptom:

Error: UPGRADE FAILED: another controller owns this field

Solution:

# Option 1: Use --force-conflicts (understand implications first!)
helm upgrade my-app ./chart --force-conflicts

# Option 2: Identify and remove conflicting controller
kubectl get <resource> <name> -o yaml --show-managed-fields

# Option 3: Revert to three-way merge temporarily
helm upgrade my-app ./chart --three-way-merge

Issue 2: Plugin Not Working

Symptom:

Error: plugin "xyz" failed: exec format error

Solution:

# Check plugin compatibility
helm plugin list

# Update to Helm 4 compatible version
helm plugin update xyz

# Or install WASM version if available
helm plugin install https://github.com/author/plugin-wasm

Issue 3: Chart Templates Failing

Symptom:

Error: template: chart/templates/deployment.yaml: undefined variable

Solution:

# Validate with debug output
helm template my-app ./chart --debug

# Check for deprecated functions
# Some template functions may have changed
# Update to Chart API v3 if needed
# Edit Chart.yaml: apiVersion: v3

Issue 4: OCI Registry Authentication Fails

Symptom:

Error: failed to authorize: failed to fetch anonymous token

Solution:

# Login to registry
helm registry login registry.example.com \
  --username your-user

# Or use credential helper
export HELM_REGISTRY_CONFIG=/path/to/config.json

# Verify
helm pull oci://registry.example.com/chart

Performance Improvements in Helm 4

Helm 4 isn’t just about features — it’s also faster:

Benchmarks (approximate):

  • Chart installation: ~15% faster

  • Template rendering: ~20% faster for complex charts

  • Dependency resolution: ~30% faster with content-based caching

What makes it faster:

  • Content-based caching for charts

  • Optimized dependency resolution

  • Parallel resource watching

  • Better memory management

What’s Coming Next

Helm 4’s release schedule:

  • Helm 4.0.0: November 2025 (KubeCon NA)

  • Helm 4.1.0: January 2026

  • Minor releases: Every 4 months

Helm 3 End of Life:

  • Helm 3 will reach EOL approximately 6–8 months after Helm 4 release

  • Estimated: July 2026

  • Action: Plan your migration accordingly

Conclusion

Helm 4.0 represents a significant evolution while maintaining the backwards compatibility that makes migration manageable. The shift to Server-Side Apply, redesigned plugin system, and enhanced status monitoring make Helm more robust and production-ready.

Key Takeaways:

Server-Side Apply is the new default — Better conflict handling
Plugin system redesigned — WASM support, better security
Advanced status monitoring — True readiness detection
OCI improvements — Digest-based installs for security
Chart v3 support — Better dependency management

Action Items:

  1. Immediate:
  • Test your charts with Helm 4 in staging

  • Audit plugin usage

  • Update CI/CD scripts for renamed flags

2. Short-term (1–2 months):

  • Migrate production deployments

  • Update chart documentation

  • Train team on new features

3. Long-term (3–6 months):

  • Adopt SSA best practices fully

  • Migrate to Chart v3

  • Update internal tooling and scripts

Getting Started:

# Install Helm 4
curl https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-4 | bash

# Test your first chart
helm template my-app ./chart --debug

# Deploy when ready
helm upgrade --install my-app ./chart --wait

Helm 4 is production-ready and brings meaningful improvements. Start testing today, and plan your migration timeline. The Helm community has done an excellent job ensuring backwards compatibility while modernizing the tooling.

Additional Resources

Have you upgraded to Helm 4 yet? What’s your experience been? Share in the comments!

More from this blog

DevOps

16 posts