Create a committed image pin file
This content is for the v0.1 version. Switch to the latest version for up-to-date documentation.
Use a pin file when an image is selected outside the cluster definition, such as by a CI build or a release decision. The file records the requested tag and the digest it resolved to. Your cluster reads that committed value during evaluation.
This guide assumes your project uses kix.lib.mkFlake and the package exposes
its image as a kix.options.image option.
Create the first pin
Section titled “Create the first pin”Run kix pin set from the project root. A new pin needs a key, repository, and
tag. --init allows Kix to create pins.json and add the key:
❱ kix pin set web --repository docker.io/nginxinc/nginx-unprivileged --tag 1.28-alpine --init Kix asks the registry what the tag resolves to and writes the resulting
digest. If your flake lives in another directory, pass that directory with
--flake; Kix will create the file beside it. You can also choose an explicit
path with --pins-file.
The generated file has this shape:
{ "version": 1, "managedBy": "kix pin", "images": { "web": { "repository": "docker.io/nginxinc/nginx-unprivileged", "tag": "1.28-alpine", "digest": "sha256:...", "promotedBy": "you@workstation", "resolvedAt": "2026-09-09T10:59:45Z" } }}Do not fill in the digest by guessing it. Let kix pin set resolve the tag,
or pass a digest you obtained through your air-gapped promotion process with
--digest.
Load the pin file from the flake
Section titled “Load the pin file from the flake”Name the file once in your mkFlake call:
# Declared once. A cluster that takes a `pins` argument gets this file # already loaded, so no cluster definition names the path. pins = ./pins.json;Any cluster function that declares a pins argument now receives the loaded,
shape-checked pin set.
Use the pin in a cluster
Section titled “Use the pin in a cluster”Pass the complete pin to the package’s image option:
config.image = pins.get "web";The package should declare the option with the shared image schema:
options.image = kix.options.image { repository = "docker.io/nginxinc/nginx-unprivileged"; tag = "1.28-alpine";};Render the container image with kix.image.ref and carry through the pull
policy:
image = kix.image.ref config.image; imagePullPolicy = config.image.pullPolicy;The rendered value is repository:tag@digest. The tag remains readable while
the digest fixes the content Kubernetes will pull.
Verify and commit the result
Section titled “Verify and commit the result”Inspect the file as Kix sees it:
❱ kix pin list --pins-file pins.json
web
docker.io/nginxinc/nginx-unprivileged:1.28-alpine
sha256:7377697a821c131a924a7105fafbe7414db4e9fcc77a6f08f776f33f141ec3f8
promoted by [email protected], 2026-09-09T10:59:45Z Check the file’s schema without contacting the registry:
❱ kix pin check --pins-file pins.json --offline
pins.json
ok web docker.io/nginxinc/nginx-unprivileged:1.28-alpine (format checked offline; registry not checked)
1 pins checked, 0 failed, 0 warned Before deploying, run the online check as well. It verifies that every recorded digest still exists in its registry:
❱ kix pin check Finally, add the pin file and the Nix changes to the same commit:
❱ git add pins.json flake.nix packages/web/default.nix cluster.nix A clean checkout can now render the same image reference. Future promotions use the existing key and need only the new tag:
❱ kix pin set web --tag 1.29-alpine