Introducing Akua CLI
A typed, sandboxed packaging runtime for the Kubernetes ecosystem. One binary, signed by default, rendered at CI.
Cloud-native packaging in 2026 looks a lot like JavaScript tooling looked in 2014: a loose coalition of single-purpose tools, each with its own version, config file, and failure mode, held together with CI glue that nobody wants to own. Every mature ecosystem reaches this phase: the first generation solves real problems, then the workflows around it harden into scripts, config drift, and tribal knowledge.
The useful successors do not pretend the old layer was a mistake. TypeScript did not erase JavaScript, Kotlin did not erase Java, and Carbon is being designed around C++ interoperability because the C++ ecosystem is too valuable to throw away. Helm, Kustomize, OPA, Kyverno, Cosign, and GitOps each earned their place. Akua exists to give developers and platform engineers a better interface to that ecosystem: one coherent runtime for the scattered path through it.
Today we’re releasing Akua: a single Rust binary that covers rendering, verification, signing, and policy for Kubernetes packages, with typed inputs in KCL, deterministic output by contract, and a WebAssembly sandbox that refuses network and shell access by default.
The pipeline your Kubernetes deploy actually runs
To take one container image and get it running on a bank’s Kubernetes cluster, a typical CI pipeline runs something like this:
helm templaterenders a chart into raw manifests.kustomize buildoverlays environment-specific patches.kyverno testoropa evalvalidates policy: no root containers, no:latesttags, required labels, network policies attached.syftproduces an SBOM from the image.cosign signissues a keyless signature through Fulcio’s OIDC flow.cosign attestemits a SLSA v1 provenance attestation to Rekor.docker buildxbuilds the image, which has to be signed and attested separately.argocd app synchands the manifests off to the cluster.- A Kyverno
verifyImagesadmission policy re-checks the signature before the pod can start.
Nine tools, nine configs, and a year’s worth of audit evidence to keep in sync. None of this is optional if you ship software to federal customers under US Executive Order 14028 or to European financial institutions under the Cyber Resilience Act. SLSA Build Level 2 is the floor; Level 3 requires isolated builds; FINOS publishes banking-specific variants that layer PCI-DSS and SOC 2 evidence collection on top. CNCF graduated Kyverno last year because admission-time policy has to hold, not just exist.
The individual tools are mature. The glue between them is not, and a team running this pipeline spends more engineering time on the seams than on any of the tools themselves.
We have seen this end before
In 2014, shipping a TypeScript web app meant wiring together npm, tsc, jest, webpack, eslint, and prettier. Six tools, six config files, six upgrade cycles, and a week of onboarding before a new hire could ship a line of code. The ecosystem evolved slowly, because every proposal to consolidate had to negotiate with the project graph of everyone else’s build.
Bun collapsed that graph into a single binary (install, run, test, bundle, transpile, format), and the numbers told the rest. Bun serves 52,000 requests per second where Node does 14,000, installs a React app in two seconds where npm takes eighteen, and its weekly downloads grew 85 percent year-over-year through 2025. X, Stripe, and Midjourney run it in production. Anthropic acquired Bun in December 2025, and it now powers Claude Code. Deno took a parallel path: built-in formatter, built-in linter, built-in test runner, native TypeScript without a tsconfig, and near-full npm compatibility in Deno 2. The config file is optional.
Zero-config is not a marketing line. It’s what happens when a coherent runtime owns the whole surface, and the reason that matters is simple: once the runtime owns the surface, the failure modes collapse from “which of nine tools blew up” to “what was the actual bug.”
Akua is that same collapse, for cloud-native packaging. A Package is a KCL file. KCL is a CNCF Sandbox project with static types, schemas, and compile-time constraints on range, regex, length, enum, and required fields, and Akua uses it as the canonical source of truth for inputs, chart values, policy bindings, dependencies, and UI metadata. Your Helm values become a typed schema. A typo fails at parse time instead of at kubectl apply. YAML is still the output; the rendered manifests are what reconcilers see, and nothing in that path needs to know KCL exists.
Helm, Kustomize, OPA, and KCL each earned their place in the ecosystem, and Akua does not try to re-implement any of them. The job is to bring them into one runtime through WebAssembly embedding, so every tool composes with the others without version drift or $PATH surgery, and to add the missing layer on top: a package manager with lockfiles, typed dependencies, signed OCI distribution, and one version-controlled source of truth per Package. The tools keep doing what they are good at. The glue stops being your problem.
A Package, and thirteen verbs
Thirteen verbs are shipped and stable in v0.1. They group into four jobs:
- Author.
akua initscaffolds a Package.akua addandakua removemanage dependencies: Helm charts, kroResourceGraphDefinitions, KCL packages, or Kustomize bases.akua fmt,akua lint, andakua checkrun the full KCL toolchain over your sources.akua diffcompares two rendered outputs andakua treeshows Package composition. - Render.
akua renderexecutes the Package and writes raw manifests to./rendered. - Inspect.
akua inspectaudits a published Package: schema, sources, signatures.akua verifyre-checks a signed OCI tarball offline, no network required. - Identify.
akua whoamiandakua versionexpose identity, capabilities, and detected agent context as structured JSON.
A Package looks like this:
import akua.ctx
import akua.helm
schema Input:
greeting: str = "hello from akua"
replicas: int = 1
input: Input = ctx.input()
resources = helm.template(helm.Template {
chart = "./chart"
values = { greeting = input.greeting, replicas = input.replicas }
release = "hello"
namespace = "default"
})To run this yourself, brew install akua-dev/tap/akua or curl -fsSL https://cli.akua.dev/install | sh. Full options at the end of the post.
Render it in one command:
akua render --inputs inputs.yamlThe v0.1 invariant is simple: if a verb is documented as shipped, its adversarial test suite passes. The rest sit at varying degrees of done; see the roadmap at the end for the breakdown.
Three invariants we do not get from the CLI ecosystem
Three commitments are wired into the runtime rather than bolted on. Each replaces something the current ecosystem treats as an optional extra step.
Typed
An Akua Package declares its inputs as a KCL schema, with typed fields, compile-time constraints, and docstrings that become documentation. A field declared image: str with a regex constraint rejects image: true at parse time; YAML accepts it, Helm’s Go template language accepts it, and Helm’s optional values.schema.json only rejects it if the chart author remembered to ship one. KCL schemas are mandatory and enforced; values.schema.json is optional and almost never present in charts published to Artifact Hub.
The constraint sits in the schema, not in a separate validator:
schema Input:
appName: str
replicas: int = 2
check:
replicas >= 1, "replicas must be at least 1"Pass replicas: 0 to akua render and the run fails before any manifest is written, with a typed error pointed at https://akua.dev/errors/E_RENDER_KCL.
Schema fields can also carry @ui decorators like @ui(order=10, group="Identity") or @ui(widget="slider", min=1, max=20) that describe how the field should render in a form. akua export --format=json-schema emits standards-pure JSON Schema (Draft 2020-12), with docstrings becoming description and decorators becoming x-ui metadata. A conforming form renderer like rjsf or JSONForms picks up the hints; anything else ignores them. The Akua marketplace will use this to generate customer-facing install forms directly from the same KCL schema the author uses to render. Helm has no equivalent path: values.schema.json is draft-7 JSON Schema with no UI semantics, and nothing in the Helm ecosystem tools it as a first-class surface.
When a chart does ship a values.schema.json, Akua generates a typed KCL schema Values { ... } mirror so Package authors get the same shape under their IDE and LSP. The existing schema is not wasted; it is not the only story.
Signed
akua publish signs every Package by default: a cosign ECDSA P-256 signature plus a SLSA v1 predicate attestation bundled with the OCI artifact. Unsigned publishing is a flag, not a default. To turn it off you pass --no-sign --no-attest and do it on purpose. Consumers running akua verify check both offline; a Kyverno verifyImages admission policy pointed at an Akua-published Package gets Rekor-backed verification on every deploy.
Sandboxed
Sandboxed rendering is on by default in v0.1. Every akua render routes through a Wasmtime worker with a sealed filesystem, no network access, a 256 MiB memory ceiling, and a three-second wall-clock epoch deadline. The host’s $PATH is not consulted, and the WASI capability model has no socket syscalls to start with, so an untrusted Package cannot read a laptop, exfiltrate a credential, or fork-bomb a CI runner.
When a render does fail, say the typed check: above, the error confirms where it ran:
{"level":"error","code":"E_RENDER_KCL",
"message":"worker trapped: error while executing at wasm backtrace:
0: 0x698e86 - <unknown>!<wasm function 9749>
1: 0x684123 - <unknown>!<wasm function 9551>
...",
"docs":"https://akua.dev/errors/E_RENDER_KCL"}Every frame is <wasm function N>. The trap is inside Wasmtime, not on the host. This matters more when the thing rendering your Package is not a human, because the blast radius of a compromised template in a shared CI environment used to be the runner; now it’s the Wasmtime store.
Why not just KCL plus its plugins?
KCL already ships a CLI and a plugin ecosystem (kubectl-kcl, kustomize-kcl, the krm-kcl specification, Crossplane and Helmfile integrations), so a fair reader will ask what Akua adds. Five things, concretely, and none are features layered on top of KCL; they are architectural choices KCL itself doesn’t make.
Engines are embedded, not invoked. KCL plugins shell out: kubectl-kcl calls kubectl, Helm integrations call the helm binary on your $PATH. Akua compiles Helm v4 and Kustomize to wasm32-wasip1 and hosts them inside its own Wasmtime runtime: one binary, one set of engine versions pinned to the Akua release, no subprocess, no $PATH dependency.
Output is deterministic by contract. From the Akua security model: “same inputs plus same akua.lock plus same Akua version equals byte-identical output.” The render path forbids now(), random(), env reads, filesystem reads, and cluster reads. KCL the language allows some of these through its stdlib; Akua the runtime strips them from the sandbox.
Signing is a default, not a verb the author remembers. akua publish emits the cosign signature and SLSA attestation as part of the render pipeline. The KCL plugin ecosystem has no equivalent path; signing lives outside the toolchain, and authors remember when they remember.
The sandbox is at the process level. Every akua render runs inside a Wasmtime worker with the caps named above, so untrusted Packages are safe to render on shared hosts; KCL run as native Rust has none of this.
The CLI contract is agent-first. Akua detects agent environments via AGENT, CLAUDECODE, GEMINI_CLI, CURSOR_CLI, or AKUA_AGENT, and silently switches to --json, --log=json, --no-color, and --no-interactive. Exit codes are typed, every verb accepts --plan for dry-run, and --idempotency-key makes writes safely retryable. kcl, built in an earlier era, was not designed for this.
Put together, those five choices produce something that is less “KCL with extras” and more “what a packaging runtime looks like when you get to define all the invariants at once.”
Render at CI, apply at the cluster
The default deployment pattern for Akua is what Flux’s kustomize-controller already does in --local mode and what the Akua docs call Compiled GitOps: akua render executes at CI commit time, the rendered YAML lands in a deploy repository under version control, and ArgoCD or Flux points at the pre-rendered output. The source Package stays in its own repo, the rendered output lives in the deploy repo, and the diff between two commits in the deploy repo is exactly what will hit the cluster.
This matters for reasons that go beyond convenience. Render-on-demand GitOps, where the controller pulls a chart or a Package at sync time and renders inside the cluster, has two structural problems. First, a pull request against the source repo cannot show what will actually deploy, because the templates and values are not the output; reviewers end up approving intent and hoping the renderer agrees. Second, any render-on-demand surface is a supply-chain target: a compromised chart or a malicious plugin executes at sync time, inside the cluster, with whatever permissions the controller holds. Flux’s authors figured this out years ago, which is why kustomize-controller --local exists.
The Compiled GitOps flow with Akua looks like this: push the KCL Package to the source repo, CI runs akua render with the right inputs, the rendered YAML is committed to a deploy path with a signed tag, and ArgoCD or Flux syncs the deploy path. Every apply is a git diff a human can read. Every deploy is a signed artifact a verifier can check. No controller needs to understand KCL; no renderer needs to run in-cluster.
The in-controller rendering path (Akua as a plugin inside ArgoCD or Flux, rendering on sync) is on the roadmap for v0.2 and later, and it is explicitly not the recommended pattern. It exists for cases where a Package needs runtime cluster input that CI cannot provide. Those cases are rare, and the right answer for almost every regulated deployment is to keep the render step where the audit evidence can be collected.
One signed OCI artifact per Package version, one Rekor entry per publish, one rendered YAML diff per deploy. The audit story becomes one artifact and one log, not nine.
Fewer decisions, surfaced errors
Akua is designed for an AI agent running in a sandbox with a human reviewing the agent’s work, and both read the same output. Structured JSON is the default when an agent is detected (via AGENT, CLAUDECODE, GEMINI_CLI, CURSOR_CLI, or AKUA_AGENT), and the akua whoami output includes which agent was detected and why. You can disable the switch with AKUA_NO_AGENT_DETECT=1 when you want to.
An agent authoring a Kubernetes deployment in the current ecosystem has to make a long chain of decisions that mostly don’t have a single correct answer: which chart to extend, which policy engine to pick, when to sign and when to attest, whether a deprecated API surface is still safe in this minor version, how to split values across environments, which admission policy to pair with which image signature. The diversity of those decisions is the reason the ecosystem works at all. But most of them have a known-good answer in most contexts, and every time a prompt has to re-derive that answer, there’s a fresh chance for the agent to pick the wrong one. Slop accumulates at decision boundaries.
Akua narrows that decision space the same way Bun narrowed JavaScript’s. Rendering is one verb, signing is a default rather than a step, the sandbox is not optional, and policy runs under one host rather than four. The Agent Skills ship the correct workflow so the agent does not have to invent it, and when the agent picks something Akua disagrees with (a deprecated chart API, an unsigned dependency, a schema violation, a sandbox escape), the error is typed, structured, and specific enough that the agent can act on it without a second round-trip to the human. Security-by-default has always been a safety feature for humans writing code; it is closer to a necessity when the thing writing the code is fast, unsupervised, and occasionally confident about things that are not true.
Nine Agent Skills ship alongside the binary: new-package, inspect-package, diff-gate, dev-loop, migrate-helmfile, rotate-secret, publish-signed, apply-policy-tier, test-and-lint. They conform to the Agent Skills Specification. Install them into your agent’s skills directory with npx skills install github:akua-dev/akua/skills and the agent learns the workflows along with the tool.
Roadmap
The long-term target is thirty verbs across authoring, publishing, deploying, operating, developing, session, and meta. v0.1 ships thirteen of them with full adversarial test coverage. The rest sit at varying degrees of done.
Usable, not polished. test, repl, eval: the verbs run; the polish doesn’t.
Wired but not executable yet. dev, deploy, policy, audit, query, infra. The surface is there; the implementations land in v0.2.
In flight.
- The adversarial test suite for the Wasmtime sandbox (fuel-exhaustion, epoch-exhaustion, memory-bomb, path-escape, symlink-escape, import-escape) needs every failure to surface a typed error rather than a panic. The happy path is tested today; the failure path is being hardened.
- The
akua-wasmSDK bundle via JSR is blocked on two upstream issues inwasm32-unknown-unknownKCL builds; three-line upstream PRs are identified. - Akua marketplace consumption of the JSON Schema / UI export comes next; the export works today.
Install
curl -fsSL https://cli.akua.dev/install | sh
akua versionOr via Homebrew:
brew install akua-dev/tap/akuaOr from source:
cargo install --git https://github.com/akua-dev/akua akua-cliRepo: akua-dev/akua. The issue tracker is where rough edges go.
Try it, break it, tell us what broke.