Skip to main content

timoni

Use when deploying applications to Kubernetes with Timoni. Covers installing and upgrading module instances from OCI registries, composing multi-app deployments with bundles, injecting values from clusters or CI with runtimes, targeting multiple clusters, and authoring, testing, signing and publishing modules with CUE.

Jump to install

Source facts

Repository
stefanprodan/timoni
Last source activity
August 30, 2026 at 22:08
Detected SKILL.md language
English
Stars
2,015
Forks
88

Install options

The review-first prompt is selected by default. You can switch to a direct command or download a local copy.

Review the source files

Read SKILL.md and any companion files shown by SkillsMP before deciding whether to install.

Showing SKILL.md

SKILL.md
Source instructions · Read-only preview
name
timoni
description
Use when deploying applications to Kubernetes with Timoni. Covers installing and upgrading module instances from OCI registries, composing multi-app deployments with bundles, injecting values from clusters or CI with runtimes, targeting multiple clusters, and authoring, testing, signing and publishing modules with CUE.
license
Apache-2.0
metadata
{"author":"Stefan Prodan","homepage":"https://timoni.sh","source":"https://github.com/stefanprodan/timoni","version":"0.3.0"}
# Timoni Timoni is a package manager for Kubernetes powered by CUE and inspired by Helm. An app is packaged as a **module** (CUE templates plus a typed config schema) and distributed as an OCI artifact next to the app container images. A module deployed to a cluster is an **instance**. A **bundle** declares a set of instances deployed together, and a **runtime** declares the target clusters and the values read from them at apply time. Helm mapping: chart = module, umbrella chart = bundle, release = instance. Full documentation index: https://timoni.sh/llms.txt ## When to use - Install, upgrade, diff or uninstall an app on Kubernetes from an OCI module. - Deploy several apps as one unit with a `bundle.cue` file. - Vary a bundle per environment or cluster, reading values from Secrets, ConfigMaps, custom resources or CI environment variables. - Deploy the same bundle to a fleet of clusters in one command. - Author a module: define the config schema, template Kubernetes objects, vendor CRD schemas, add test jobs and health checks, sign and publish it. Do not manage resources owned by a Timoni instance with `helm`, `kustomize` or `kubectl apply`. Timoni owns them through server-side apply and prunes what is no longer rendered. With Flux, Timoni is a templating engine only (`timoni build` / `bundle build` output pushed as manifests): lifecycle management, runtime value injection, bundling and multi-cluster delivery do not apply once Flux takes over. ## Quick reference Every CLI command supports `--help` and prints usage examples along with the available flags. Run `timoni <command> --help` for details beyond the tables below. ### Instances | Task | Command | |------|---------| | Install or upgrade | `timoni -n <ns> apply <name> oci://<repo> -v <version> -f values.cue` | | Preview as a server-side diff | `timoni -n <ns> apply <name> oci://<repo> -v <version> --diff` | | Recreate objects with immutable-field changes | `... apply ... --force` | | Take over an instance owned by a bundle | `... apply ... --overwrite-ownership` | | Uninstall | `timoni -n <ns> delete <name>` | | List instances | `timoni list -A` | | Status and readiness | `timoni -n <ns> status <name>` | | Show module or managed resources | `timoni -n <ns> inspect module\|resources <name>` | | Show stored values | `timoni -n <ns> inspect values <name>` | | Render manifests, no cluster needed | `timoni build <name> oci://<repo> -v <version> -f values.cue [--mask-secrets]` | ### Bundles and runtimes | Task | Command | |------|---------| | Validate | `timoni bundle vet -f bundle.cue` | | Validate without a cluster | `timoni bundle vet -f bundle.cue -r runtime.cue --offline` | | Validate and print the computed bundle | `timoni bundle vet -f bundle.cue --print-value` | | Preview | `timoni bundle apply -f bundle.cue --diff` | | Apply | `timoni bundle apply -f bundle.cue [-f bundle_secrets.cue]` | | Render to files | `timoni bundle build -f bundle.cue --output-dir ./manifests` | | Render to stdout without Secret values | `timoni bundle build -f bundle.cue --mask-secrets` | | Status | `timoni bundle status -f bundle.cue` or `timoni bundle status <name>` | | Delete | `timoni bundle delete -f bundle.cue` or `timoni bundle delete <name>` | | Update module versions per policy | `timoni bundle update -f bundle.cue [--level patch\|minor\|major]` | | Preview module updates | `timoni bundle update -f bundle.cue --dry-run` (prints `old -> new` per instance, writes nothing) | | With a runtime | add `-r runtime.cue`; select with `--runtime-cluster <name>` / `--runtime-group <group>` | | Runtime values from CI env vars | add `--runtime-from-env` | | Print resolved runtime values | `timoni runtime build -f runtime.cue [--cluster <name>] [--cluster-group <group>]` | ### Modules and registries | Task | Command | |------|---------| | Log in to a registry | `echo $TOKEN \| timoni registry login ghcr.io -u <user> --password-stdin` | | List published versions | `timoni mod list oci://<repo>` (newest 100; `--limit 0` for all, `--with-digest=false` to skip digests) | | Pull a module to disk | `timoni mod pull oci://<repo> -v <version> -o ./module` | | Show the README | `timoni mod show readme oci://<repo> -v <version>` (or a local `./module` path) | | Show the config schema | `timoni mod show config oci://<repo> -v <version>` (or a local `./module` path) | | Verify signature on pull | `... mod pull ... --verify=cosign --cosign-key=cosign.pub`, or keyless: `--verify=cosign --certificate-identity-regexp=<re> --certificate-oidc-issuer=<url>` | | Create a module | `timoni mod init <name> --blueprint oci://ghcr.io/stefanprodan/timoni/blueprints/starter` | | Validate a module | `timoni mod vet [path] [--debug]` | | Vendor Kubernetes schemas | `timoni mod vendor k8s [-v 1.30]` | | Vendor CRD schemas | `timoni mod vendor crd -f <crds.yaml or URL> [--kind Kind,Kind.group] [-v v1] [--prune]` (`--list` prints the kinds and versions without vendoring) | | Publish | `timoni mod push ./module oci://<repo> -v <semver> [--latest=false] [--sign=cosign [--cosign-key=cosign.key]]` | | Build an OCI archive without a registry | `timoni mod build ./module -v <semver> -o module.oci.tar` | | Generic artifacts | `timoni artifact push oci://<repo> -t <tag> -f ./dir`, `timoni artifact pull oci://<repo>:<tag>`, `timoni artifact build -f ./dir -t <tag> -o out.oci.tar` | | Format CUE | `timoni fmt` | Global flags: `-n/--namespace`, `--kube-context`, `--kubeconfig`, `--timeout`. Registry commands read `~/.docker/config.json`; `--creds user:token` is a per-command flag that exposes the token in shell history and process lists, so prefer `timoni registry login --password-stdin`. ## Values Values are supplied with `-f/--values` as CUE, YAML or JSON files, merged in order, or from stdin with `-f -`. A CUE values file wraps everything in `values:`: ```cue values: { replicas: 2 image: tag: "1.27-alpine" resources: limits: memory: "128Mi" } ``` Values are validated against the module's `#Config`; a type or constraint mismatch fails the build before anything reaches the cluster. To discover the available values, run `timoni mod show config oci://<repo> -v <version>` (or `timoni mod show config ./module` for a local module). ## Bundles ```cue bundle: { apiVersion: "v1alpha1" name: "podinfo" instances: { redis: { module: { url: "oci://ghcr.io/stefanprodan/modules/redis" version: "8.10.1" @timoni(update:semver:8.x) } namespace: "podinfo" values: maxmemory: 256 } podinfo: { module: { url: "oci://ghcr.io/stefanprodan/modules/podinfo" version: "6.14.0" @timoni(update:semver:6.x) } namespace: "podinfo" values: caching: { enabled: true redisURL: "tcp://redis:6379" } } } } ``` - Before adding an instance, read the module docs first: `timoni mod show readme oci://<repo> -v <version>`, then `timoni mod show config oci://<repo> -v <version>` for the full `#Config` schema the `values:` are validated against. - Editing loop: `timoni fmt bundle.cue` formats the file, `timoni bundle vet -f bundle.cue` validates the definition without a cluster (add `--print-value` to inspect the computed bundle), `timoni bundle build -f bundle.cue` renders the manifests offline, and `timoni bundle apply -f bundle.cue --diff` previews the changes against the cluster before applying (exits 1 on drift, 2 on failure). - Instances are applied in declaration order, each waiting for readiness before the next (`--wait=false` disables waiting). Deletion runs in reverse order. - `module.version` defaults to `latest`. A semver tag can be overwritten unless the registry enforces tag immutability; pin `module.digest` when you need deterministic retrieval. - Local modules in a bundle use `module: url: "file://../modules/app"`, relative to the bundle file, or an absolute path as `file:///abs/path/to/module`; `version`/`digest` are ignored and the instance gets version `0.0.0-devel`. - Split a bundle across files and merge with repeated `-f` (for example a `bundle_secrets.cue` kept out of git or piped from stdin with `-f -`). SOPS-encrypted YAML/JSON partials: `sops exec-file --filename secrets.yaml bundle.secrets.yaml 'timoni bundle apply -f bundle.cue -f {}'`. - Instances applied by a bundle are owned by it. `timoni apply` on such an instance, or another bundle claiming it, fails unless `--overwrite-ownership` is passed. - Bundles can import CUE packages from `cue.mod` in the working directory (`--workdir` selects the CUE module root). - `timoni bundle update -f bundle.cue` rewrites the module versions and digests according to the `@timoni(update:semver:<constraint>|digest|none)` attribute on the `version` field; `--level patch|minor|major` updates the references without an attribute, and `--dry-run` only prints the changes. Run `timoni bundle vet` and `timoni bundle build` afterwards, the update does not validate the values against the new module schema. ## Runtimes and multi-cluster A runtime declares clusters and values to read from them: ```cue runtime: { apiVersion: "v1alpha1" name: "fleet" clusters: { "staging": {group: "staging", kubeContext: "eks-staging"} "prod-eu": {group: "production", kubeContext: "eks-prod-eu"} "prod-us": {group: "production", kubeContext: "eks-prod-us"} } values: [ { query: "k8s:v1:Secret:infra:redis-auth" for: {"REDIS_PASS": "obj.data.password"} }, { query: "k8s:v1:ConfigMap:infra:aws-info" for: {"REGION": "obj.data.region"} optional: true }, ] } ``` Bind runtime values in a bundle with `@timoni(runtime:<string|number|bool>:<VAR>)`; a concrete value next to the attribute is the default when the variable is absent: ```cue bundle: { _pass: string @timoni(runtime:string:REDIS_PASS) _region: "eu-west-1" @timoni(runtime:string:REGION) _env: string @timoni(runtime:string:TIMONI_CLUSTER_GROUP) ... } ``` With `--runtime-from-env`, accepted by every `timoni bundle` subcommand, the variables come from the process environment instead of a cluster; no `-r` runtime file is needed: ```shell REDIS_PASS=$SECRET timoni bundle apply -f bundle.cue --runtime-from-env ``` - With `-r runtime.cue`, `bundle apply|vet|status|delete` iterate over the selected clusters, switching kube-context per cluster. `bundle build` requires exactly one selected cluster (`--runtime-cluster`). - `TIMONI_CLUSTER_NAME` and `TIMONI_CLUSTER_GROUP` are set for clusters declared in a runtime, not for the implicit current-context default. - Value precedence: cluster identity values override cluster query results, which override `--runtime-from-env` values. Secret `data` is base64-decoded. - `optional: true` only tolerates the queried object being absent. A missing variable bound to a field without a default still fails with an incomplete value error. - `bundle build -r` reads runtime queries from the selected cluster; only bundles without cluster queries build offline. ## Apply semantics `timoni apply` and `bundle apply`, per instance: 1. Pull the module, merge values, build and validate the objects. 2. Create the namespace if missing. 3. Record the intended inventory (install) or a pending revision (upgrade) in Secret `timoni.<instance>`. 4. Server-side apply the `timoni: apply:` sets one after another, in declaration order (for example CRDs before workloads, tests last), waiting for readiness after each set (kstatus plus module-declared health checks). 5. Prune objects from the previous inventory that are no longer rendered. 6. Commit the final inventory. Applying over objects created by `kubectl apply` or `helm install` takes ownership: field ownership moves to Timoni and the `kubectl.kubernetes.io/last-applied-configuration` and `meta.helm.sh/*` annotations are removed. This is the supported Helm migration path; make sure a name collision is intentional before applying. Per-object behavior is set with annotations in the module templates: | Annotation | Effect | |------------|--------| | `action.timoni.sh/force: "enabled"` | Recreate the object when its immutable fields change (`--force` does this for all objects) | | `action.timoni.sh/one-off: "enabled"` | Apply only if the object does not exist | | `action.timoni.sh/prune: "disabled"` | Never garbage-collect the object, also survives `delete` | | `action.timoni.sh/wait: "disabled"` | Skip readiness waiting for the object | `timoni delete` and `bundle delete` remove the inventory objects (except `prune: disabled` ones) and wait for finalizers; namespaces are left in place. ## Module authoring Typical layout produced by the starter blueprint (a convention, not a contract): ``` myapp/ ├── cue.mod/ │ ├── gen/ # vendored Kubernetes API and CRD schemas │ ├── pkg/ # timoni.sh/core/v1alpha1 schemas │ └── module.cue # module: "timoni.sh/myapp" ├── templates/ │ ├── config.cue # #Config schema with defaults, #Instance │ ├── deployment.cue # #Deployment: {#config: #Config, ...} │ └── service.cue ├── images.cue # container images defaults (repository, tag, digest) ├── timoni.cue # entry point: values, timoni.instance, timoni.apply ├── timoni.ignore # files excluded from mod push ├── values.cue # placeholder for user values ├── README.md └── LICENSE ``` - `#Config` holds defaults (`*value | type`) and constraints (`int & >0`);
View on GitHub
This SKILL.md is very large, so SkillsMP previews the first section here. View on GitHub