Kustomize’s FieldSpec lets you transform fields in your Kubernetes manifests, but it’s not just for setting values; it’s primarily a way to selectively apply transformations based on field path, enabling powerful conditional logic at build time.

Let’s see FieldSpec in action. Imagine we have a base deployment with a standard image and a specific replicas count.

base/deployment.yaml:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-app
spec:
  replicas: 3
  selector:
    matchLabels:
      app: my-app
  template:
    metadata:
      labels:
        app: my-app
    spec:
      containers:
      - name: main
        image: nginx:latest
        ports:
        - containerPort: 80

Now, we want to create an overlay that, for this specific deployment, sets the image to nginx:1.21.6 but only if the deployment has replicas greater than 2. We also want to ensure that if we do change the image, we don’t accidentally revert the replica count to a default.

overlays/production/kustomization.yaml:

apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- ../../base

patchesStrategicMerge:
- deployment-patch.yaml

overlays/production/deployment-patch.yaml:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-app
spec:
  template:
    spec:
      containers:
      - name: main
        image: nginx:1.21.6
  replicas: 5 # This will be managed by FieldSpec

This deployment-patch.yaml applies a strategic merge. If we were to just apply this, replicas would become 5 and the image would be updated. However, we want to control the replicas update more granularly. This is where FieldSpec comes in.

We can modify overlays/production/kustomization.yaml to include fieldSpecs.

overlays/production/kustomization.yaml (with fieldSpecs):

apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- ../../base

patchesStrategicMerge:
- deployment-patch.yaml

fieldSpecs:
  - path: spec/replicas
    createIfNotExists: true
    # This ensures that if spec/replicas is greater than 2, it's preserved.
    # If it's 2 or less, Kustomize will use the value from the patch (5).
    # This is effectively a conditional override.
    # Note: Kustomize doesn't have direct "greater than" logic here.
    # The effect is achieved by how it applies the patch.
    # If the base has replicas > 2, and the patch also sets replicas,
    # Kustomize's strategic merge will pick the *last* value seen for replicas.
    # By using fieldSpec, we tell Kustomize to *not* apply the patch's replica value
    # if the original (base) value meets a certain implicit condition.
    # The actual mechanism is that if a field is *not* present in the patch,
    # and fieldSpec is set to createIfNotExists, it will be created.
    # If it *is* present in the patch, and fieldSpec doesn't prevent it, it's applied.
    # The "conditional" aspect comes from how we *construct* our patches.
    # For this specific example, let's rethink the goal:
    # We want to set image to nginx:1.21.6 IF replicas > 2.
    # If replicas <= 2, keep original image.
    # This is better handled by separate patches or conditional application logic outside fieldSpec.

    # Let's re-evaluate FieldSpec's true power: selective application and transformation.
    # It's not for complex *conditional logic* based on field values themselves.
    # It's for *how Kustomize operates on specific fields*.

    # A better example for FieldSpec:
    # Ensure 'spec.template.spec.containers[name=main].image' is always set
    # to 'nginx:1.21.6' IF the deployment has replicas > 2.
    # And we want to ensure the replica count itself is set to 5.

    # The core problem with the above attempt is that FieldSpec itself doesn't evaluate the *value* of spec/replicas to decide whether to apply a patch.
    # FieldSpec controls *how* Kustomize treats a field *during the merge/patching process*.

    # Let's use FieldSpec to ensure that the 'spec/replicas' field *always* gets set to 5, regardless of the base value.
    # And we'll use a separate patch for the image, conditionally applied.

    # Revised strategy:
    # 1. Base deployment with replicas: 3, image: nginx:latest
    # 2. Overlay:
    #    a. Patch to set replicas to 5.
    #    b. Patch to set image to nginx:1.21.6.
    #    c. Use FieldSpec to ensure the replica count is applied, and maybe the image.

    # The most common use of FieldSpec is to ensure a field is created if it doesn't exist, or to specify how it should be handled during transformations.
    # It's less about "if X then Y" on the *value* of a field, and more about "always operate on field Z this way".

    # Let's simplify and show a direct FieldSpec application.
    # Suppose we have a base with a Service that has an annotation, and we want to *add* another annotation, but *only* if the Service has a specific label.
    # Kustomize's FieldSpec doesn't directly support this kind of value-based condition for its own operations.
    # This is where you'd typically use multiple patches, or external tools.

    # However, FieldSpec *is* powerful for ensuring fields are set, especially in complex nested structures.
    # Let's go back to a simpler, more direct use case for FieldSpec.

    # Imagine we have a base where 'spec.template.spec.containers[name=main].ports' might be missing or have only one port.
    # We want to *ensure* there's a port 80, and if there are multiple ports, we want to add a specific one.
    # This still feels like it's pushing FieldSpec beyond its intended scope of simple field manipulation.

    # The true power of FieldSpec is in its declarative control over how Kustomize interacts with specific fields during its patching and generation phases.
    # It's about *how* Kustomize treats a field, not *under what condition* based on the field's value.

    # Let's demonstrate a common FieldSpec pattern: ensuring a field exists and is set.
    # We'll use it to ensure the `name` field for the container is always present, even if the base manifest somehow omitted it.

  - path: spec/template/spec/containers/name
    createIfNotExists: true
    # This field spec ensures that if a container definition is encountered
    # and it doesn't have a 'name' field, Kustomize will add one.
    # In practice, Kubernetes requires container names, so this is more illustrative
    # of the `createIfNotExists` capability.

  # Another example: ensuring a specific label is present.
  - path: metadata/labels/app
    createIfNotExists: true
    # This would ensure the 'app' label is always present on the Deployment.

    # The key takeaway is that FieldSpec is about *how* Kustomize manipulates fields during the build process,
    # enabling fine-grained control over which fields are targeted and how they are processed (created, modified, etc.).
    # It's not a general-purpose conditional logic engine for manifest values.

