CDK8s: Generating Kubernetes Manifests With Real Code
YAML sprawl and Helm's templating soup both fail at scale. CDK8s lets you define Kubernetes manifests in TypeScript or Python with types, loops, and abstraction.
- #iac
- #cdk8s
- #kubernetes
- #manifests
- #typescript
- #helm
There are two ways most teams generate Kubernetes manifests, and both hurt at scale. Raw YAML means copy-pasting near-identical Deployments across services with no way to enforce consistency. Helm means templating YAML with Go templates — string interpolation inside whitespace-sensitive YAML, which is exactly as fun as it sounds when an {{ if }} block breaks your indentation.
CDK8s takes a different path: define your manifests in real code — TypeScript, Python, Go, or Java — and let it synthesize plain YAML. You get types, loops, functions, IDE autocomplete, and unit tests, and the output is still vanilla Kubernetes YAML that any cluster accepts. After moving a few services to it, here’s the case for CDK8s and how to use it well.
The core idea: code in, YAML out
CDK8s doesn’t run in your cluster. It’s a synthesizer: you write code, run cdk8s synth, and get a dist/ folder of plain YAML you apply with kubectl or hand to Argo CD. The cluster never knows CDK8s existed.
import { App, Chart } from 'cdk8s';
import { KubeDeployment, KubeService } from './imports/k8s';
import { Construct } from 'constructs';
class WebService extends Chart {
constructor(scope: Construct, id: string, image: string, replicas: number) {
super(scope, id);
const labels = { app: id };
new KubeDeployment(this, 'deployment', {
spec: {
replicas,
selector: { matchLabels: labels },
template: {
metadata: { labels },
spec: {
containers: [{
name: id,
image,
ports: [{ containerPort: 8080 }],
}],
},
},
},
});
new KubeService(this, 'service', {
spec: {
selector: labels,
ports: [{ port: 80, targetPort: { value: 8080 } as any }],
},
});
}
}
const app = new App();
new WebService(app, 'checkout', 'checkout:1.4.0', 3);
new WebService(app, 'payments', 'payments:2.1.0', 5);
app.synth();
Two fully-formed services from one reusable class, no copy-paste. The KubeDeployment and KubeService types are generated from the Kubernetes API schema, so a typo in a field name is a compile error, not a 3am kubectl apply rejection.
Why this beats Helm for complex charts
Helm’s pain is that logic lives inside YAML as text templates. CDK8s inverts it: logic lives in code, and YAML is just the output. The differences that matter day to day:
- Real abstraction. A
WebServiceconstruct encapsulates the Deployment + Service + HPA + PDB pattern once. Every service inherits the same correct structure. In Helm you’d duplicate templates or fight with library charts. - Loops and conditionals are just code. Need a Deployment per region?
for (const region of regions). No{{ range }}gymnastics. - Type safety. Misspell
containerPortand the compiler catches it before synth. Helm finds out when the API server rejects the manifest. - Testable. You can unit-test that your construct always sets resource limits or a
securityContext.
test('every deployment has resource limits', () => {
const app = Testing.app();
const chart = new WebService(app, 'test', 'img:1', 2);
const manifest = Testing.synth(chart);
const dep = manifest.find(o => o.kind === 'Deployment');
expect(dep.spec.template.spec.containers[0].resources.limits).toBeDefined();
});
A unit test that enforces an org-wide policy on every manifest you generate — that’s something neither raw YAML nor Helm gives you cleanly.
Importing CRDs and existing Helm charts
CDK8s isn’t limited to core resources. cdk8s import generates typed constructs from any CRD (Prometheus ServiceMonitor, cert-manager Certificate, your own operators) so they get the same type safety. And you can pull an existing Helm chart in via the Helm construct, so adoption is incremental — wrap what you have, write new things in CDK8s.
# Generate typed constructs for cert-manager CRDs
cdk8s import cert-manager:=https://example.com/cert-manager-crds.yaml
Fitting it into GitOps
CDK8s synthesizes plain YAML, which is perfect for GitOps. The pipeline is: CDK8s code in Git → CI runs cdk8s synth → commit the generated YAML to a manifests repo (or synth in a CI step) → Argo CD or Flux applies it. The cluster reconciles ordinary YAML; the authoring experience is real code. You get the developer ergonomics of a programming language and the operational simplicity of declarative manifests.
Where AI fits
The friction point in CDK8s is the verbosity of the generated Kubernetes types — deeply nested objects with a lot of required fields. I use an assistant to scaffold constructs from a description (“a Deployment with an HPA, a PDB, and a ServiceMonitor”), and to convert an existing Helm template or raw YAML into a typed CDK8s construct. Keep a few Kubernetes manifest prompts for generating constructs, then run cdk8s synth and diff the output to confirm it matches what you expect.
When to choose CDK8s
Reach for CDK8s when you have many similar services that should share structure, when you want to enforce policy in code (every manifest gets limits, probes, a security context), or when your team is already comfortable in TypeScript/Python and Helm’s templating feels like fighting the tool. Stick with plain YAML for a handful of static manifests, and Helm where you’re consuming third-party charts that ship as Helm.
The mental unlock is that Kubernetes manifests are just data, and generating data is something programming languages are vastly better at than text templates. Define the pattern once as a typed, tested construct, synthesize YAML, and apply it the boring way. For more cross-tool IaC, see our Infrastructure as Code category.
Generated CDK8s constructs are assistive, not authoritative. Always cdk8s synth and review the output YAML before applying to any cluster.
Download the Free 500-Prompt DevOps AI Toolkit
500 battle-tested, copy-paste AI prompts engineered by a senior systems engineer — every one with fill-in placeholders and safety/back-out notes. Drop your email and it's yours.
- 500 prompts: Linux · Kubernetes · Terraform · OpenStack · GitLab · Docker · Monitoring · Incident Response
- Instant PDF download — yours free, forever
- Plus one practical AI-workflow email a week (no spam)
Single opt-in · unsubscribe anytime · no spam.