Walkthrough: Bring Your Own Helm Chart
The Installable and Installation CRDs are two halves of one flow:
- Installable — what to deploy: a Helm chart, its version, and a values template.
- Installation — where to deploy it: which cluster(s), plus any per-cluster overrides.
This walkthrough takes you end-to-end using a real public chart — Bitnami Redis —
so the focus stays on the CRDs, not the application. By the end you'll have authored
both resources, stored them in your org's .platform repo, and watched ArgoCD deploy
Redis to a cluster.
What You'll Learn
- How to author an
Installablefor a public Helm chart, including a values template - How to author an
Installationthat targets one cluster, then several - How to apply a per-destination override (a different value on one cluster)
- How the resources flow through
.platform→ ArgoCD → your cluster - How deletion works (auto-prune is intentionally disabled)
Prerequisites
- Access to your GitHub org's
.platformrepository. - The names of your provisioned clusters (
{solution}-{env}-{region}— see Discovering Available Cluster Names). - ArgoCD access for your target environment.
The Big Picture
Installable (what) Installation (where)
┌────────────────┐ ┌───────────────────────┐
│ chart: redis │◄─────────│ installableRef: redis │
│ repo, version │ refers │ destinations: │
│ values template│ to │ - acme-corp-dev-... │
└────────────────┘ │ - acme-corp-prd-... │
└───────────┬───────────┘
│ installation-operator
▼ renders one per destination
┌───────────────────────┐
│ ArgoCD Application │──► Redis runs on
│ redis-<cluster> │ each target cluster
└───────────────────────┘
You author the two CRDs; the installation-operator (running on the orchestration
cluster) renders one ArgoCD Application per destination and hands off the actual sync
to ArgoCD.
Step 1 — Author the Installable (what to deploy)
Create an Installable describing the Redis chart. Because you're introducing this
chart yourself, you author it in your own org's .platform repo — it lands in your
org namespace on the orchestration cluster (leave metadata.namespace unset; it's
applied for you).
Widely-shared installables instead live in the central p6m-run/installables catalog in
the installables namespace — see the namespace note below.
apiVersion: p6m.dev/v1alpha1
kind: Installable
metadata:
name: redis
spec:
source:
type: helm
repoURL: registry-1.docker.io/bitnamicharts
chart: redis
targetRevision: 27.0.15
namespace: redis
releaseName: redis
# Rendered as a Helm values file. {{variable}} tokens are interpolated
# per-destination from that cluster's cluster-info (see Template Variables).
template: |
global:
security:
allowInsecureImages: true
image:
registry: docker.io
repository: bitnamilegacy/redis
tag: 8.2.1
architecture: standalone
auth:
enabled: false
commonLabels:
p6m.dev/cluster: "{{cluster.name}}"
p6m.dev/environment: "{{environment.name}}"
A few things worth calling out:
repoURL/chart/targetRevisionpoint at any Helm chart source. Here it's Bitnami's OCI registry, pinned to chart version27.0.15.namespaceis where the chart's resources land on the target cluster; the operator creates it for you (CreateNamespace=true).releaseName— always set this explicitly, even when it matches the name. See Best Practice: Set a releaseName.templateis a Helm values file with{{variable}}tokens. Those tokens are filled in per-destination from each cluster'scluster-info— so the same Installable adapts to wherever it lands. See Cluster Info Template Variables.
Use registry-1.docker.io/bitnamicharts (Bitnami's OCI registry), not the old
https://charts.bitnami.com/bitnami HTTP repo. Bitnami now publishes OCI-style entries
in the legacy HTTP index that ArgoCD's chart fetcher rejects with
invalid_reference: invalid tag; the OCI registry works cleanly. How an OCI chart is
split across the two fields — registry path in repoURL, chart name in chart, no
oci:// prefix — is standard ArgoCD Helm behavior, documented in
ArgoCD's Helm source guide.
bitnamilegacy image override?Bitnami's free docker.io/bitnami/* images stopped publishing in 2025; the community
snapshot now lives under docker.io/bitnamilegacy/*. Pointing the image at
bitnamilegacy/redis:8.2.1 with allowInsecureImages: true is a good example of using
template to make a public chart work in your environment. Full background in
Common Observations below.
Step 2 — Author the Installation (where to deploy)
An Installation references the Installable and lists the destination cluster(s).
Start with a single cluster:
apiVersion: p6m.dev/v1alpha1
kind: Installation
metadata:
name: redis
spec:
installableRef:
kind: Installable
name: redis
namespace: <your-github-org> # where your Installable lands (org namespace)
destinations:
- clusterRef:
name: acme-corp-dev-us-east-2
spec.destinations[].clusterRef.name must match a provisioned cluster name
({solution}-{env}-{region}). The operator looks up that cluster's cluster-info to
resolve the {{variables}} in your template and to name the resulting ArgoCD project.
Step 3 — Deploy to multiple clusters
Add more entries under destinations to fan the same Installable out across clusters:
destinations:
- clusterRef:
name: acme-corp-dev-us-east-2
- clusterRef:
name: acme-corp-stg-us-east-2
- clusterRef:
name: acme-corp-prd-us-east-2
The operator renders one ArgoCD Application per destination
(redis-acme-corp-dev-us-east-2, redis-acme-corp-stg-us-east-2, …). Because the
template pulls values from each cluster's cluster-info, the p6m.dev/cluster and
p6m.dev/environment labels come out different on each cluster with no extra work.
Step 4 — Apply a per-destination override
Sometimes one cluster needs something different. Use overrides on a destination to
change chart source fields or values for just that cluster, without touching the shared
Installable. Here, production pins a different chart version and turns on persistence:
destinations:
- clusterRef:
name: acme-corp-dev-us-east-2
- clusterRef:
name: acme-corp-prd-us-east-2
overrides:
source:
targetRevision: 27.0.14 # pin prod to a specific chart version
template: |
global:
security:
allowInsecureImages: true
image:
registry: docker.io
repository: bitnamilegacy/redis
tag: 8.2.1
architecture: standalone
auth:
enabled: false
master:
persistence:
enabled: true
size: 8Gi
overrides.source accepts the same fields as the Installable's spec.source; only the
fields you set are overridden, the rest are inherited. Override template values
support the same {{variable}} interpolation. See
Per-Destination Overrides.
Step 5 — Store both resources in .platform and open a PR
In your org's .platform repository, add a single file installations/redis.yaml
holding both resources, separated by ---:
# installations/redis.yaml
apiVersion: p6m.dev/v1alpha1
kind: Installable
metadata:
name: redis
spec:
source:
type: helm
repoURL: registry-1.docker.io/bitnamicharts
chart: redis
targetRevision: 27.0.15
namespace: redis
releaseName: redis
template: |
global:
security:
allowInsecureImages: true
image:
registry: docker.io
repository: bitnamilegacy/redis
tag: 8.2.1
architecture: standalone
auth:
enabled: false
commonLabels:
p6m.dev/cluster: "{{cluster.name}}"
p6m.dev/environment: "{{environment.name}}"
---
apiVersion: p6m.dev/v1alpha1
kind: Installation
metadata:
name: redis
spec:
installableRef:
kind: Installable
name: redis
namespace: <your-github-org> # the org namespace on the orchestration cluster
destinations:
- clusterRef:
name: acme-corp-dev-us-east-2
Everything under installations/ in the .platform repo is managed by the
{github-org}-installations root ArgoCD application. Open a PR and merge to main;
ArgoCD picks it up from there. (Keeping the Installable next to its Installation is the
common pattern for a chart you're introducing yourself. Widely-shared installables instead
live in the central p6m-run/installables catalog and are referenced by name.)
On the orchestration cluster, Installations live in your org namespace and
Installables you author yourself land there too — so .platform installations/*.yaml
files leave metadata.namespace unset and point installableRef.namespace at the org.
The dedicated installables namespace (shown on the Installable
concept page) is where the shared catalog (p6m-run/installables) lives; reference
those with installableRef.namespace: installables.
Step 6 — Validate via ArgoCD
Once the root app syncs, the operator renders one child ArgoCD Application per
destination. For the acme-corp-dev-us-east-2 destination above, the operator
produces (lines with a # comment are derived per-destination from that cluster's
cluster-info):
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: redis-acme-corp-dev-us-east-2 # <installation-name>-<cluster>
spec:
project: dev-aws-us-east-2 # from cluster-info: {env}-{cloud}-{region}
destination:
namespace: redis # from spec.source.namespace
server: https://<api-endpoint-of-acme-corp-dev-us-east-2> # the target cluster's API server, from cluster-info
source:
repoURL: registry-1.docker.io/bitnamicharts
chart: redis
targetRevision: 27.0.15
helm:
releaseName: redis
values: | # your template, with {{variables}} resolved
global:
security:
allowInsecureImages: true
image:
registry: docker.io
repository: bitnamilegacy/redis
tag: 8.2.1
architecture: standalone
auth:
enabled: false
commonLabels:
p6m.dev/cluster: "acme-corp-dev-us-east-2" # {{cluster.name}} -> resolved
p6m.dev/environment: "dev" # {{environment.name}} -> resolved
syncPolicy:
automated:
prune: false # deletes are never automatic (see Teardown)
syncOptions:
- CreateNamespace=true
- ServerSideApply=true
Validation happens in the ArgoCD UI — ArgoCD itself runs on an orchestration
vcluster you don't have direct kubectl access to. Open your org's ArgoCD
({github-org}-argocd.o11n.p6m.run; see the
ArgoCD Cheat Sheet for login and navigation) and confirm:
- An application named
redis-<cluster>(hereredis-acme-corp-dev-us-east-2) exists and is Synced / Healthy. - Its resource tree shows
redis-master-0Running in theredisnamespace, on thebitnamilegacy/redis:8.2.1image.
If you have kubectl access to the target cluster (the one Redis was deployed to, not
the ArgoCD vcluster), you can double-check the workload directly:
$ kubectl get pods -n redis
NAME READY STATUS RESTARTS AGE
redis-master-0 1/1 Running 0 68s
$ kubectl get pod redis-master-0 -n redis -o jsonpath='{.spec.containers[0].image}'
docker.io/bitnamilegacy/redis:8.2.1
The operator sets syncPolicy.automated.prune: false on every Application it creates.
Syncing will add and update resources, but it will never delete them automatically —
deletions are always a deliberate, manual step (see below).
Teardown (Optional)
To undo everything above, reverse the steps. Because auto-prune is disabled, removing the
resources from .platform does not tear down what's already deployed — you delete the
child Applications by hand in the ArgoCD UI. This undoes Steps 5–6 (and, by removing the
Installable, Steps 1–4):
-
Remove
installations/redis.yamlfrom.platform. Open a PR against your org's.platformrepo deleting the file (it holds both theInstallableand theInstallation), and merge tomain. -
Refresh and sync the root app. In the ArgoCD UI, find the
{github-org}-installationsapplication, click Refresh, then Sync. Once synced, theredis-<cluster>child Application shows as OutOfSync with a prune indicator — but is not removed, because auto-prune is off. -
Delete the per-destination Application(s). For each destination you deployed to (here just
redis-acme-corp-dev-us-east-2), open the three-dot (⋮) menu on that Application and select Delete. This removes Redis from the target cluster.
Confirm in the ArgoCD UI that the {github-org}-installations root app is Synced /
Healthy and no redis-* Application remains. For the full reference (including the
Kargo caveat and the "verify no Installation still references this Installable" check
before removing a shared one), see
Deleting an Installation and
Deleting an Installable.
Common Observations
Why is prune: false on the rendered Application?
The operator disables auto-prune on every Application it creates, so a sync never deletes
resources on its own. This prevents an accidental removal from .platform (or a bad
render) from tearing down a running install. Deletions are always the deliberate manual
step in Teardown.
Why point the image at bitnamilegacy instead of bitnami?
As of 2025 Bitnami moved its maintained catalog behind Bitnami Secure, and the free
docker.io/bitnami/* images (plus the chart's default latest tags) stopped being
published. The community images now live under docker.io/bitnamilegacy/* — a frozen
snapshot — so we override the image and pin a tag that exists there (8.2.1), with
global.security.allowInsecureImages: true to allow the non-default registry.
Why the OCI registry-1.docker.io/bitnamicharts repoURL and not https://charts.bitnami.com/bitnami?
The legacy HTTP repo now serves OCI-style index entries that ArgoCD's chart fetcher
rejects (invalid_reference: invalid tag). Pointing repoURL at Bitnami's OCI registry
avoids that. This applies to any Bitnami chart, not just Redis.
Do I need a cd block or Kargo?
No. The base "bring your own chart" flow renders a plain ArgoCD Application and syncs
it directly. A cd block (and Kargo) only comes into play when you want automated
promotion between environments — see the Installation
reference.
Where does the Application object itself live?
The operator creates it in the orchestration cluster's ArgoCD namespace, named
<installation>-<cluster> (e.g. redis-acme-corp-dev-us-east-2) — not in your org
namespace, where the Installable/Installation CRs live.
Troubleshooting
| Symptom | Cause / Fix |
|---|---|
ArgoCD app ComparisonError: … invalid_reference: invalid tag | The repoURL is the legacy https://charts.bitnami.com/bitnami HTTP repo. Switch to the OCI registry registry-1.docker.io/bitnamicharts (chart name stays in chart:, no oci:// prefix). |
Pods stuck ImagePullBackOff / ErrImagePull on a bitnami/* image | The free bitnami/* tag no longer exists. Override the image to bitnamilegacy/<name> with a tag present in that repo, and set global.security.allowInsecureImages: true. |
| App is Synced but resources you removed are still running | Expected — auto-prune is disabled. Delete the child Application(s) manually (see Teardown). |
installableRef not resolving / Installation never produces an Application | spec.installableRef.namespace must match the namespace the Installable actually lives in (your org namespace for a self-authored one; installables for the shared catalog). |
{{variable}} tokens render empty | The value isn't present in that cluster's cluster-info, or isn't available for the cluster type — see Cluster Info Template Variables. |
For general deployment issues, see the Troubleshooting Guide.
Related
Platform CRDs
- Installable — the CRD reference for what to deploy
- Installation — the CRD reference for where to deploy
- Cluster Info Template Variables — the full
{{variable}}list
Tools & external references
- Helm Charts — chart structure and values
- ArgoCD Application — the resource the operator renders
- ArgoCD Helm source — how ArgoCD renders Helm (incl. OCI) sources
bitnamilegacyon Docker Hub — the frozen community image namespace used in the override