# Helm Charts (OCI)

> Use a hosted CloudRepo Docker repository as a Helm OCI registry: log in with a repository token, package, push, pull and install a chart, step by step.

CloudRepo has no separate Helm repository type. Helm stores a chart as an OCI artifact, and a CloudRepo **Docker** repository is an OCI registry, so you keep your charts in a Docker repository and sign in to it with a repository token. You do not run `helm repo add`: Helm addresses a chart by an `oci://` reference.

## Before you start

- A Docker repository to hold the charts. For the steps to create one, see [Creating a Repository](/docs/manage/repositories.html#creating-a-repository).
- A repository token that covers it. Choose **Read + write** when you push. To create one, follow [Repository Tokens](/docs/authenticate/repository-tokens.html). Use a Generic token, whose value starts with `crp_v1_`; an npm-format token (`crn_v1_`) is turned away at this registry. Helm’s password is the token, and its username is your account email address.
- Helm itself. The login, package, push and pull commands below were run with Helm 4.2.4.

> **Note:** The credential Helm sends is a repository token, not the password you use to sign in to the admin portal.

## Log in, push and pull

**Helm**

**1. Log in to the host.** `helm registry login` takes the host alone, as `docker login` does. Pipe the token in on standard input:

**Terminal**

```bash
echo "$CLOUDREPO_TOKEN" | helm registry login your-org.mycloudrepo.io \
  --username you@example.com \
  --password-stdin
```

Expected: `Login Succeeded`. Helm keeps the login in its own registry config, which is `~/.config/helm/registry/config.json` by default on Linux (`helm env HELM_REGISTRY_CONFIG` prints the path).

**2. Package the chart.** Run this beside your chart’s directory:

**Terminal**

```bash
helm package chart
```

Expected: `Successfully packaged chart and saved it to: .../docs-helm-1.0.0.tgz`. The file name is the chart’s `name` and `version` from `Chart.yaml`.

**3. Push to the repository.** The address ends at the **repository**: the host, the literal `repositories/` segment, and your repository. Helm adds the chart’s name and its version as the tag:

**Terminal**

```bash
helm push docs-helm-1.0.0.tgz oci://your-org.mycloudrepo.io/repositories/your-repo
```

Expected: `Pushed: your-org.mycloudrepo.io/repositories/your-repo/docs-helm:1.0.0` and a `Digest:` line.

**4. Pull.** `helm pull` takes the **chart**, so here the address ends with its name, and the version is a flag:

**Terminal**

```bash
helm pull oci://your-org.mycloudrepo.io/repositories/your-repo/docs-helm --version 1.0.0
```

Expected: `Pulled: your-org.mycloudrepo.io/repositories/your-repo/docs-helm:1.0.0` and a `Digest:` line, and `docs-helm-1.0.0.tgz` in the current directory.

To install a chart from the registry, give `helm install` the same chart address as `helm pull`, and the version:

**Terminal**

```bash
helm install my-release oci://your-org.mycloudrepo.io/repositories/your-repo/docs-helm --version 1.0.0
```

> **Which address ends where:** `helm push` ends at the **repository**: `oci://your-org.mycloudrepo.io/repositories/your-repo`. Helm adds the chart’s name itself, so leave the name out of the push address. `helm pull` and `helm install` end at the **chart**: `.../repositories/your-repo/docs-helm`. `docker push` is the opposite: you name the whole image reference yourself.

## In CI

Keep the token in the platform’s secret store, expose it to the job as the environment variable `CLOUDREPO_TOKEN`, and use the same login as above, on standard input. Do not pass the token to Helm’s `--password` flag, on `helm registry login` or on `helm push`: it would be on the command line, where a process listing shows it. The [CI/CD](/docs/ci/overview.html) pages cover where each CI system keeps a secret.

## Argo CD and Flux

To pull a chart with Argo CD or Flux, see [Pull CloudRepo Helm charts from Argo CD and Flux](/docs/ci/argo-cd-and-flux.html): the setup for each tool as declarative YAML, with the token kept in a Secret and out of every manifest.

## Troubleshooting

**`helm push` fails with `response status code 400: Bad Request`.** The address has no `repositories/` segment, so the registry does not recognize the path. Check that the push address reads `oci://your-org.mycloudrepo.io/repositories/your-repo`, with the literal `repositories/` before your repository’s name. CloudRepo answers `400` with the code [`NAME_INVALID`](/docs/reference/error-codes.html#name_invalid); Helm prints only the status, because it fails on a `HEAD` request, which has no body.

**`helm registry login`, `helm push` or `helm pull` is refused with `401`.** Use a repository token as the password, not your portal password. Use a Generic token (`crp_v1_`): an npm-format token (`crn_v1_`) is refused, and so is a token that has expired or been revoked. See [`UNAUTHORIZED`](/docs/reference/error-codes.html#unauthorized).

**`403 denied`.** The token does not reach this repository, or it is **Read only** and you are pushing. Create a token that reaches the repository with **Read + write**. See [`DENIED`](/docs/reference/error-codes.html#denied).

**`helm pull` cannot find a chart you just pushed.** Helm ends its error with `not found`. Check two things:

1. The push address. Helm adds the chart’s name to it, so if it ended with the chart’s name, Helm’s `Pushed:` line shows the name twice, as in `.../repositories/your-repo/docs-helm/docs-helm:1.0.0`. That push went to the address Helm printed, so a pull at `.../repositories/your-repo/docs-helm` does not find it. Pull at the address Helm printed, or push again with an address that ends at the repository.
2. The tag. It is the chart’s `version` from `Chart.yaml`, not a name you choose, and `helm pull` takes it as `--version`. A reference with `:1.0.0` appended to the chart name is not the form Helm expects.

---

The page: https://www.cloudrepo.io/docs/formats/helm.html
