Kubernetes Configuration Management with Kapitan
Managing Kubernetes YAML across development, staging, and production is one of the most common reasons teams adopt Kapitan. This page explains how Kapitan helps you generate, organize, and validate Kubernetes configuration while avoiding duplication and drift.
The problem
As your infrastructure grows, you typically face one or more of these issues:
- Duplication: the same
namespace,replicas, orimageis copy-pasted across dozens of files. - Drift: a value changes in one place but not in another, causing environment inconsistencies.
- Secrets sprawl: sensitive values are hardcoded in YAML, scattered across repositories, or managed with ad-hoc scripts.
- Tool fragmentation: Helm values, Kustomize overlays, Terraform variables, and documentation all contain overlapping data that is never in sync.
Kapitan addresses these problems with an inventory-driven model: you define data once in reusable classes, assign them to targets (environments or services), and let Kapitan compile that data into the files each tool expects.
How Kapitan manages Kubernetes configuration
Kapitan does not replace Kubernetes, kubectl, or your cluster. It is a pre-deployment configuration generator. The workflow looks like this:
- Model your data in the Kapitan inventory (
inventory/classes/andinventory/targets/). - Choose an input type for generating manifests: Jsonnet, Jinja, Kadet, Helm, or Kustomize.
- Run
kapitan compileto render the final YAML or JSON files intocompiled/<target-name>/. - Apply the output to your cluster with
kubectl apply -f compiled/<target-name>/manifests/or via a GitOps pipeline.
Because the inventory is plain YAML, it is easy to read, diff, and review in pull requests. Because compilation is deterministic, you can validate the output before it ever reaches a cluster.
Example: deploying an nginx service across environments
Without Kapitan
You might have:
helm/values-dev.yaml
helm/values-staging.yaml
helm/values-prod.yaml
kustomize/overlays/dev/patch.yaml
kustomize/overlays/staging/patch.yaml
kustomize/overlays/prod/patch.yaml
terraform/variables.tf
Every time you change the image tag or replicas count, you must update multiple files. If you forget one, environments drift.
With Kapitan
You define the common data once:
# inventory/classes/components/nginx.yml
parameters:
nginx:
image: nginx:1.25
replicas: 2
ports:
- 80
You override per environment:
# inventory/classes/environments/production.yml
parameters:
nginx:
replicas: 5
You define the target:
# inventory/targets/production/web.yml
classes:
- components.nginx
- environments.production
parameters:
kapitan:
compile:
- output_path: manifests
input_type: jinja2
input_paths:
- templates/nginx-deployment.yml.j2
The Jinja template can use the inventory directly:
# templates/nginx-deployment.yml.j2
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx
namespace: {{ inventory.parameters.target_name }}
spec:
replicas: {{ inventory.parameters.nginx.replicas }}
template:
spec:
containers:
- name: nginx
image: {{ inventory.parameters.nginx.image }}
ports:
{% for port in inventory.parameters.nginx.ports %}
- containerPort: {{ port }}
{% endfor %}
Running kapitan compile -t production.web produces:
compiled/production/web/manifests/nginx-deployment.yml
The replicas value is 5 because the production class overrides the component default. The image value is inherited from the component class. There is no duplication, and the entire configuration state is visible in one PR.
Choosing an input type for Kubernetes
| Input type | Best for | Kubernetes workflow |
|---|---|---|
| Jsonnet | Structured manifests, libraries, validation | Write reusable Jsonnet libraries that output Deployment, Service, and ConfigMap objects |
| Jinja | Simple templates, documentation, scripts | Write plain YAML templates with loops and conditionals |
| Kadet | Python-based generation, complex logic | Build Kubernetes resources as Python classes and reuse them across components |
| Helm | Existing Helm charts | Render upstream charts with values driven from the Kapitan inventory |
| Kustomize | Patching base manifests | Apply environment-specific patches to manifests generated by another input type |
Many teams use more than one input type in the same project. For example, you might generate base manifests with Kadet and then patch them with Kustomize for environment-specific labels.
Secrets in Kubernetes manifests
Kapitan's References let you embed secrets in manifests without exposing plaintext values in the repository.
parameters:
mysql:
root_password: ?{gpg:targets/${target_name}/mysql/root_password}
At compile time, the reference tag is embedded in the manifest. You can reveal it later with kapitan refs --reveal or use a tool like Tesoro to decrypt it inside the cluster.
Validation
You can validate generated manifests before applying them:
- Use the
kapitan validatecommand with JSON Schema. - Use the
jsonschema()function in Jsonnet to validate inventory structures at compile time. - Pipe compiled output to
kubevalorkubectl apply --dry-run=clientin CI.
GitOps integration
Kapitan fits naturally into GitOps workflows:
- Engineers change inventory classes or templates in a pull request.
- CI runs
kapitan compileand validates the output. - The compiled directory is committed (or rendered on-the-fly by the CD pipeline).
- ArgoCD, Flux, or a custom controller applies the compiled manifests to the cluster.
Because the inventory is declarative YAML, changes are easy to review. Because compilation is reproducible, the CD pipeline can trust the output.
Next steps
- Learn how Kapitan inventory works with targets and classes.
- Explore the Kapitan vs Helm vs Kustomize comparison.
- Try the Kapitan examples or clone the kapitan-reference repository.
- Set up References for managing secrets in your Kubernetes configuration.