The FieldSpec directive in Kustomize is a powerful mechanism for controlling how Kustomize applies transformations to specific fields within your Kubernetes manifests. It’s not a general-purpose templating engine or a complex conditional logic system based on the values of your manifest fields. Instead, it dictates Kustomize’s behavior towards those fields during the build process, particularly when patches or generators are involved.

Think of FieldSpec as a set of instructions for Kustomize itself, telling it how to treat a particular field path. The most common and impactful use cases revolve around ensuring fields exist (createIfNotExists: true) and specifying how Kustomize should handle them during strategic merges or JSON merges.

Let’s illustrate a scenario where FieldSpec shines: ensuring specific fields are always present and correctly populated, even if your base manifests are incomplete or vary.

Suppose you have a base ConfigMap that might not have a specific key, or a Deployment that might not define a resource limit. You want to enforce these.

base/configmap.yaml:

apiVersion: v1
kind: ConfigMap
metadata:
  name: app-config
data:
  ANOTHER_KEY: "some_value"

base/deployment.yaml:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-app
spec:
  replicas: 1
  template:
    spec:
      containers:
      - name: main
        image: busybox:latest
        command: ["sleep", "3600"]
        # No resource limits defined here

Now, let’s create an overlay to enforce these. We’ll use fieldSpecs to ensure data.MY_KEY exists in the ConfigMap and spec.template.spec.containers[name=main].resources.limits.cpu exists in the Deployment.

overlays/staging/kustomization.yaml:

apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- ../../base

# Ensure MY_KEY is present in app-config ConfigMap.
# If it's not in the base, and we don't provide it via a patch,
# this will cause Kustomize to error unless we tell it to create it.
# However, FieldSpec with createIfNotExists is more about *how* Kustomize
# handles a field *if it's being modified or generated*.
# The primary way to *add* a new field is via a patch.
# FieldSpec then tells Kustomize how to treat it.

# Let's refine: FieldSpec is most useful when combined with generators or patches
# that *might* create or modify fields. It ensures Kustomize handles them as intended.

# Example: We want to ensure the 'app' label is present on all resources.
# This is a common use case for FieldSpec.

commonLabels:
  app: my-app

# This ensures that if 'metadata/labels/app' is being set (e.g., by commonLabels),
# Kustomize will correctly merge it. If it doesn't exist, it will be created.
# The `createIfNotExists: true` tells Kustomize to add the field if it's missing
# and is being targeted by a Kustomize operation (like applying commonLabels).
# This is crucial for ensuring consistency across your deployments.

# The subtle point: FieldSpec doesn't *inject* values on its own.
# It modifies Kustomize's *behavior* when processing other directives like `patches`, `generators`, or `commonLabels`.

# Let's use FieldSpec to ensure that if we apply a patch to 'spec/replicas',
# Kustomize treats it correctly, especially if the field might not exist in the base.

patches:
- path: deployment-patch.yaml
  target:
    kind: Deployment
    name: my-app

# FieldSpec can guide how Kustomize applies the patch.
# For example, if the patch *removes* a field, and FieldSpec says createIfNotExists,
# it might prevent the removal or cause an error depending on the exact scenario.
# However, the most straightforward use is ensuring fields are handled when *added* or *modified*.

# A more concrete FieldSpec usage:
# Suppose we are using a generator that produces a ConfigMap, and we want to ensure
# a specific key is always present in its 'data' field.
# Or, we have a patch that adds a container and we want to ensure its name is set.

# Let's focus on the `createIfNotExists` aspect for a common field.
# We want to ensure that `spec/template/spec/containers[name=main]/image` is set.
# If a patch is modifying this field, and it doesn't exist in the base,
# FieldSpec with `createIfNotExists: true` ensures Kustomize adds it.

fieldSpecs:
  - path: spec/template/spec/containers/image
    createIfNotExists: true
    # This ensures that if a patch or generator attempts to set the container image
    # and the container definition exists but lacks an image field, Kustomize will
    # create that 'image' field within the container spec.

  - path: metadata/annotations/description
    createIfNotExists: true
    # This ensures that if we have a patch or generator that adds an annotation named 'description'
    # to the metadata, and the 'annotations' map itself doesn't exist, Kustomize will create it.

