# 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:

```bash
# 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:

```bash
# 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:

```go
// 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:

```bash
# 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 &gt;= 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:**

```yaml
# 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:**

```bash
# 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:**

```yaml
# 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:**

```bash
# 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:**

```yaml
# 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:**

```bash
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:**

```yaml
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:**

```yaml
# 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:**

```bash
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:**

```yaml
# 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:**

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

# Include sealed-secret.yaml in your chart
```

**What to avoid:**

```yaml
# 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:**

```bash
# 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:**

```yaml
# 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:**

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

**Lock dependencies:**

```bash
# 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:**

```yaml
# 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:**

```yaml
# 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:**

```bash
# 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:**

```yaml
# 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: your.email@example.com
```

**README.md template:**

```markdown
# 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**

```bash
# 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**

```bash
# 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**

```bash
# 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**

```bash
# 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**

```yaml
# 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**

```bash
# 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:**

```bash
Error: UPGRADE FAILED: another controller owns this field
```

**Solution:**

```bash
# 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:**

```bash
Error: plugin "xyz" failed: exec format error
```

**Solution:**

```bash
# 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:**

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

**Solution:**

```bash
# 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:**

```bash
Error: failed to authorize: failed to fetch anonymous token
```

**Solution:**

```bash
# 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:**

```bash
# 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**

* [Helm 4 Official Documentation](https://helm.sh/docs/next/)
    
* [Server-Side Apply in Kubernetes](https://kubernetes.io/docs/reference/using-api/server-side-apply/)
    
* [Helm GitHub Repository](https://github.com/helm/helm)
    

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