# 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`.
]