kdef

Declarative HCL for Kubernetes: typed, loopable, renders plain YAML.

Latest release: v0.7.7

One block, every manifest you need

Declare a deployment. Get a Deployment, Service, Ingress, Certificate, and HPA, rendered as plain Kubernetes YAML you can read, diff, and commit.

api.kdef

deployment "api" {
  namespace = "production"

  container "api" {
    image = image("api")

    port "8080" "http" {
      health = "/health"
    }

    env {
      APP_ENV      = var.environment
      DATABASE_URL = secret("db-credentials", "url")
    }

    resources {
      cpu    = "300m..800m"
      memory = "256M..1G"
    }
  }

  service {
    port "8080" "http" {}
  }

  ingress {
    host = "api.example.com"
    tls  = true
  }

  autoscale {
    min = 2
    max = 10
    cpu = 70
  }
}

Rendered Kubernetes YAML

# Generated by kdef 0.7.5
apiVersion: v1
kind: Service
metadata:
  labels:
    app.kubernetes.io/managed-by: kdef
    app.kubernetes.io/name: api
    kdef.dev/version: 0.7.5
  name: api
  namespace: production
spec:
  ports:
  - name: http
    port: 8080
    protocol: TCP
    targetPort: 8080
  selector:
    app.kubernetes.io/name: api
  type: ClusterIP
---
apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app.kubernetes.io/managed-by: kdef
    app.kubernetes.io/name: api
    kdef.dev/version: 0.7.5
  name: api
  namespace: production
spec:
  replicas: 1
  selector:
    matchLabels:
      app.kubernetes.io/name: api
  template:
    metadata:
      labels:
        app.kubernetes.io/managed-by: kdef
        app.kubernetes.io/name: api
        kdef.dev/version: 0.7.5
    spec:
      containers:
      - env:
        - name: APP_ENV
          value: production
        - name: DATABASE_URL
          valueFrom:
            secretKeyRef:
              key: url
              name: db-credentials
        image: registry.example.com/api:1.4.2
        livenessProbe:
          httpGet:
            path: /health
            port: 8080
        name: api
        ports:
        - containerPort: 8080
          name: http
          protocol: TCP
        resources:
          limits:
            cpu: 800m
            memory: 1G
          requests:
            cpu: 300m
            memory: 256M
---
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  labels:
    app.kubernetes.io/managed-by: kdef
    app.kubernetes.io/name: api
    kdef.dev/version: 0.7.5
  name: api
  namespace: production
spec:
  maxReplicas: 10
  metrics:
  - resource:
      name: cpu
      target:
        averageUtilization: 70
        type: Utilization
    type: Resource
  minReplicas: 2
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: api
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  labels:
    app.kubernetes.io/managed-by: kdef
    app.kubernetes.io/name: api
    kdef.dev/version: 0.7.5
  name: api
  namespace: production
spec:
  ingressClassName: nginx
  rules:
  - host: api.example.com
    http:
      paths:
      - backend:
          service:
            name: api
            port:
              number: 8080
        path: /
        pathType: Prefix
  tls:
  - hosts:
    - api.example.com
    secretName: api-example-com-tls
---
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
  labels:
    app.kubernetes.io/managed-by: kdef
    app.kubernetes.io/name: api
    kdef.dev/version: 0.7.5
  name: api-example-com-tls
  namespace: production
spec:
  dnsNames:
  - api.example.com
  issuerRef:
    kind: ClusterIssuer
    name: letsencrypt-production
  secretName: api-example-com-tls

Why people pick kdef

Typed variables

Strings, numbers, bools, enums, lists. Typos caught at validate-time, not 3AM.

Native loops

One for block → a Deployment per tenant, env, or region. No Helm template gymnastics.

Transparent output

Plain YAML every render. kdef diff shows exactly what will change.

Import existing

kdef import --namespace my-app turns a live cluster into .kdef files.

Sealed secrets

First-class sealedsecret block. Encrypt with kdef seal, commit safely.

Works with GitOps

Flux controller and ArgoCD plugin ship in the box.

kdef sits between Kustomize and Helm

Kustomize

No variables. No loops. Patch files multiply for every environment.

Helm

Go templates over YAML. Opaque, error-prone, needs helm template to see what's going out.

kdef

HCL syntax, typed variables, native loops, renders plain YAML. One block = app + service + ingress + HPA.

Full comparison →

Install

# Go install
go install github.com/gsid-nl/kdef/cmd/kdef@latest

# Or grab a binary from releases
# https://github.com/gsid-nl/kdef/releases

Platform packages (.deb, .rpm, .apk) are on the releases page.

Next steps

  • Quickstart: import a live namespace, render, diff, apply, in five minutes.
  • API with sidecar: real-world deployment with API, nginx, a sealed DB password and ingress.
  • Per-tenant loops: one block, N tenants, driven by a JSON values file.
  • GitOps with Flux: point a KdefRelease at a GitRepository. Done.

Start here

  • Why kdef: the case for a purpose-built Kubernetes deployment language.
  • Install: binaries, Go install, Linux packages, or build from source.
  • Quickstart: import a live namespace, render, diff and apply.

Guides

Reference

  • Block types: every block kdef understands, from root.kdef and deployment to crd.
  • Variables: declarations, types, env, overrides, ingress defaults, environment overrides.
  • Functions: image(), secret(), configmap() and file().
  • Conditionals and loops: ternaries, if blocks and for loops.
  • CLI commands: every command and its flags.
  • Comparison: kdef against Kustomize, Helm, CUE, KCL and Pkl.
  • Release notes: what changed in each release, on GitHub.

GitOps

  • Flux CD: a KdefRelease per GitRepository, rendered and applied on every commit.
  • ArgoCD: kdef as a Config Management Plugin sidecar.

Tooling

  • Editors & LSP: kdef-lsp in VS Code, JetBrains and Neovim, plus the pre-commit hook.