Skip to content
Install the CLI

Explanation

Helm integration

You do not need to rewrite every third-party operator from scratch or migrate an entire cluster in a single weekend to use Kix. Kix can render upstream Helm charts at build time when a native Kix package is not available yet, and it can link to existing cluster services, Secrets, and CRDs that are still managed outside Kix.

Helm support in Kix is a migration bridge and a fallback for third-party software that does not have a native Kix package yet. Wrapping a Helm chart brings its rendered YAML into Kix’s deployment workflow, but a Helm chart is still templated YAML under the hood. When you deploy a Helm chart instead of a native Kix package, you give up several guarantees that Kix normally provides:

  • Untyped values instead of validated options: With kix.helmChart, configuration goes through an untyped values map. A misspelled key or wrong value type in values is silently ignored by the chart’s Go templates or produces broken YAML, whereas a native Kix package validates every option with lib.types and enforces the option-default contract before anything renders.
  • Heuristic dependency and network discovery instead of exact out wiring: Native Kix packages wire resources together through .out attributes (self.configmap.out.name, postgres.out.fqdn), which guarantees that Kix knows every deploy-ordering edge and network connection. Inside a rendered Helm chart, references are plain strings in YAML. Kix reconstructs intra-chart ordering with heuristics (matching selectors, volume mounts, and serviceAccountName), and cannot automatically see cross-package network connections unless you wire deps and networkDeps yourself.
  • No automatic availability scaling: Native Kix packages scale replicas, inject topologySpreadConstraints, and synthesize PodDisruptionBudget objects automatically from the cluster’s availability setting (none, pod, node, or zone). A Helm chart ignores cluster.availability unless you translate it into chart-specific values by hand.
  • Upstream defaults often fail Kix scorecard rules: Many upstream charts omit resource limits, run containers as root, use mutable image tags, or grant broad RBAC. Bringing a chart into a cluster with strict scorecard policies often requires patching resources (patchResources), stripping hooks (strip), or adding policy exemptions.
  • Opaque upgrades and template sprawl: When a chart upgrade changes dozens of templated manifests at once, you still have to audit the resulting diff and work around upstream template quirks through values or patchResources.

Use kix.helmChart or scope.helm.importChart when it unblocks a migration or wraps a complex third-party operator. When authoring a long-term package for an operator, prefer writing native Kix workloads and using scope.helm.importChartCrds only to extract the upstream CRD schemas.

When you use Helm directly in a cluster, helm upgrade renders Go templates at deploy time and stores release state in cluster Secrets. That makes it hard to validate cross-chart dependencies, derive network policies, or preview exact diffs before touching the cluster.

