Why kdef

The case for a purpose-built Kubernetes deployment language.

You have two options for templating Kubernetes manifests, and neither feels right.

Kustomize has no variables and no loops. You end up maintaining a tree of base/ and overlays/, duplicating blocks of YAML, and writing strategic-merge patches that only make sense if you can hold the whole tree in your head.

Helm has variables and loops, but it does them with Go templates wrapped around YAML. The result is a language where a missing space or a forgotten toYaml | nindent 4 produces an invalid manifest at deploy time. You can't read a Helm chart. You run helm template and read the output.

kdef is what sits between them.

What kdef is

  • A language: HCL syntax. Typed variables. Native for loops. Conditionals that aren't strings.
  • A compiler: kdef render produces plain Kubernetes YAML. Nothing hidden. kdef diff shows exactly what will change in the cluster.
  • A deployment tool: kdef apply does server-side apply. kdef import turns a live namespace into .kdef files.

kdef is purpose-built for Kubernetes: a deployment block generates a Deployment, Service, Ingress, Certificate, and HPA. You write your app in one block instead of five.

What kdef gives you

Typed variables

variable "environment" {
  type    = "enum[staging, production]"
  default = "staging"
}

variable "replicas" {
  type    = "number"
  default = 1
}

Typo in the value? Caught at kdef validate, not by your paging on-call.

Loops that look like loops

for "tenant" "var.tenants" {
  deployment "booking" {
    name = "booking-${tenant.name}"

    container "booking" {
      image = image("booking")
      env { TENANT_ID = tenant.name }
    }

    ingress {
      host = tenant.domain
      tls  = true
    }
  }
}

No range blocks, no nindent, no escaping. Give it a JSON values file and you get one Deployment + Service + Ingress per tenant.

Environment overrides without duplication

# environments/production.kdef
use_vars {
  environment = "production"
  image_tag   = "v2.4.1"
}

override "app" "api" {
  scale { replicas = 3 }
}

One file per environment. No overlays/ trees.

Sealed secrets that aren't magic

sealedsecret "db-credentials" {
  data = {
    DATABASE_URL = "AgBy3i4OJSWK+PiTySYZZA9rO..."
  }
}

kdef seal --secret db-credentials --key DATABASE_URL --value "$PLAINTEXT" outputs the encrypted blob. Paste it in. Commit it. Safe.

Import what you already have

kdef import --namespace my-app --output-dir k8s/

Turns every Deployment, DaemonSet, StatefulSet, Service, Ingress, CronJob, ConfigMap and PersistentVolumeClaim in the namespace into idiomatic .kdef files. Secrets stay in the cluster; env vars that read them become secret() references. Migrate incrementally.

What kdef isn't

  • It isn't a general-purpose config language. If you need CUE's constraint system or KCL's type system, use those.
  • It isn't a drop-in Helm replacement for every chart on Artifact Hub: if you pull charts from vendors, you'll keep using Helm for those.
  • It isn't trying to be fancy. HCL, Go, plain YAML output. That's the stack.

Ready?

  • Install: binary, Go install, or Linux packages
  • Quickstart: import a live namespace, render, diff, apply
  • Comparison: kdef vs Kustomize, Helm, CUE, KCL, Pkl