# Kubrain — full documentation > A managed Kubernetes cloud built MCP- and CLI-first. Every operation is one idempotent, cost-aware tool call your agent makes. No web console. --- # Documentation Source: https://kubrain.dev/docs/ Kubrain is a deployment platform with **one interface: an MCP server and a matching CLI**. There is no web console. Every capability — a VPC, a cluster, a deployment, DNS, TLS, storage, metrics, alerts — is one idempotent, cost-aware tool call your agent (Claude, or any LLM) makes on your behalf. Underneath it is vanilla, upstream Kubernetes, which is why the platform doesn't dead-end when your app grows past a single service — but you never have to operate it yourself. These docs teach you the model and the moves. {{< cards >}} {{< card link="overview" title="Overview" subtitle="The agent-first model, and why the tools are the source of truth." >}} {{< card link="quickstart" title="Quickstart" subtitle="From a code directory to a live, internet-reachable app." >}} {{< card link="reference/mcp-tools" title="MCP tools" subtitle="The full tool catalogue — the product's contract." >}} {{< card link="reference/cli" title="CLI" subtitle="Every kubrain verb. Same engine, same results as the tools." >}} {{< card link="reference/kubrain-yaml" title="kubrain.yaml" subtitle="The deploy manifest that travels with every release." >}} {{< /cards >}} ## The one idea You are not administering Kubernetes by hand. You express **intent** to a cloud that owns the machinery. For any task, the reflex is *"which tool expresses this intent?"* — not *"what kubectl steps achieve it?"*. Create/set operations are **idempotent**, so it is always safe to "ensure" a resource exists. ## Where to go next - New here? Read the **[Overview](/docs/overview)**, then run the **[Quickstart](/docs/quickstart)**. - Building an agent or script against Kubrain? Start with the **[MCP tools](/docs/reference/mcp-tools)** and **[CLI](/docs/reference/cli)** references. - Machine-readable index of this site: [`/llms.txt`](/llms.txt). --- # Overview Source: https://kubrain.dev/docs/overview/ **Kubrain is a deployment platform you talk to, not click.** The only interface is an MCP server plus a matching `kubrain` CLI — you point Claude (or any LLM agent) at it, and the agent provisions, deploys, scales, and bills your infrastructure through a small set of tools. No dashboards, ever. It runs on vanilla, upstream Kubernetes. That matters for one reason: your app can grow from a single node to multi-service and highly available without ever moving to a different platform. You get that headroom without operating a cluster yourself. ## Why it works this way The whole product is designed around one customer: **your agent**. That leads to a few properties you can rely on everywhere: - **A tiny, declarative, idempotent surface.** Re-running a create/set operation with the same arguments is a no-op that returns the existing resource — never a duplicate. So an agent can always "ensure" a resource exists without checking first. - **Cost is visible before you commit.** Anything that spends money has a dry path: [`pricing`](/docs/reference/mcp-tools#cost--quota) and [`estimate`](/docs/reference/mcp-tools#cost--quota) quote a shape without creating anything, `dry_run` previews a change, and `plan_blueprint` diffs a whole tenant. The agent sees the € and the diff, then commits. - **Structured, natural-language errors.** When something can't happen ("capacity full for 32 GB nodes in this zone; largest available is 16 GB"), the error is written for an agent to read and self-correct. - **Instant `kubectl`.** The moment a cluster is ready, its kubeconfig points at a stable public endpoint — the agent has working `kubectl` with no extra steps. ## The one idea **Everything is a control-plane operation over the tools, and the tools are the source of truth.** You express intent to a cloud that owns the machinery; you do not run `kubectl`/CAPI steps by hand. For any request, the reflex is *"which tool expresses this intent?"*. Reach for raw `kubectl` (against a fetched kubeconfig) only to **observe** a running workload, or when no tool covers the intent. When memory and a tool result disagree, the tool wins. ## How you approach any task The same shape works for a cluster, a DNS record, or an app: 1. **Look before you leap.** Start from a `list_*`/`get_*` view, not memory. Half of "create X" requests are really "reconcile the X that already exists." 2. **Preview cost and change.** Run the dry path — `estimate`/`get_quota` for spend, `dry_run`/`plan_blueprint` for change — and surface the numbers. 3. **Commit the intent** through the one tool that owns it. 4. **Expect async.** Provisioning, scaling, resizing, and upgrading return when the change is *requested*. Poll the matching `get_*` view until the state is what you need before the next step. 5. **Report plainly** — what changed, what it costs, what is still converging. ## One price, billed on RAM Kubrain has one transparent price model. The binding, billed resource is **RAM**: **€0.006 per GiB-hour** (≈ €4.38 / GiB-month), with the control plane, bundled vCPU (~1 per 2 GiB), and egress included. Two add-on dimensions have a published rate — **standard volumes** (€0.05 / GiB-month, first 20 GiB free) and **LoadBalancer public IPs** (€3.50 / month each). Your agent can compute the bill in one multiplication, before creating anything. See [Pricing](/docs/reference/pricing) and [Quotas](/docs/reference/quotas) for the details. ## Working discipline A few rules that keep an agent safe on real infrastructure: - **Secrets go to files, never the transcript.** `get_kubeconfig` and `get_bucket_credentials` write to a file (mode `0600`) and return only a confirmation; `set_secret` reads its value from a file or an env var. Hand these tools a path, then *use the file*. - **Destructive and outward-facing actions are confirm-first.** Deleting a cluster, VPC, zone, bucket, or deployment — or pruning a blueprint — cascades to real machines, data, or public DNS. Name exactly what cascades and get an explicit OK; approval for one action does not extend to the next. - **Production wants HA.** For anything you care about, provision an `ha` tier (≥3 control planes). Reserve `dev`/`normal` for prototypes. ## Next - **[Quickstart](/docs/quickstart)** — take a code directory all the way to a live URL. - **[MCP tools](/docs/reference/mcp-tools)** — the full catalogue of what an agent can call. --- # Quickstart Source: https://kubrain.dev/docs/quickstart/ This is the flow Kubrain is built for: hand it a directory of code, and get back a running service on a URL. Every step is one tool call — shown here as MCP tool names, with the equivalent [CLI](/docs/reference/cli) verb alongside. {{< callout type="info" >}} Kubrain is driven by an agent. In practice you *ask* (“ship this app and put it on `app.example.com`”) and the agent runs the steps below. They’re spelled out so you know what it’s doing — and so you can script the same flow in CI with the CLI. {{< /callout >}} ## Install the CLI Grab a static binary from the [latest release](https://github.com/kubrain-dev/kubrain-cli/releases/latest) — Linux, macOS, and Windows, `amd64` and `arm64`, no dependencies. ```bash # Linux / macOS — pick your os/arch: linux_amd64 · linux_arm64 · darwin_amd64 · darwin_arm64 curl -fsSL -o kubrain \ https://github.com/kubrain-dev/kubrain-cli/releases/latest/download/kubrain_linux_amd64 chmod +x kubrain && sudo mv kubrain /usr/local/bin/kubrain kubrain --version ``` On **Windows**, download `kubrain_windows_amd64.exe` (or `_arm64`), rename it to `kubrain.exe`, and add it to your `PATH`. Verify any download against the release's `checksums.txt` with `sha256sum -c checksums.txt`. ## 0 · Sign up & connect Create an account **either way** — on the website at [kubrain.dev](https://kubrain.dev), or straight from the CLI: ```bash kubrain auth signup --email you@example.com # register from the terminal (prompts for a password) kubrain auth login --email you@example.com # already have an account kubrain quota # sanity check: your limits and €/month view ``` Either path stores a tenant **Bearer token** in `~/.kubrain/config`. To drive Kubrain from Claude, add that same token — with the remote MCP server URL — to your agent's config. `pricing`, `estimate`, `versions`, and `regions` need no token — use them to plan before you sign up. ## 1 · Understand the code ``` detect # CLI: kubrain deploy detect ``` Reports language, package manager, entrypoint, ports, and whether a `Dockerfile`, `kubrain.yaml`, or `kubrain/k8s-templates/` already exist. ## 2 · Make it buildable If there is no `kubrain.yaml` (and/or no Dockerfile): ``` scaffold # CLI: kubrain deploy scaffold ``` writes sensible defaults you then tailor — see the [`kubrain.yaml` reference](/docs/reference/kubrain-yaml). A Dockerfile is the most predictable build; without one the cloud builder falls back to buildpacks. ## 3 · Prove it renders ``` validate # CLI: kubrain deploy validate ``` runs the *exact* build ladder and template engine the cloud uses, so "valid locally" ≡ "will build in the cloud." To *see* the Kubernetes objects it will produce, `kubrain deploy render --dir `. ## 4 · Ensure a cluster exists ``` list_clusters ``` If there is nowhere to deploy, provision one — always estimate the cost first: ``` estimate tier=ha ram=8 nodes=2 # returns €/hour and €/month create_cluster tier=ha ram=8 nodes=2 k8s= # dry_run first ``` `create_cluster` defaults to `dry_run` in MCP — it returns the full plan (node IPs, public endpoint, cost) and applies nothing. Run it for real, then poll: ``` get_cluster # until state = running ``` Pick a version with `versions`, a region/zone with `regions`, and check headroom with `get_quota`. Tiers: `dev` (1 schedulable control plane, prototypes), `normal` (1 dedicated control plane + workers), `ha` (3 control planes, for production). ## 5 · Add the web-facing add-ons For anything served over HTTP(S), make sure the cluster has an ingress controller and TLS: ``` install_addon ingress-nginx # +€3.50/mo for the LB IP install_addon cert-manager issuer_email=you@example.com # free TLS ``` Both are idempotent. `list_addons` shows the catalogue and what's installed. ## 6 · Register the deployment ``` create_deployment cluster= ``` establishes a stable handle; the name becomes the namespace `kbn-`. Add `repo`/`branch` to bind Git for auto-builds (retrieve the deploy key to add to your repo with `get_deploy_key `). ## 7 · Supply secrets (if any) ``` set_secret --value-file # value read from a file, never inline ``` Every managed key is exposed to the workload as an env var automatically. ## 8 · Ship it ``` deploy dir= # CLI: kubrain deploy ship --dir ``` builds from your local source in the cloud and rolls it out (or pass `image=` for a prebuilt image). Then poll: ``` get_deployment # until the release is live ``` ## 9 · Put it on the internet If your `kubrain.yaml` declares an `ingress`, the rollout already created the Ingress and requested TLS. Point DNS at the cluster's ingress IP: ``` create_zone example.com # if new; delegate the NS at your registrar set_record example.com app A ``` Find the ingress IP from `kubectl --kubeconfig get svc -n ingress-nginx` (its `EXTERNAL-IP`). cert-manager issues the certificate within a minute or two. ## 10 · You're live Report the URL, the release id, and the monthly cost. From here: - **[Deploy an app](/docs/guides/deploy)** — releases, triggers, promote/rollback. - **[Metrics](/docs/operate/metrics)** — `get_metrics` gives golden signals with zero instrumentation. - **[Alerting](/docs/operate/alerting)** — page yourself on errors, latency, pod health, or an ingress going down. --- # Core concepts Source: https://kubrain.dev/docs/concepts/ A short vocabulary. Everything else in these docs builds on these ideas. ## Tenant & token You are a **tenant**. Sign up **on the website at [kubrain.dev](https://kubrain.dev) or from the CLI** (`kubrain auth signup`) — either way you get a **Bearer token** that authorizes only your operations, within your quota and budget. The server **re-verifies your identity, funds, and quota on every call** — it never trusts the client. You only ever see your own resources. - **Agents** connect over **remote MCP** (streamable HTTP) with the token in an `Authorization: Bearer` header. - The **`kubrain` CLI** hits the same HTTP API with the same token (`kubrain auth login` stores it in `~/.kubrain/config`; or set `$KUBRAIN_TOKEN`). A few tools — `pricing`, `estimate`, `versions`, `regions`, `list_addons` — are **public** (no token): the pre-signup quote surface. ## One endpoint Agents talk to **one endpoint**, `api.kubrain.dev` (override with `--api` / `$KUBRAIN_API`). There is no per-region URL to juggle — the region is a property of your VPC, not something you route to by hand. ## Region & zone - A **region** is a latency domain. Clusters and VPCs live in a region. - A **zone** is a named **failure domain** within a region. An `ha` cluster spreads its 3 control-plane nodes across ≥3 zones so it survives losing one. - A **VPC** is a regional, tenant-isolated network. Clusters in the *same* VPC talk to each other across the region; different VPCs are isolated. A cluster inherits its region from its VPC. Use `regions` to see regions and zones that actually exist, with coarse free capacity, before you place a cluster. ## Idempotency Every **create/set** operation is idempotent: running it twice with the same arguments is a no-op that returns the existing resource, never a duplicate. This is what lets an agent safely "ensure" a resource exists without checking first — and what makes [blueprints](/docs/operate/blueprints) (declarative apply) work. ## Async Provisioning, scaling, resizing, and upgrading a cluster **return when the change is requested**, not when it is done. The response hands you a resource in a transitional state; you **poll** the matching `get_*` view until it reaches the state you need. A resource fetched too early may look empty — re-check. ## Cost is a first-class output Anything that spends money has a dry path, so the € is visible before you commit: - **[`pricing`](/docs/reference/pricing)** — the rate card. - **[`estimate`](/docs/reference/pricing)** — an exact quote for a specific shape; creates nothing. - **`dry_run`** on `create_cluster`/`install_addon` — the plan plus the monthly delta. - **[`plan_blueprint`](/docs/operate/blueprints)** — the full diff for a whole tenant. - **[`get_quota`](/docs/reference/quotas)** — your limits, current usage, and €/month burn. Billing is on **RAM**, with two add-on dimensions (volumes, LB IPs) — see [Pricing](/docs/reference/pricing). ## Names are addresses Cluster, deployment, bucket, and zone names are lowercase letters, digits, and hyphens. A **deployment name is also its namespace** (`kbn-`). Choose them deliberately — they are how everything is addressed. --- # Guides Source: https://kubrain.dev/docs/guides/ How to get real work done. Each guide is a task, start to finish. {{< cards >}} {{< card link="deploy" title="Deploy an app" subtitle="Releases, triggers, promote & rollback." >}} {{< card link="expose" title="Expose it" subtitle="DNS, TLS, and ingress to a public URL." >}} {{< card link="clusters" title="Clusters" subtitle="Create, scale, resize, upgrade." >}} {{< card link="volumes" title="Volumes" subtitle="Persistent storage for stateful workloads." >}} {{< card link="buckets" title="Object storage" subtitle="S3-compatible buckets." >}} {{< card link="registry" title="Container registry" subtitle="Push once, pull with no secrets." >}} {{< card link="addons" title="Add-ons" subtitle="Ingress, TLS, metrics, Redis." >}} {{< card link="secrets" title="Secrets" subtitle="Give a workload credentials, safely." >}} {{< /cards >}} --- # Deploy an app Source: https://kubrain.dev/docs/guides/deploy/ A **deployment** is a named handle on a cluster that you ship releases to. Its name is also its Kubernetes namespace (`kbn-`). What to run is described by [`kubrain.yaml`](/docs/reference/kubrain-yaml), which travels with every release. ## 1 · Create the handle ```bash kubrain deploy create myapp --cluster prod ``` Idempotent. As created, this is a **manual** deployment — you ship it from a local checkout (step 2). To have Kubrain build and roll out automatically on every Git push instead, bind a repository — see [Git deployments](#git-deployments-auto-build-on-push) below. ## 2 · Ship a release ```bash kubrain deploy ship myapp --dir . # build local source in the cloud, then roll out kubrain deploy ship myapp --image ghcr.io/me/myapp:1.4.0 # or roll out a prebuilt image ``` `--dir` tars your working tree and the cloud builds it (a privileged BuildKit job in your cluster); multi-component apps build each source component independently and apply the whole set as **one release**. Then poll: ```bash kubrain deploy get myapp # until the release is live ``` **Releases are immutable**, named `r-N`, and flow `building → live → superseded` (or `building → failed`). The rendered manifests travel with the release, so rollback re-applies exact prior bytes. {{< callout type="info" >}} A release can be **failed while pods are healthy** — most often because the rollout's Ingress was rejected (another namespace already claims the same host+path). Read the reason from `deploy get`, resolve the clash, and re-ship. {{< /callout >}} ## 3 · Build once, promote Test an artifact in staging, then ship *that exact image* to prod — no rebuild: ```bash kubrain deploy promote --src staging --to prod # ships staging's current image on prod kubrain deploy promote --src staging --to prod --release r-7 ``` `prod` re-renders with **its own** `kubrain.yaml` and secrets, but the image bytes are identical to what you tested. ## 4 · Roll back ```bash kubrain deploy rollback myapp # to the last good release kubrain deploy rollback myapp --to r-5 # to a specific one ``` No rebuild — it re-points and re-applies the stored manifests. ## Git deployments (auto-build on push) Bind a repository once and Kubrain builds every push for you — no external CI to wire up. This is the "watch my repo and ship it" path. ### 1 · Bind the repo and add the deploy key Binding Git mints a **read-only, per-deployment `ed25519` deploy key**; its private half never leaves the platform. Add the **public** half to your repository (on GitHub/GitLab: *Settings → Deploy keys*, read-only is enough) so Kubrain can clone and pull: ```bash kubrain deploy create myapp --cluster prod \ --repo git@github.com:me/myapp.git --branch main # bind (idempotent — safe to re-run) kubrain deploy deploy-key myapp # print the public key to add to the repo ``` You can bind an existing manual deployment the same way — re-running `deploy create` with `--repo` just updates the binding. ### 2 · Choose when it builds A **trigger** decides which refs auto-build: ```bash kubrain deploy set-trigger myapp --spec commit:main # every push to main kubrain deploy set-trigger myapp --spec 'commit:release/*' # any release branch kubrain deploy set-trigger myapp --spec 'tag:v*' # version tags kubrain deploy set-trigger myapp --spec 'schedule:0 3 * * *' # nightly rebuild kubrain deploy set-trigger myapp --spec manual # off — ship by hand only ``` ### 3 · Push A background poller watches the bound repo, resolves the matching ref to a concrete **SHA**, and builds **only when it differs** from the current release — then pins that exact SHA into the release, so a moving branch can never desync the running image. `kubrain deploy get myapp` shows the current commit for a Git deployment. **Gates (monorepos).** Restrict auto-builds to commits that actually matter: `only_paths` builds only when matching paths changed; `only_message` only when the commit message matches — so one service in a shared repo doesn't rebuild on another's changes. Manual and Git-watched shipping coexist: you can still run `kubrain deploy ship myapp` by hand at any time. ## Observe & tear down ```bash kubrain deploy logs myapp # tail recent pod logs, live (nothing stored) kubrain deploy list kubrain deploy delete myapp # tears down the namespace — confirm first ``` ## See also - **[kubrain.yaml](/docs/reference/kubrain-yaml)** — the manifest you ship. - **[Secrets](/docs/guides/secrets)** · **[Expose it](/docs/guides/expose)** · **[Metrics](/docs/operate/metrics)**. --- # Expose it Source: https://kubrain.dev/docs/guides/expose/ To serve `https://app.example.com` end to end you need three things: an **ingress controller** (with **TLS**), an **Ingress** for your app, and **DNS** pointing at the cluster's ingress IP. ## 1 · Ensure the add-ons ```bash kubrain addon install prod ingress-nginx # +€3.50/mo for the LB IP kubrain addon install prod cert-manager --issuer-email you@example.com # free TLS ``` `ingress-nginx` gets a dedicated public LoadBalancer IP; `cert-manager` issues Let's Encrypt certificates via HTTP-01 through it. Both idempotent. See [Add-ons](/docs/guides/addons). ## 2 · Declare the Ingress In [`kubrain.yaml`](/docs/reference/kubrain-yaml): ```yaml ingress: enabled: true host: app.example.com # hosts: [app.example.com, www.app.example.com] # several names on one SAN cert tls: true ``` `kubrain deploy ship` creates the Ingress and requests the certificate automatically. (For anything the preset doesn't cover — extra paths, custom annotations — drop an `ingress.yaml` override in `kubrain/k8s-templates/`.) ## 3 · Host the DNS zone ```bash kubrain zone create example.com # idempotent, free; prints the NS set to delegate ``` At your **domain registrar**, set the domain's NS records to the nameservers `zone create` returns (currently `ns1.kubrain.dev` and `ns2.kubrain.dev` — always use the exact list it prints). Delegation typically propagates in minutes to a few hours. ## 4 · Point a record at the ingress IP Find the ingress LoadBalancer's `EXTERNAL-IP`: ```bash kubrain cluster kubeconfig prod --output kubeconfig kubectl --kubeconfig kubeconfig get svc -n ingress-nginx ``` Then create the record: ```bash kubrain zone set example.com app A ``` Records go live on both nameservers **immediately**. cert-manager solves the challenge and issues the certificate within a minute or two — then the URL serves valid TLS. ## Record types & syntax `zone set [--ttl]` upserts a whole RRset (replacing by name+type). Types: **A, AAAA, CNAME, TXT, MX, SRV, CAA**. `@` is the apex, `*` a wildcard leftmost label; TTL is 30–86400 (default 300). Repeat values for a round-robin set. `kubrain zone records ` lists; `kubrain zone unset` deletes. ## Raw TCP/UDP A non-HTTP service can skip ingress entirely: give it a `Service` of `type: LoadBalancer` and it allocates its own dedicated public IP (€3.50/mo). ## See also - **[Add-ons](/docs/guides/addons)** · **[Clusters](/docs/guides/clusters)** · **[MCP tools › DNS](/docs/reference/mcp-tools#dns)**. --- # Clusters Source: https://kubrain.dev/docs/guides/clusters/ A cluster is where your apps run. Provisioning is a thin, cost-aware facade over the platform — you express the shape, preview the €, and poll until it's ready. ## Pick a VPC A **VPC** is a regional, tenant-isolated network; clusters in the same VPC talk to each other region-wide. ```bash kubrain vpc create prod-net # or let a standalone cluster use the default kubrain vpc list ``` ## Create and reach a cluster ```bash kubrain versions # selectable Kubernetes versions kubrain regions # regions/zones with live capacity kubrain estimate --tier ha --ram 8 --nodes 2 # €/month before committing kubrain cluster create prod --vpc prod-net --tier ha --ram 8 --nodes 2 --k8s v1.30.2 kubrain cluster status prod # poll until state = running ``` In MCP, `create_cluster` **defaults to a dry-run** that returns the full plan (node IPs, the public endpoint, cost) and applies nothing; run it for real, then poll `get_cluster`. The region is inherited from the VPC; `ram` is GiB per node (the [billed dimension](/docs/reference/pricing)). **Tiers:** `dev` (1 schedulable control plane — prototypes) · `normal` (1 dedicated control plane, 1+ workers) · `ha` (3 control planes across ≥3 zones — production). For anything you care about, choose `ha`: it tolerates node loss and rolls the control plane cleanly. ### Get kubectl ```bash kubrain cluster kubeconfig prod --output kubeconfig # writes mode-0600 file kubectl --kubeconfig kubeconfig get nodes # until nodes are Ready ``` The kubeconfig's server URL is a stable public endpoint, reachable immediately. Use `kubectl` to **observe** workloads; reach for a tool to change infrastructure. ## Change a running cluster All of these are **async** — poll `cluster status` until `running` again. ```bash kubrain cluster scale prod --workers 4 # add/remove worker nodes kubrain cluster resize prod --ram 16 # change per-node RAM (rolls the pool) kubrain cluster upgrade prod --k8s v1.31.0 # control plane first, then workers kubrain cluster reapply prod # re-render to pick up new platform features ``` `scale`, `resize`, and `upgrade` re-check your [quota and host capacity](/docs/reference/quotas) and return a clear error if the change won't fit. `reapply` re-applies the manifest unchanged and may roll the control plane once; shape and cost are unchanged. ## Delete ```bash kubrain cluster delete prod # cascades to the VMs, frees the network block ``` Destructive — confirm first. Deleting also frees any LB IPs (their charge stops). ## See also - **[Pricing](/docs/reference/pricing)** · **[Quotas](/docs/reference/quotas)** · **[Add-ons](/docs/guides/addons)**. --- # Volumes Source: https://kubrain.dev/docs/guides/volumes/ Every cluster ships a default StorageClass, **`kubrain`**. Declare a standard `PersistentVolumeClaim` and a real volume is created and attached when a pod first uses it (`WaitForFirstConsumer`); data survives pod restarts and reschedules. ```yaml apiVersion: v1 kind: PersistentVolumeClaim metadata: name: data spec: accessModes: ["ReadWriteOnce"] storageClassName: kubrain resources: requests: storage: 20Gi ``` ## Design around ReadWriteOnce The `kubrain` class is **RWO** — a volume attaches to one node at a time. Shape your workload accordingly: - **Single-instance stateful** (a database): `replicas: 1`, one PVC, and `deployment.strategy: Recreate` in [`kubrain.yaml`](/docs/reference/kubrain-yaml) so the volume detaches cleanly before the new pod attaches. - **Multi-replica stateful**: use a **StatefulSet** via a `kubrain/k8s-templates/statefulset.yaml` override with `volumeClaimTemplates` (one volume per replica). ## Cost Volumes bill at **€0.05 / GiB-month**, with the **first 20 GiB free** per tenant. Deleting the PVC frees the volume and stops the charge. ## See also - **[kubrain.yaml › Storage](/docs/reference/kubrain-yaml#storage)** · **[Pricing](/docs/reference/pricing)**. --- # Object storage Source: https://kubrain.dev/docs/guides/buckets/ Kubrain gives you **S3-compatible object storage**. You address buckets by name with **path-style** addressing, using any S3 client or SDK. ## Create and list ```bash kubrain bucket create assets # private by default kubrain bucket create media --public # anonymous world-readable at its public URL kubrain bucket list # live object count + size per bucket kubrain bucket get assets ``` `--public` is **fixed at creation** and makes objects world-readable. Buckets are idempotent; the first one also provisions your tenant S3 user. ## Get credentials ```bash kubrain bucket credentials --output s3.env # writes endpoint + key pair, mode 0600 ``` **One key pair per tenant** opens every one of your buckets (and nothing else). The tool writes to a file and returns only a confirmation — never echo it into the conversation. Use it with any S3 client, path-style: ```bash # s3.env sets the endpoint + AWS_ACCESS_KEY_ID / AWS_SECRET_ACCESS_KEY aws --endpoint-url "$S3_ENDPOINT" s3 cp ./logo.png s3://assets/logo.png ``` Configure your SDK with **`forcePathStyle: true`** and the endpoint from `bucket credentials`. ## Delete ```bash kubrain bucket delete assets # EMPTY buckets only — empty it first ``` Non-empty deletes are refused; clear the objects with your S3 credentials, then delete. Confirm first — it's destructive. ## See also - **[MCP tools › Object storage](/docs/reference/mcp-tools#object-storage-buckets)**. --- # Container registry Source: https://kubrain.dev/docs/guides/registry/ Kubrain hosts a container registry so you can ship prebuilt images. The headline property: **your cluster nodes pull with no `imagePullSecrets`** — a pull-only credential is baked into every node at cluster creation and shared across all your clusters. ## Push coordinates ```bash kubrain registry ``` Returns the registry **host**, your tenant image **namespace**, and the login flow. It carries **no secret** — the docker-login password is the Kubrain API token you already hold. Push with standard tooling: ```bash docker login # username any, password = your API token docker tag myapp:1.4.0 //myapp:1.4.0 docker push //myapp:1.4.0 ``` Images live under `//:` — the `/` namespace is yours. ## Use a pushed image Reference it directly from [`kubrain.yaml`](/docs/reference/kubrain-yaml) as a prebuilt component, or roll it out straight: ```yaml components: web: image: //myapp:1.4.0 # prebuilt — skips the build port: 8080 ``` ```bash kubrain deploy ship myapp --image //myapp:1.4.0 ``` No `imagePullSecrets`, no per-deployment credential wiring — the node already knows how to pull from your namespace. ## See also - **[Deploy an app](/docs/guides/deploy)** — building in the cloud vs. shipping a prebuilt image. --- # Add-ons Source: https://kubrain.dev/docs/guides/addons/ Add-ons are curated, version-pinned platform features you install into a running cluster. Installs are **idempotent** (re-run to bump to the latest pinned version) and **cost-aware** (a dry-run reports the monthly delta). ```bash kubrain addon list # catalogue + what's installed — no token needed kubrain addon install prod ingress-nginx kubrain addon install prod cert-manager --issuer-email you@example.com kubrain addon uninstall prod ingress-nginx ``` ## Catalogue | Add-on | Cost | What it gives you | |---|---|---| | **ingress-nginx** | +€3.50/mo (the LB IP) | HTTP(S) ingress controller behind a dedicated public LoadBalancer IP. Required to expose apps over HTTP(S). The IP is the ingress Service's `EXTERNAL-IP`. | | **cert-manager** | free | Automatic TLS via Let's Encrypt. Pass `--issuer-email`; it creates a production `letsencrypt` ClusterIssuer solving HTTP-01 through ingress-nginx. Give it ~a minute to be ready. | | **metrics-server** | free | Resource metrics for `kubectl top` and HPA. | | **redis** | free | In-cluster Redis cache (ClusterIP, no auth, ephemeral). Publishes a `redis-auth` Secret you wire in with `env: {REDIS_URL: {addon: redis, key: url}}` — no secret names to memorize. | ## Credential-producing add-ons `redis` publishes its connection details as a Secret. Reference it from [`kubrain.yaml`](/docs/reference/kubrain-yaml) with the `addon:` sugar — Kubrain resolves the right namespace/secret/keys for you: ```yaml env: REDIS_URL: {addon: redis, key: url} # or import all its keys: envFrom: - addon: redis ``` ## Uninstall `kubrain addon uninstall ` is idempotent. Uninstalling `ingress-nginx` **releases its LoadBalancer IP and stops that charge**. ## See also - **[Expose it](/docs/guides/expose)** — ingress + TLS end to end. - **[Secrets](/docs/guides/secrets)** — the four `env:` forms, including `addon:`. --- # Secrets Source: https://kubrain.dev/docs/guides/secrets/ There are two ways an app gets secrets, and they compose. In both, the value never enters `kubrain.yaml`, the rendered manifest, the image, or any tool output — Kubrain records only **key names**, never values. ## 1 · Kubrain-managed secrets Write a value straight into the deployment's own Kubernetes Secret — from a file or an env var, never an inline argument: ```bash kubrain deploy set-secret myapp DB_PASSWORD --value-file ./db-pass.txt kubrain deploy set-secret myapp API_KEY --from-env API_KEY kubrain deploy unset-secret myapp DB_PASSWORD ``` Every managed key is exposed to the workload as an **env var automatically**. Because secrets are *referenced* (not baked into the image), rollback and promotion keep working and pick up the current values on the next ship. ## 2 · Reference an existing Secret / ConfigMap In [`kubrain.yaml`](/docs/reference/kubrain-yaml), an `env:` entry can point at something else that created a Secret or ConfigMap (an operator, an add-on, a hand-applied Secret). Kubernetes resolves it at pod start. Each `env:` entry is **exactly one of four forms**: | Form | Syntax | Becomes | |---|---|---| | **Literal** | `NAME: value` | `value:` | | **Secret key** | `NAME: {secret: s, key: k}` | `secretKeyRef` | | **ConfigMap key** | `NAME: {configMap: c, key: k}` | `configMapKeyRef` | | **Addon credential** | `NAME: {addon: redis, key: url}` | a resolved `secretKeyRef` | ```yaml env: LOG_LEVEL: info # literal DB_PASSWORD: {secret: db-app, key: password} # same namespace DB_TOKEN: {secret: db-app, key: token, namespace: data} # another namespace * REDIS_URL: {addon: redis, key: url} # a built-in addon envFrom: - secret: db-app # import every key - addon: redis ``` `key:` defaults to the env var's own name when omitted. **`namespace:`** — because Kubernetes references can't cross namespaces, naming one makes Kubrain **mirror** that Secret into the app's namespace at rollout (re-synced each deploy, so rotation follows). {{< callout type="warning" >}} Never pass a secret value as an inline argument or echo it back. Hand tools a **file** (`--value-file`) or an env var (`--from-env`); write kubeconfigs and bucket credentials to a path and use the file. {{< /callout >}} ## See also - **[kubrain.yaml › Secrets & env](/docs/reference/kubrain-yaml#secrets--env)** · **[Add-ons](/docs/guides/addons)**. --- # Operate Source: https://kubrain.dev/docs/operate/ Once it's live: observe it, get paged when it breaks, and manage the whole tenant as one document. {{< cards >}} {{< card link="metrics" title="Metrics" subtitle="Golden signals with zero instrumentation." >}} {{< card link="alerting" title="Alerting" subtitle="Page on errors, latency, pod health, uptime." >}} {{< card link="blueprints" title="Blueprints" subtitle="Export, plan, and apply your whole cloud." >}} {{< card link="terraform" title="Terraform" subtitle="Run Yandex Cloud or AWS configs against Kubrain (educational)." >}} {{< /cards >}} --- # Metrics Source: https://kubrain.dev/docs/operate/metrics/ Every tenant cluster ships an eBPF + ingress telemetry stack, so a deployment has **golden signals with zero app instrumentation**. One tool reads them. ```bash kubrain deploy metrics myapp kubrain deploy metrics myapp --window 1h --by component ``` `get_metrics(name, [component], [window], [by])` returns a curated summary: - **request rate** — requests/sec - **error rate** — 5xx as a fraction (`0..1`) - **p95 latency** — milliseconds - **`has_data`** — `false` when no traffic was seen in the window ## Options - **`window`** — look-back, e.g. `5m` (default), `1h`, `1d`. - **`component`** — narrow to one component's service; omit for the whole deployment. - **`by: component`** — a row per component; **`by: pod`** — a row per replica. Use these to answer *which component* or *which pod*. ## Read the source The summary reports its **`source`**: - **`ingress`** — measured at the front door (north-south). This p95 is real, end-to-end. - **`mesh`** — east-west/internal, from eBPF (server-side only). Check it before reasoning about latency — `ingress` numbers include the network; `mesh` numbers don't. ## Before you alert Always read `get_metrics` before [arming an alert](/docs/operate/alerting) — the alert thresholds the same numbers, so a quick read tells you a sane threshold and whether there's traffic at all. ## See also - **[Alerting](/docs/operate/alerting)** — turn a threshold into a page. --- # Alerting Source: https://kubrain.dev/docs/operate/alerting/ An alert puts a threshold on one signal; the api-server evaluates it on a schedule and fires a **webhook or email** when it breaches for a sustained window, and again when it recovers. Notification config never enters your cluster. A rule's **source** decides where the sample comes from. ```bash # Golden-signal alert: 5xx rate above 5% for 5 minutes. kubrain deploy alert set myapp \ --signal error_rate --op '>' --threshold 0.05 --for 5m \ --notify https://hooks.slack.com/services/XXX kubrain deploy alert list myapp # each rule's live state + last value kubrain deploy alert delete myapp error_rate ``` ## Three sources ### `metric` (default) — golden signals Thresholds a signal from [`get_metrics`](/docs/operate/metrics): `error_rate` (fraction), `p95_ms`, or `req_rate`. `--op` is `>` (default) or `<`. ```bash kubrain deploy alert set myapp --component api \ --signal p95_ms --op '>' --threshold 500 --notify oncall@example.com ``` ### `pod` — pod health Watches the deployment's **pods** with a live cluster check (no metrics backend needed). Fires when pods are **failed**, **not ready** (CrashLoopBackOff, ImagePullBackOff, …), or **stuck pending** past a 5-minute grace. The single signal `pods_unhealthy` defaults to `> 0`, so only `--notify` is required: ```bash kubrain deploy alert set myapp --source pod --for 5m \ --notify https://hooks.slack.com/services/XXX ``` The firing notification names the offending pods and why. ### `probe` — uptime & TLS A black-box HTTP(S) check the api-server makes from **outside** the cluster to one of your app's public ingress hosts — the true end-to-end "is my site up." Two signals: - **`http_up`** (default) — `GET https:///`; fires when it stops answering 2xx/3xx. - **`cert_days`** — days until the host's TLS certificate expires; `cert_days < 14` by default warns two weeks ahead. ```bash kubrain deploy alert set myapp --source probe --host app.example.com --for 2m \ --notify oncall@example.com kubrain deploy alert set myapp --source probe --host app.example.com \ --signal cert_days --threshold 21 --notify oncall@example.com ``` {{< callout type="info" >}} The probe host is **not arbitrary** — it must be a host the deployment actually serves through its own Ingress. Kubrain checks ownership at set time and refuses to probe anything else. {{< /callout >}} ## How it fires A debounced state machine: `ok → pending → firing`, where `--for` is how long a breach must persist before firing (omit to fire on the first breaching sample). A recovery moves it back to `ok`. **Steady-state firing notifies once** (on start) and once on resolve — not every tick. Rules are **idempotent per (component, signal)** — setting the same signal again replaces the rule and resets its state. `list_alerts` shows each rule's state (`ok`/`pending`/`firing`) and last sampled value. ## Notifications `--notify` takes a **webhook URL** (Slack, Discord, PagerDuty, any incoming webhook) or an **email address** — the channel is inferred from the shape. Webhooks POST a stable JSON payload; email needs the platform SMTP relay configured. ## Gotchas - **`req_rate < N` won't fire on total silence** — a no-traffic window reads as no-data, treated as "not breaching." Fine for `error_rate`, `p95_ms`, and `req_rate >`. - **`source=log`** (log-keyword alerts) is accepted but not wired yet. ## Playbook 1. `kubrain deploy metrics myapp` → confirm it's serving; note the baseline. 2. `kubrain deploy alert set myapp --signal error_rate --op '>' --threshold 0.05 --for 5m --notify …` 3. `kubrain deploy alert list myapp` → verify it's stored in state `ok`. ## See also - **[Metrics](/docs/operate/metrics)** — read the numbers you're thresholding. --- # Blueprints Source: https://kubrain.dev/docs/operate/blueprints/ When you want the whole tenant as one versioned, diffable document instead of many imperative calls, use a **blueprint**. It declares VPCs, clusters (with their add-ons), DNS zones + records, and buckets. ```bash kubrain export > tenant.yaml # snapshot live resources as a blueprint kubrain estimate --file tenant.yaml # price the whole document kubrain plan --file tenant.yaml # diff spec vs. live (read-only) kubrain apply --file tenant.yaml # converge (additive by default) kubrain apply --file tenant.yaml --prune # also delete undeclared resources ``` ## The workflow 1. **`export`** snapshots current resources as an **intent** spec — only the fields you choose. Allocated IPs, versions, and cost are *status* and left out, so re-applying an unedited export is a no-op. 2. **`plan`** diffs the document against live state, classifying each resource as **create / update / delete / no-op**. Read it and show it before applying. 3. **`apply`** converges live state to the document. Default is **additive** (creates + updates, leaves undeclared resources alone). **`--prune`** also deletes resources absent from the document — destructive, confirm first. ## Example ```yaml version: 1 vpcs: - {name: prod-net, region: gra} clusters: - name: shop vpc: prod-net tier: ha ram_gib: 16 workers: 3 k8s_version: v1.30.2 addons: ingress-nginx: v1.15.1 cert-manager: v1.14.4 dns: - name: shop.example.com records: - {name: "@", type: A, ttl: 300, values: ["${cluster.shop.ingress_ip}"]} - {name: www, type: CNAME, ttl: 300, values: ["shop.example.com."]} buckets: - {name: assets, region: gra, public: true} ``` ## Rules that matter - **Runtime references.** A record value can reference a cluster's ingress IP with `${cluster..ingress_ip}` — resolved at apply time; a value that isn't resolvable yet defers and completes on a later apply. - **In-place vs. replace.** Changing `workers`, `ram_gib`, or `k8s_version` maps to [scale / resize / upgrade](/docs/guides/clusters). Changing `tier` or `vpc` is a **replace** and is refused — delete and recreate deliberately instead. - **Managing add-ons.** An explicit `addons: {}` means "manage add-ons: remove any not listed." **Omitting** `addons` means "don't manage add-ons on this cluster." ## See also - **[MCP tools › Blueprints](/docs/reference/mcp-tools#blueprints-declarative-fleet)** · **[Pricing](/docs/reference/pricing)**. --- # Terraform Source: https://kubrain.dev/docs/operate/terraform/ Kubrain ships two **drop-in Terraform providers** that present the resource surface of a big cloud — **Yandex Cloud** (`yandex_*`) and **AWS** (`aws_*`) — but provision everything on Kubrain. An existing configuration runs against Kubrain by changing only the `source` in `required_providers`; arguments that have no Kubrain equivalent are accepted and silently ignored. {{< callout type="warning" >}} **Educational use only.** These providers exist to *learn* how the big clouds' Terraform resources map onto a small, real cloud — not to run production estates. They cover a deliberately narrow slice of each cloud, translate loosely (many fields are ignored, some are faked for compatibility), and are distributed as a local binary, not a signed registry release. For real workloads on Kubrain, use [Blueprints](/docs/operate/blueprints) or the [CLI](/docs/reference/cli). {{< /callout >}} ## Install Each provider lives in its own repository: - **AWS** — [github.com/kubrain-dev/terraform-provider-aws](https://github.com/kubrain-dev/terraform-provider-aws) - **Yandex Cloud** — [github.com/kubrain-dev/terraform-provider-yandex](https://github.com/kubrain-dev/terraform-provider-yandex) They aren't published to a Terraform registry. Instead you get the binary — either a prebuilt release asset or a local build — and point Terraform at it with a `dev_overrides` block, which skips `terraform init` entirely. ### Download a release binary Grab the asset for your OS/architecture from the repo's Releases page and save it under the name Terraform expects (`terraform-provider-`): ```bash mkdir -p ~/.kubrain/tf # pick the asset matching your platform (linux_amd64, linux_arm64, # darwin_amd64, darwin_arm64, windows_amd64) curl -fsSL -o ~/.kubrain/tf/terraform-provider-aws \ https://github.com/kubrain-dev/terraform-provider-aws/releases/latest/download/terraform-provider-aws_linux_amd64 chmod +x ~/.kubrain/tf/terraform-provider-aws ``` Do the same for `terraform-provider-yandex` if you want the Yandex provider. No Go toolchain needed. ### Build from source Alternatively, build it yourself (Go 1.23+): ```bash git clone https://github.com/kubrain-dev/terraform-provider-aws cd terraform-provider-aws && go build -o terraform-provider-aws . ``` ### Point Terraform at the binary Either way, add a `dev_overrides` block to `~/.terraformrc` (or `$TF_CLI_CONFIG_FILE`) pointing at the **directory** that holds the binary: ```hcl provider_installation { dev_overrides { # the directory containing the binary (e.g. ~/.kubrain/tf from above) "kubrain.dev/kubrain/aws" = "/home/you/.kubrain/tf" "kubrain.dev/kubrain/yandex" = "/home/you/.kubrain/tf" } direct {} } ``` With `dev_overrides` set, run `terraform plan` / `terraform apply` directly — **do not** run `terraform init`. ## Authentication Both providers authenticate with your Kubrain tenant token, resolved in this order: 1. the provider block — `token`/`endpoint` (Yandex) or `kubrain_token`/`kubrain_endpoint` (AWS); 2. `$KUBRAIN_TOKEN` / `$KUBRAIN_API`; 3. `~/.kubrain/config` (written by `kubrain auth login`); 4. the default endpoint `https://api.kubrain.dev`. The big cloud's own credential arguments (`cloud_id`, `folder_id`, `region`, `access_key`, `secret_key`, `profile`, …) are accepted and ignored, so a `provider "aws" {}` / `provider "yandex" {}` block usually needs no edits. ```bash export KUBRAIN_TOKEN= ``` ## Yandex Cloud provider ```hcl terraform { required_providers { yandex = { source = "kubrain.dev/kubrain/yandex" } } } ``` | Resource | Kubrain backing | |---|---| | `yandex_vpc_network` | VPC (`name` maps) | | `yandex_vpc_subnet` | passthrough (Kubrain fuses network + subnet) | | `yandex_kubernetes_cluster` | Cluster control plane — zonal master → tier `normal`, regional → `ha`; async, waits for `running` | | `yandex_kubernetes_node_group` | Worker pool — `fixed_scale.size` → workers, `resources.memory` → RAM | | `yandex_storage_bucket` | Bucket — `acl` public-* → anonymous read | | `yandex_iam_service_account` | virtual (Kubrain identity is the tenant token) | | `yandex_iam_service_account_static_access_key` | Tenant S3 credentials (real, working keys) | | `yandex_dns_zone` | DNS zone — `zone` → domain | | `yandex_dns_recordset` | DNS record set | | `yandex_container_registry` | virtual — auto-provisioned per tenant; exposes `host` | ```hcl resource "yandex_vpc_network" "net" { name = "prod-net" } resource "yandex_kubernetes_cluster" "cluster" { name = "shop" network_id = yandex_vpc_network.net.id master { version = "v1.31.4" zonal { zone = "ru-central1-a" } # zonal → tier `normal` } } resource "yandex_kubernetes_node_group" "workers" { cluster_id = yandex_kubernetes_cluster.cluster.id scale_policy { fixed_scale { size = 2 } } # → 2 workers instance_template { resources { memory = 8 } # → 8 GiB per worker } } ``` ## AWS provider ```hcl terraform { required_providers { aws = { source = "kubrain.dev/kubrain/aws" } } } ``` | Resource | Kubrain backing | |---|---| | `aws_vpc` | VPC — name comes from the `Name` tag; `cidr_block` is ignored (auto-allocated) | | `aws_subnet` | passthrough — its `id` equals its `vpc_id` | | `aws_eks_cluster` | Cluster control plane — `vpc_config.subnet_ids[0]` selects the VPC, `version` → k8s, `kubrain-tier = "ha"` tag → tier `ha` (else `normal`); async | | `aws_eks_node_group` | Worker pool — `scaling_config.desired_size` → workers, `instance_types[0]` → RAM | | `aws_s3_bucket` | Bucket — inline `acl` public-* → anonymous read | | `aws_s3_bucket_acl` / `aws_s3_bucket_public_access_block` | Reconcile the bucket's public flag | | `aws_iam_role` / `aws_iam_user` | virtual — synthesized ARNs so references resolve | | `aws_iam_access_key` | Tenant S3 credentials (real, working keys) | | `aws_route53_zone` | DNS zone — `name` → domain, `zone_id` = domain | | `aws_route53_record` | DNS record set | | `aws_ecr_repository` | virtual — exposes `repository_url` | ```hcl resource "aws_vpc" "net" { cidr_block = "10.0.0.0/16" # ignored (Kubrain auto-allocates) tags = { Name = "prod-net" } # becomes the Kubrain VPC name } resource "aws_subnet" "a" { vpc_id = aws_vpc.net.id } resource "aws_eks_cluster" "cluster" { name = "shop" version = "v1.31.4" vpc_config { subnet_ids = [aws_subnet.a.id] # [0] resolves to the Kubrain VPC name } } resource "aws_eks_node_group" "workers" { cluster_name = aws_eks_cluster.cluster.name node_group_name = "workers" scaling_config { desired_size = 2 } # → 2 workers instance_types = ["t3.large"] # → 8 GiB per worker (lookup table) } ``` The AWS node group sizes workers from `instance_types` via a small lookup table (`t3.medium` = 4 GiB, `t3.large` = 8 GiB, `m5.large` = 8 GiB, `c5.large` = 4 GiB, `r5.large` = 16 GiB, …); an unrecognized type falls back to a default with a warning. ## Mapping caveats The same semantics apply to both providers — they're the interesting part of the translation: - **Clusters and node groups are one object.** Kubrain couples the control plane and workers. The cluster resource creates it (tier from the master block or a tag); the node group sets the worker count/RAM via [scale + resize](/docs/guides/clusters). Only one worker pool exists per cluster, so multiple node groups collapse onto it (last apply wins) and emit a warning. - **Cluster create is asynchronous.** It blocks until Kubrain reports `running` (minutes — real VMs boot). A Kubernetes version bump maps to an in-place [upgrade](/docs/guides/clusters); changing the network or tier forces replacement. - **Server-decided fields.** Kubrain allocates a VPC's CIDR itself, so a configured `cidr_block` / subnet CIDR is accepted but not honored. Omit it to see the CIDR Kubrain actually assigned. - **S3 credentials are tenant-wide.** Every static access key resolves to the same real, working key pair for your tenant's buckets. - **Bucket visibility is fixed at creation** and immutable; the separate AWS ACL resources reconcile read-only and warn on a mismatch rather than mutating. - **Non-empty buckets can't be deleted** — empty the bucket first (via S3), then destroy. ## See also - **[Blueprints](/docs/operate/blueprints)** — the native declarative surface for real workloads · **[Clusters](/docs/guides/clusters)** · **[Buckets](/docs/guides/buckets)** · **[Pricing](/docs/reference/pricing)**. --- # Reference Source: https://kubrain.dev/docs/reference/ The precise contract. Everything a client faces: {{< cards >}} {{< card link="mcp-tools" title="MCP tools" subtitle="The full tool catalogue an agent calls." >}} {{< card link="cli" title="CLI" subtitle="Every kubrain verb — same engine as the tools." >}} {{< card link="kubrain-yaml" title="kubrain.yaml" subtitle="The deploy manifest schema." >}} {{< /cards >}} --- # MCP tools Source: https://kubrain.dev/docs/reference/mcp-tools/ This is the product's contract: the complete set of `mcp__kubrain__*` tools an agent drives. Every tool also exists as a [`kubrain` CLI verb](/docs/reference/cli) — same engine, identical results. **Each tool carries its own current parameter docs; read the tool's own description before first use** — the tables below are the map, not a substitute for it. Conventions used below: - **Mutates** — `no` tools are always safe to call; `yes` tools change infra or spend. Create/set tools are **idempotent** (safe to repeat to "ensure" state). - **Cost-aware** tools have a dry path (`dry_run`, or a dedicated `estimate`/`plan`). - **Public** tools need no tenant token (the pre-signup quote surface); everything else authorizes against your tenant, funds, and quota on every call. ## Networking (VPCs) A **VPC** is a regional, tenant-isolated network. Clusters in the same VPC talk to each other region-wide; different VPCs are isolated. | Tool | Mutates | What it does | |---|---|---| | `create_vpc(name, region?)` | yes | Ensure a VPC exists (idempotent). | | `get_network(name)` | no | Show one VPC (CIDR, gateway, region). | | `list_networks()` | no | All your VPCs. | | `delete_vpc(name)` | yes | Tear down the VPC's network. | ## Clusters | Tool | Mutates | What it does | |---|---|---| | `list_versions()` | no | Selectable Kubernetes versions (+ the Talos image version). | | `get_regions()` | no | Regions/zones that actually exist, with coarse free capacity. | | `create_cluster(name, vpc?, tier?, ram?, nodes?, k8s?, dry_run?)` | yes | Plan (dry-run, the default) or provision a cluster. Dry-run returns IPs, public endpoint, and cost, applying nothing. | | `list_clusters()` | no | Your clusters with state, tier, VPC, node count, public hostname. | | `get_cluster(name)` | no | One cluster's status (state, shape, network, endpoint, recent Events). | | `get_kubeconfig(name, output_file)` | no | Write a kubeconfig (server set to the public endpoint) to a file. | | `scale_cluster(name, nodes)` | yes | Add/remove worker nodes (`normal`/`ha`). Async. | | `resize_cluster(name, ram)` | yes | Change per-node RAM; rolls the worker pool. Async. | | `upgrade_cluster(name, k8s)` | yes | Upgrade Kubernetes — control plane first, then workers. Async. | | `reapply_cluster(name)` | yes | Re-render the manifest unchanged to pick up platform features (may roll the CP once). | | `delete_cluster(name)` | yes | Tear down (cascades to VMs), free the network block. **Confirm first.** | **Tiers.** `dev` (1 schedulable control plane, 0+ workers — prototypes) · `normal` (1 dedicated control plane, 1+ workers — small non-critical apps) · `ha` (3 control planes across ≥3 zones, 1+ workers — production). `ram` is GiB per node (the billed dimension); `region` is inherited from the VPC. ## Add-ons | Tool | Mutates | What it does | |---|---|---| | `list_addons()` *(public)* | no | The curated catalogue with pinned versions + cost impact. | | `install_addon(cluster, addon, issuer_email?, dry_run?)` | yes | Install a pinned add-on (idempotent). Dry-run returns `monthly_delta_eur`. | | `uninstall_addon(cluster, addon)` | yes | Remove it; uninstalling ingress-nginx releases its LB IP (charge stops). | Catalogue: **ingress-nginx** (HTTP(S) ingress behind a dedicated LB IP, +€3.50/mo) · **cert-manager** (free Let's Encrypt TLS; pass `issuer_email`) · **metrics-server** (free; `kubectl top`, HPA) · **redis** (free in-cluster cache; publishes a `redis-auth` Secret you reference with `addon: redis`). ## Cost & quota | Tool | Mutates | What it does | |---|---|---| | `pricing()` *(public)* | no | The rate card: RAM €/GiB-h, volume €/GiB-mo (+ free allowance), LB IP €/mo, tier shapes. | | `estimate(tier, ram, nodes, volume_gib?, lb_ips?)` *(public)* | no | Exact €/hour and €/month for a shape, with a per-dimension breakdown. **Creates nothing.** | | `get_quota()` | no | Limits and usage per dimension + €/month burn and spend cap. | | `usage(month?)` | no | Month-to-date consumption: GiB-hours + € per dimension and per resource, including deleted ones. `month=YYYY-MM` for a past month. | ## Deployments An app is a **deployment** — a named handle on a cluster you ship releases to. Its name is also its namespace, `kbn-`. | Tool | Mutates | What it does | |---|---|---| | `create_deployment(name, cluster, repo?, branch?)` | yes | Create the handle (idempotent). `repo`/`branch` binds Git for auto-builds. | | `list_deployments()` | no | Inventory + state. | | `get_deployment(name)` | no | Release ledger, current pointer, rollout status, secret *names* (never values). | | `deploy(name, dir?/image?)` | yes | Ship a release: build local source in the cloud → push → roll out, or roll out a prebuilt `image`. | | `rollback_deployment(name, release?)` | yes | Re-point to a prior release and re-apply its exact manifests (no rebuild). | | `promote_deployment(src, to, release?)` | yes | Ship one deployment's tested image on another — no rebuild (e.g. staging → prod). | | `set_trigger(name, spec)` | yes | Auto-build policy for a Git deployment: `manual`, `commit:`, `tag:`, `schedule:`. | | `get_deploy_key(name)` | no | The read-only ed25519 public deploy key to add to your Git repo. | | `delete_deployment(name)` | yes | Tear down the app's namespace. **Confirm first.** | ## Build helpers (client-local) These run **on your machine** — stack detection and manifest authoring against the same render engine the cloud uses. They upload nothing. | Tool | Mutates | What it does | |---|---|---| | `detect(path)` | no | Report the detected stack (language, ports, existing files). | | `scaffold(path)` | no* | Write a starter `kubrain.yaml` (+ Dockerfile); never clobbers. | | `validate(path)` | no | Lint `kubrain.yaml` (+ templates) with the cloud's exact checks. | ## Secrets | Tool | Mutates | What it does | |---|---|---| | `set_secret(name, key, value_file)` | yes | Write an app env secret into the cluster's Secret; only the key *name* is recorded centrally, never the value. | | `unset_secret(name, key)` | yes | Remove a secret key. | ## Registry | Tool | Mutates | What it does | |---|---|---| | `get_registry()` | no | Push coordinates (host + your `/` namespace + login). Clusters pull with **no `imagePullSecrets`**. | ## DNS | Tool | Mutates | What it does | |---|---|---| | `create_zone(domain)` | yes | Host a domain (free, idempotent); returns the NS set to delegate at your registrar. | | `list_zones()` / `delete_zone(domain)` | no / yes | List hosted zones / stop hosting (removes all records). | | `set_record(zone, name, type, value, ttl?)` | yes | Upsert an RRset (A, AAAA, CNAME, TXT, MX, SRV, CAA). Live on both nameservers immediately. | | `list_records(zone)` / `delete_record(...)` | no / yes | List / delete a record set. | ## Object storage (buckets) | Tool | Mutates | What it does | |---|---|---| | `create_bucket(name, public?)` | yes | An S3-compatible bucket; `public` (fixed at creation) = anonymous read. | | `list_buckets()` / `delete_bucket(name)` | no / yes | Inventory with live object count + size / delete (empty buckets only). | | `get_bucket_credentials(output_file)` | no | Write the tenant's S3 key pair to a file — one pair opens all your buckets. | ## Observability | Tool | Mutates | What it does | |---|---|---| | `get_metrics(name, component?, window?, by?)` | no | Golden signals — request rate, 5xx error rate (0..1), p95 latency (ms) — with zero app instrumentation. | | `set_alert(deployment, …, notify)` | yes | Threshold on a signal (metric / pod health / ingress uptime+TLS); fires a webhook or email on breach and recovery. Idempotent per rule. | | `list_alerts(deployment)` | no | Each rule's live state (`ok`/`pending`/`firing`) and last sampled value. | ## Blueprints (declarative fleet) Manage the whole tenant as one versioned, diffable document. | Tool | Mutates | What it does | |---|---|---| | `export_blueprint(refs?)` | no | Snapshot current resources as an intent spec (status fields left out). | | `estimate_blueprint(file)` | no | Price the whole document. | | `plan_blueprint(file)` | no | Diff spec vs. live — create / update / delete / no-op per resource. | | `apply_blueprint(file, prune?)` | yes | Converge live state to the document. `prune` also deletes undeclared resources (**confirm first**). | ## See also - **[CLI](/docs/reference/cli)** — the same surface as `kubrain` commands. - **[kubrain.yaml](/docs/reference/kubrain-yaml)** — the manifest `deploy` ships. --- # CLI Source: https://kubrain.dev/docs/reference/cli/ The `kubrain` CLI hits the same HTTP API as the [MCP tools](/docs/reference/mcp-tools), with the same auth — use it for scripting and CI. Commands are grouped; the MCP tool `create_cluster` is `kubrain cluster create`, `deploy` is `kubrain deploy ship`, and so on. ```text kubrain [global options] [subcommand] [options] Global options: --api api-server base URL (default https://api.kubrain.dev; or ~/.kubrain/config) [$KUBRAIN_API] --token tenant bearer token (default ~/.kubrain/config; set via `kubrain auth login`) [$KUBRAIN_TOKEN] ``` ## Top-level commands | Command | What it does | |---|---| | `auth` | Sign up / sign in; manage credentials in `~/.kubrain/config`. | | `vpc` | Manage VPCs (isolated networks). | | `cluster` | Manage Kubernetes clusters. | | `addon` | Install curated add-ons into clusters. | | `deploy` | App deployments — a named handle on a cluster you ship code to. | | `zone` | Host DNS zones for your domains (free). | | `registry` | Show the hosted container registry push coordinates. | | `bucket` | S3-compatible object storage. | | `export` / `plan` / `apply` | Declarative [blueprint](/docs/reference/mcp-tools#blueprints-declarative-fleet) workflow. | | `versions` | Kubernetes versions you can create/upgrade to. | | `regions` | Regions and zones with coarse free capacity. | | `quota` | Your limits and usage with the €/month view. | | `usage` | Month-to-date consumption (GiB-hours + €) per resource, including deleted ones; `--month YYYY-MM` for a past month. | | `pricing` | The rate card — no token needed. | | `estimate` | Price a cluster or a blueprint file — no token needed. | | `mcp` | Run the MCP server (stdio) for Claude / agents. | | `admin` | Administer tenants (requires an admin token). | ## `kubrain cluster` | Subcommand | What it does | |---|---| | `create` | Plan/create a cluster in a VPC. | | `list` | List your clusters. | | `status` | Show a cluster's status. | | `scale` | Change the worker count (`normal`/`ha`). | | `resize` | Change worker RAM; CAPI rolls the worker pool. | | `upgrade` | Upgrade the Kubernetes version (CP first, then workers). | | `reapply` | Re-apply the manifest unchanged to pick up new platform features. | | `kubeconfig` | Print a kubeconfig, or write it to `--output`. | | `delete` | Delete a cluster (cascades to its VMs). | ## `kubrain deploy` The busiest group — build, ship, observe, and secure an app. | Subcommand | What it does | |---|---| | `create` | Create a deployment handle on a cluster (idempotent); `--repo` auto-builds on push. | | `list` / `get` | Inventory / show one deployment. | | `ship` | Build the local source in the cloud and roll it out (or `--image` for prebuilt). | | `rollback` | Re-point to a prior release (default: last good). | | `promote` | Ship one deployment's tested image on another — no rebuild. | | `set-trigger` | Auto-build policy: `manual` \| `commit:` \| `tag:` \| `schedule:`. | | `deploy-key` | Print the read-only Git deploy key to add to your repo. | | `logs` | Tail recent pod logs live from the cluster (nothing stored). | | `metrics` | Golden signals (req rate, 5xx rate, p95) — no instrumentation. | | `alert` | Arm alerts on a deployment (evaluated server-side; webhook/email). | | `set-secret` / `unset-secret` | Set/remove a secret (value from `--value-file` or `--from-env`). | | `detect` / `scaffold` / `validate` / `render` | Local: inspect stack, write starter manifest, lint, render manifests to stdout. | | `delete` | Tear down a deployment. | ## `kubrain zone` | Subcommand | What it does | |---|---| | `create` | Host a domain (idempotent, free); prints the NS set to delegate. | | `list` / `get` | List zones / show a zone with its records and NS set. | | `set` / `unset` | Create-or-replace / delete a record set (repeat values for round-robin). | | `records` | List a zone's records. | | `delete` | Stop hosting a domain (removes all records). | ## `kubrain bucket` | Subcommand | What it does | |---|---| | `create` | Create a bucket (the first one also provisions your S3 user). | | `list` / `get` | Inventory / show a bucket with live stats. | | `credentials` | Print your S3 endpoint + key pair, or write to `--output`. | | `delete` | Delete an **empty** bucket (non-empty is refused). | ## `kubrain addon` | Subcommand | What it does | |---|---| | `list` | Installable add-ons with versions and cost — no token needed. | | `install` | Install an add-on into a running cluster (idempotent). | | `uninstall` | Remove an add-on (ingress-nginx also releases its LB IP). | ## Blueprints ```bash kubrain export > tenant.yaml # snapshot live resources as a blueprint kubrain plan --file tenant.yaml # diff spec vs. live (read-only) kubrain apply --file tenant.yaml # converge (add --prune to delete orphans) kubrain estimate --file tenant.yaml # price the whole document ``` See the [MCP tools](/docs/reference/mcp-tools) reference for return shapes; the two surfaces are the same engine. --- # kubrain.yaml Source: https://kubrain.dev/docs/reference/kubrain-yaml/ `kubrain.yaml` lives in your repository and travels with every release. It describes what to build and how to run it; the cloud renders Kubernetes objects from it with a deterministic engine, so `kubrain deploy validate` locally matches what the cloud produces. It comes in two mutually-exclusive shapes. ## Flat (single component) ```yaml build: dockerfile: Dockerfile # path relative to the context root; OR # buildpack: auto # no Dockerfile → Cloud Native Buildpacks / Nixpacks port: 8080 # container port command: ["./server"] # optional; overrides the image entrypoint replicas: 3 resources: ram: 512Mi # billed dimension health: path: /healthz # probe path # port: 8080 # defaults to the top-level port env: # each entry is ONE of four forms (see Secrets & env) LOG_LEVEL: info # 1. literal value DB_PASSWORD: {secret: db-app, key: password} # 2. from a Secret key TUNING: {configMap: app-config, key: level} # 3. from a ConfigMap key REDIS_URL: {addon: redis, key: url} # 4. from a built-in addon's Secret envFrom: # import every key of a Secret/ConfigMap as env vars - secret: db-app # (add `prefix: FOO_` to namespace the keys) - addon: redis service: enabled: true # default true when `port` is set type: ClusterIP # ClusterIP | LoadBalancer ingress: enabled: true # default true when any host is set host: app.example.com # shorthand for a single host # hosts: [app.example.com, www.app.example.com] # multiple hosts on one cert tls: true # one SAN cert covering all hosts, via cert-manager deployment: strategy: RollingUpdate # RollingUpdate | Recreate ``` ## Multi-component A deployment can be several workloads that ship as one release and roll/rollback together: ```yaml components: web: build: {dockerfile: Dockerfile} port: 8080 replicas: 3 ingress: {enabled: true, host: app.example.com, tls: true} env: {LOG_LEVEL: info} worker: build: {dockerfile: Dockerfile.worker} command: ["./worker"] service: {enabled: false} # a background worker needs no Service redis: image: redis:7-alpine # prebuilt dependency — NOT built from source port: 6379 service: {enabled: true} # reachable as -redis:6379 ``` ## Rules that matter - **Flat and `components:` are mutually exclusive.** - A component has **either** `build:` (built from source) **or** `image:` (prebuilt) — never both. Prebuilt components skip the build. - Each built component's image is named `-` (e.g. `myapp-web`); a flat app builds one image named for the deployment. - Components find each other by the derived Service name `-` on their port — inject those addresses via `env`. ## Secrets & env Each `env:` entry is exactly **one of four forms** (the validator rejects combinations, e.g. a literal alongside a ref): | Form | Syntax | Becomes | |---|---|---| | **1. Literal** | `NAME: value` | `value:` — no lookup | | **2. Secret key** | `NAME: {secret: s, key: k}` | `secretKeyRef` | | **3. ConfigMap key** | `NAME: {configMap: c, key: k}` | `configMapKeyRef` | | **4. Addon credential** | `NAME: {addon: redis, key: url}` | a resolved `secretKeyRef` | `key:` defaults to the env var's own name when omitted. `envFrom:` mirrors forms 2–4 for whole-secret/ConfigMap import (each entry sets exactly one of `secret:`, `configMap:`, or `addon:`, plus an optional `prefix:`). A `namespace:` on a Secret ref makes Kubrain **mirror** that Secret into the app's namespace at rollout (re-synced each deploy, so rotation follows). Values set via [`set_secret`](/docs/reference/mcp-tools#secrets) are referenced, never baked into the image — so rollback and promotion keep working and pick up current values on the next deploy. ## Custom manifests Kubrain renders from templates, resolved per kind: 1. **Built-in presets** — Deployment (always), Service and Ingress (when enabled). Enough for most apps. 2. **Per-repo override** — a file at `kubrain/k8s-templates/.yaml` (`deployment.yaml`, `service.yaml`, `ingress.yaml`) *replaces* the built-in for that kind. Use for a StatefulSet, custom annotations, etc. 3. **Extra resources** — any other file in `kubrain/k8s-templates/` (a `cronjob.yaml`, `hpa.yaml`, a CRD) renders as an additional object applied with the release. Any custom workload template **must** reference `{{ .Image }}` (the built digest for the current component). Iterate with `kubrain deploy render --dir ` to see the exact objects before shipping. ## Storage Every cluster ships a default StorageClass **`kubrain`**. Declare a standard PVC (`accessModes: ["ReadWriteOnce"]`, `storageClassName: kubrain`); it binds when a pod first uses it, and data survives restarts and reschedules. Design around the RWO access mode — single-instance stateful workloads use `replicas: 1` + one PVC + `deployment.strategy: Recreate`; multi-replica stateful workloads use a StatefulSet override with `volumeClaimTemplates`. Volumes bill at €0.05/GiB-month (first 20 GiB free). ## See also - **[Quickstart](/docs/quickstart)** — where `kubrain.yaml` fits in the ship flow. - **[MCP tools](/docs/reference/mcp-tools)** — `deploy`, `set_secret`, `validate`. --- # Pricing Source: https://kubrain.dev/docs/reference/pricing/ Kubrain has **one transparent price**, and your agent can compute the bill in one multiplication before creating anything. The `pricing` and `estimate` tools are **public** (no token) — they are pure math over published rates, and `estimate` runs the same code as `create_cluster`, so a quote, a dry-run plan, and a quota admission check can never disagree. ## The billed dimensions | Dimension | Rate | Notes | |---|---|---| | **RAM** | **€0.006 / GiB-hour** (≈ **€4.38 / GiB-month**) | The binding resource. Control plane, bundled vCPU (~1 per 2 GiB), and egress are **included**. | | **Standard volumes** | **€0.05 / GiB-month** | **First 20 GiB free** per tenant. | | **LoadBalancer public IPs** | **€3.50 / month** each | One is used by `ingress-nginx`; raw `type: LoadBalancer` Services allocate their own. | RAM is the one dimension that scales with your cluster shape — `ram` is GiB **per node**. Everything else about compute (CPU, the control plane, network egress) is bundled into that number. ## Tiers `tier` sets the control-plane shape; it does not change the RAM rate. | Tier | Control plane | Workers | Use for | |---|---|---|---| | `dev` | 1, schedulable | 0+ | prototypes, sandboxes | | `normal` | 1, dedicated | 1+ | small non-critical apps | | `ha` | 3, etcd quorum across ≥3 zones | 1+ | production | ## Quote before you create ```bash kubrain pricing # the rate card + tier shapes kubrain estimate --tier ha --ram 8 --nodes 2 # exact €/hour and €/month kubrain estimate --tier ha --ram 8 --nodes 2 --volume-gib 100 --lb-ips 1 ``` `estimate` returns the €/hour and €/month with a per-dimension breakdown and **creates nothing**. The same numbers come back as `monthly_eur` in a `create_cluster` dry-run and in `install_addon`'s `monthly_delta_eur`. ## Worked example An `ha` cluster, 3 control-plane nodes + 2 workers, all 8 GiB, with `ingress-nginx` and a 100 GiB volume: - RAM: 5 nodes × 8 GiB × €4.38 = **€175.20 / month** - Volume: (100 − 20 free) × €0.05 = **€4.00 / month** - LB IP (ingress-nginx): **€3.50 / month** - **Total ≈ €182.70 / month** ## See also - **[Quotas](/docs/reference/quotas)** — your RAM cap is your spend cap. - **[Clusters](/docs/guides/clusters)** — picking a tier and size. --- # Quotas Source: https://kubrain.dev/docs/reference/quotas/ Two gates sit in front of everything that spends money or consumes hardware. Both are enforced **server-side on every call** — an agent in a loop cannot spend past them. ## Your per-tenant quota — the spend cap Your quota caps each billed dimension. Because billing is on RAM, **your RAM cap is effectively your spend cap**. `get_quota` reports, per dimension, your limit, current usage, and the resulting €/month burn against the cap: ```bash kubrain quota ``` Returns limits & usage for **RAM**, **clusters**, **volumes**, and **LB IPs**, plus the €/month view. Read it before provisioning anything large — a `create_cluster` that would breach the cap is refused with a structured error telling you by how much. ## Per-host capacity — the metal gate Separately, a cluster's nodes have to physically fit on a host in the target zone. `regions` shows coarse free RAM headroom per zone; if a shape doesn't fit, the error is written for an agent to act on — e.g. *"capacity full for 32 GB nodes in this zone; largest available is 16 GB"* — so you can pick a smaller node or a different zone and retry. ## How this shows up in practice - **Planning:** `estimate` + `get_quota` together answer "can I afford this, and will it fit?" before you commit. - **At apply:** `create_cluster`/`scale_cluster`/`resize_cluster` re-check both gates; a refusal is always a clear, actionable error, never a silent partial. - **No runaway:** hard caps plus rate limits mean a misbehaving agent can't drain the IP pool or provision 200 clusters. ## See also - **[Pricing](/docs/reference/pricing)** — the rates the quota meters. - **[MCP tools › Cost & quota](/docs/reference/mcp-tools#cost--quota)** — `pricing`, `estimate`, `get_quota`.