A single static binary that takes a freshly created cluster from “API server answers” to “workloads can be deployed.” No kubectl, helm, or shell scripts required.
apiVersion: khook.io/v1
kind: Khook
metadata:
name: bootstrap
defaults:
timeout: 5m
steps:
- name: cilium
helm:
chart: cilium
repo: https://helm.cilium.io/
version: 1.18.4
namespace: kube-system
atomic: true
- name: all-ready
needs: [cilium]
wait:
for: condition=Ready
on: pods
allNamespaces: true$ khook apply -f bootstrap.yaml
✓ cilium (helm) 24.108s
✓ all-ready (wait) 9.412s
The problem
Terraform, eksctl, or CAPI creates the cluster; Argo CD or Flux manages
it once installed. The steps in between usually live in a
bootstrap script of kubectl apply,
helm upgrade --install, sleep 30, and retry
loops, often run from a null_resource. khook replaces the
script with a declarative spec: a DAG of steps that it validates, plans,
and converges.
Why khook
The sequenced, wait-heavy steps between “cluster exists” and “GitOps controller is running.”
The Kubernetes and Helm SDKs are embedded and khook never shells out. The runner needs only khook.
Steps declare needs: and run in parallel levels.
Cycles fail validation before the cluster is touched.
Helm release history decides install vs upgrade, applies converge
existing objects, deletes treat “not found” as success. Safe to run on
every terraform apply.
Per-step timeouts and retries, onError: fail | continue,
a summary of what succeeded, failed, or was skipped, and separate exit
codes for validation and execution errors.
khook plan predicts install, upgrade, or no-op per step
against the live cluster; --diff shows object changes via
server-side dry-run. Plan never mutates.
khook installs the CNI, secrets tooling, and GitOps controller, then stops. It is not a GitOps engine.
The DSL
Each step has exactly one action key, which sets its type. Variables
(${VAR}, ${VAR:-default}, sprig pipelines) and
when: CEL conditions let one spec serve several
environments.
| Verb | What it does | Instead of |
|---|---|---|
helm: |
install or upgrade a chart | helm repo add + helm upgrade --install |
apply: |
apply manifests (inline, file, URL, kustomize), optionally waiting on them | kubectl apply -f/-k (&& kubectl wait) |
delete: |
remove resources by manifest, selector, or Helm release | kubectl delete / helm uninstall |
patch: |
modify a resource in place (strategic/merge/json) | kubectl patch |
wait: |
wait for a condition, a jsonpath value, or deletion | kubectl wait, sleep loops |
rollout: |
restart or wait for workload rollouts | kubectl rollout restart/status |
job: |
run a container to completion in-cluster | one-off kubectl run, shell scripts |
Variables & conditions
--set,
--var-file, or KHOOK_VAR_* env vars.
Missing variables fail validation, all reported at once.KHOOK_SECRET_*
values, and pipeline outputs derived from them, are masked in logs,
plan, and diff.plan and apply see the
same spec.when: is evaluated at
load time, before cluster access. Skipped steps still satisfy their
dependents’ needs.steps:
- name: app-namespace
apply:
manifests:
- inline: |
apiVersion: v1
kind: Namespace
metadata:
name: ${APP_NAME | lower | trunc 63}
- name: argocd
needs: [app-namespace]
when: vars.get("ENABLE_ARGOCD", "false") == "true"
helm:
chart: argo-cd
repo: https://argoproj.github.io/argo-helm
version: ${ARGOCD_CHART_VERSION:-7.7.5}
namespace: argocd
createNamespace: truevs Terraform
kubernetes/helm providers?wait: and rollout: steps, not
time_sleep and local-exec.Quickstart
$ go install github.com/dvrkn/khook/cmd/khook@latest
$ k3d cluster create dev
$ khook apply -f bootstrap.yaml
✓ cilium (helm) 24.108s
✓ all-ready (wait) 9.412sRe-running converges without changes.
Declare the day-zero state of a cluster; khook converges it.