Reference / Clusters and instances
Instances and composition
Once you have a cluster module, you populate it by declaring package instances under instances.<namespace>.<name>. Reach for this page when you are adding an application or database to a cluster, passing configuration values to a package, wiring a service to a dependency in another namespace, or sharing common configuration across staging and production environments.
Instances
Section titled “Instances”Each entry under instances.<namespace>.<name> places a managed package instance or an external resource marker (kix.mkImport) into <namespace> with the instance name <name>.
1{ packages, ref, ... }:2{3 instances.apps.web = {4 package = packages.web;5 config = {6 replicas = 3;7 ingress.host = "web.example.com";8 };9 deps.database = ref.data.postgres;10 };11}Summary of instance fields
Section titled “Summary of instance fields”| Field | Type | Default | Merge behavior across modules | Purpose |
|---|---|---|---|---|
package | raw function or attrset | required | single definition (conflict on duplicate) | Package function, evaluated package attribute set, or kix.mkImport marker. |
config | deferredModule | { } | NixOS module merge (lib.mkDefault, lib.mkForce) | Values evaluated against the package’s options. |
deps | attrsOf anything | { } | collision on same key is an error | Explicit dependency bindings (ref.<ns>.<inst>, list of refs, or direct values) keyed by build argument name. |
aliases | nullOr (listOf str) | null | list merge | Dependency-resolution names for this instance. A list replaces meta.defaultAliases. |
optional | bool | false | boolean merge | Keeps the instance dormant unless an active instance transitively depends on it. |
availability | null | "none" | "pod" | "node" | "zone" | null | single definition | Overrides cluster.availability.default for this instance. |
partsOverlays | listOf raw | [ ] | lists concatenate in module order | Post-build functions (scope: self: base: attrset) that modify, add, or remove rendered parts. |
labelStyle | "kix" | "helm" | "kix" | single definition | Selector and metadata label convention: "kix" (app.kubernetes.io/*) or "helm" (app and release). |
selectorLabels | nullOr (attrsOf str) | null | attribute set merge | Explicit spec.selector.matchLabels override for adopting workloads with immutable live selectors. |
extraDeps | listOf raw | [ ] | lists concatenate | Additional deployment-ordering dependencies (ref.<ns>.<inst> or resource values) on a kix.mkImport instance’s marker. |
platformIntents | attrsOf anything | { } | attribute set merge | Network connectivity intents for a kix.mkImport instance’s pre-installed pods. |
version | nullOr str | null | single definition | Overrides meta.version on the instance’s PackageInstance record. |
package, labelStyle, selectorLabels, extraDeps, platformIntents, and version
Section titled “package, labelStyle, selectorLabels, extraDeps, platformIntents, and version”package: Required for every instance. Takes a package function ({ scope, lib, kix, ... }: { ... }), an inline package attribute set ({ meta?; options?; build = ...; }), or akix.mkImportmarker. Do not redefinepackagewhen writing an override module for an instance declared in another module; set onlyconfig,deps,aliases, orpartsOverlays.labelStyleandselectorLabels: Kubernetes forbids changingspec.selector.matchLabelson an existing Deployment or StatefulSet. When adopting a live workload that was installed with Helm (appandreleaselabels), setlabelStyle = "helm". When the live workload uses custom selector keys, setselectorLabelsto the exact label map from the cluster.extraDepsandplatformIntents: Valid only onkix.mkImportinstances. SettingextraDepson a managed instance fails evaluation. Each entry inextraDepsis a Kix resource orref.<namespace>.<instance>; when the import is active,refentries must point to active instances (anextraDepsreference does not activate a dormantoptionalinstance).platformIntentsuses the same schema asnetworkPolicy.platformIntentsand attaches the generated policies as dependencies of the import marker.version: Overrides the version string written onto thePackageInstanceresource. Without it, the record carries the package’smeta.version, or theout.versionit builds (stackgres-clusterandcnpg-clusterreport the PostgreSQL version this way), or no version at all. Dependency compatibility checks (meta.compatibleWith) still check the package’smeta.version, or itsout.versionwhenmeta.versionis unset.
config
Section titled “config”instances.<namespace>.<name>.config sets values for the options declared in the package’s options block.
1instances.apps.web = {2 package = packages.web;3 config = {4 replicas = 3;5 ingress.host = "web.example.com";6 };7};Module merging and priorities
Section titled “Module merging and priorities”Because config has type deferredModule, multiple cluster modules can contribute configuration to the same instance. Kix evaluates four layers together using lib.evalModules:
| Priority (lower number wins) | Source | Notes |
|---|---|---|
50 (lib.mkForce) | Any module using lib.mkForce | Overrides normal definitions and defaults. |
100 (normal definition) | Instance config and dependency config injections (deps.<arg>.config) | Conflicting plain definitions fail evaluation with file attribution. |
1000 (lib.mkDefault) | Instance config or dependency injection using lib.mkDefault | Wins over package-level defaults. |
1250 (packageDefaultPriority) | lib.mkDefault inside the package’s own config block | Replaces option defaults (1500) while yielding to any instance or injected value. |
1500 (option default) | default in the package’s options declarations | Base fallback value. |
An instance config block can also be written as a module function taking { config, clusterConfig, lib, ... }:
1instances.apps.web.config = { config, clusterConfig, lib, ... }: {2 ingress.host = lib.mkDefault "web.${clusterConfig.cluster.domain}";3};Locking fields against dependency injection (kix.mkLocked)
Section titled “Locking fields against dependency injection (kix.mkLocked)”A package can inject configuration into one of its dependencies via deps.<arg>.config (for example, a database consumer injecting required PostgreSQL extensions into the database cluster). To prevent any consumer from injecting into a top-level option on an instance, wrap the value in kix.mkLocked:
1instances.data.postgres.config.extensions = kix.mkLocked [ "pg_stat_statements" ];If a consumer attempts to inject extensions into data/postgres, evaluation fails and names the consumer that tried to inject the locked field.
instances.<namespace>.<name>.deps overrides automatic dependency resolution for specific build arguments of the package.
1{ packages, ref, ... }:2{3 instances.apps.web = {4 package = packages.web;5 deps = {6 database = ref.data.postgres-primary;7 cache = ref.apps.valkey;8 };9 };10}Wiring instances with ref
Section titled “Wiring instances with ref”Every cluster module receives ref in its module arguments. Writing ref.<namespace>.<instance> points a dependency argument directly at that instance:
- Single instance:
deps.database = ref.data.postgres;resolvesdatabaseto the package view ofinstances.data.postgres. - List of instances:
deps.registryLogins = [ ref.apps.operator-logins ref.apps.extra-logins ];resolves the argument to a list of instance handles in the given order. Mixingrefpointers with non-refvalues in the same list fails evaluation. - Full dependency checks: A
ref-wired dependency receives the same validation and wiring as an auto-resolved dependency:meta.scope = "namespace"boundaries are enforced,meta.compatibleWithversion constraints are checked,deps.<arg>.configinjections are forwarded to the target instance, and pointingrefat anoptional = trueinstance activates it. - Namespace rule: Kix fills a package argument from the consumer’s own namespace. It uses an instance in another namespace only when the cluster definition names it with
ref, or when the provider’s package serves the whole cluster (by settingmeta.scope = "cluster", claiming a role inmeta.roles, or declaring acluster:entry inmeta.conflictsWith). Stand-ins created withkix.mkImportalso serve the whole cluster unless they setscope = "namespace"; when several imports match one name, select one withref. An instance that Kix creates fromavailablePackagesor enables viaoptional = trueto satisfy a dependency also binds from any namespace, because the cluster definition chose where it lives. When the only match for a required argument is in another namespace and does not serve the whole cluster, evaluation fails and the error prints thedepsline to add. When several instances in other namespaces answer the name, the error lists adepsline for each. An optional argument (x ? null) in that situation takes its default, and Kix prints a warning that names the instance and thedepsline to add.
Direct values and null
Section titled “Direct values and null”- Any value that is not a
ref(or a list ofrefs) is passed as-is to the build argument without package view wrapping, compatibility checks, or config injection. - Setting
deps.<arg> = null;behaves the same as leavingdeps.<arg>unset: Kix falls back to automatic resolution or the build argument’s default value. - Explicitly wiring
deps.<arg>suppressesavailablePackagesauto-instantiation for that argument.
aliases
Section titled “aliases”instances.<namespace>.<name>.aliases sets the dependency-resolution aliases for an instance.
1instances.apps.valkey-cache = {2 package = packages.valkey;3 aliases = [ "cache" "valkey" ];4};- The instance’s own
<name>(valkey-cacheabove) and any roles in the package’smeta.rolesare always registered as resolution keys. - When
aliasesis unset (the default), Kix registers the package’smeta.defaultAliases. - When
aliasesis set, it replacesmeta.defaultAliases. Include any default alias the instance should still answer.aliases = [ ]gives the instance no aliases, so it answers only its instance name and the package’smeta.roles.
When multiple instances in a cluster provide the same service (such as two PostgreSQL clusters or two ingress controllers), prefer wiring deps.<arg> = ref.<namespace>.<instance> on the consumer rather than inventing per-instance aliases.
optional
Section titled “optional”Setting optional = true keeps a declared instance dormant unless an active instance transitively requires it.
1instances.data.clickhouse = {2 package = packages.clickhouse-cluster;3 optional = true;4 config.shards = 2;5};When an optional instance activates
Section titled “When an optional instance activates”Kix starts from all non-optional instances (optional = false) and walks the dependency graph to find all transitively required instances. An optional instance becomes active when an active instance reaches it through:
- a required build argument (one with no
? nulldefault) that matches the optional instance’s name,aliases,meta.defaultAliases, ormeta.roles; or - an explicit
deps.<arg> = ref.<namespace>.<instance>(or an entry in adepsref list).
An optional build argument ({ prometheus ? null, ... }) never activates a dormant optional instance, and a dormant optional instance or import never answers an optional build argument (the argument receives null unless an active instance matches). Note that extraDeps or a ref in out on a kix.mkImport instance also does not activate an optional instance.
When activated, the instance is stamped with kix.run/optional-activated: "true" on its PackageInstance record and deployed normally. When dormant, it emits no manifests.
availability
Section titled “availability”instances.<namespace>.<name>.availability overrides cluster.availability.default for a single instance.
1instances.apps.preview = {2 package = packages.web;3 availability = "none";4};| Property | Value |
|---|---|
| Type | null | "none" | "pod" | "node" | "zone" |
| Default | null (inherits cluster.availability.default) |
| Maximum allowed | cluster.availability.substrate |
An instance can request a higher or lower availability level than cluster.availability.default, as long as it does not exceed cluster.availability.substrate. If the package declares meta.availability.max (or a workload builder declares availability.max) below the requested level, Kix caps the effective level at that ceiling and emits a warning.
partsOverlays
Section titled “partsOverlays”instances.<namespace>.<name>.partsOverlays transforms, adds, or removes resources in an instance’s evaluated parts tree without modifying the package definition.
1instances.apps.web = {2 package = packages.web;3 partsOverlays = [4 (scope: self: base: {5 # Modify an existing resource part:6 deployment = base.deployment // {7 spec = lib.recursiveUpdate base.deployment.spec {8 template.spec.priorityClassName = self.highPriority.out.name;9 };10 };11
12 # Add a new resource part:13 highPriority = scope.mkClusterResource {14 apiVersion = "scheduling.k8s.io/v1";15 kind = "PriorityClass";16 name = "web-high-priority";17 extra.value = 1000;18 };19 })20 ];21};Evaluation order and arguments
Section titled “Evaluation order and arguments”Kix evaluates an instance’s parts inside a single lib.fix fixed point in this order:
- All
buildentries in the package run and merge (duplicate part names acrossbuildentries are an error). - Cluster-wide
globalPartsOverlaysrun in order. - The instance’s
partsOverlaysrun in order.
Each overlay function has signature scope: self: base: attrset and its return value is merged onto base with //:
scope: the instance’s resource builder scope.self: the final fixed point after all overlays have run. Readself.<part>.out.*when referencing another part so dependencies are tracked.base: the parts attribute set produced before the current overlay ran. Readbase.<part>when wrapping or modifying an existing part so you do not create an infinite recursion onself.<part>.- Setting a non-
rootpart tonullremoves it from the emitted manifests (rootitself must remain a valid resource).
Multi-environment stamping and fragments
Section titled “Multi-environment stamping and fragments”kix.mkEnv
Section titled “kix.mkEnv”kix.mkEnv stamps a reusable set of instances into environment-prefixed namespaces (<envName>-<setName>).
1kix.mkEnv envName envConfig instanceSets| Argument | Type | Meaning |
|---|---|---|
envName | str | Environment prefix prepended to each instance set key ("${envName}-${setName}"). |
envConfig.config | attrs (optional, default { }) | Environment-wide parameters passed to each set function as env.config. |
envConfig.overrides | attrs (optional, default { }) | Per-set, per-instance overrides (overrides.<setName>.<instName>.{ config?, parts? }). |
instanceSets | attrsOf ({ ref, env } -> attrsOf instanceDef) | Functions returning instance definitions for each logical namespace suffix <setName>. |
1let2 appSet = { env, ... }: {3 api = {4 package = packages.api;5 config = {6 replicas = env.config.replicas;7 ingress.host = "api.${env.name}.example.com";8 };9 };10 };11in12{13 instances =14 (kix.mkEnv "staging" {15 config.replicas = 1;16 } { app = appSet; })17 // (kix.mkEnv "prod" {18 config.replicas = 3;19 overrides.app.api.config.replicas = 5;20 } { app = appSet; });21}Key behaviors of kix.mkEnv:
- The example above produces
instances."staging-app".apiandinstances."prod-app".api. - Because dependency resolution prefers instances in the same namespace, services stamped into
"staging-app"resolve dependencies in"staging-app"first. overrides.<setName>.<instName>.configis shallow-merged (//) onto the instance’sconfigattribute set. If you need deep module merging for nested options, define an additional module targetinginstances."<envName>-<setName>".<instName>.configinstead.- All environments stamped into one cluster share a single
Activationrecord and deploy together. For environments that must deploy independently, build separate cluster definitions that import a shared base module.
kix.mergeFragments
Section titled “kix.mergeFragments”kix.mergeFragments combines cluster fragments authored by different teams into a single namespaces and instances configuration while rejecting collisions.
1kix.mergeFragments {2 fragments = [3 (import ./teams/checkout.nix)4 (import ./teams/search.nix)5 ];6 namespacePolicies = {7 checkout.podSecurity = "restricted";8 search.podSecurity = "restricted";9 };10}| Field | Type | Default | Behavior |
|---|---|---|---|
fragments | listOf { namespaces?, instances? } | required | Each fragment’s instances may be an attribute set or a function args: attrset. Defining the same instances.<namespace>.<name> in more than one fragment throws an evaluation error. |
namespacePolicies | attrsOf attrs | { } | Deep-merged with the fragments’ namespaces definitions. Any conflicting leaf attribute across fragments or namespacePolicies throws an evaluation error. |