All posts
22 March 20243 min read

cdk8s vs Terraform — A Real-World Comparison

I've used both tools in production. They solve overlapping problems differently. Here's when I reach for each, and where the lines blur.

KubernetesTerraformcdk8sIaC

The Question

Both cdk8s and Terraform let you write infrastructure as code. Both can manage Kubernetes resources. So which one should you use?

The honest answer: it depends on where your boundary is between cloud infrastructure and Kubernetes workloads. Here's how I think about it.

cdk8s: When Kubernetes Is Your Domain

cdk8s (Cloud Development Kit for Kubernetes) lets you write Kubernetes manifests in TypeScript, Python, or Go instead of YAML. You compile your code down to standard Kubernetes manifests.

We adopted cdk8s for a microservices migration. The win was immediate: instead of copy-pasting deployment YAML across 12 services and manually keeping them consistent, we had a Service construct that encoded our standards — resource limits, probes, service accounts — by default. Adding a new service meant importing the construct and overriding the fields that differed.

Where cdk8s shines:

  • Large numbers of similar Kubernetes resources
  • Teams that are already comfortable in TypeScript/Python
  • You want real programming constructs (loops, conditionals, inheritance) rather than Helm's Go template DSL
  • Your infra boundary is Kubernetes

Where it falls short:

  • cdk8s doesn't manage AWS RDS, S3, IAM — anything outside the Kubernetes API
  • No state management — the compiled manifests are applied with kubectl apply or your CD tool
  • Ecosystem is smaller than Helm; fewer off-the-shelf charts

Terraform: When Your Boundary Is the Cloud

Terraform owns the cloud layer: VPCs, RDS instances, IAM roles, S3 buckets, EKS clusters, DNS records. It has a state backend that lets it reason about what exists and what needs to change.

With the Kubernetes and Helm providers, you can also manage Kubernetes resources from Terraform — but this gets awkward. Kubernetes has its own reconciliation loop; Terraform's plan/apply model doesn't map cleanly onto Kubernetes controllers. I've seen Terraform state get out of sync with actual cluster state in ways that required manual repair.

Where Terraform shines:

  • Cloud infrastructure (AWS, GCP, Azure) — this is its native domain
  • Multi-cloud or hybrid environments
  • You need drift detection and state reconciliation across cloud resources
  • Team has HCL muscle memory

Where it falls short:

  • Managing Kubernetes application workloads with the k8s provider is awkward
  • HCL's expressiveness limits (no real loops before Terraform 0.13, limited OOP)
  • Large Terraform states become slow and operationally risky

How I Use Both

In practice, I use Terraform for the cloud layer and Helm or cdk8s for the Kubernetes layer, with a clean boundary between them:

Terraform: EKS cluster, node groups, VPC, RDS, IAM, ACM certs
           ↓ outputs cluster endpoint, credentials
Helm/cdk8s: Deployments, Services, Ingresses, CRDs, ConfigMaps

Terraform provisions the infrastructure and outputs the values that Kubernetes workloads need (e.g., the RDS endpoint as a Kubernetes secret). The Kubernetes layer is applied separately.

This separation keeps each tool in its domain, avoids state management hell, and lets the Kubernetes and cloud infrastructure teams move independently.

The Short Answer

  • cdk8s: reaching for it when you have a lot of Kubernetes workload code and want real programming language constructs.
  • Terraform: cloud infrastructure, full stop. Don't manage Kubernetes workloads from it.
  • Helm: still the right choice for third-party Kubernetes applications (cert-manager, prometheus-operator, ingress controllers).

The tools aren't competing — they're complementary at different layers of the stack.