CT vs Kustomize
At a glance
Section titled “At a glance”| Feature | CT | Kustomize |
|---|---|---|
| Language | TypeScript | YAML patches + overlays |
| IDE support | Full IntelliSense, autocomplete, jump-to-definition | YAML schema only |
| Type safety | Compile-time — generated .d.ts | None — patches fail silently on wrong paths |
| Values / config | Typed JS objects (values.json) | configMapGenerator, secretGenerator, vars |
| Logic | Real functions, loops, conditionals | Strategic merge patches, JSON patches |
| Reuse | ES module imports from any Git URL | bases / components references |
| Composition | Function calls + imports | Directory-based overlays |
| Learning curve | Know TypeScript? You’re done. | Patch mechanics, overlay hierarchy, transformer order |
| Output | ct template → YAML | kustomize build → YAML |
Patching vs generating
Section titled “Patching vs generating”Kustomize starts from base YAML and patches it. CT generates YAML from code. This is a fundamental difference.
import { deployment, service, hpa } from "github.com/cloudticon/k8s@master";
const env = Values.env; // "dev" | "staging" | "prod"
deployment({ name: `${env === "dev" ? "dev-" : ""}api`, namespace: Values.namespace, replicas: Values.replicas, image: Values.image,});
service({ name: "api", namespace: Values.namespace, port: 80, targetPort: 8080,});
if (env === "prod") { hpa({ name: "api", minReplicas: Values.replicas, maxReplicas: Values.maxReplicas, });}# deployment.yaml, service.yaml, kustomization.yaml# overlays/# dev/ → kustomization.yaml, replicas-patch.yaml# staging/ → kustomization.yaml# prod/ → kustomization.yaml, hpa.yaml
# overlays/dev/kustomization.yamlapiVersion: kustomize.config.k8s.io/v1beta1kind: Kustomizationresources: - ../../basepatches: - path: replicas-patch.yamlnamePrefix: dev-namespace: development---# overlays/dev/replicas-patch.yamlapiVersion: apps/v1kind: Deploymentmetadata: name: apispec: replicas: 1One file, real if, real variables. No directory trees, no patch files, no mental model of “what is the base + what patches apply in what order.”
Environment handling
Section titled “Environment handling”ct template . \ --values values-dev.json \ --namespace dev
ct template . \ --values values-staging.json \ --namespace staging
ct template . \ --values values-prod.json \ --namespace prod# dev/# kustomization.yaml# staging/# kustomization.yaml# prod/# kustomization.yaml## Each environment is a directory with its own# kustomization.yaml that references the base# and adds patches. Shared changes must be# replicated or extracted into components.Same template, different values. No directory duplication.
Conditional resources
Section titled “Conditional resources”if (Values.hpa?.enabled) { hpa({ name: "api", minReplicas: Values.hpa.min, maxReplicas: Values.hpa.max, });}apiVersion: kustomize.config.k8s.io/v1alpha1kind: Componentresources: - hpa.yaml---# overlays/prod/kustomization.yamlcomponents: - ../../components/hpa
# Components can only be included or excluded# per overlay — no runtime conditionals.Labels and naming
Section titled “Labels and naming”const labels = { "app.kubernetes.io/name": "myapp", "app.kubernetes.io/managed-by": "ct",};
deployment({ name: "myapp-api", labels, // ...});commonLabels: app: myapp team: platformnamePrefix: myapp-namespace: production
# Labels are injected everywhere including# selectors, which can cause issues with# immutable fields. namePrefix/nameSuffix# apply blindly to all resource names.You decide what gets labeled and how. No transformer surprises.
Debugging
Section titled “Debugging”Kustomize
Section titled “Kustomize”When a patch doesn’t apply correctly, it fails silently or produces unexpected output. You debug by running kustomize build and diffing the output with expectations. Strategic merge patches can be especially confusing with arrays.
TypeScript errors show at write time in the editor. Runtime errors include stack traces. You can console.log intermediate values. Output is deterministic — same values always produce same YAML.
When Kustomize still makes sense
Section titled “When Kustomize still makes sense”- You have existing YAML manifests and want minimal changes (just patches on top).
- Your team doesn’t know TypeScript but is comfortable with YAML.
- You use kubectl apply -k in a simple CI pipeline and don’t need more.
- You consume upstream YAML manifests (e.g. from a vendor) and just need to adjust namespace/labels.
CT can coexist — render CT manifests alongside Kustomize overlays in the same GitOps repo.
Migration path
Section titled “Migration path”- Keep existing Kustomize bases running.
- Start new services with
ct init. - Extract shared patterns into CT factory packages.
- When a Kustomize overlay becomes too complex (3+ patch files, nested components), rewrite it as CT.