Reference Package schema
options
The typed configuration interface a package exposes to its instances.
options = { replicas = lib.mkOption { type = lib.types.ints.positive; default = 1; description = "Number of application replicas."; };};options is an attribute set of Nix module option declarations. It defines
the values accepted under instances.<namespace>.<name>.config and the
evaluated config passed to the package’s build entries.
The attribute is optional. Its default is { }.
Option declarations
Section titled “Option declarations”Declare options with lib.mkOption. The commonly used fields are:
| Field | Meaning |
|---|---|
type | Validation and merge behavior for the value. |
default | Value used when no module sets the option. |
description | Explanation shown in generated option reference material. |
example | Example value for reference material. |
Nested attribute paths create nested configuration:
options.metrics.enabled = lib.mkOption { type = lib.types.bool; default = true;};Kix also provides reusable declarations such as kix.options.image,
kix.options.ingress, kix.options.monitor, and
kix.options.healthCheck.
Evaluation
Section titled “Evaluation”Kix evaluates four sources together:
- defaults declared in
options; - defaults or fixed values in the package’s top-level
configmodule; - configuration injected by dependent packages; and
- the instance’s
configmodules.
Each of these modules can take clusterConfig as a module argument, for
example config = { clusterConfig, ... }: { zone = lib.mkDefault clusterConfig.clusterDomain; };.
A lib.mkDefault in the package’s own config takes precedence over an
option declaration’s default, while an instance value or dependency
injection takes precedence over that package default. Standard Nix module
types, merge rules, lib.mkForce, and validation still apply.
The resulting values are passed to each build entry as config. Setting an
undeclared option is an evaluation error.
Option contract
Section titled “Option contract”A package option’s default must survive an instance that sets only part of the option. The module system replaces an attribute-set default as a whole as soon as any definition exists, so the option contract refuses three declarations whose defaults an instance would partly discard:
| Rule | Declaration | What an instance loses |
|---|---|---|
| 1 | An untyped option (anything, unspecified, raw, attrs, or attrsOf of one of those) with a non-empty attribute-set default | Every default key once it sets any key, and misspelled keys are accepted |
| 2 | An attrsOf or lazyAttrsOf of any other type with a non-empty default | Every package entry once it sets one key |
| 3 | A submodule whose own default sets a key that the field does not default to the same value | That key once it sets any field |
nullOr, uniq, and unique are looked through, and each branch of an
either or oneOf is checked. A coercedTo whose source type rejects the
default is checked as its final type. An option declared without type
merges as unspecified, so rule 1 applies to it. A default wrapped in
lib.mkDefault, lib.mkOverride, lib.mkOrder, lib.mkMerge, or
lib.mkIf is checked as the value it wraps. The default of an option whose
type takes no attribute set, such as str, int, or listOf, is not
evaluated, so it may read a sibling field that has no default. List
defaults are not checked, because every Nix merge replaces a list.
A declaration the check cannot read also breaks the contract: a default
that throws when evaluated, a default with any other _type, a submodule
whose fields throw when evaluated with { }, a non-empty attribute-set
default on a type none of the rules covers (such as attrTag, or a
coercedTo whose source type accepts the default), an entry in options
that is not an option, and an option nested more than twelve levels deep.
A recursive submodule type stops at the first option that repeats a
declaration on its path, so the depth limit applies only to options
without a declaration position.
The check fails with one bullet per violating option, each naming the file and line of the declaration:
the options of kix/packages/<package> break the option contract (docs: reference/package-schema/options, "Option contract"): - <file>:<line>: option `hpa` has type `anything` and a non-empty default (keys: maxReplicas, minReplicas). ...Where the contract is checked
Section titled “Where the contract is checked”The every-package-evaluates check of kixpkgs checks every package under
kix/packages/ before it evaluates any package in a cluster. Evaluating a
cluster does not check the contract, so a package outside kixpkgs is
checked only by its own flake or its author.
kix.checkPackageOptions runs the check on one package. It calls the
package the way instance evaluation does, at each availability level up to
the package’s meta.availability.max, so a default that depends on
availability is checked at every level the package can run at. A
violation that occurs at some levels only names them. It returns null
when the options pass and throws otherwise:
kix.checkPackageOptions { package = import ./my-package; # Names the package in the error message. who = "./my-package"; # Optional; the phase-1 scope the package is called with. scope = { instanceName = "my-package"; namespaceName = "my-package"; };}From the directory that holds a package, with the kixpkgs flake reference your flake uses:
nix eval --impure --extra-experimental-features 'nix-command flakes pipe-operators' --expr ' let kixpkgs = builtins.getFlake "<kixpkgs flake reference>"; inherit (kixpkgs.inputs.nixpkgs) lib; pkgs = kixpkgs.inputs.nixpkgs.legacyPackages.x86_64-linux; inherit (kixpkgs.lib.forPkgs { inherit pkgs lib; }) kix; in kix.checkPackageOptions { package = import ./my-package; who = "./my-package"; }'A flake built with mkFlake gets one check per package listed in its
packages argument, named option-contract-<name>, so nix flake check
runs the contract:
outputs = inputs: inputs.kixpkgs.lib.mkFlake { inherit inputs; clusters.production = ./clusters/production.nix; packages.my-package = ./packages/my-package;};To check one kixpkgs package from the repository root, run the following.
It prints null when the options pass:
nix eval --impure --extra-experimental-features 'nix-command flakes pipe-operators' --expr ' let nixpkgs = (builtins.getFlake (toString ./.)).inputs.nixpkgs; in (import ./kix/package-eval.nix { inherit (nixpkgs) lib; kix = import ./kix/lib { pkgs = nixpkgs.legacyPackages.x86_64-linux; inherit (nixpkgs) lib; }; }).checkOptionContract "<package>"'Each rule has a remedy that keeps the package’s values:
- Declare the option as a submodule whose fields carry their own defaults.
For container resources, use
kix.options.resources. For a submodule (rule 3), put each value on its field’sdefaultand give the option the default{ }. - For a typed collection (rule 2) whose entries should stay unless
overridden, default the option to
{ }and set each entry in the package’sconfigwithlib.mkDefault. - When an instance should replace the value as a whole, such as a selector
or a program’s configuration file, default the option to
nulland apply the package’s value in the build when it is null.
With the second and third remedies, give the option a defaultText that
names the package’s value, so the generated reference shows it.