# kimage > kimage declares immutable, versioned container images as Kubernetes resources, > manages their production and verified publication to a retained cluster-local > registry, and resolves workload image selectors to digest-pinned pulls. Apache > 2.0. Alpha. Public Cargo packages: kimage-core and kimage-control, version 0.4.0. **When to use it.** Use kimage when inputs are already provisioned in Kubernetes, when images should remain available independently of source/build cache, or when a cluster needs local build and pull workflows without an external registry. Application authors declare an Image, exact version, and source; the controller handles attempts and publication. There is no per-image BuildRun or output registry configuration. The registry and node-local serving are included in the installation; administrators provision storage and installation generates service credentials. Workloads request Image identity and accepted versions, e.g. platform://apps/service:^1.2.0, without knowing a registry hostname, repository path, or digest. Admission pins new Pods; existing Pods do not roll automatically. kpack also declares Images and exposes registry digest output; declarative Images and ARM64 alone are not differentiators. kimage combines a retained version catalog, builder, included registry, and placement-aware workload resolution. See the [kpack comparison](https://kimage.dev/docs/#kpack). Shipwright provides configurable build strategies; Tekton handles general task pipelines. The current kimage builder is narrower than those general workflows. **Current boundaries.** Builds support Containerfile, strategy Direct, builderRef default, FROM scratch, provisioned local files, and network-none RUN. No remote base images, remote ADD/context URLs, external syntax frontends, remote cache, general base provisioning, signing/attestations, HA, arbitrary storage/schema migration, or automatic retained-artifact GC. Administrator-managed builder isolation is not a hostile tenant security claim. **Install (0.4.0).** Rust 1.99.0+, administrator kubeconfig, Ready Linux amd64/arm64 nodes, persistent storage and the supported Pod user-namespace runtime: ```sh cargo install kimage-control --version 0.4.0 --locked --bin kimage --bin kimage-install kimage install --application-namespace apps ``` No flags means current kubeconfig application namespace, kimage-system system namespace, installation kimage, 1Gi retained PVC, default StorageClass, port 17701. The CLI fetches only the exact release's small SHA-verified component-images.json; kubelet pulls complete digest-pinned native OCI indexes from ghcr.io/inferal-oss/kimage (admission,builder,exporter,gateway,input,operator,proxy,registry). No binary/image uploads through ConfigMaps, installer Pods or port-forwarding. No required carrier, issuer scripts or source checkout. Registry/admission/builder credentials are generated in process, with separate CAs and read/publish identities; CA private keys are not deployed. Reapply identical arguments to resume, preserving Secrets and administrator build policy. kimage-install is a compatible alias. The Cargo crate named kimage is unrelated. Prebuilt installers: GitHub releases. Gateway/proxy serving version remains 0.3.0 separately from Cargo 0.4.0. **Helm alternative.** Helm 3.14+ with live cluster access: ```sh helm repo add kimage https://kimage.dev/charts helm repo update kubectl create namespace apps helm install kimage kimage/kimage --version 0.4.0 \ --namespace kimage-system --create-namespace \ --set applicationNamespace=apps --wait --wait-for-jobs ``` Published charts contain the same eight native index pins. Helm discovers live Nodes; offline template requires full Node objects via nodes. It does not create the application namespace. Values: scope namespace|cluster; applicationNamespace (default default for namespace scope); excludedNamespaces list; runtimeProfile inferal|k3s; persistence.storageClass/size(default1Gi)/existingClaim; registryNode; port(default17701); workerNodes arch-to-node map; buildPolicy(empty preserves existing or defaults OnDemand, can Eager); images.repository and images.digests. The operator manages shared CRDs; Helm does not own/delete them. **Runtime prerequisites.** Default inferal profile: RKE2 v1.36.5+rke2r1 / containerd 2.3.4-k3s1. Tested k3s profile: K3s v1.36.5+k3s1 / containerd 2.3.4-k3s1.36; add --runtime-profile k3s or Helm --set runtimeProfile=k3s. Untested exact versions are refused. Generic kind/k3d is not automatically supported. User namespaces, idmapped volumes, kernel/filesystem support, CNI policies and storage matter. BuildKit 0.33.0 / builtin frontend1.27.0 workers use hostUsers:false, native snapshotter, no host/runtime mounts or SA token, bounded nested namespace capabilities and unconfined seccomp/AppArmor inside their Pod. This is administrator-managed isolation, not a hostile-tenant guarantee. **Delivery and mirrors.** GHCR supplies system components. The included retained cluster-local registry stores application images, whose publication needs no external registry configuration. Nodes need HTTPS access to GHCR or an approved mirror. Mirror all eight complete multi-platform indexes preserving digests; change only repository in an independently verified local catalog and pass --image-catalog /path/component-images.json. Helm: set images.repository and retain published digest pins. Nodes must already trust/reach the mirror; kimage does not alter trust stores or import images through the API. Catalog alone is not enough for disconnected use. Optional KIMAGE_GITHUB_TOKEN applies only to fixed GitHub metadata requests, never asset downloads. Catalog size/asset SHA, roles, versions, API revision and both native platforms are checked. **Minimal complete build example.** Assumes namespace apps and default builder are already configured. On native Linux/amd64 or Linux/arm64 with Rust and a musl linker, create a small program in hello.rs: `fn main() { println!("Hello from kimage!"); loop { std::thread::park_timeout(std::time::Duration::from_secs(3600)); } }`. ```sh case "$(uname -m)" in x86_64) ARCH=amd64; TARGET=x86_64-unknown-linux-musl ;; aarch64) ARCH=arm64; TARGET=aarch64-unknown-linux-musl ;; *) echo "Use a supported Linux build machine"; exit 1 ;; esac rustup target add "$TARGET" mkdir -p .local/tutorial rustc --edition=2024 --target "$TARGET" \ -C opt-level=z -C strip=symbols -C panic=abort hello.rs -o ".local/tutorial/hello-$ARCH" # On a mixed cluster, collect both architecture binaries here before this step. kubectl -n apps create configmap hello-context --from-file=.local/tutorial \ --dry-run=client -o yaml | kubectl apply -f - ``` On macOS use a matching native Linux machine or build container for that executable; a native macOS binary cannot run inside the scratch image. Save as image.yaml: ```yaml apiVersion: kimage.dev/v1alpha1 kind: Image metadata: name: hello namespace: apps spec: version: "1.0.0" source: build: format: Containerfile execution: {strategy: Direct, builderRef: default} cache: {mode: Local} definition: inline: | FROM scratch ARG TARGETARCH COPY hello-${TARGETARCH} /hello ENTRYPOINT ["/hello"] inputs: contextPath: /workspace/context volumes: - name: context configMap: name: hello-context defaultMode: 365 volumeMounts: - {name: context, mountPath: /workspace/context, readOnly: true} ``` Mode 365 is decimal for octal 0555. Apply the Image and Deployment, wait for verified publication, and run the downloadable Deployment (or use the selector in your own Pod template): ```sh kubectl apply -f image.yaml # Automatic Images remain AwaitingDemand until a workload requests them. kubectl apply -f https://kimage.dev/examples/hello/deployment.yaml kubectl -n apps wait --for=condition=Ready image/hello --timeout=10m kubectl -n apps get imagereleases kubectl -n apps rollout status deployment/hello --timeout=5m kubectl -n apps logs deployment/hello ``` The workload's image field is quoted YAML: ```yaml containers: - name: hello image: "platform://apps/hello:^1.0.0" imagePullPolicy: IfNotPresent ``` Admission leaves workload templates symbolic and pins actual CREATE Pods, including init containers, to 127.0.0.1://@sha256:, with IfNotPresent and eligible-node constraints. Kubelet uses its normal runtime and node-local proxy. Changing a range/channel affects newly created Pods, not existing Pods; use exact versions for a uniform rollout. Unsupported image-update/ephemeral-container paths are rejected. Create replacements through the workload controller. **Workload compatibility.** Deployments, StatefulSets, DaemonSets, ReplicaSets, ReplicationControllers, and Jobs use spec.template.spec.containers[].image (or initContainers[].image). CronJobs use spec.jobTemplate.spec.template.spec.containers[].image (or initContainers). Regular containers, init containers, and native restartPolicy: Always init sidecars all resolve through actual Pod CREATE admission. Custom controllers work when their schema/validation accepts platform:// and they copy the selector unchanged into a Pod image field in the configured application namespace. The controller may run elsewhere; the Image and Pod must share the application scope. Controllers that pre-parse OCI references, fetch image metadata before creating Pods, or rewrite selectors require integration. No arbitrary CRD field scanning is performed. Image strings in ConfigMaps, env/args, Containerfiles, image-backed volumes, static Pods, or ephemeral-container updates are not supported selectors in 0.3.0. Edit workload templates for ordinary rollouts; publishing versions or moving channels does not itself restart workloads. Full reference: https://kimage.dev/docs/#workload-compatibility **Platforms and build policy (0.3.0).** Omitted spec.platform/spec.platforms means native automatic selection. Default policy is OnDemand: without a consumer, automatic Images remain AwaitingDemand and no worker build starts. Administrator ConfigMap -config in the system namespace has data.configuration.json set to {"schema":1,"buildPolicy":"OnDemand"}; change buildPolicy to "Eager" to build all available supported platforms before consumers exist. The operator watches changes; installation preserves this config. Admission evaluates actual Pod nodeName, nodeSelector and required nodeAffinity against eligible Ready nodes with approved local proxies. Only amd64 placement builds/uses amd64; only arm64 placement builds/uses arm64. If both remain possible, build both and pin a fully verified OCI index, whose native child kubelet selects. One eligible architecture needs one variant. Preferred affinity does not narrow the platform set. All managed containers' platform constraints are intersected. Unsupported/contradictory placement fails; there is no automatic build emulation. Admission preserves required placement and restricts scheduling to verified native platforms and eligible nodes. Explicit spec.platform: {os: linux, architecture: arm64} requests one native build. Alternatively use spec.platforms as a list of native platform objects, omitting platform, to explicitly request a set. These are direct build requests, even under OnDemand. Available native architectures bound builds. Version identity is Image UID + version + platform; the aggregate index has its own immutable key. Native refs appear in status.platformReleases; status.releaseRef points at the available native or aggregate release. Adding arm64 cannot overwrite published amd64 bytes. Aggregates require matching private ordinary/definition captures and object/PVC identities across variants; changed sources require a new version. A real unresolved Pod CREATE records bounded ImageDemand for the current Image UID/generation/version and requested platforms, then fails with ImageBuildPending. Workload controllers retry while the operator produces the image. Direct Pod apply may need a retry after Image Ready. Server dry-run records no demand. Do not wait for an automatic Image before applying its Deployment. Demand accumulates within a generation; stale UIDs/generations and arbitrary historical build requests fail. The CLI and Helm discover one native worker per available architecture and a read proxy per eligible node. Component indexes select native manifests through kubelet, independent of laptop architecture. Reapply after node additions. The original registry node/UID stays pinned to protect retained storage; replacement needs explicit recovery. There is no QEMU emulation. Copied executable inputs must match the requested architecture. The hello example uses ARG TARGETARCH and COPY hello-${TARGETARCH}; provide hello-amd64 and hello-arm64 for a mixed cluster. A homogeneous cluster needs only its matching binary. kimage does not translate source binaries. Compile these on matching native Linux build machines, then populate the input ConfigMap directory before applying workloads. **Installation scope.** Default --scope namespace targets the current kubeconfig namespace, or explicit --application-namespace apps. --scope cluster omits application-namespace, manages ordinary namespaces including future ones, and accepts repeated --exclude-namespace NAME flags. The system namespace and kube-system/kube-public/kube-node-lease are always excluded from application admission/reconciliation. Overlaps with other kimage installations are refused; scope is checkpoint-bound and not changed by a rerun. Both scopes require cluster administrator setup for shared CRDs/webhooks. Namespace mode uses application Roles; cluster mode has broader application RBAC plus runtime scope filters. Images, inputs, destination credentials and selectors stay in the consuming namespace: platform://other-ns/image is denied even in cluster mode. Each namespace may define its own same-named Image. Optional exporter follows this scope. Namespace routing is not hostile-tenant registry confidentiality. Native Kubernetes E2E tests both installation scopes on amd64 and arm64, real builds/pulls and built-in/custom controllers, default/on-demand/eager policy, dry-run, RBAC and namespace boundaries, exclusions and retained PVC on uninstall. The release workflow requires the matrix to pass before crates/GitHub publication. **Selector rules.** `platform://apps/hello:1.2.4` selects that exact release; `:^1.2.0` selects the highest compatible published release below 2.0.0; `:~1.2.0` stays in the 1.2 patch series; `:stable` reads the explicit mapping `spec.channels: {stable: "1.2.4"}`. A channel does not produce that version. Colon ranges start with ^, ~, >, <, =, or *. Compound ranges such as `platform://apps/hello:>=1.2.0, <2.0.0` work in quoted YAML. Numeric-leading colon selectors must be complete exact versions: :1.2 and :1.2.* are rejected. The readable query `platform://apps/hello?version=^1.2.0` and the older encoded query `?version=%5E1.2.0` both work, decoded once. Colon selectors use literal characters. Do not combine a colon selector with a query. Prereleases need an explicitly matching prerelease requirement. No implicit latest, version build metadata, or cross-namespace consumption. The application webhook is fail-closed; its outage can block other Pod creation in scope too. Direct internal registry references and opt-in label bypasses are not supported consumption paths. **Image and input contracts.** Image/ImageRelease use kimage.dev/v1alpha1. Choose exactly one source.build or source.ociArchive. An Image UID/version is immutable after publication; differing output is VersionConflict, so request a new exact version. Image recreation gets a new UID/repository. Published releases survive source/Image/cache removal. ConfigMap/Secret inputs freeze UID and resourceVersion per attempt. Mounted definitions use definition.fromFile.path; ordinary definitions live under /workspace/inputs, secret definitions under /run/kimage-build-secrets, independently of COPY contextPath. Mounts must be read-only and non-overlapping. Traversal and symlink escapes are rejected. PVC inputs need inputs.producerHandoff:true and stopped producers; this is not an atomic filesystem snapshot. Use buildSecrets to bind declared Secret files to RUN --mount=type=secret; keep them outside ordinary context. Secret values do not automatically invalidate cache: use a non-sensitive cache.invalidationToken when needed. Local cache is disposable; Disabled requests a no-cache solve. Cancellation fences the dedicated worker and clears its cache. OCI archive sources require a same-namespace claimName, relative path, producerHandoff:true, and expected root {mediaType,digest,size}. Root digest is not the archive-file checksum. Files must be readable by UID 65532. Complete original OCI bytes, platform, compressed layer digests, and DiffIDs are verified. Native amd64/arm64 manifests, nested indexes and complete multi-platform indexes are supported; zstd, descriptor URLs/embedded data, and subject/referrer graphs are unsupported. `kimage-install schemas` prints full CRD JSON without changing the cluster. **Operations.** Use the original manager. CLI install reapplication resumes the same version; it refuses implicit changed component resources. Helm upgrade/rollback uses pinned release history; preserve storage/schema compatibility and never downgrade shared CRDs. 0.4.0 is the first image-pull chart, with no automatic adoption of older bootstrap installations. Retain older installers/bundles for legacy recovery. Reapply after adding nodes. System components can restart by normal external pulls independently of the included registry. Running application containers survive registry downtime; new uncached starts may wait/fail. kimage uninstall --namespace kimage-system --installation kimage, or helm uninstall kimage --namespace kimage-system --wait, preserves PVCs, CRDs, Images and releases. Never remove retained namespaces/backends/finalizers as normal cleanup. Back up whole registry backend plus release UIDs, roots and schema coordination state. No automatic retained-artifact GC or purge. Generated leaves last ten years; reapply preserves them and does not perform automatic renewal/CA rotation. **Optional export.** Enable at CLI install with repeated --export-origin https://registry.example/ flags, or Helm exporter.approvedOrigins list. Empty disables the separate exporter. Only explicit HTTPS origins are approved. Image spec.exports contains named registry destinations with repository (authority/path), explicit tags, credentialsSecretRef or explicit unauthenticated:true, optional caSecretRef with ca.crt, and overwritePolicy:Never. Credential Secret type is kubernetes.io/dockerconfigjson, with auths matching the exact authority/port. Basic auth or anonymous only; no Bearer exchange/helpers. Original retained graph bytes are copied and remotely verified without a rebuild. Existing same-digest tags are idempotent; differing tags are TagConflict. Other writers require registry-side immutability. Export status/retries are separate from internal Ready. Target removal/Image deletion never deletes remote content. **Diagnostics and trust.** Read Image status.conditions/observedGeneration, operation/releaseRef/build, separate status.exports, and normal Pod events. Operator/admission expose /metrics on health listeners; proxy 0.3.0 exposes it on its canonical-authority loopback listener, inspectable by API forwarding. Stage counters/durations cover build/publication/pull without identity labels. Check catalog/node sample completeness and timestamps before using gauges. Admission records bounded ImageDemand objects for real missing-release CREATEs; publishers and readers have distinct credentials. Eligible node administrators are trusted readers; namespace routing is not hostile-tenant image confidentiality. Native clients keep TLS verification on. Rust tests run on macOS/arm64 and native Linux/amd64 and Linux/arm64 musl. Separate two-node acceptance harnesses cover lifecycle/recovery/binary rollback/export and remain historical evidence; native amd64/arm64 namespace/cluster install E2E is now a release gate; historical live bundles predate the latest selector/CLI metadata. ## Reference - [Handbook, Markdown](https://kimage.dev/docs/index.md): Full tutorial, installation commands, field reference, operations, troubleshooting, and trust model. - [Hello Image](https://kimage.dev/examples/hello/image.yaml): Complete build declaration used above. - [Hello Deployment](https://kimage.dev/examples/hello/deployment.yaml): Complete restricted workload using a readable range. - [Hello program](https://kimage.dev/examples/hello/main.rs): Static Linux tutorial executable source. ## Optional - [Source and issues](https://github.com/inferal-oss/kimage): Apache 2.0 repository, engineering contracts, release procedure, acceptance receipts, and contribution workflow.