Skip to content

Create a local package

Use a local package when a workload belongs to your project rather than the shared package catalogue. A package groups its configuration interface and Kubernetes resources so the cluster can create one or more instances from it.

This guide creates web-package.nix beside cluster.nix. The complete example is available in the how-to/application scenario.

Start with a function that accepts the helpers used by the package. Add package metadata to the returned attribute set:

how-to/application/web-package.nix (L17–L20)
meta = {
version = "1.0.0";
description = "Small nginx application used by the Kix how-to guides";
};

View source on GitHub ↗

The version identifies the package definition. Change it when you make a meaningful package release. The description appears in package inspection and generated reference material.

The package’s build value creates named parts. This example begins with a ConfigMap containing the page nginx will serve:

how-to/application/web-package.nix (L74–L79)
content = scope.mkResource {
apiVersion = "v1";
kind = "ConfigMap";
inherit name;
data."index.html" = "${config.message}\n";
};

View source on GitHub ↗

scope.mkResource adds the instance namespace, Kix metadata, and an out interface. Other parts should refer to self.content.out.name rather than repeating the eventual Kubernetes name.

Add the Deployment:

how-to/application/web-package.nix (L83–L118)
deployment =
{
inherit name;
spec = {
replicas = config.replicas;
selector.matchLabels = scope.selectorLabels;
template.spec.containers = [
{
name = "nginx";
image = "docker.io/library/nginx:1.27-alpine";
imagePullPolicy = "IfNotPresent";
ports = [ (kix.port "http" 80) ];
env = kix.mkEnvVars {
APP_ENV = config.environment;
};
readinessProbe.httpGet = {
path = "/";
port = "http";
};
resources = {
requests = {
cpu = "10m";
memory = "16Mi";
};
limits.memory = "64Mi";
};
}
];
};
}
|> kix.withMounts [ contentMount ]
|> scope.mkDeployment;

View source on GitHub ↗

scope.mkDeployment fills in the standard workload metadata and validates the Deployment shape. The example also uses scope.selectorLabels so the workload and its Service agree on the Pod selector.

Finish with a Service and select the package root:

how-to/application/web-package.nix (L122–L124)
service = self.deployment |> kix.service.fromWorkload { inherit name; } |> scope.mkResource;
root = self.service;

View source on GitHub ↗

The root is the resource that represents the instance to its consumers. A service-shaped package normally uses its Service as the root.

Import the file before calling kix.buildCluster:

how-to/application/cluster.nix (L12–L12)
webPackage = import ./web-package.nix;

View source on GitHub ↗

Assign the imported value to an instance just like a catalogue package:

cluster.nix
instances.apps.web = {
package = webPackage;
};

Kix calls the package function for the instance and supplies scope, lib, and kix. You do not need to call the package function yourself.

Run the cluster checks after adding the package and instance:

Run in kix-examples/
❱ kix check how-to-application
 TOOL         RESULT  DETAILS                                                       
 eval         pass    16 manifests evaluated                                        
 kubeconform  pass    skipped (this validation tool is not yet integrated with Kix) 
 pluto        pass    skipped (this validation tool is not yet integrated with Kix) 
 kyverno      pass    skipped (this validation tool is not yet integrated with Kix) 
 scorecard    pass    0 errors, 11 warnings, 2 info

Then render the manifests and inspect the resources owned by the instance:

Run in kix-examples/
❱ kix build how-to-application Show output
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
  annotations:
    kix.run/identity-hash: gvrlspcfgh9a2qnsx5kqxw9f4l3x52jj
    kix.run/package: _cluster
    kix.run/package-namespace: _cluster
  labels:
    app.kubernetes.io/managed-by: kix
  name: activations.kix.run
spec:
  group: kix.run
  names:
    kind: Activation
    listKind: ActivationList
    plural: activations
    singular: activation
  scope: Cluster
  versions:
  - name: v1alpha1
    schema:
      openAPIV3Schema:
        properties:
          apiVersion:
            type: string
          kind:
            type: string
          metadata:
            type: object
          spec:
            type: object
            x-kubernetes-preserve-unknown-fields: true
          status:
            type: object
            x-kubernetes-preserve-unknown-fields: true
        type: object
    served: true
    storage: true
    subresources:
      status: {}
---
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
  annotations:
    kix.run/identity-hash: pk4hih6jm4yj336rlaqk2qycz2k46xvs
    kix.run/package: _cluster
    kix.run/package-namespace: _cluster
  labels:
    app.kubernetes.io/managed-by: kix
  name: packageinstances.kix.run