Kix evaluates Helm charts at build time instead:

  1. Content-addressed chart fetching (scope.helm.fetchHelmChart): Kix fetches the upstream chart tarball from an HTTP or OCI registry and verifies it against the cryptographic SRI hash pinned in your package or cluster.nix. The chart version and SRI hash are committed in version control alongside your cluster code, locking the exact tarball contents that evaluation fetches. Kix caches the verified tarball in its local store (~/.cache/kix/store by default; see kix.toml), so later runs reuse the cached bytes.
  2. Deterministic template rendering (scope.helm.importChart): When evaluation reaches a chart, Kix runs helm template (4.3.1 by default, or 3.19.1 when helmVersion = "3") with your merged values, parses the rendered YAML documents, stores the rendered output in Kix’s local store, and continues evaluating the cluster. Kix strips Helm-specific release bookkeeping (helm.sh/chart, meta.helm.sh/*, app.kubernetes.io/managed-by: Helm, and test hooks) so the manifests match what the Kubernetes API server stores.
  3. Sealing into the Kix graph: Every rendered object is wrapped through scope.mkResource or scope.mkHelmWorkload, giving each resource a content hash (identityHash) and an out surface.

Both project builders (kix.toml) run the same pinned Helm release (kix/lib/helm-versions.json) with the same helm template arguments, so a chart renders the same way under either builder:

  • Under builder = "kix" (the default): Kix runs the helm programs in the Kix runtime and writes the fetched charts and rendered manifests into Kix’s local store (~/.cache/kix/store).
  • Under builder = "nix": The machine’s Nix fetches the chart and runs helm template during evaluation (allow-import-from-derivation).

Because rendered chart hashes depend on the exact Helm version, Kix pins the Helm versions in kix/lib/helm-versions.json. If your installed Kix and the Kix library in flake.lock ever disagree on the pinned Helm versions, run kix lib update to align flake.lock with your installed Kix.

Kix supports three tiers of Helm integration depending on how much control you need:

TierEntry pointBest for
Tier 1: Inline chartkix.helmChart { repo; name; version; hash; ... } in cluster.nixQuick one-off charts that only need config.values and an automatic root resource (autoRoot).
Tier 2: Package-wrapped chartscope.helm.importChart + resources.mkParts inside a package’s buildFull Kix packages that wrap an upstream chart, expose typed options, wire cross-package dependencies (deps and networkDeps), or replace chart resources (replaceResource).
Tier 3: Native package with chart CRDsscope.helm.importChartCrds (or vendored CRD JSON) + native buildersPackages where Kix authors the Deployments, Services, and RBAC directly for full safety and clarity while borrowing the upstream operator’s CRD schemas verbatim.

Reconstructing the intra-chart dependency graph

Section titled “Reconstructing the intra-chart dependency graph”

Inside a native Kix package, resources reference each other through self.<part>.out.*, which automatically populates kix.run/depends-on. Inside a rendered Helm chart, however, references are plain strings in YAML (serviceAccountName: "my-sa" or a Service selector matching pod labels) that carry no Nix string context.

Before wrapping the rendered chart resources, kix/lib/helm-dag.nix analyzes the raw JSON documents and reconstructs the internal dependencies automatically:

  • Workloads to ServiceAccounts, ConfigMaps, and Secrets: Discovered from serviceAccountName, volume mounts, projected volumes, envFrom, and secretKeyRef / configMapKeyRef.
  • RBAC bindings to ServiceAccounts: Discovered from subjects in RoleBinding and ClusterRoleBinding objects.
  • Services, PodDisruptionBudgets, and HPAs to workloads: Discovered by matching label selectors against workload pod templates and reading scaleTargetRef.
  • Routes and monitors to Services: Discovered from Ingress and HTTPRoute backend references and ServiceMonitor label selectors.
  • Custom resources to CRDs: Every custom resource in the chart is linked to the chart’s own CustomResourceDefinition for that group and kind, and any CRD builders exposed on out.crds.<Kind> automatically wait for both CRD establishment and the chart’s controller workloads (which may serve admission or conversion webhooks).

Kix turns these inferred relationships into real requires dependencies before sealing each resource. If a chart contains an implicit runtime dependency that does not appear in manifest fields (for example, a proxy Deployment whose startup script dials a sibling StatefulSet by a hardcoded name), package authors can add it with extraEdges. For cross-package connections into a Helm chart, passing resolved dependencies in importChart’s deps = [ ... ] argument collects their out string contexts so Kix can still derive network policy egress rules.

Real clusters rarely start empty. Cloud providers pre-install CoreDNS and default StorageClasses, security teams run external secret stores, and migrations from Helm or kubectl happen one service at a time.

Kix models existing resources without taking ownership of their lifecycle through import markers:

  • Cluster-level imports (kix.mkImport): Used in cluster.nix or cluster flavors for existing Services, StorageClasses, or infrastructure roles. An import renders no Kubernetes Service or Deployment; instead, it emits a lightweight PackageInstance marker annotated with kix.run/mode: import and exposes the same out attributes (name, fqdn, port, or crds) that a managed package would.
  • Secret and CRD references (scope.mkSecretRef and scope.mkCRDRef): Used inside packages when a Secret is populated out-of-band (by External Secrets Operator, Vault, or an administrator) or when an operator installs its own CRDs at runtime.

Imports participate in both compile-time validation and deploy-time scheduling:

  1. Compile-time contracts: Consuming packages read legacyApi.out.fqdn or creds.out.keyRef "password" identically whether the provider is managed by Kix or imported. Cross-resource checks verify that referenced StorageClasses, CRDs, and Secret keys are declared.
  2. Deploy-time readiness barriers: Before kix deploy applies any workload that depends on an import marker, the scheduler queries the live cluster to confirm that the imported object exists, that a referenced CRD is Established and serving the expected versions, or that a referenced Secret contains every declared key in data.
  3. Shadowing weak defaults: In dependency resolution, a managed package instance always takes precedence over an import on the same alias or infrastructure role. A cluster can start with flavor-provided imports (such as platform-dns or platform-storage) or imported legacy services, and later replace them with Kix-managed packages without changing consumer wiring.

When you are ready to bring an existing Helm or kubectl workload under full Kix management rather than merely importing its address, instance settings like labelStyle = "helm" and selectorLabels let Kix match immutable live pod selectors so the transition can happen in place via server-side apply.