What is kform?
kform generates Kubernetes manifests from HCL — the configuration language behind Terraform. Instead of stitching YAML together as text the way Helm’s Go templates do, kform evaluates a real configuration language: resources are structured data with types, expressions, and control flow, and YAML is only what comes out at the end.
Resources are expressed directly in HCL, with locals blocks to deduplicate
shared values, typed variable blocks for inputs, and conditional resources
built on for_each — concepts borrowed directly from Terraform.
Why kform?
The main reason to reach for kform is maintainability.
Templating treats manifests as text with placeholders. That’s fragile: YAML is whitespace-sensitive, so a misplaced indent, or a template expression that emits one extra space or newline, can silently produce invalid or wrong output. kform doesn’t have this problem, because it isn’t templating — HCL has an actual grammar, so expressions, interpolation, and variables are parsed and evaluated properly instead of stitched together as text. Whitespace carries no meaning at all.
Inputs are typed: variable blocks declare the shape values must take, so a
wrong type or a typo’d field is caught when the configuration is evaluated,
not after a broken manifest reaches kubectl apply.
That’s on top of everything else kform inherits from HCL and Terraform — string interpolation, values files equivalent to Helm’s, and more. See the project README for the full feature list.
How do I use kform?
Install the CLI:
go install gitlab.com/fforster/kform/cmd/kform@latest
Here’s a config that declares a typed input variable, a locals block
shared by two resources, and a HorizontalPodAutoscaler that’s only
generated when var.app.replicas is greater than one:
variable "name" {
type = string
default = "my-app"
}
variable "replicas" {
type = number
default = 1
}
locals {
selectorLabels = {
"app.kubernetes.io/name" = var.name
}
labels = merge({
"app.kubernetes.io/managed-by" = "kform"
}, local.selectorLabels)
}
resource "Deployment" {
apiVersion = "apps/v1"
metadata = {
name = var.name
labels = local.labels
}
spec = {
replicas = var.replicas
selector = {
matchLabels = local.selectorLabels
}
template = {
metadata = {
labels = local.labels
}
spec = {
containers = [
{
name = var.name
image = "nginx:latest"
}
]
}
}
}
}
# Only generated when replicas > 1, since for_each on an empty
# collection produces zero resources.
resource "HorizontalPodAutoscaler" {
for_each = var.replicas > 1 ? { (var.name) = { replicas = var.replicas } } : {}
apiVersion = "autoscaling/v2"
metadata = {
name = each.key
labels = local.labels
}
spec = {
scaleTargetRef = {
apiVersion = "apps/v1"
kind = "Deployment"
name = each.key
}
minReplicas = each.value.replicas
maxReplicas = each.value.replicas * 3
}
}
Without specifying any variables, kform template chart.hcl generates just the Deployment by default.
The HorizontalPodAutoscaler is omitted.
By providing the --var 'replicas=3' command line argument, kform also generates the HorizontalPodAutoscaler, with minReplicas: 3 and maxReplicas: 9.
# Output of: kform template config.hcl --var 'replicas=3'
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app.kubernetes.io/managed-by: kform
app.kubernetes.io/name: my-app
name: my-app
spec:
replicas: 3
selector:
matchLabels:
app.kubernetes.io/name: my-app
template:
metadata:
labels:
app.kubernetes.io/managed-by: kform
app.kubernetes.io/name: my-app
spec:
containers:
- image: nginx:latest
name: my-app
---
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
labels:
app.kubernetes.io/managed-by: kform
app.kubernetes.io/name: my-app
name: my-app
spec:
maxReplicas: 9
minReplicas: 3
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: my-app
Contributing
kform is developed in the open on GitLab. Issues and merge requests are welcome.