spec:
  group: kix.run
  names:
    kind: PackageInstance
    listKind: PackageInstanceList
    plural: packageinstances
    singular: packageinstance
  scope: Namespaced
  versions:
  - name: v1alpha1
    schema:
      openAPIV3Schema:
        properties:
          apiVersion:
            type: string
          kind:
            type: string
          metadata:
            type: object
          spec:
            type: object
            x-kubernetes-preserve-unknown-fields: true
          status:
            type: object
            x-kubernetes-preserve-unknown-fields: true
        type: object
    served: true
    storage: true
    subresources:
      status: {}
---
apiVersion: apps/v1
kind: Deployment
metadata:
  annotations:
    kix.run/depends-on: '8767b7nzgc1x5bpfa9gv71cpk9z8h0iw,asa4ywz8yjjql7grm2rnm774q96ncs3m'
    kix.run/identity-hash: hd2br513fjyfgbl9sf97ank37hqzrmzl
    kix.run/package: preview
    kix.run/package-namespace: how-to-app
  labels:
    app.kubernetes.io/instance: preview
    app.kubernetes.io/managed-by: kix
    app.kubernetes.io/name: preview
  name: preview
  namespace: how-to-app
spec:
  replicas: 1
  selector:
    matchLabels:
      app.kubernetes.io/instance: preview
      app.kubernetes.io/name: preview
  template:
    metadata:
      labels:
        app.kubernetes.io/instance: preview
        app.kubernetes.io/name: preview
    spec:
      containers:
      - env:
        - name: APP_ENV
          value: preview
        image: docker.io/library/nginx:1.27-alpine
        imagePullPolicy: IfNotPresent
        name: nginx
        ports:
        - containerPort: 80
          name: http
          protocol: TCP
        readinessProbe:
          httpGet:
            path: /
            port: http
        resources:
          limits:
            memory: '64Mi'
          requests:
            cpu: '10m'
            memory: '16Mi'
        volumeMounts:
        - mountPath: /usr/share/nginx/html
          name: content
          readOnly: true
      volumes:
      - configMap:
          name: preview
        name: content
---
apiVersion: apps/v1
kind: Deployment
metadata:
  annotations:
    kix.run/depends-on: '4x75h2z7rsj3dvrdg9vzqc8l789xvm4w,8767b7nzgc1x5bpfa9gv71cpk9z8h0iw'
    kix.run/identity-hash: kr7i34wz1ja58c0vf9gizx8161ji0idk
    kix.run/package: production
    kix.run/package-namespace: how-to-app
  labels:
    app.kubernetes.io/instance: production
    app.kubernetes.io/managed-by: kix
    app.kubernetes.io/name: production
  name: production
  namespace: how-to-app
spec:
  replicas: 2
  selector:
    matchLabels:
      app.kubernetes.io/instance: production
      app.kubernetes.io/name: production
  template:
    metadata:
      labels:
        app.kubernetes.io/instance: production
        app.kubernetes.io/name: production
    spec:
      containers:
      - env:
        - name: APP_ENV
          value: production
        image: docker.io/library/nginx:1.27-alpine
        imagePullPolicy: IfNotPresent
        name: nginx
        ports:
        - containerPort: 80
          name: http
          protocol: TCP
        readinessProbe:
          httpGet:
            path: /
            port: http
        resources:
          limits:
            memory: '64Mi'
          requests:
            cpu: '10m'
            memory: '16Mi'
        volumeMounts:
        - mountPath: /usr/share/nginx/html
          name: content
          readOnly: true
      volumes:
      - configMap:
          name: production
        name: content
---
apiVersion: batch/v1
kind: Job
metadata:
  annotations:
    kix.run/depends-on: '8767b7nzgc1x5bpfa9gv71cpk9z8h0iw,cd3l507fvaf0wg5azfza49x6wjsiiynz,dlzbv1nahmar8f7b9jm6rmyvmqczmzdb,requires:jh345cnnffy1bnj4cz5x0hwq6ibxvkkr'
    kix.run/identity-hash: '67bgr7ggiw82bhlc2c3ddxma573vpibj'
    kix.run/package: production
    kix.run/package-namespace: how-to-app
    kix.run/rerun: on-change
  labels:
    app.kubernetes.io/component: health-check
    app.kubernetes.io/instance: production
    app.kubernetes.io/managed-by: kix
    app.kubernetes.io/name: production-health
  name: production-health
  namespace: how-to-app
