> ## Documentation Index
> Fetch the complete documentation index at: https://docs.controlplane.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Manage Helm Releases

> Deploy and manage Control Plane resources using Helm charts with the cpln helm command.

The `cpln helm` command lets you manage Control Plane resources using [Helm charts](https://helm.sh/docs/topics/charts/). Define your infrastructure as Helm templates and deploy them as versioned releases with full rollback support.

## When to use this

<CardGroup cols={2}>
  <Card title="Templated deployments" icon="code">
    Use Helm's templating engine for complex, parameterized configurations
  </Card>

  <Card title="Environment management" icon="layer-group">
    Deploy the same chart with different values for dev, staging, and production
  </Card>

  <Card title="Versioned releases" icon="clock-rotate-left">
    Track deployment history and rollback to previous versions
  </Card>

  <Card title="Helm ecosystem" icon="dharmachakra">
    Leverage existing Helm workflows and chart repositories
  </Card>
</CardGroup>

## Prerequisites

<AccordionGroup>
  <Accordion title="CLI installed">
    Install the Control Plane CLI. See [Installation](/cli-reference/installation).
  </Accordion>

  <Accordion title="Helm installed">
    Install [Helm](https://helm.sh/docs/intro/install/) (v3 or later).
  </Accordion>

  <Accordion title="Required permissions">
    You need [policies](/reference/policy) to create the resources defined in your chart, plus `reveal` permission on secrets to manage release state.
  </Accordion>
</AccordionGroup>

## Core concepts

### Releases

A **release** is a deployed instance of a Helm chart. Each release:

* Has a unique name within your organization
* Stores its state in an [opaque secret](/reference/secret#opaque)
* Tracks revisions for rollback (10 by default, configurable with [`--history-limit`](#manage-revision-history))
* Tags resources with `cpln/release` for tracking

#### Release name constraints

Release names must follow these rules:

| Rule | Detail |
| - | - |
| **Format** | DNS-1123 label — lowercase alphanumeric characters and hyphens only |
| **Pattern** | Must match `^[a-z0-9]([-a-z0-9]*[a-z0-9])?$` |
| **Max length** | 53 characters |
| **Uniqueness** | Must be unique within the same organization |

Alternatively, use `--generate-name` (or `-g`) to auto-generate a name in the format `<chart-name>-<unix-timestamp>`.

### Release state

The release state tracks all resources deployed by the release, enabling:

* Detection of added, modified, or removed resources
* Automatic cleanup of deleted resources on upgrade
* Rollback to any previous revision

### Supported resource types

Charts must contain **only Control Plane resource definitions**. Standard Kubernetes manifests (objects with `apiVersion` or `metadata` fields) are not supported and will cause the operation to fail.

<Warning>
  If your chart contains any resource with an `apiVersion` or `metadata` field, or an unsupported `kind`, the install/upgrade will abort with:

  ```text theme={null}
  ERROR: Some resources in the rendered template are not CPLN resources.
  ```

  Remove any Kubernetes manifests and ensure every resource kind is a supported Control Plane kind.
</Warning>

### GVC-scoped resources

The following resource kinds require a GVC context (via `--gvc` or your default profile context). The GVC must already exist or be created earlier in the same chart:

`workload`, `identity`, `volumeset`

## Quick start

<Steps>
  <Step title="Create a chart">
    Generate a new Helm chart:

    ```bash theme={null}
    helm create my-chart
    ```
  </Step>

  <Step title="Define resources">
    Replace the contents of `my-chart/templates/` with Control Plane resources:

    ```yaml my-chart/templates/resources.yaml theme={null}
    kind: gvc
    name: {{ .Values.gvc.name }}
    spec:
      staticPlacement:
        locationLinks:
          - //location/aws-eu-central-1
    ---
    kind: workload
    name: {{ .Values.workload.name }}
    gvc: {{ .Values.gvc.name }}
    spec:
      type: serverless
      containers:
        - name: main
          image: {{ .Values.workload.image }}
          ports:
            - number: 80
              protocol: http
    ```
  </Step>

  <Step title="Configure values">
    Update `my-chart/values.yaml`:

    ```yaml my-chart/values.yaml theme={null}
    gvc:
      name: my-gvc

    workload:
      name: my-app
      image: nginx:latest
    ```
  </Step>

  <Step title="Install the release">
    Deploy the chart:

    ```bash theme={null}
    cpln helm install my-release ./my-chart
    ```
  </Step>
</Steps>

## Install a release

```bash theme={null}
cpln helm install <release-name> <chart-path>
```

Install from different sources:

<Tabs>
  <Tab title="Local chart">
    ```bash theme={null}
    cpln helm install my-release ./my-chart
    ```
  </Tab>

  <Tab title="Helm repository">
    ```bash theme={null}
    cpln helm install my-release postgres \
      --repo https://controlplane-com.github.io/helm-packages \
      --version 2.1.0
    ```
  </Tab>

  <Tab title="OCI registry">
    ```bash theme={null}
    cpln helm install my-release \
      <oci-url> \
      --version <version>
    ```
  </Tab>

  <Tab title="Auto-generated name">
    ```bash theme={null}
    cpln helm install --generate-name ./my-chart
    ```

    Creates a release named `<chart-name>-<unix-timestamp>` (e.g., `my-chart-1709913600`).
  </Tab>
</Tabs>

### Wait for readiness

Wait for workloads to become ready:

```bash theme={null}
cpln helm install my-release ./my-chart --wait --timeout 600
```

### Handle chart dependencies

If your chart has dependencies defined in `Chart.yaml`, use `--dependency-update` to fetch them automatically:

```bash theme={null}
cpln helm install my-release ./my-chart --dependency-update
```

Without this flag, missing dependencies will cause an error prompting you to re-run with the flag.

## Upgrade a release

Update a release after modifying the chart:

```bash theme={null}
cpln helm upgrade my-release ./my-chart
```

The CLI:

1. Renders the updated chart
2. Prints the cleanup plan: the resources the chart no longer declares and what happens to each of them
3. Applies new and modified resources
4. Deletes the resources the chart no longer declares, skipping any that carry `helm.sh/resource-policy: keep`, that belong to another release, or that are a GVC still holding something
5. Updates the release state

The plan is printed before anything is written, so a surprising deletion is visible in time to cancel:

```text theme={null}
Cleanup will delete 2 resources the chart no longer declares once the new revision is applied:
  /org/my-org/gvc/my-gvc/workload/old-api
  /org/my-org/gvc/staging-gvc (only if it holds nothing by then)
```

### Install or upgrade

Use `--install` to create the release if it does not exist, or upgrade it if it does:

```bash theme={null}
cpln helm upgrade my-release ./my-chart --install
```

This is useful in CI/CD pipelines where you don't want to check whether the release already exists.

<Warning>
  Use the same release name **and chart name** to ensure proper tracking of resources. Upgrading with a different chart name than the original install will fail with:

  ```text theme={null}
  ERROR: UPGRADE FAILED: cannot upgrade with different chart name
  ```
</Warning>

## Moving a release between GVCs

An upgrade or rollback that moves a release out of a GVC is refused before anything is written, because cleanup would then delete every resource the release still has there, volume sets and the data on them included:

```text theme={null}
ERROR: This upgrade would move release 'my-release' out of GVC 'old-gvc' and into GVC 'new-gvc'.
Cleanup would then permanently delete 3 leftover resources in GVC 'old-gvc', including volume set 'data' and the data on it:
  /org/my-org/gvc/old-gvc/workload/api
  /org/my-org/gvc/old-gvc/volumeset/data
  /org/my-org/gvc/old-gvc/identity/api-identity
Nothing has been written.
To upgrade in place, run the same command with --gvc old-gvc, the GVC the release currently lives in.
To move the release into GVC 'new-gvc', install it there as a new release, migrate the data across, then uninstall this one.
Pass --delete-leftover-resources to proceed anyway.
```

A chart that stops declaring a GVC together with everything it had in it is an ordinary removal, not a move, and is not refused.

<CardGroup cols={2}>
  <Card title="Upgrade in place" icon="arrows-rotate">
    Run the same command with `--gvc` pointing at the GVC the release already lives in. Nothing is stranded and nothing is deleted.
  </Card>

  <Card title="Move deliberately" icon="right-left">
    Install the chart into the new GVC as a separate release, migrate the data across, then uninstall the old release.
  </Card>
</CardGroup>

To delete what the release would leave behind and go ahead with the move, pass `--delete-leftover-resources` on `upgrade` or `rollback`:

```bash theme={null}
cpln helm upgrade my-release ./my-chart --gvc new-gvc --delete-leftover-resources
```

The refusal becomes a warning, and cleanup deletes the stranded resources.

<Warning>
  `--delete-leftover-resources` deletes volume sets and the data on them. Snapshot or copy anything you need first.
</Warning>

### GVCs the chart stops declaring

A GVC is deleted only once it holds nothing. If it still holds anything by the time cleanup reaches it, whether the release put it there or not, it is left in place and the CLI lists what is inside:

```text theme={null}
Skipped deletion of /org/my-org/gvc/old-gvc because it still holds 2 resources that release 'my-release' does not own:
  /org/my-org/gvc/old-gvc/workload/other-app
  /org/my-org/gvc/old-gvc/volumeset/other-data
  If you no longer need them either, 'cpln gvc delete old-gvc --org my-org' deletes it together with everything still in it; add --force if it holds a volume set.
```

`--delete-leftover-resources` does not change this. It only decides whether a release may strand its own resources in a GVC it is leaving.

A GVC the release still places resources in is never deleted, however many revisions ago the chart stopped declaring it. The release simply stops managing it:

```text theme={null}
The release stops managing 1 GVC the chart no longer declares, and cleanup leaves it in place:
  /org/my-org/gvc/my-gvc
```

## Manage revision history

By default, each release retains up to **10 revisions** for rollback. Use `--history-limit` on `upgrade` to adjust this:

```bash theme={null}
# Keep 20 revisions
cpln helm upgrade my-release ./my-chart --history-limit 20

# Keep unlimited revisions
cpln helm upgrade my-release ./my-chart --history-limit 0
```

<Warning>
  The `--history-limit` flag must be provided on **every upgrade**. If omitted, it resets to the default of 10 revisions. For consistent behavior, always include `--history-limit` in your upgrade commands or CI/CD pipelines.
</Warning>

<Tip>
  You can also set the `HELM_MAX_HISTORY` environment variable as an alternative to the `--history-limit` flag. The CLI flag takes precedence over the environment variable.
</Tip>

| Value | Behavior |
| - | - |
| `10` (default) | Keeps the 10 most recent revisions |
| `0` | Keeps all revisions indefinitely |
| `N` | Keeps the N most recent revisions |

<Tip>
  Setting `--history-limit 0` is useful for audit compliance or when you need the ability to rollback to any previous state.
</Tip>

## Release description

Use `--description` (or `--desc`) to attach a custom description to a release revision. It appears in `cpln helm history` output:

```bash theme={null}
cpln helm install my-release ./my-chart --description "Initial deployment"
cpln helm upgrade my-release ./my-chart --description "Added caching layer"
```

On rollback, if no description is provided, it auto-generates `"Rollback to <version>"`.

## Rollback a release

Revert to a previous revision:

```bash theme={null}
# Rollback to previous revision
cpln helm rollback my-release

# Rollback to specific revision
cpln helm rollback my-release 2
```

### Clean up on failure

Use `--cleanup-on-fail` to automatically delete resources created during a failed rollback:

```bash theme={null}
cpln helm rollback my-release --cleanup-on-fail
```

## View release information

<Tabs>
  <Tab title="List releases">
    ```bash theme={null}
    cpln helm list
    ```

    Lists all releases across all GVCs in the organization.

    ```text theme={null}
    +------------+--------+---------+---------------+---------+--------+
    |NAME        |GVC     |REVISION |UPDATED        |STATUS   |CHART   |
    +------------+--------+---------+---------------+---------+--------+
    |my-release  |my-gvc  |3        |5 minutes ago  |deployed |my-chart|
    +------------+--------+---------+---------------+---------+--------+
    ```
  </Tab>

  <Tab title="Release history">
    ```bash theme={null}
    cpln helm history my-release
    ```

    ```text theme={null}
    +---------+--------------+-----------+----------+----------------------+
    |REVISION |UPDATED       |STATUS     |CHART     |DESCRIPTION           |
    +---------+--------------+-----------+----------+----------------------+
    |1        |1 hour ago    |superseded |my-chart  |Install complete      |
    |2        |30 min ago    |superseded |my-chart  |Upgrade complete      |
    |3        |5 min ago     |deployed   |my-chart  |Upgrade complete      |
    +---------+--------------+-----------+----------+----------------------+
    ```
  </Tab>

  <Tab title="Get manifest">
    ```bash theme={null}
    cpln helm get manifest my-release
    ```

    Shows the rendered YAML for the current release.
  </Tab>

  <Tab title="Get all">
    ```bash theme={null}
    cpln helm get all my-release
    ```

    Shows combined release info, values, and manifest.
  </Tab>

  <Tab title="Get values">
    ```bash theme={null}
    # User-provided values only
    cpln helm get values my-release

    # All values (chart defaults + user overrides)
    cpln helm get values my-release --all
    ```
  </Tab>

  <Tab title="Get notes">
    ```bash theme={null}
    cpln helm get notes my-release
    ```

    Shows the chart notes (from `NOTES.txt`) for the current release.
  </Tab>
</Tabs>

All `helm get` subcommands accept `--revision <num>` to view data from a specific historical revision:

```bash theme={null}
cpln helm get values my-release --revision 2
cpln helm get manifest my-release --revision 2
```

## Uninstall a release

Remove the resources the release created and delete the release:

```bash theme={null}
cpln helm uninstall my-release
```

This deletes every resource tracked by the release, then the release state secret.

Some resources are deliberately left in place, and each one is reported with its reason:

| Left in place | Why |
| - | - |
| A resource tagged `helm.sh/resource-policy: keep` | The chart asked for it to outlive the release. |
| A resource whose `cpln/release` tag is missing or names another release | It is no longer this release's to delete. |
| A GVC the newest completed revision no longer declares | The release stopped managing that GVC before the uninstall. |
| A GVC still holding resources the release does not own | Deleting it would take those with it. |
| A GVC the platform refuses to delete | The platform's reason is printed, for example a volume set still inside it. |

A skipped GVC is reported with the command that deletes it by hand, once you no longer need what is inside:

```text theme={null}
Skipped deletion of /org/my-org/gvc/my-gvc because the latest revision of release 'my-release' does not declare it, so the release no longer manages it. When you no longer need it, 'cpln gvc delete my-gvc --org my-org' deletes it together with everything still in it; add --force if it holds a volume set.
```

Every resource the uninstall leaves in place loses its `cpln/release` tag, so nothing still claims to belong to a release that is gone. Only that tag is removed: `helm.sh/resource-policy: keep` and your own tags stay as they are.

## Template preview

Preview the rendered output without deploying:

```bash theme={null}
cpln helm template my-release ./my-chart
```

## Injected values

The CLI automatically injects these values into your chart, making them available in templates without requiring you to define them:

| Key | Description |
| - | - |
| `cpln.org` | Current organization name |
| `cpln.gvc` | Current GVC name (when `--gvc` is provided) |

Use in templates:

```yaml theme={null}
gvc: {{ .Values.cpln.gvc }}
```

<Warning>
  Avoid defining a top-level `cpln` key in your `values.yaml` as it will be overwritten by the injected values.
</Warning>

<Note>
  `--set` and `--values` are **not carried over** between operations. Each upgrade generates a fresh config from the chart defaults plus whatever you pass in that command. CI/CD pipelines must pass the full set of values on every upgrade.
</Note>

## Tags and labels

### Tagging resources

Use `--tag` to attach custom tags to **all resources** deployed by the release:

```bash theme={null}
cpln helm install my-release ./my-chart \
  --tag environment=production \
  --tag team=backend
```

Tags defined in your chart templates are also applied. The precedence order (highest wins):

1. **Helm metadata tags** (`cpln/release`) — always applied, cannot be overridden
2. **Template-level tags** — tags defined on individual resources in your chart YAML
3. **Release-level tags** — tags from `--tag` and `--state-tag` flags

This means tags set on a specific resource in your template override the same key passed via `--tag`.

Use `--remove-tag` on upgrade to remove tags from resources:

```bash theme={null}
cpln helm upgrade my-release ./my-chart --remove-tag environment
```

### Tagging the release secret

Use `--state-tag` to attach tags to the **release state secret** itself (not the deployed resources):

```bash theme={null}
cpln helm install my-release ./my-chart \
  --state-tag managed-by=ci \
  --state-tag pipeline-id=12345
```

Use `--remove-state-tag` on upgrade to remove state tags:

```bash theme={null}
cpln helm upgrade my-release ./my-chart --remove-state-tag pipeline-id
```

### Tag behavior across revisions

Unlike values, **tags carry over** between revisions. You don't need to re-specify `--tag` or `--state-tag` on every upgrade — previous tags are used as a base and new ones merge on top. Use `--remove-tag` or `--remove-state-tag` to explicitly drop a tag.

## Secret change detection

When a chart contains both secrets and workloads that reference those secrets, the CLI automatically tags workloads with a hash of each referenced secret's content:

```text theme={null}
cpln/secret:<secret-name> = <hash>
```

When you upgrade a release and a secret's content changes, the hash changes, triggering a workload redeployment. This ensures workloads always run with the latest secret values.

## Protect resources from deletion

Prevent a resource from being deleted during upgrades or uninstalls:

```yaml theme={null}
kind: volumeset
name: persistent-data
tags:
  helm.sh/resource-policy: keep
```

Resources with this tag are skipped during cleanup operations. The CLI will print a message confirming the resource was preserved:

```text theme={null}
Skipped deletion of /org/my-org/gvc/my-gvc/volumeset/persistent-data because it has the 'helm.sh/resource-policy' tag set to 'keep'
```

A kept resource also counts as foreign when cleanup considers deleting the GVC around it, so the GVC stays too.

## Chart notes

Create `templates/NOTES.txt` to display messages after install/upgrade:

```text theme={null}
Release {{ .Release.Name }} deployed!
Access your app at: https://{{ .Values.workload.name }}.cpln.app
```

## Environment-specific values

Use different values files for each environment:

```bash theme={null}
# Development
cpln helm install dev-release ./my-chart -f values-dev.yaml

# Production
cpln helm install prod-release ./my-chart -f values-prod.yaml
```

## Troubleshooting

<AccordionGroup>
  <Accordion title="Release already exists">
    Use `upgrade` instead of `install` for existing releases:

    ```bash theme={null}
    cpln helm upgrade my-release ./my-chart
    ```

    Or use `upgrade --install` to handle both cases:

    ```bash theme={null}
    cpln helm upgrade my-release ./my-chart --install
    ```
  </Accordion>

  <Accordion title="Release is stuck in pending state">
    If a previous operation was interrupted, the release may be stuck in `pending-install`, `pending-upgrade`, or `pending-rollback` status. The CLI will show an error that includes the exact secret name and the command to delete it:

    ```text theme={null}
    ERROR: Release 'my-release' is 'pending-install', please wait for the install
    or upgrade to finish. If this release revision is still pending or stuck, you
    will need to manually delete the secret 'cpln-helm-release-my-release-abc12xyz-v1'.
    Use the 'cpln secret delete cpln-helm-release-my-release-abc12xyz-v1' command to do so.
    ```

    Run the `cpln secret delete` command shown in the error message to unblock the release.
  </Accordion>

  <Accordion title="Non-CPLN resources in chart">
    If your chart contains Kubernetes manifests (resources with `apiVersion` or `metadata` fields), you'll see:

    ```text theme={null}
    ERROR: Some resources in the rendered template are not CPLN resources.
    ```

    Remove any standard Kubernetes resources from your templates. Charts must contain only Control Plane resource definitions.
  </Accordion>

  <Accordion title="Cannot upgrade with different chart name">
    When upgrading a release, the chart name must match the one used during install:

    ```text theme={null}
    ERROR: UPGRADE FAILED: cannot upgrade with different chart name
    ```

    If you need to switch charts, uninstall the release first, then install with the new chart.
  </Accordion>

  <Accordion title="Resources not deleted on upgrade">
    Ensure you're using the same release name. Resources are only tracked within a single release. Also check if the resources have the `helm.sh/resource-policy: keep` tag, which prevents deletion. A resource whose `cpln/release` tag was removed or reassigned to another release is skipped too, and so is a GVC that still holds anything. Each skip is printed with its reason.
  </Accordion>

  <Accordion title="Upgrade or rollback refused: it would move the release out of a GVC">
    ```text theme={null}
    ERROR: This upgrade would move release 'my-release' out of GVC 'old-gvc' and into GVC 'new-gvc'.
    ```

    Cleanup would delete everything the release still has in the GVC it leaves. Upgrade in place with `--gvc old-gvc`, or install into the new GVC as a separate release and migrate the data across. Pass `--delete-leftover-resources` to delete the stranded resources and go ahead with the move. See [Moving a release between GVCs](#moving-a-release-between-gvcs).
  </Accordion>

  <Accordion title="A GVC was not deleted">
    A GVC is deleted only when it holds nothing at that point, and only while the release still manages it. If it holds anything, the release no longer declares it, or the platform refuses the deletion, it is left in place and the reason is printed, along with the `cpln gvc delete` command that removes it by hand.
  </Accordion>

  <Accordion title="Rollback fails">
    Check available revisions with `cpln helm history`. Rollback can only target revisions with `deployed` or `superseded` status. By default, only 10 revisions are retained. See [Manage revision history](#manage-revision-history) to adjust this limit.
  </Accordion>

  <Accordion title="Missing chart dependencies">
    If your chart has dependencies in `Chart.yaml` that haven't been fetched:

    ```text theme={null}
    ERROR: Helm chart dependencies are missing.
    ```

    Re-run with the `--dependency-update` flag:

    ```bash theme={null}
    cpln helm install my-release ./my-chart --dependency-update
    ```
  </Accordion>

  <Accordion title="GVC does not exist">
    GVC-scoped resources (workload, identity, volumeset) require the GVC to exist. Either create the GVC in a separate step before installing, or include the GVC as a resource in your chart so it's created first.
  </Accordion>

  <Accordion title="Permission denied on install">
    Ensure you have `reveal` permission on secrets to manage release state.
  </Accordion>

  <Accordion title="Release name invalid">
    Release names must be DNS-1123 compliant: lowercase alphanumeric and hyphens only, max 53 characters, cannot start or end with a hyphen. Use `--generate-name` if you don't want to manage names manually.
  </Accordion>
</AccordionGroup>

## Next steps

<CardGroup cols={2}>
  <Card title="Apply YAML Manifests" href="/guides/cpln-apply" icon="file-code">
    Apply resources without Helm
  </Card>

  <Card title="CI/CD Usage" href="/cli-reference/ci-cd-development/ci-cd" icon="code">
    Automate Helm deployments
  </Card>

  <Card title="Workload Reference" href="/reference/workload/general" icon="server">
    Understand workload configuration
  </Card>

  <Card title="Helm Command Reference" href="/cli-reference/commands/helm" icon="book">
    Full helm command reference
  </Card>
</CardGroup>
