Skip to content
Install the CLI

Tutorial 09

Typed package options

In earlier tutorials, you passed config = { message = "..."; }; to packages.echo-server to customize the HTTP response body.

In this tutorial, you will look at the package author’s side of that contract. Using tutorials/09-typed-options, you will add typed options to the local reverse-proxy package (proxyPath and maxBodySize), use those values inside build, override them in cluster.nix, and see how lib.types catches invalid configuration at evaluation time.

Open tutorials/09-typed-options/reverse-proxy-package.nix. Unlike the tutorial 06 version of the reverse proxy, this package is written as a function { scope, lib, kix }: { ... } so it can use lib.mkOption and lib.types to declare an options section:

tutorials/09-typed-options/reverse-proxy-package.nix
options = {
proxyPath = lib.mkOption {
# `lib.types.strMatching` runs the regex at module-merge time;
# if `proxyPath = "api"` (no leading slash) is set, you get a
# type error from the cluster build, not a nginx 404 at runtime.
type = lib.types.strMatching "^/.*$";
default = "/";
description = "URL path this proxy forwards. Must start with `/`.";
example = "/api/";
};
maxBodySize = lib.mkOption {
# nginx body-size format: number with optional k/K/m/M/g/G suffix.
type = lib.types.strMatching "^[0-9]+[kKmMgG]?$";
default = "1m";
description = ''
Max request body size, in nginx format (e.g. "1m", "100k", "50M").
Sets `client_max_body_size`.
'';
example = "10m";
};
};

View source on GitHub ↗

Each option defines:

  • type: A NixOS module type from lib.types. Here, lib.types.strMatching "^/.*$" requires proxyPath to start with /, and lib.types.strMatching "^[0-9]+[kKmMgG]?$" requires maxBodySize to match nginx’s size syntax (1m, 100k, 50M).
  • default: The value used when a cluster instance omits the option from config.
  • description and example: Human-readable documentation for the option, used in generated package reference pages and evaluation diagnostics.

Why validate proxyPath and maxBodySize with lib.types.strMatching? If someone configures proxyPath = "api" (missing the leading slash) or maxBodySize = "10mb" (invalid mb suffix for nginx), raw templating would generate a broken default.conf that only fails when the nginx container starts in the cluster. With lib.types.strMatching, kix check and kix build catch the mistake immediately on your workstation.

Still in tutorials/09-typed-options/reverse-proxy-package.nix, look at how build receives config and uses config.maxBodySize and config.proxyPath inside the nginx ConfigMap:

tutorials/09-typed-options/reverse-proxy-package.nix
build =
{
self,
scope,
kix,
backend,
config,
...
}:
let
name = scope.instanceName;
in
{
configmap = scope.mkResource {
apiVersion = "v1";
kind = "ConfigMap";
inherit name;
data."default.conf" = ''
server {
listen 80;
client_max_body_size ${config.maxBodySize};
location /healthz {
default_type text/plain;
return 200 'ok\n';
}
location ${config.proxyPath} {
proxy_pass http://${backend.out.fqdn}:${backend.out.port}/;
}
}
'';
};
# ...
};

View source on GitHub ↗

When Kix evaluates an instance of reverse-proxy, it merges the package’s options declarations with the instance’s config = { ... } overrides and passes the validated result into build as config.

Open tutorials/09-typed-options/cluster.nix to see how the gateway instance sets those options:

tutorials/09-typed-options/cluster.nix
gateway = {
package = reverseProxyPackage;
# Both options are typed in the package. Try a
# `proxyPath` that does not start with `/`, or a
# `maxBodySize` such as "1mb" (wrong suffix), and the
# build will fail with a type error.
config = {
proxyPath = "/";
maxBodySize = "10m";
};
deps.backend = ref.tutorial-09.backend;
};

View source on GitHub ↗

Run kix check and kix build on 09-typed-options:

kix-examples/
❱ kix check 09-typed-options
❱ kix build 09-typed-options

In the rendered ConfigMap/gateway manifest, confirm that both options were interpolated into default.conf:

server {
listen 80;
client_max_body_size 10m;
...
location / {
proxy_pass http://backend.tutorial-09.svc.cluster.local:80/;
}
}

Open tutorials/09-typed-options/cluster.nix and change proxyPath to "api/" (without a leading slash):

tutorials/09-typed-options/cluster.nix
gateway = {
package = reverseProxyPackage;
config = {
proxyPath = "api/";
maxBodySize = "10m";
};
deps.backend = ref.tutorial-09.backend;
};

Run kix check 09-typed-options:

kix-examples/
❱ kix check 09-typed-options

Evaluation fails with a type error explaining that "api/" does not match the pattern "^/.*$" required by proxyPath.

Also try adding an unknown option key, such as timeout = 30;, inside gateway.config. Kix rejects unknown options immediately rather than ignoring typos.

Change proxyPath back to "/" and remove any extra test keys before moving on.

You turned hardcoded package strings into a typed configuration contract:

  • packages declare their configuration schema in options using lib.mkOption and lib.types.*
  • lib.types.strMatching, lib.types.ints.positive, lib.types.enum , and other types validate config values at evaluation time
  • build receives the merged, validated config attribute set
  • invalid values or misspelled option names fail kix check and kix build before any manifests reach a cluster

In the next tutorial, you will instantiate the same package multiple times in a single cluster and see how scope keeps each instance’s resources isolated.