Skip to content

Keep intentionally empty values

Some Kubernetes values are meaningful precisely because they are empty. emptyDir: {} selects a volume source. An empty label selector matches everything. subresources.status: {} enables a CRD subresource. Kix keeps these, but it also removes the empty leftovers that appear when every field inside a container is unset. This guide shows how to stay on the right side of that line.

A container you write empty renders exactly as written. No wrapper, no special syntax:

spec.template.spec.volumes = [
{
name = "tmp";
emptyDir = { };
}
];

This renders emptyDir: {} in the output. The same holds for lists: ingress = [ ]; renders ingress: []. Pasting a standard Kubernetes manifest into a package works without changes.

A container whose members are all null is treated as unset and omitted, along with any wrappers that end up empty because of it:

resources = {
limits = null;
requests = null;
};
# renders nothing: the container collapses and the field is absent

This is the shape merged, unset options produce, and removing it is what keeps rendered manifests free of resources: {} noise.

The trap sits between those two cases: a helper that builds a presence-meaningful container out of optional arguments. When every argument is null, the result is a container of nulls, and it collapses. The field disappears from the manifest without an error.

Wrap the result in kix.keep to pin it:

source.emptyDir = kix.keep {
medium = config.medium; # may be null
sizeLimit = config.sizeLimit; # may be null
};
# renders emptyDir: {} when both are null,
# emptyDir: { sizeLimit = "1Gi"; } when one is set

Filtering the nulls yourself works too, and is what kix.mount.emptyDir does internally:

source.emptyDir =
{ }
// lib.optionalAttrs (medium != null) { inherit medium; }
// lib.optionalAttrs (sizeLimit != null) { inherit sizeLimit; };

Prefer kix.keep when the container is assembled across several places or merged after construction: the protection travels with the value, so a later merge that introduces fresh nulls cannot collapse it.

Render the cluster and check the field survived:

# in the rendered YAML for the workload:
volumes:
- name: tmp
emptyDir: {}

The volumeHasSource scorecard rule backs this up for the volume case. It is part of the built-in rule set, which runs on every cluster by default, so a volume that would render without a source is reported with a message naming the volume and the fix.

The rule’s severity is error, but a cluster caps every finding at scorecard.maxSeverity, which defaults to warning. Set the cap to error when this finding should stop the build. See Fail the build on scorecard findings.