One static binary that takes a freshly created cluster from
“API server answers” to
“workloads can be deployed.”
No kubectl, no helm binary, no bash, no sleep 30.
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 (or eksctl, or CAPI) hands you a cluster. ArgoCD takes over once
it’s installed. In between lives everybody’s least favorite artifact: the
bootstrap script — a few hundred lines of
kubectl apply, helm upgrade --install,
sleep 30, and retry loops, duct-taped into a
null_resource and feared by everyone on call.
khook replaces that gap with a declarative spec: a DAG of steps it
validates, plans, and converges — the same way every time.
Why khook
Everything between “cluster exists” and “GitOps has the wheel” — sequenced, wait-heavy, run-once-converge-always.
The Kubernetes and Helm SDKs are embedded. khook never shells out — nothing to install on the runner but khook itself.
Steps declare needs:; khook topologically sorts them and
runs each level in parallel. Cycles are caught before anything touches
the cluster.
Re-running a spec is always safe: Helm release history decides
install-vs-upgrade, applies converge existing resources, deletes treat
“already gone” as success. Run it on every terraform apply.
Per-step timeouts and retries, onError: fail | continue,
and a summary naming exactly what succeeded, failed, or was skipped —
with distinct exit codes for validation vs execution.
khook plan predicts install / upgrade / no-op per step
against the live cluster; --diff renders exact object
changes via server-side dry-run. Reads only — plan never mutates.
khook installs your CNI, secrets tooling, and GitOps controller — then gets out of the way. It is deliberately not a GitOps engine.
The DSL
A spec is a set of steps, each with exactly one action — the key implies
the type, no discriminator field. Variables
(${VAR}, ${VAR:-default}, sprig pipelines) and
when: CEL conditions keep one spec serving many environments.
| Verb | What it does | Instead of |
|---|---|---|
helm: |
install-or-upgrade a chart (history decides) | helm repo add + helm upgrade --install |
apply: |
apply manifests — inline, file, URL, or 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: |
block until a condition or jsonpath holds (or gone) | kubectl wait + sleep-and-pray |
rollout: |
restart / await workload rollouts | kubectl rollout restart/status |
job: |
run a container to completion in-cluster | one-off kubectl run / bash scripts |
Variables & conditions
--set, --var-file, or
KHOOK_VAR_* environment variables; a missing variable is
a validation error, reported all at once.KHOOK_SECRET_* values (and anything derived from them
through pipelines) are redacted from logs, plan, and diff.plan and apply
always see the same spec.when: decides at
load time, before anything touches the cluster; 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?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.412sRun it again — everything converges, nothing breaks. That’s the point.
Declare the day-zero state of your cluster. khook converges it — every time.