spec:
  activeDeadlineSeconds: 150
  backoffLimit: 0
  template:
    metadata:
      labels:
        app.kubernetes.io/component: health-check
        app.kubernetes.io/name: production-health
    spec:
      automountServiceAccountToken: false
      containers:
      - command:
        - /bin/sh
        - '-eu'
        - /probe/health-check.sh
        env:
        - name: APP_URL
          value: http://production.how-to-app.svc.cluster.local:80
        - name: RETRY_FOR
          value: '60'
        image: docker.io/curlimages/curl:8.14.1@sha256:9a1ed35addb45476afa911696297f8e115993df459278ed036182dd2cd22b67b
        name: probe
        resources:
          limits:
            memory: '128Mi'
          requests:
            cpu: '50m'
            memory: '64Mi'
        securityContext:
          allowPrivilegeEscalation: false
          capabilities:
            drop:
            - ALL
          readOnlyRootFilesystem: true
          runAsNonRoot: true
        terminationMessagePolicy: FallbackToLogsOnError
        volumeMounts:
        - mountPath: /probe
          name: production-health-script
          readOnly: true
      restartPolicy: Never
      securityContext:
        runAsGroup: 65534
        runAsNonRoot: true
        runAsUser: 65534
        seccompProfile:
          type: RuntimeDefault
      volumes:
      - configMap:
          name: production-health-script
        name: production-health-script
  ttlSecondsAfterFinished: 3600
---
apiVersion: kix.run/v1alpha1
kind: Activation
metadata:
  annotations:
    kix.run/built-via: /nix/store/8knghw2ziwh65bl6x0iyxr4iy83j8l05-k8s-activation-how-to-application
    kix.run/depends-on: '84pfjn0kg74qqvp291b01dzd35ngql6z,avvdnz5yy5602xd1095ga9z40qbfkkwc,jh345cnnffy1bnj4cz5x0hwq6ibxvkkr,requires:gvrlspcfgh9a2qnsx5kqxw9f4l3x52jj'
    kix.run/identity-hash: gb5d6ry45b5ll664ahagxldh9na9z3y5
    kix.run/package: _cluster
    kix.run/package-namespace: _cluster
  labels:
    app.kubernetes.io/managed-by: kix
    kix.run/cluster: how-to-application
  name: how-to-application-gb5d6ry45b5l
spec:
  instances:
    how-to-app:
      preview: preview
      production: production
    kube-system:
      platform-dns: platform-dns
---
apiVersion: kix.run/v1alpha1
kind: PackageInstance
metadata:
  annotations:
    kix.run/depends-on: '8767b7nzgc1x5bpfa9gv71cpk9z8h0iw,91lbws8gjsbxfvr8p7kjx0dc7nzybhqs,requires:pk4hih6jm4yj336rlaqk2qycz2k46xvs'
    kix.run/identity-hash: '84pfjn0kg74qqvp291b01dzd35ngql6z'
    kix.run/package: _cluster
    kix.run/package-namespace: _cluster
    kix.run/root: preview
  labels:
    app.kubernetes.io/instance: preview
    app.kubernetes.io/managed-by: kix
  name: preview
  namespace: how-to-app
spec:
  instanceName: preview
  namespaceName: how-to-app
  version: '1.0.0'
---
apiVersion: kix.run/v1alpha1
kind: PackageInstance
metadata:
  annotations:
    kix.run/depends-on: '67bgr7ggiw82bhlc2c3ddxma573vpibj,8767b7nzgc1x5bpfa9gv71cpk9z8h0iw,cd3l507fvaf0wg5azfza49x6wjsiiynz,requires:pk4hih6jm4yj336rlaqk2qycz2k46xvs'
    kix.run/identity-hash: avvdnz5yy5602xd1095ga9z40qbfkkwc
    kix.run/inverse-deps: Job/production-health
    kix.run/package: _cluster
    kix.run/package-namespace: _cluster
    kix.run/root: production
  labels:
    app.kubernetes.io/instance: production
    app.kubernetes.io/managed-by: kix
  name: production
  namespace: how-to-app
spec:
  instanceName: production
  namespaceName: how-to-app
  version: '1.0.0'
---
apiVersion: kix.run/v1alpha1
kind: PackageInstance
metadata:
  annotations:
    kix.run/depends-on: sj0fv5bs2hnh9zcqdh62ph34l2wm0b1m,requires:pk4hih6jm4yj336rlaqk2qycz2k46xvs
    kix.run/identity-hash: jh345cnnffy1bnj4cz5x0hwq6ibxvkkr
    kix.run/import-apiversion: apps/v1
    kix.run/import-fqdn: kube-dns.kube-system.svc.cluster.local
    kix.run/import-kind: Deployment
    kix.run/import-name: coredns
    kix.run/mode: import
    kix.run/package: _cluster
    kix.run/package-namespace: _cluster
  labels:
    app.kubernetes.io/instance: platform-dns
    app.kubernetes.io/managed-by: kix
  name: platform-dns
  namespace: kube-system
spec:
  instanceName: platform-dns
  mode: import
  namespaceName: kube-system
