Identifies this resource within spec.resources/spec.appliedResources. Must be unique within a cluster. For a Helm resource this also doubles as the Helm release name; for a Secret resource, the resulting Secret object’s name.
'Cluster Resources'
Overview
Modules provision the cluster itself — the control plane, node pools, the infrastructure layer. spec.resources covers the layer on top: Kubernetes manifests, Helm releases, and Secrets rendered from Hyve’s own environment, that Hyve should own, drift-check, and re-apply, using the same reconcile cadence it already uses for everything else.
A module is still required even if you only care about this layer — there’s no driverless cluster type. See Auth-only modules for how to write a minimal module when the cluster itself is provisioned elsewhere.
On the cycle that creates a cluster, the full order is beforeCreate → create → onCreate → spec.resources → afterCreate (see Lifecycle workflow hooks) — afterCreate is the hook to use for anything that needs a resource-created object (a namespace, a Deployment) to already exist.
This is the same problem Terraform solves with its kubernetes/helm providers — declare a resource once, and have it continuously compared against both your desired config and the live cluster on every plan/apply. It is not a replacement for a continuous GitOps controller like Argo CD or Flux — see ArgoCD Integration for that pattern, which remains the right choice when you want health checks, sync waves, and a UI. spec.resources is for the narrower case: a handful of foundational resources (an ingress controller, cert-manager, a couple of CRDs) that a team wants Hyve itself to own, alongside the cluster that hosts them.
Three kinds of resource
A spec.resources entry is a raw manifest, a Helm release, or a Secret rendered from Hyve’s own environment — exactly one of source, helm, or secret must be set.
spec: resources: - name: nginx-ingress source: ./resource-files/nginx-ingress.yaml namespace: ingress-nginx
- name: cert-manager-crds source: github.com/myorg/manifests//cert-manager/crds.yaml@v1.14.0
- name: portainer-public-resource # The same manifest file can be reused across templates/clusters — # each reference supplies its own values here, so the file itself # never hardcodes anything cluster-specific. source: ./resource-files/portainer-public-resource.yaml values: FULL_DOMAIN: portainer.example.com
- name: cert-manager helm: chart: cert-manager repo: https://charts.jetstack.io version: v1.14.0 namespace: cert-manager values: installCRDs: "true" # ${VAR} expands against Hyve's own process environment at # reconcile time — useful for a value that's real but not secret # (an org ID, an API endpoint) and shouldn't be committed # literally into a template reused across many cluster instances. acme.email: ${LETSENCRYPT_EMAIL}
- name: github-secrets secret: namespace: default keys: [PANGOLIN_ENDPOINT, NEWT_ID, NEWT_SECRET]source reuses the exact same source-string convention as spec.driver.source and remote workflow references: a local path, or github.com/<org>/<repo>[//<path>][@<version>]. It always names a single file — there’s no directory-expansion form, since each resource needs one clear identity to track and prune. Omit the version to track the latest commit; unlike modules and workflows, resources aren’t locked in hyve.lock — they’re meant to be freshly checked every reconcile cycle by design, so drift in the source itself (someone pushed a new commit to the manifest repo) is exactly the kind of drift this feature is built to catch.
Field reference
name string required source string A local path or remote ref, resolving to exactly one manifest file. Mutually exclusive with helm/secret.
namespace string Applied via kubectl apply/diff -n <namespace> as a default. An object’s own metadata.namespace still wins per normal kubectl behavior. Only meaningful with source — a Helm resource uses helm.namespace instead, and a Secret resource uses secret.namespace instead.
values object ${VAR_NAME} substitutions (braced form only) applied to this resource’s manifest file — the raw text, comments included. Only meaningful with source. Lets one manifest file be reused across multiple resource entries — even within the same template — while each reference supplies its own values, instead of hardcoding something reference-specific (a hostname, an org ID) directly into a shared file.
Three-tier lookup per reference, most specific wins:
- This resource’s own
values:— verbatim keys, no case transform, exactly likehelm.values. - The owning cluster’s
spec.params, asHYVE_PARAM_<KEY>(see theHYVE_PARAM_*naming convention) — a template-wide default when every resource sharing a file wants the same value. - Hyve’s own process environment (
.envlocally, CI secrets) — same fallbackhelm.valuesalready gets.
A referenced variable that resolves in none of the three is a hard reconcile error naming it, not a silent empty-string substitution — same fail-loud behavior as helm.values and Secret resources.
delete boolean default: false Marks this resource for removal. See Removing a resource below.
helm object Declares this resource as a Helm chart release instead of a raw manifest. Mutually exclusive with source/secret. Sub-fields: chart (required — a chart repo name, or a local path with no repo: set, for a chart living in your own repository), repo, version, namespace, values (a flat string map, passed as --set key=value).
A value may reference Hyve’s own process environment with ${VAR_NAME} (braced form only — a bare $VAR or a literal $ elsewhere in a value is left untouched), resolved fresh on every reconcile. Useful for a value that legitimately differs per environment but isn’t itself secret — an API endpoint, an org ID — so it can live in .env/CI secrets instead of being committed literally into a template reused across many cluster instances. A referenced variable that isn’t set is a hard reconcile error naming it, not a silent empty-string substitution — the same fail-loud behavior Secret resources use for a missing key.
secret object Declares this resource as a Kubernetes Secret rendered from Hyve’s own process environment at reconcile time. Mutually exclusive with source/helm. Sub-fields: namespace, type (defaults to Opaque), keys (a list — see Secret resources below).
Secret resources
A secret: resource resolves each entry in keys from the environment Hyve’s own reconcile process is running in — not from anything committed to Git — and renders/applies a v1/Secret through the exact same drift-detect/apply path every other resource uses. Each keys entry is either:
- a bare string — the environment variable name, which doubles as the resulting Secret key (identity mapping):
keys: [PANGOLIN_ENDPOINT, NEWT_ID, NEWT_SECRET]
- a
{env, key}mapping — resolve from one environment variable, store under a different Secret key, e.g. to satisfy a chart’s fixed expected key name:keys:- {env: PORTAINER_PASSWORD, key: password}
Where that environment comes from is up to how you’re running Hyve — a GitHub Actions job’s env: block forwarding repo secrets, a systemd unit’s EnvironmentFile, or (in a hyve serve deployment) whatever injects secrets into the server’s container. Hyve itself never talks to a secrets backend (Vault, cloud Secrets Manager, etc.) on your behalf — if you already run one of those, keep using it and declare its output (a SecretStore/ExternalSecret, a Vault Agent Injector annotation, whatever your tool needs) as an ordinary source: resource instead. secret: is the batteries-included default for a handful of application secrets when you don’t have that infrastructure yet, not a replacement for it.
Drift detection
On every reconcile cycle, for an ACTIVE cluster, resource reconciliation runs unconditionally — unlike spec.workflows.preReconcile, which only fires when param drift is detected (see Workflows). For each declared resource, in list order:
- Config drift — the resource’s current content hash (of the resolved manifest bytes, or of the Helm chart/version/values configuration) is compared against what was hashed at last apply.
- Live drift —
kubectl diff --server-side(manifest resources) orhelm templatepiped into the same diff (Helm resources) checks whether the live object has changed out-of-band — someone rankubectl edit, for instance.
Either kind of drift triggers a re-apply (kubectl apply --server-side, or helm upgrade --install). If neither has drifted, the resource is left alone. A failure — apply, delete, or resolve — stops the loop for that cluster with no rollback (whatever already applied this cycle stays applied) and fails the reconcile cycle so it’s retried next time, rather than silently continuing past a broken resource.
What Hyve applied is recorded in spec.appliedResources, keyed by resource name — reconciler-owned, never hand-edit, the same convention as spec.driverOutputs. Both live in a separate reconciler-owned file, cluster-state/<name>.state.yaml — a sibling directory of clusters/, not alongside the cluster’s desired-state YAML itself — see Reconciler state file. Inspect the tracked state with hyve cluster resources <name>.
Removing a resource
Two mechanisms, mirroring how whole-cluster deletion already works — and identical across all three resource kinds, since a Secret resource is cleaned up through the same tracked-object path a manifest resource uses:
| Mechanism | Safe by default? | |
|---|---|---|
| Explicit | Set delete: true on the resource entry | Yes — always honored |
| Implicit | Just remove the YAML block | No — only acted on if strictResourceDelete: true |
Explicit (delete: true) is the recommended path. On the next reconcile, Hyve removes the tracked objects (kubectl delete, or helm uninstall for a Helm resource), drops the spec.appliedResources entry, and removes the resource entry itself from spec.resources — self-cleaning, the same way a cluster’s YAML file is removed once spec.delete: true finishes processing. This works regardless of strictResourceDelete.
Implicit is what happens if a resource entry just disappears from spec.resources without ever being marked delete: true — the reconciler notices an orphan (tracked in spec.appliedResources but no longer declared) and, by default, only logs a warning. Set reconcile.strictResourceDelete: true in hyve.yaml to have orphans pruned automatically instead:
reconcile: strictDelete: false # whole-cluster prune (existing) strictResourceDelete: false # in-cluster resource prune (default: off)This mirrors strictDelete’s existing default — safety first, opt into automatic pruning once you trust the workflow.
Dropping one key from a Secret resource
You don’t need delete: true to remove a single key — just remove it from keys: and reconcile as usual:
secret: namespace: default keys: - PANGOLIN_ENDPOINT - NEWT_ID - NEWT_SECRETThe key change is detected the same way any other config change is (it’s part of the resource’s content hash), triggering a re-apply — and the dropped key is actually removed from the live Secret, not just left stale. Hyve renders Secret resources using data (base64), never stringData: stringData is a write-only convenience the API server merges into data but never prunes from, so a key removed there would silently linger in the live object forever. data is a real server-side-apply-tracked field, so omitting a key from it on the next apply actually deletes it.
Dry-run preview
hyve reconcile --dry-run makes the whole cycle read-only: resolution and diffing still run for real (both are read-only), but every mutating call is skipped and logged instead.
[production] Resource nginx-ingress: drift detected (config changed) — applyingbecomes, under --dry-run:
[production] DRY RUN: resource nginx-ingress drift detected (config changed) — would apply...[production] DRY RUN: 2 resource(s) with drift, 3 unchangedSee hyve reconcile --dry-run — it also covers cluster create/delete/scale and lifecycle workflows, not just resources, so it’s safe to run against a repo with pending changes anywhere.