rune cast
Install or upgrade a release from YAML files, directories, runeset bundles, or remote URLs — plan, confirm, apply.
Aliases: apply.
cast is the universal "apply this spec" command. It accepts:
- A single YAML file
- A directory of YAML files
- A glob (
services/*.yaml) - A runeset directory or
.runeset.tgzarchive - A remote URL or git ref
rune cast my-service.yaml
rune cast my-service.yaml --namespace=production
rune cast my-service.yaml --tag=stable
rune cast my-directory/ --recursive
rune cast services/*.yaml
rune cast github.com/org/repo/path@ref --create-namespace
rune cast https://example.com/runeset.tgz --release=my-release
rune cast ./runeset.tgz --release=my-release
rune cast ./runeset --render --set=key=value
rune cast ./runeset --render --values=values.yaml
:::note
Every cast is a tracked release. There is no
fire-and-forget path. A loose service.yaml is treated as an inline runeset and
gets a release named after the file (with a warning); pin it with --release.
Operate releases afterward with rune release.
:::
The cast pipeline
A cast is declarative — closer to terraform apply than kubectl apply. It runs:
render ─► lint ─► PLAN (3-way diff) ─► [confirm] ─► APPLY (create → update → verify) ─► PRUNE
The CLI renders and lints locally, then the server computes the plan, applies it in order, and prunes last. You see a plan block before anything changes:
Release "runesight" → revision 3 (namespace: observability)
+ create 2 (service/query-api, volume/ch-data)
~ update 1 (service/ingester)
- prune 1 (configmap/old-settings) ⚠ destructive
↪ adopt 0
= reference 0
A plan that prunes or adopts asks for confirmation before applying — type yes,
or pass --yes (CI and --detach skip the prompt). Pure create/update proceeds
automatically.
Flags
| Flag | Default | Notes |
|---|---|---|
-n, --namespace | from context | Target / home namespace for the release. |
--create-namespace | false | Create the namespace if it doesn't exist (referenced, never owned). |
--release <name> | derived | Release identity. Defaults to the runeset/file basename. |
--dry-run | false | Compute and print the plan; apply nothing. See below. |
--detach | false | Return once accepted; don't wait for rollout. Create/update-only. |
--yes | false | Skip the confirm prompt for plans that prune or adopt. |
--adopt | false | Take over resources that are unmanaged or owned by another release. |
--timeout <dur> | 5m | Rollout wait timeout for the whole release. |
-r, --recursive | false | Recurse into directories. |
--force | false | Force generation increment even with no diff. |
--tag <name> | — | Tag this deployment for later reference. |
--render | false | Print rendered YAML and exit (works for inline casts too). |
--set k=v | — | Override a value (repeatable). |
-f, --values <file> | — | Extra values file (repeatable; last wins). |
-o, --output | — | json — emit the structured plan + result. |
:::note
--release, --render, --set, and --values are universal — they apply to
loose castfiles (inline runesets), not just packaged runesets.
:::
Plan and dry-run
--dry-run renders, lints, and prints the 3-way plan — but applies nothing.
It now contacts the server to compare against the release's current state, so the
plan includes prunes and adoption conflicts:
rune cast ./my-app --release=my-app-prod --values=prod.yaml --dry-run
This is the same plan rune release diff shows for an
already-installed release; use --dry-run when you're casting from changed source
or values. For purely static, offline validation (schema, templates, cycles) use
rune lint.
Adoption
A cast that would overwrite a resource not owned by this release fails with an
ownership conflict — the guardrail that keeps two releases from fighting over the
same resource. Pass --adopt to take ownership (e.g. pulling pre-existing,
pre-release resources under management on first cast):
rune cast ./my-app --release=my-app-prod --adopt
Detach
--detach returns as soon as the cast is accepted instead of waiting for rollout.
It is create/update-only: a plan that prunes refuses --detach so the
destructive half is never orphaned. Drop --detach to apply a destructive plan,
or inspect it first with rune release diff.
Behavior
- Idempotent. Re-casting the same spec into the same release is a no-op plan
(unless
--force). - Aggregate timeout.
--timeoutbounds the whole release rollout, not each service. - Deterministic apply. Resources apply in a stable order, then prune runs last.
- Multi-document YAML is supported — separate documents with
---. - Mixed resource files (services + secrets + configmaps in one file) are fine.
- Failures surface inline. Without
--detach,castwaits for the release to become ready. If the reconciler reportsFailedit stops waiting immediately, prints a short reason + the one-sentence cause + arune get service <name>hint, and exits non-zero. The release is leftfailedwith the prior revision live and nothing pruned.
✗ ImageUnreachable
pull access denied for ghcr.io/acme/api, repository does not
exist or may require 'docker login'
image: ghcr.io/acme/api:1.4.2
service: prod/api
rune get service api -n prod
Common patterns
# Validate first (offline, static)
rune lint manifests/ --recursive
# Preview the full plan against live state
rune cast manifests/ --release=app --dry-run
# Install / upgrade a release
rune cast ./my-app --release=my-app-prod --values=prod.yaml
# Non-interactive deploy in CI (structured output, skip the prompt)
rune cast ./my-app --release=my-app-prod -f prod.yaml --yes --output=json
# Diff a runeset's rendered YAML before installing
rune cast ./my-app --render --values=prod.yaml | diff - last-rendered.yaml
See also
- Releases — the stateful model behind every cast.
rune release— list, status, history, diff, rollback, uninstall.- Service spec
- Runesets
rune lint