---
apiVersion: v1
data:
  index.html: |
    Hello from preview
kind: ConfigMap
metadata:
  annotations:
    kix.run/depends-on: '8767b7nzgc1x5bpfa9gv71cpk9z8h0iw'
    kix.run/identity-hash: asa4ywz8yjjql7grm2rnm774q96ncs3m
    kix.run/package: preview
    kix.run/package-namespace: how-to-app
  labels:
    app.kubernetes.io/instance: preview
    app.kubernetes.io/managed-by: kix
  name: preview
  namespace: how-to-app
---
apiVersion: v1
data:
  index.html: |
    Hello from production
kind: ConfigMap
metadata:
  annotations:
    kix.run/depends-on: '8767b7nzgc1x5bpfa9gv71cpk9z8h0iw'
    kix.run/identity-hash: '4x75h2z7rsj3dvrdg9vzqc8l789xvm4w'
    kix.run/package: production
    kix.run/package-namespace: how-to-app
  labels:
    app.kubernetes.io/instance: production
    app.kubernetes.io/managed-by: kix
  name: production
  namespace: how-to-app
---
apiVersion: v1
data:
  health-check.sh: |
    #!/bin/sh
    # region:healthCheckScript
    set -u

    deadline=$(( $(date +%s) + RETRY_FOR ))

    while true; do
      if curl --fail --silent --show-error --max-time 10 "$APP_URL" >/dev/null; then
        echo "ok    GET $APP_URL"
        exit 0
      fi

      echo "FAIL  GET $APP_URL"
      if [ "$(date +%s)" -ge "$deadline" ]; then
        echo "gave up after ${RETRY_FOR}s"
        exit 1
      fi

      sleep 5
    done
    # endregion:healthCheckScript
kind: ConfigMap
metadata:
  annotations:
    kix.run/depends-on: '8767b7nzgc1x5bpfa9gv71cpk9z8h0iw'
    kix.run/identity-hash: dlzbv1nahmar8f7b9jm6rmyvmqczmzdb
    kix.run/package: production
    kix.run/package-namespace: how-to-app
  labels:
    app.kubernetes.io/instance: production
    app.kubernetes.io/managed-by: kix
  name: production-health-script
  namespace: how-to-app
---
apiVersion: v1
kind: Namespace
metadata:
  annotations:
    kix.run/identity-hash: '8767b7nzgc1x5bpfa9gv71cpk9z8h0iw'
    kix.run/package: _cluster
    kix.run/package-namespace: _cluster
  labels:
    app.kubernetes.io/managed-by: kix
    kubernetes.io/metadata.name: how-to-app
  name: how-to-app
---
apiVersion: v1
kind: Namespace
metadata:
  annotations:
    kix.run/identity-hash: sj0fv5bs2hnh9zcqdh62ph34l2wm0b1m
    kix.run/package: _cluster
    kix.run/package-namespace: _cluster
  labels:
    app.kubernetes.io/managed-by: kix
    kubernetes.io/metadata.name: kube-system
  name: kube-system
---
apiVersion: v1
kind: Service
metadata:
  annotations:
    kix.run/depends-on: '8767b7nzgc1x5bpfa9gv71cpk9z8h0iw,hd2br513fjyfgbl9sf97ank37hqzrmzl'
    kix.run/identity-hash: '91lbws8gjsbxfvr8p7kjx0dc7nzybhqs'
    kix.run/package: preview
    kix.run/package-namespace: how-to-app
  labels:
    app.kubernetes.io/instance: preview
    app.kubernetes.io/managed-by: kix
  name: preview
  namespace: how-to-app
spec:
  ports:
  - name: http
    port: 80
    protocol: TCP
  selector:
    app.kubernetes.io/instance: preview
    app.kubernetes.io/name: preview
  type: ClusterIP
---
apiVersion: v1
kind: Service
metadata:
  annotations:
    kix.run/depends-on: '8767b7nzgc1x5bpfa9gv71cpk9z8h0iw,kr7i34wz1ja58c0vf9gizx8161ji0idk'
    kix.run/identity-hash: cd3l507fvaf0wg5azfza49x6wjsiiynz
    kix.run/package: production
    kix.run/package-namespace: how-to-app
  labels:
    app.kubernetes.io/instance: production
    app.kubernetes.io/managed-by: kix
  name: production
  namespace: how-to-app
spec:
  ports:
  - name: http
    port: 80
    protocol: TCP
  selector:
    app.kubernetes.io/instance: production
    app.kubernetes.io/name: production
  type: ClusterIP

The rendered cluster contains the ConfigMap, Deployment, and Service declared by the package, along with Kix’s cluster bookkeeping resources.