Skip to content

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, or image is 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:

  1. Model your data in the Kapitan inventory (inventory/classes/ and inventory/targets/).
  2. Choose an input type for generating manifests: Jsonnet, Jinja, Kadet, Helm, or Kustomize.
  3. Run kapitan compile to render the final YAML or JSON files into compiled/<target-name>/.
  4. 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 validate command with JSON Schema.
  • Use the jsonschema() function in Jsonnet to validate inventory structures at compile time.
  • Pipe compiled output to kubeval or kubectl apply --dry-run=client in CI.

GitOps integration

Kapitan fits naturally into GitOps workflows:

  1. Engineers change inventory classes or templates in a pull request.
  2. CI runs kapitan compile and validates the output.
  3. The compiled directory is committed (or rendered on-the-fly by the CD pipeline).
  4. 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