# Self-hosted runtime

> Run a Botyard bot's runtime in your own Kubernetes cluster: how it connects, what it needs, what happens while it is offline, and who can use it.

Canonical URL: https://docs.botyard.io/docs/self-hosted

A self-hosted bot is a Botyard bot whose runtime, `botyardd`, runs in **your** Kubernetes cluster
instead of on Botyard's infrastructure. It has the same chat, schedules and skills as a hosted
bot, and most of the same tools; the few that aren't available yet are listed under
[Known limitations](#known-limitations). You use and manage it in the Botyard app as usual.

The exception is tools that act on the runtime's own host, such as reading files or starting
processes. Which host tools the bot is offered depends on what it is assigned in Botyard, not on
the runtime. The runtime's local host policy decides what it will actually perform, and a default
install's empty policy performs none: an assigned host tool can still be offered, but the runtime
refuses the call and audits it. The **Host policy and security** page in this section covers this.

> [!NOTE]
> **Enabled per organization**
>
> The self-hosted runtime is available only to organizations that Botyard has enabled. If your
> organization doesn't offer a self-hosted option when you create a bot, contact Botyard to request
> access. Kubernetes is the only supported platform today. Linux VM and macOS installs are not yet
> available.

## In this section

| Page                     | What it covers                                                                                        |
| ------------------------ | ----------------------------------------------------------------------------------------------------- |
| Install on Kubernetes    | Install the Helm chart, verify the signed image and chart, and configure a proxy or private registry. |
| Enrollment               | Enrollment tokens, how the device identity works, and replacing or moving a device.                   |
| Host policy and security | What the runtime may do on your cluster, the pod's security posture, and audit logs.                  |
| Operations               | Status, upgrades, lost volumes, uninstalling, and troubleshooting.                                    |

## How it works

* **Outbound-only control plane.** For its own control-plane traffic, the runtime opens HTTPS
  and WebSocket (WSS) connections to Botyard and listens on no network port for Botyard, so it
  needs no Service, Ingress or inbound firewall rule. This covers the runtime's own transport
  only. Commands it starts through `exec`, and services it supervises, can open listeners and
  connect on other ports, and having no Service or Ingress does not stop direct connections to
  the Pod IP. Use the chart's
  [NetworkPolicy](/docs/self-hosted/install-kubernetes#egress-networkpolicy-networkpolicy) (off
  by default) or your own ingress controls.
* **No model credentials in your cluster.** The runtime sends model requests to Botyard's model
  broker over its authenticated connection. Botyard makes the provider call with the LLM
  credential configured in Botyard, so no provider key is stored in your cluster.
* **Secrets on demand.** When a bot needs a secret from its Runtime Vault, the runtime requests
  a short-lived lease from Botyard over the same connection. The bot's secrets aren't copied into
  your cluster's configuration.
* **Device identity.** You install the runtime with a single-use enrollment token, minted on
  the bot's page in Botyard. On first start, the runtime enrolls and creates a device key on its
  persistent volume. From then on, that key identifies the runtime. The volume **is** the
  device: if you lose it, you revoke the device in Botyard and enroll again. The
  **Enrollment** page in this section covers this in detail.

Botyard allows one active device per bot, so a bot runs as exactly one replica.

## Requirements

**In Botyard**

* Your organization is enabled for the self-hosted runtime.
* An LLM provider credential, added under **User menu** › **Settings** › **Credentials** › **LLM Providers** or on
  **User menu** › **Settings** › **Bots** › *‹your bot›* › **Credentials**, **before the bot's first turn**. Without one, every turn fails with
  "No credential is configured for provider scope 'llm'". The app can show the bot as ready
  even when no credential exists, so don't rely on that as a check.

**Cluster and tools**

* Kubernetes **1.25** or later.
* Helm **3.8** or later. The chart is published to an OCI registry.
* A storage class that can provision a **ReadWriteOnce** persistent volume.
* The pod runs under the `restricted` Pod Security Standard. It runs as a non-root user with a
  read-only root filesystem and no privilege escalation.

**Resources** (chart defaults, all adjustable)

| Resource               | Default             |
| ---------------------- | ------------------- |
| Replicas               | 1 (fixed)           |
| Persistent volume      | 10Gi, ReadWriteOnce |
| CPU request            | 250m                |
| Memory request / limit | 512Mi / 2Gi         |
| Scratch space (`/tmp`) | 1Gi                 |

**Network egress** (HTTPS, TCP 443)

| Destination                                                                 | Used for                                                                        |
| --------------------------------------------------------------------------- | ------------------------------------------------------------------------------- |
| `api.botyard.io`                                                            | Enrollment and session setup                                                    |
| `runtime.botyard.io`                                                        | The runtime's live connection (WSS): chat, tools, model calls and secret leases |
| `hel1.your-objectstorage.com` and `botyard-api.hel1.your-objectstorage.com` | File transfers through presigned URLs                                           |
| `ghcr.io`                                                                   | Pulling the image and chart, unless you mirror them to your own registry        |

The runtime also needs DNS. If your egress goes through a proxy, the runtime supports an HTTP
`CONNECT` proxy with `NO_PROXY` exceptions, and can trust an extra CA bundle for TLS-inspecting
proxies. The **Install on Kubernetes** page in this section covers the settings.

## When the runtime is offline

A self-hosted bot can only work while its runtime is connected. If the pod is stopped,
restarting or has lost its network:

* **Chat.** Botyard waits a few seconds for the runtime to reconnect. If it doesn't, your message
  is not sent. Instead, a card under it says **The bot's machine is offline**, with when the
  runtime was last seen. Choose **Retry** once the runtime is back.
* **Scheduled messages and awakes.** Botyard keeps trying to deliver the message for a
  reconnect window: the schedule's own wait, or **3 minutes** if it sets none, up to a maximum
  of 1 hour. For a recurring schedule, the window also ends when the next occurrence is due,
  whichever comes first. If the runtime is back within the window, the run happens late. If
  not, the run is recorded as missed and isn't caught up later, even when the schedule is set
  to **Run once when it's back**.
* **Scheduled commands** are not retried. A command that falls due while the runtime is offline
  is missed.
* **The bot itself is unaffected.** It stays enrolled and authorized. On the bot's page, its
  device shows as offline with when it was last seen. When the runtime reconnects, it picks up
  new messages and runs normally.

## Known limitations

* **Kubernetes only.** Linux VM and macOS installs are not yet available.
* **No high availability.** A bot runs as one replica. Restarts are covered by the runtime
  reconnecting, not by failover.
* **Enrollment token only.** Workload identity federation (for example IRSA or service account
  OIDC) is not supported yet, and the device key is stored on the volume rather than in a KMS
  or TPM.
* **No customer SPIRE mesh.** Don't inject a SPIRE socket (`SPIFFE_ENDPOINT_SOCKET`) into the
  runtime pod. The runtime would treat it as Botyard's hosted identity path and fail to connect.
* **No self-update.** You upgrade by changing the chart and image version you pin.
* **No background services or Bot Pages.** The bot can't register or supervise long-running
  services on the runtime (the `service_*` tools), and it can't publish a Bot Page from it.
