_The reproducible per-chart procedure under the helm-expt test map._
This runbook records the reproducible per-chart path for delivering a catalog package through ConfigHub OCI into a live Kubernetes cluster. It is parameterized so the same job can run on another machine or rig.
Reference runs:
- 2026-06-01: first NGINX
http-clusterippass. - 2026-06-04: rig
helm-expt-oci-0604, NGINXhttp-clusterip,
Argo CD OCI revision sha256:98ec5a4bfccb925381b1ec304dcb4a9e884bbe06ac1bb5d69d34076a791a7f72.
- 2026-06-05: rig
helm-expt-oci-0605, NGINXhttp-clusterip,
Argo CD OCI revision sha256:0dc30c7049393a17fa7575bd45bad0918f5162d4014f80228010b8e914b49bf5.
- The same retained rig was then used for three more first-wave rows:
Metrics Server default passed after follow-up observation (sha256:34716a4697a7f05c4ebde2587d1666fe0e0b47978c7fdbc2b3efb1ab42de1465); External Secrets no-crds blocked on missing CRDs and separated webhook Secret delivery (sha256:9d6050df91f6c7be4549a7a32daa0c924c0f5a855050e87fd880e28701df13e6); ingress-nginx admission-disabled synced and reached Deployment readiness but remained Argo Progressing because the kind target has no LoadBalancer external IP (sha256:9672fd1b2da39cffada83cfa7b4cc8610ff65c7bbc2e2ba68dd3a20a1dc10960). Redis reuse-existing-secret was also delivered through Flux OCI on the same rig after staging the required redis/redis-existing-secret; Flux applied revision sha256:800fa47d22c99a250a8f3f4bed3004c334c2c1818cb6f1805e1cb892d490c1e2 and Redis returned PONG. Prometheus server-only-ephemeral and PostgreSQL existing-secret were then delivered through the same Flux controller. Prometheus reached Deployment readiness at sha256:5002930116cef5be34ec07de6445b29956d73627ae1d02b17446187d9a58810b. PostgreSQL reached StatefulSet readiness after staging postgresql/postgresql-auth, and a select 1 probe passed at sha256:d820d9bde5bbeeeee6bbd3f3e8ba90da43ce0dd1aefcbaa098920b75e1ea1f3b. kube-prometheus-stack no-crds was then attempted through Flux. Flux pulled revision sha256:ca3114731bde626fc078765e4b1f8cb12858b6f31e9af8c083175662bbe2eecd, but reconciliation blocked because Alertmanager.monitoring.coreos.com/v1 and related Prometheus Operator CRDs were absent; the run also recorded two separated Secrets that were not delivered through the workload OCI path.
This lane installs a vanilla public chart at its default base, entirely via cub installer (not the helm CLI), onto a BYO cluster whose GitOps controller pulls from oci.hub.confighub.com. This runbook is the exact, parameterized procedure so the same job runs identically on another machine.
The test harness lives in tests/; the core flow it exercises lives in confighub/helm-expt. See strategy.md.
Prerequisites (verify, don't assume)
cub auth logincomplete. Verify withcub organization list, notcub info.kind,kubectl,docker(daemon running),cubon PATH.- Current
cubwithcub cluster upandcub cluster down. helm-exptchecked out as a sibling of this repo (path is machine-specific - resolve it, don't hardcode): ``bash HELM_EXPT="$(cd "$(git rev-parse --show-toplevel)/../helm-expt" 2>/dev/null && pwd)" # fallbacks if not a sibling: /Users/<you>/Public/github-repos/helm-expt # /Users/<you>/code/helm-expt``- Resource budget: ≤2 kind clusters; this run creates 1, torn down at the end.
Target Profiles
Some rows need a target capability that vanilla kind does not provide. The live parity harness calls those capabilities target profiles.
| Profile | Purpose | Command shape |
|---|---|---|
kind-ingress-nginx | Proves Ingress behavior on kind by installing the target ingress controller. | npm run live-parity:top20 -- --chart nginx --base existing-tls-ingress --target-profile kind-ingress-nginx |
kind-loadbalancer | Proves Service.type=LoadBalancer behavior on kind by running cloud-provider-kind. | npm run live-parity:top20 -- --chart ingress-nginx --base default --target-profile kind-loadbalancer |
kind-three-node | Proves a base that needs multiple schedulable Kubernetes nodes for anti-affinity or quorum. This is target shape, not a ConfigHub worker requirement. | npm run live-parity:run -- --recipe recipes/hashicorp/consul/2.0.0 --base secure-mesh-existing-secrets --target-profile kind-three-node |
Before starting a guarded target-profile run, use preflight:
npm run live-parity:top20 -- --preflight --chart ingress-nginx --base default --target-profile kind-loadbalancer
kind-loadbalancer needs the cloud-provider-kind binary on PATH. It is guarded because the provider observes kind clusters host-wide. The harness refuses to start the profile if another kind cluster is present. Wait for other agents' live lanes to finish before using it.
Parameters
New to cub? Install the cub CLI first. You can pull and render public catalog packages without an account. Commands that save or change ConfigHub data require you to sign in.
CLUSTER=helm-expt-demo # cub-managed kind cluster + name prefix
SPACE_CLUSTER=${CLUSTER}-cluster # cub cluster up creates this (root app + OCI target live here)
WORKLOAD_SPACE=${CLUSTER}-nginx # we create this for the workload units
TARGET=${SPACE_CLUSTER}/oci # the OCI target created with the cluster
PKG=$HELM_EXPT/packages/bitnami/nginx/24.0.2
BASE=http-clusterip # nginx default base (default:true), no Secret
NS=nginx # MUST match the base's frozen namespace - see "Known defect"
WORKDIR=/tmp/${CLUSTER}-nginx
KUBECONFIG_FILE=$HOME/.confighub/clusters/${CLUSTER}.kubeconfig
KCTX=kind-${CLUSTER}
Steps
What this command does. cub installer is a released, open-source plugin for the cub CLI. cub installer setup pulls a catalog package and writes its Kubernetes files locally. It does not apply those files to a cluster; use kubectl, Argo CD, or Flux for delivery. The generated scripts stop before doing any work when the plugin or kustomize is missing.
# 1. Stand up the BYO-cluster simulation (kind + Argo + ConfigHub space/target/root-app).
# Long step (kind create + Argo install + rollout wait). Dedicated kubeconfig;
# never merged into ~/.kube/config.
cub cluster up --name "$CLUSTER"
# 2. Render the vanilla chart at its default base via cub installer (NO helm CLI).
# ⚠ NS must equal the base's frozen namespace (see "Known defect"). For nginx that is "nginx".
rm -rf "$WORKDIR"
cub installer setup --pull "$PKG" --base "$BASE" --work-dir "$WORKDIR" \
--non-interactive --namespace "$NS"
# Coherence guard (catches the known defect before anything is applied):
test "$(grep -rhE '^\s*namespace:' "$WORKDIR"/out/manifests | sort -u | wc -l)" -eq 1 \
|| { echo "ABORT: incoherent namespaces in render"; exit 1; }
# 3. Upload the rendered manifests as units, targeted at the cluster's OCI target.
cub installer upload --work-dir "$WORKDIR" --space "$WORKLOAD_SPACE" --target "$TARGET"
# 4. Set the Space release target, then publish the release that Argo will pull.
cub space update "$WORKLOAD_SPACE" --release-target "$TARGET"
cub release publish "$WORKLOAD_SPACE"
# 5. Create the Argo Application record, then publish the cluster Space release
# that the root application reads.
cat > /tmp/app-nginx.yaml <<YAML
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata: {name: nginx, namespace: argocd}
spec:
project: default
source:
repoURL: oci://oci.hub.confighub.com:443/space/${WORKLOAD_SPACE}
targetRevision: latest
path: .
destination: {server: https://kubernetes.default.svc, namespace: ${NS}}
syncPolicy:
automated: {selfHeal: true, prune: true}
syncOptions: [ServerSideApply=true, CreateNamespace=true]
YAML
cub unit create --space "$SPACE_CLUSTER" nginx-app /tmp/app-nginx.yaml --target "$TARGET"
cub release publish "$SPACE_CLUSTER"
# 6. Verify (dedicated kubeconfig + explicit --context; never `kubectl config use-context`).
export KUBECONFIG="$KUBECONFIG_FILE"
kubectl --context "$KCTX" get applications -n argocd \
-o custom-columns='NAME:.metadata.name,SYNC:.status.sync.status,HEALTH:.status.health.status,REV:.status.sync.revision'
kubectl --context "$KCTX" rollout status deploy/nginx -n "$NS" --timeout=150s
kubectl --context "$KCTX" get deploy,pods -n "$NS" -o wide
# 7. Tear down (kind cluster + ConfigHub space, recursive).
cub cluster down --name "$CLUSTER"
Expected proof (reference values from the 2026-06-01 PASS run)
| Layer | Expected |
|---|---|
| Render | 6 manifests; single namespace nginx across all |
| ConfigHub | 6 workload units + installer-record in $WORKLOAD_SPACE; workload Space release published |
| OCI | served at oci://oci.hub.confighub.com:443/space/$WORKLOAD_SPACE |
| Argo | root ($SPACE_CLUSTER) + child nginx both Synced/Healthy, same revision (e.g. sha256:90a8a441…) |
| Runtime | deploy/nginx 1/1 Available; pod Running 1/1; image registry-1.docker.io/bitnami/nginx:latest |
| Teardown | cub cluster down deletes the kind cluster and Space; kind get clusters → none |
Namespace Coherence Guard
Earlier live runs found that packages without the namespace transformer could render a Namespace object for one namespace while workloads retained the recipe's frozen namespace. The top-20 packages now include the transformer where it is supported, but complex charts can still contain namespace references in embedded fields. Keep the step 2 coherence guard in place and read findings.md before using an arbitrary namespace for a complex chart.