# The actual *setting* of values for these fields still comes from other Kustomize directives
# like `patches`, `configMapGenerator`, `secretGenerator`, `commonLabels`, etc.
# FieldSpec governs the *process* of Kustomize operating on these paths.

The core idea behind FieldSpec is to tell Kustomize how to treat specific fields when applying transformations. It’s not about defining conditional logic based on the values of those fields (e.g., "if replicas > 2, then change image"). Instead, it’s about controlling Kustomize’s behavior when it encounters a field path during patching, generation, or other modification processes.

The most common and practical applications of FieldSpec involve:

  1. Ensuring Fields Exist (createIfNotExists: true): This is invaluable when you’re applying patches or using generators, and you want to guarantee that a specific field will be created if it’s missing in the base manifest. Kustomize will add the field (and any necessary parent maps/arrays) if the operation targets it and it doesn’t exist.

  2. Guiding Patch Application: While FieldSpec doesn’t dictate which patch to apply based on field values, it can influence how a patch is applied to a specific field. For instance, if a patch is meant to add a field, createIfNotExists: true ensures Kustomize doesn’t fail if the parent structure is missing.

Let’s demonstrate a concrete scenario using createIfNotExists with a ConfigMapGenerator and a patch.

Imagine you have a base ConfigMap and a deployment. You want to ensure a specific key (MY_CONFIG_KEY) is always present in the ConfigMap’s data field, and you want to ensure the Deployment has a specific annotation.

base/configmap.yaml:

apiVersion: v1
kind: ConfigMap
metadata:
  name: app-config
data:
  EXISTING_KEY: "some_value"

base/deployment.yaml:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-app
spec:
  replicas: 1
  template:
    spec:
      containers:
      - name: main
        image: nginx:latest

Now, let’s use overlays/production/kustomization.yaml to enforce these:

overlays/production/kustomization.yaml:

apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- ../../base

# We'll use a ConfigMapGenerator to add the missing key.
# FieldSpec helps ensure Kustomize handles the 'data' field correctly.
configMapGenerator:
- name: app-config-generated
  literals:
  - MY_CONFIG_KEY=enforced_value
  # This generator creates a NEW ConfigMap. To modify the base, we'd use patches.
  # Let's use a patch for the ConfigMap instead to modify the base.

# Patch to add MY_CONFIG_KEY to the existing app-config.
patches:
- path: configmap-patch.yaml
  target:
    kind: ConfigMap
    name: app-config

# Patch to add a specific annotation to the deployment.
- path: deployment-patch.yaml
  target:
    kind: Deployment
    name: my-app

fieldSpecs:
  # This ensures that if 'data/MY_CONFIG_KEY' is being added via a patch
  # (or generated), and the 'data' map doesn't exist in the base ConfigMap,
  # Kustomize will create the 'data' map first.
  - path: data/MY_CONFIG_KEY
    createIfNotExists: true

  # This ensures that if 'metadata/annotations/description' is being added
  # via a patch, and the 'annotations' map doesn't exist in the base Deployment,
  # Kustomize will create the 'annotations' map first.
  - path: metadata/annotations/description
    createIfNotExists: true

  # This is a more advanced use: If you were to patch the container image,
  # and the container definition itself was missing an 'image' field,
  # this would ensure it gets created.
  - path: spec/template/spec/containers/image
    createIfNotExists: true

overlays/production/configmap-patch.yaml:

apiVersion: v1
kind: ConfigMap
metadata:
  name: app-config
data:
  MY_CONFIG_KEY: "enforced_value" # This key is added

overlays/production/deployment-patch.yaml:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-app
  annotations:
    description: "This is the production deployment" # This annotation is added

When you run kustomize build overlays/production, Kustomize will:

  1. Load the base resources.
  2. Apply configmap-patch.yaml. Because of fieldSpecs.data/MY_CONFIG_KEY with createIfNotExists: true, if the base ConfigMap had no data field, Kustomize would create it before adding MY_CONFIG_KEY. In this case, data exists, so it just adds the key.
  3. Apply deployment-patch.yaml. Because of fieldSpecs.metadata/annotations/description with createIfNotExists: true, if the base Deployment had no metadata.annotations map, Kustomize would create it before adding the description annotation. It then merges the annotation.
  4. The spec/template/spec/containers/image fieldSpec would ensure that if a patch attempted to set the image and the container spec was missing an image field, it would be created.

The most surprising thing about FieldSpec is that it doesn’t directly modify manifest content; it modifies Kustomize’s behavior when processing specific field paths, acting as a crucial control layer for transformations.

This mechanism is essential for building robust Kustomize configurations where you need to guarantee the presence of certain fields, especially when integrating with various patching strategies or generators that might operate on incomplete base manifests.

The next concept you’ll likely encounter is how to conditionally include or exclude entire resources based on environment variables or other external factors, often managed through Kustomize’s helmCharts or by orchestrating multiple Kustomize builds.

Want structured learning?

Take the full Kustomize course →