# Helm Workflow

> Helm chart development and release management including chart structure, values design, template best practices, hooks, dependency management, testing, and repository management. Covers Helm v3/v4 and Kustomize selection. Use when creating, reviewing, or managing Helm charts.

- Skill: `iceflower/helm-workflow` (Agent Skill, multi-file: 4 files)
- Install (CLI): `npx skillmds@latest add iceflower/helm-workflow`
- Raw SKILL.md: https://api.skillmd.com/api/skills/iceflower/helm-workflow/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: DevOps & Infra
- License: MIT
- Author: iceflower (https://skillmd.com/u/iceflower)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/iceflower/helm-workflow

---


# Helm Workflow Rules

## 1. Chart Structure

### Standard Directory Layout

```text
mychart/
├── Chart.yaml          # Chart metadata (required)
├── Chart.lock          # Dependency lock file (auto-generated)
├── values.yaml         # Default configuration values
├── values.schema.json  # JSON Schema for values validation
├── .helmignore         # Patterns to ignore when packaging
├── templates/          # Template files
│   ├── _helpers.tpl    # Named template definitions
│   ├── NOTES.txt       # Post-install usage notes
│   ├── deployment.yaml
│   ├── service.yaml
│   ├── ingress.yaml
│   ├── configmap.yaml
│   ├── secret.yaml
│   ├── serviceaccount.yaml
│   ├── hpa.yaml
│   └── tests/
│       └── test-connection.yaml
├── charts/             # Dependency charts (auto-populated)
└── crds/               # Custom Resource Definitions
```

### Chart.yaml Conventions

```yaml
apiVersion: v2
name: myapp
description: A Helm chart for MyApp
type: application          # application or library
version: 1.2.0             # Chart version (SemVer)
appVersion: "3.4.1"        # Application version
kubeVersion: ">=1.28.0"    # Required K8s version constraint

maintainers:
  - name: team-platform
    email: platform@example.com

dependencies:
  - name: postgresql
    version: "~15.x"
    repository: "oci://registry-1.docker.io/bitnamicharts"
    condition: postgresql.enabled
```

| Field | Rule |
| --- | --- |
| `version` | SemVer — bump on every chart change |
| `appVersion` | Match the deployed application version |
| `kubeVersion` | Set minimum K8s version constraint |
| `type` | Use `library` for shared templates only |

---

## 2. Values Design

### Organization Principles

```yaml
# Group by component, not by K8s resource type
replicaCount: 2

image:
  repository: myapp
  tag: "3.4.1"
  pullPolicy: IfNotPresent

service:
  type: ClusterIP
  port: 8080

ingress:
  enabled: false
  className: nginx
  hosts:
    - host: myapp.example.com
      paths:
        - path: /
          pathType: Prefix

resources:
  requests:
    cpu: 100m
    memory: 256Mi
  limits:
    cpu: 500m
    memory: 512Mi

# Sub-chart toggle
postgresql:
  enabled: true
```

### Naming Conventions

| Pattern | Example | Purpose |
| --- | --- | --- |
| Boolean toggle | `ingress.enabled` | Enable/disable features |
| camelCase keys | `replicaCount` | Helm convention |
| Nested objects | `image.repository` | Group related config |
| Resource presets | `resources.requests.cpu` | Standard K8s structure |

### Values Schema Validation

```json
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "type": "object",
  "required": ["image", "service"],
  "properties": {
    "replicaCount": {
      "type": "integer",
      "minimum": 1
    },
    "image": {
      "type": "object",
      "required": ["repository"],
      "properties": {
        "repository": { "type": "string" },
        "tag": { "type": "string" }
      }
    }
  }
}
```

### Environment-Specific Values

```text
values/
├── values.yaml            # Defaults
├── values-dev.yaml        # Dev overrides
├── values-staging.yaml    # Staging overrides
└── values-prod.yaml       # Production overrides
```

```bash
helm upgrade myapp ./mychart \
  -f values.yaml \
  -f values-prod.yaml \
  --namespace production
```

---

## 3. Template Best Practices

> See [references/template-patterns.md](references/template-patterns.md) for detailed patterns including helper templates, template functions, whitespace control, NOTES.txt, and testing examples.

### §3.1 Library Charts for Template Sharing

Library charts (`type: library`) are Helm charts that contain only templates — they produce no Kubernetes resources when rendered. Use them to share common template logic across multiple application charts.

#### Defining a Library Chart

```yaml
# lib-chart/Chart.yaml
apiVersion: v2
name: lib-chart
description: Shared Helm templates for platform services
type: library            # No resources rendered — templates only
version: 1.0.0
```

A library chart typically contains:

```text
lib-chart/
├── Chart.yaml              # type: library
├── templates/
│   ├── _deployment.tpl     # Reusable Deployment template
│   ├── _service.tpl        # Reusable Service template
│   ├── _ingress.tpl        # Reusable Ingress template
│   └── _helpers.tpl        # Shared labels, selectors, names
```

#### Consuming a Library Chart

Reference the library chart as a dependency in the parent chart:

```yaml
# app-a/Chart.yaml
apiVersion: v2
name: app-a
type: application
version: 1.0.0

dependencies:
  - name: lib-chart
    version: "1.x"
    repository: "file://../lib-chart"
    # or: repository: "oci://ghcr.io/org/charts"
```

Then invoke shared templates using `include`:

```yaml
# app-a/templates/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: {{ include "lib-chart.fullname" . }}
  labels:
    {{- include "lib-chart.labels" . | nindent 4 }}
spec:
  {{- include "lib-chart.deploymentSpec" . | nindent 2 }}
```

#### When to Use Library Charts

- Multiple services share common template patterns (Deployment, Service, Ingress structure)
- Platform teams want to enforce standard labels, annotations, or resource structures
- `_helpers.tpl` has grown too large — split into purpose-specific library charts
- Replacing copy-paste template patterns with reusable chart dependencies

#### Library Chart Rules

- Library charts must declare `type: library` — Helm will skip resource rendering
- Library charts should not include `values.yaml` with defaults — consumers provide all values
- Version library charts independently — bump when shared templates change
- Use `file://` for local development, OCI registries for distribution

---

## 4. Release Management

### Release Naming

| Element | Convention | Example |
| --- | --- | --- |
| Release name | `{app}-{env}` or `{app}` | `myapp-prod`, `myapp` |
| Namespace | Match environment | `production`, `staging` |
| Chart version | SemVer, bump on change | `1.2.0` → `1.3.0` |

### Install and Upgrade Commands

```bash
# Install with atomic (auto-rollback on failure)
# Helm v4: --atomic renamed to --rollback-on-failure
helm install myapp ./mychart \
  --namespace production \
  --create-namespace \
  --atomic \
  --timeout 5m \
  -f values-prod.yaml

# Upgrade with wait (wait for pods ready)
helm upgrade myapp ./mychart \
  --namespace production \
  --atomic \
  --timeout 5m \
  --cleanup-on-fail \
  -f values-prod.yaml

# Install or upgrade (idempotent)
helm upgrade --install myapp ./mychart \
  --namespace production \
  --create-namespace \
  --atomic \
  --timeout 5m \
  -f values-prod.yaml
```

> **Helm v4 Breaking Changes**: `--atomic` → `--rollback-on-failure`, `--force` → `--force-replace`. Server-side apply is now the default for new releases. See [§11 Helm v4 Migration](#11-helm-v4-migration) for details.

### Rollback

```bash
# View history
helm history myapp -n production

# Rollback to previous revision
helm rollback myapp 0 -n production --wait

# Rollback to specific revision
helm rollback myapp 3 -n production --wait
```

---

## 5. Hook Lifecycle

### Available Hooks

| Hook | Timing | Use Case |
| --- | --- | --- |
| `pre-install` | Before resources created | DB migration, prerequisite check |
| `post-install` | After resources created | Seed data, notifications |
| `pre-upgrade` | Before upgrade starts | DB migration, backup |
| `post-upgrade` | After upgrade completes | Cache warm-up, verification |
| `pre-delete` | Before release deleted | Data export, cleanup |
| `post-delete` | After release deleted | External resource cleanup |
| `pre-rollback` | Before rollback | Backup current state |
| `post-rollback` | After rollback | Verify rollback success |

> **Note**: `crd-install` hook was **removed in Helm v4**. Use the `crds/` directory for CRD management instead.

### Hook Definition

```yaml
apiVersion: batch/v1
kind: Job
metadata:
  name: {{ include "mychart.fullname" . }}-db-migrate
  annotations:
    "helm.sh/hook": pre-upgrade,pre-install
    "helm.sh/hook-weight": "-5"          # Lower runs first
    "helm.sh/hook-delete-policy": before-hook-creation,hook-succeeded
spec:
  backoffLimit: 3
  template:
    spec:
      restartPolicy: Never
      containers:
        - name: migrate
          image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
          command: ["./migrate", "up"]
```

### Hook Delete Policies

| Policy | Behavior |
| --- | --- |
| `before-hook-creation` | Delete previous hook resource before new one |
| `hook-succeeded` | Delete after hook succeeds |
| `hook-failed` | Delete after hook fails |

---

## 6. Dependency Management

### Declaring Dependencies

```yaml
# Chart.yaml
dependencies:
  - name: postgresql
    version: "~15.5"
    repository: "oci://registry-1.docker.io/bitnamicharts"
    condition: postgresql.enabled
    alias: db

  - name: redis
    version: "~19.x"
    repository: "oci://registry-1.docker.io/bitnamicharts"
    tags:
      - cache
    import-values:
      - child: master.service
        parent: redis.service
```

### Commands

```bash
# Download dependencies
helm dependency update ./mychart

# Rebuild Chart.lock
helm dependency build ./mychart

# List dependencies
helm dependency list ./mychart
```

### Sub-Chart Value Override

```yaml
# values.yaml — override sub-chart values by chart name
postgresql:
  enabled: true
  auth:
    database: myapp
    username: myapp

# Using alias
db:
  enabled: true
```

---

## 7. Testing

> See [references/template-patterns.md](references/template-patterns.md) for testing examples including built-in tests, validation pipeline, and helm-unittest.

---

## 8. Security

### Chart Signing

```bash
# Package and sign
helm package ./mychart --sign --key "my-key" --keyring ~/.gnupg/pubring.gpg

# Verify signature
helm verify mychart-1.2.0.tgz --keyring ~/.gnupg/pubring.gpg

# Install with verification
helm install myapp mychart-1.2.0.tgz --verify --keyring ~/.gnupg/pubring.gpg
```

### RBAC Templates

```yaml
{{- if .Values.serviceAccount.create }}
apiVersion: v1
kind: ServiceAccount
metadata:
  name: {{ include "mychart.fullname" . }}
  labels:
    {{- include "mychart.labels" . | nindent 4 }}
  {{- with .Values.serviceAccount.annotations }}
  annotations:
    {{- toYaml . | nindent 4 }}
  {{- end }}
{{- end }}
```

### Secrets Handling

- Never store plaintext secrets in `values.yaml`
- Use `--set` or external secret managers for sensitive values
- Template secrets from references, not hardcoded values
- Consider `ExternalSecret` or `SealedSecret` CRDs instead of Helm-managed secrets

---

## 9. Repository Management

### OCI Registry (Recommended)

```bash
# Login to OCI registry
helm registry login ghcr.io -u USERNAME

# Push chart
helm push mychart-1.2.0.tgz oci://ghcr.io/org/charts

# Pull chart
helm pull oci://ghcr.io/org/charts/mychart --version 1.2.0

# Install from OCI
helm install myapp oci://ghcr.io/org/charts/mychart --version 1.2.0
```

### Versioning Strategy

| Change Type | Version Bump | Example |
| --- | --- | --- |
| Breaking changes | Major | `1.2.0` → `2.0.0` |
| New features, non-breaking | Minor | `1.2.0` → `1.3.0` |
| Bug fixes, doc updates | Patch | `1.2.0` → `1.2.1` |

- Always bump `version` when chart content changes
- `appVersion` tracks the deployed application version independently

---

## 10. Helm vs Kustomize

| Criteria | Helm | Kustomize |
| --- | --- | --- |
| Parameterization | Values-based templating | Patch-based overlays |
| Packaging | Distributable chart archives | Directory-based |
| Dependency management | Built-in | Manual |
| Release tracking | Built-in (helm history) | External (GitOps) |
| Learning curve | Higher (Go templates) | Lower (YAML patches) |
| Best for | Reusable packages, complex logic | Simple overlays, in-house apps |

### When to Use Each

- **Helm**: Distributing charts to others, complex conditional logic, lifecycle hooks, release management
- **Kustomize**: Internal applications, simple environment overlays, no templating needed
- **Hybrid**: Use Helm for packaging + Kustomize for environment overlays (`helm template | kustomize`)

---

## 11. Helm v4 Migration

Helm v4 (current: v4.1.4) introduces breaking changes from v3. Most charts work without modification, but CLI usage and some behaviors have changed.

### Breaking Changes from v3

| v3 Flag / Behavior | v4 Equivalent | Notes |
| --- | --- | --- |
| `--atomic` | `--rollback-on-failure` | Same behavior, renamed flag |
| `--force` | `--force-replace` | Same behavior, renamed flag |
| `helm registry login https://ghcr.io` | `helm registry login ghcr.io` | Domain only, no `https://` prefix |
| `crd-install` hook | Use `crds/` directory | `crd-install` hook removed |
| Client-side apply (default) | Server-side apply (default) | New releases use SSA |
| In-process post-renderers | Plugin-based post-renderers only | External binary or plugin required |

### New Features in v4

- **Wasm-based plugins** — optional WebAssembly runtime for custom functionality
- **OCI digest support** — install charts by digest for supply chain security: `helm install myapp oci://registry/chart --version "sha256:abc..."`
- **Multi-document values** — split complex values across multiple YAML files
- **Custom template functions** — extend Go templates via plugins
- **kstatus watcher** — improved resource readiness monitoring
- **Content-based caching** — faster dependency resolution

### Argo CD Compatibility

> **Important**: Argo CD (as of v3.3.x) does **not** fully support Helm v4. Argo CD internally uses Helm 3.x. If Helm v4 is installed locally, the `argocd` CLI may produce errors. Track [argoproj/argo-cd#27280](https://github.com/argoproj/argo-cd/issues/27280) for status.

**Recommendation**: Use Helm v3.x for Argo CD-integrated workflows. Helm v4 can be used for local development and direct CLI operations.

### Migration Checklist

- [ ] Update CI/CD scripts: `--atomic` → `--rollback-on-failure`, `--force` → `--force-replace`
- [ ] Replace `crd-install` hooks with `crds/` directory
- [ ] Update `helm registry login` to use domain names only (no `https://`)
- [ ] Verify Argo CD compatibility if using GitOps workflows
- [ ] Test post-renderer plugins if using custom post-renderers

---

## 12. Anti-Patterns

- Using `helm install` without `--atomic` in CI/CD — leaves failed releases behind
- Hardcoding values in templates instead of using `values.yaml`
- Not setting `kubeVersion` constraint — chart may deploy to incompatible clusters
- Skipping `values.schema.json` — no validation on user-supplied values
- Using `lookup` function without fallback — breaks `helm template`
- Not bumping chart `version` on changes — cache serves stale charts
- Storing secrets in `values.yaml` committed to VCS
- Deeply nested values without documentation — users cannot discover options
- Using `helm install` instead of `helm upgrade --install` — not idempotent
- Ignoring `helm lint` and `helm template` in CI — catches errors too late

## Related Skills

- For Argo CD + Helm integration (App of Apps, helm diff sync, Image Updater, Helm values overrides), see [gitops-argocd](../gitops-argocd/) skill — Argo CD + Helm integration is primarily handled in gitops-argocd
- For Kubernetes manifest conventions and best practices, see [k8s-workflow](../k8s-workflow/) skill
- For secret management in Helm charts, see [secrets-management](../secrets-management/) skill

