> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://docs.ovalbee.com/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.ovalbee.com/_mcp/server.

# Compute

Every node run and every deployment executes as a container. Where those containers start is set once per installation by an instance administrator, under **Admin** → **Configuration** → **Executor**. On OvalBee Cloud, compute is provided and this page is background reading.

## Executor backends

| Backend        | How it works                                                                  | Good for                                                              |
| -------------- | ----------------------------------------------------------------------------- | --------------------------------------------------------------------- |
| **Docker**     | Containers start on the Docker host OvalBee runs on.                          | A single powerful machine, evaluations, small teams.                  |
| **Kubernetes** | Each run is a pod in a Kubernetes cluster, placed by the cluster's scheduler. | Teams sharing a pool of GPU nodes, scaling out, separating workloads. |

With Kubernetes, the cluster can be the one OvalBee itself runs in, or a **separate cluster** — for example a GPU cluster in another account or on premises, while OvalBee runs elsewhere. The separate cluster gets a small executor-only installation, and the main installation is given its API address and credentials. Deployments started on a separate cluster need to reach the OvalBee API over the network.

On a self-hosted installation, the Kubernetes backend needs the Business plan; Community runs on Docker. Without the plan, **Kubernetes** is shown locked on the **Executor** tab and saving it is refused. An installation already set to Kubernetes, in the UI or in the configuration file, starts nothing new until it is upgraded or switched to Docker: a manual run, a deployment or a webhook is refused (HTTP 403) saying the plan does not include it, a scheduled run is skipped, a step waiting on an upstream node does not run, and a run or deployment that was already on its way fails with the same message. Runs and deployments started earlier keep running and can still be stopped, also after the executor is switched to Docker, as long as the Kubernetes options stay in the executor settings and the cluster stays reachable. Otherwise a stopped run is marked cancelled, but its Kubernetes Job is left for you to delete: the run's log names it.

### Isolation on Kubernetes

Node runs execute code from whoever built the flow, so on Kubernetes every run and deployment pod is locked down by default. Each restriction can be switched off or replaced.

* By default the Helm chart runs tasks and deployments in a namespace of their own (`ovalbee-workloads` for a release named `ovalbee`, or the name you give) and creates it with the Pod Security `restricted` level enforced. Uninstalling the chart keeps that namespace, so running tasks survive; delete it yourself. Set `executorNamespace.create: false` with `executorNamespace.name` to use a namespace you manage, or with no name to run tasks in OvalBee's own namespace, which leaves them next to its databases and Secrets and without a quota. Image pull secrets named in the executor options must exist in the workloads namespace. Pod settings that `restricted` rejects, such as a root user, need `executorNamespace.podSecurity: baseline`.
* The pod runs as an unprivileged user (uid 1000) with no Linux capabilities and a read-only image filesystem. Only `/tmp` is writable, and its size counts toward the container's `ephemeral-storage` limit, which you set in the executor's `containerResources`. These settings come from `podSecurityContext` and `containerSecurityContext` in the executor options. A value you set replaces the default, and `{}` removes it. OvalBee's node images are built for uid 1000 and a read-only filesystem, so a different user needs images that user can read.
* The pod gets no Kubernetes service account token. Set `automountServiceAccountToken: true` in the executor options to give it one.
* The Helm chart adds a NetworkPolicy to the executor namespace. Pods may reach DNS, the OvalBee API and the public internet, but not other cluster addresses, private networks or cloud metadata. A model deployment accepts connections only from the OvalBee API and from the workloads' own task pods, which call it when a node takes a deployment input. The policy needs a CNI that enforces NetworkPolicy. Under `executor.networkPolicy` you can:

  * switch each built-in rule (`dns`, `api`, `apiToDeployments`, `tasksToDeployments`, `internet`) on or off;
  * add your own rules with `extraEgress` and `extraIngress`, for example a private S3 endpoint;
  * replace either direction with `ingress` or `egress`;
  * turn the whole policy off with `enabled: false`.

  DNS is allowed on port 53 to any address, which works with every DNS layout, including NodeLocal DNS. Set `dns.to` to limit it to your resolver.

  Without a controller that enforces NetworkPolicy, Kubernetes stores the policy, enforces nothing, and gives no warning. Plain flannel is one such setup. To check your cluster, start a pod that the policy selects in your workloads namespace and try to reach the Kubernetes API from it:

  ```bash
  kubectl -n ovalbee-workloads run ovalbee-netcheck --rm -i --restart=Never --image=curlimages/curl \
    --labels=app.kubernetes.io/managed-by=ovalbee,app.kubernetes.io/component=task-runtime \
    --overrides='{"spec":{"securityContext":{"runAsNonRoot":true,"runAsUser":1000,"seccompProfile":{"type":"RuntimeDefault"}},"containers":[{"name":"ovalbee-netcheck","image":"curlimages/curl","args":["-sk","-m","5","https://kubernetes.default.svc/version"],"securityContext":{"allowPrivilegeEscalation":false,"capabilities":{"drop":["ALL"]}}}]}}'
  ```

  With the policy enforced, curl times out and the pod ends in `Error`. Any HTTP response, including `401 Unauthorized`, means the policy is not enforced.
* When the executor namespace is separate from the release namespace, the chart also adds a LimitRange and, if you enable it, a ResourceQuota. The LimitRange gives containers that set no `ephemeral-storage` limit 50 Gi; replace it with `executor.limitRange.limits`. The ResourceQuota (`executor.resourceQuota.spec`) caps the pods, GPUs and scratch space the namespace may hold at once. Both apply to every pod in their namespace, so the chart skips them when runs share a namespace with OvalBee itself.
* `activeDeadlineSeconds` in the executor options stops any run that goes over its time limit, and the task's error names the deadline.

With an executor-only installation, the OvalBee API runs elsewhere, so the chart refuses to render until you set `executor.networkPolicy.apiPeers` to its address, for example `[{cidr: 203.0.113.10/32, ports: [443]}]`, or switch the policy off with `executor.networkPolicy.enabled: false`. Runs may reach each peer on its ports, and each peer may connect to deployments. The API calls a deployment by its cluster-internal name, so serving a model from a separate cluster also needs an API that can resolve and route to it. A request mapping can override any pod setting for its machine class.

## Machine classes

Every node that runs a container, and every deployment, runs on a **machine class** — a named kind of machine that people pick from a node's run button and in the Deploy dialog. Classes are managed under **Admin** → **Configuration** → **Machine classes**.

A class points to one entry of `requestMappings` in the executor options, or to none, which means the executor's defaults. The entry is what the class means on the chosen backend:

* On **Docker**, a mapping such as `gpu` runs containers with the NVIDIA runtime, so the GPUs must be on the Docker host with the NVIDIA container toolkit installed.
* On **Kubernetes**, a mapping sets pod settings: resource requests such as one `nvidia.com/gpu`, node selectors, tolerations and affinity. That's how you steer training to your GPU nodes and keep everything else off them.

For each class you set its name, its **device type**, the request mapping it points to, and optionally a description and, on OvalBee Cloud, the pricing key its credits come from. The device type is a free name — `cpu`, `gpu`, or whatever else your cluster offers — and it is what matches a class to a node: a node declares the device types it can run on, and only classes of those types are offered for it. Give a class the device type its mapping really provides, or nodes will be placed on machines that cannot run them. A class you disable is hidden from users.

On OvalBee Cloud a class can also differ by plan: a second request mapping used for workspaces on the Free plan, so the same class is placed on different hardware for them, and a **Paid plans only** switch for a class Free workspaces may not run at all.

A new installation starts with two classes: **CPU**, with the executor's defaults, and **GPU**, pointing to the `gpu` mapping both default executor configurations include. On Docker, GPU is only created when the Docker daemon has the `nvidia` runtime that mapping asks for; on a host without one, nodes that need a GPU say so instead of failing. If you add a GPU later, add the class yourself. On Kubernetes both classes are always created — disable GPU if your cluster has none. Saving the **Executor** tab is refused when it would remove a mapping an enabled class still points to, and saving **Machine classes** is refused while an enabled class points to a mapping that does not exist.

The order of the list is the order people see, and the **first enabled class of each device type is that type's default**. A node or deployment left on its default follows them: at every launch it takes the default class of the first device type it declares that has one, so reordering the list moves those nodes too. Someone can instead pin a class, and a pinned class is never swapped for another — if it is later disabled, removed, or no longer fits the node, the next run is refused with a message saying so.

## What users notice

* A node's page in the Node Library shows the device types it runs on, and the node only offers classes of those types. A node with no class to run on shows a warning instead of a machine to pick.
* On OvalBee Cloud, each class shows its price in credits per hour, and the run button shows it in euros: 1,000 credits are €1. A workspace's plan can cap how many runs it holds of a class at once: at the cap, starting another run on that class is refused, saying how many are in progress, until one of them finishes; runs are never queued. A class reserved for paid plans still appears to a Free workspace, greyed out with an upgrade, and a run started on it anyway is refused (HTTP 402).
* On OvalBee Cloud, the ring next to the space selector shows how much of the month's credits is spent, and a banner warns once the balance needs attention — under a fifth of the allowance, or already spent. The Billing page lists what each run, deployment and agent chat cost this month, and lets a workspace owner on Pro buy more credits at the same rate. Bought credits never expire and are spent only once the month's allowance is gone, so they sit beside the monthly figure rather than in it. A workspace downgraded to Free can still spend credits it bought on Pro, but cannot buy more until it upgrades again.
* On OvalBee Cloud, a workspace that spends the month's credits and has none left over is stopped: its running tasks are cancelled and its deployments stopped, each keeping "ran out of credits for this month" as its reason, and nothing new starts — a manual run, a deployment or a webhook is refused (HTTP 402) saying the month's credits are used up, a scheduled run is skipped, and a step waiting on an upstream node does not run. Runs resume when the credits reset at the start of the next month, when the plan's limit is raised, or once more credits are bought. Compute is charged as the cluster reports it, about once a minute, so a run can overspend by around that much before it is stopped.
* The first run of a node on a machine can take a little longer while its container image is pulled.
* A **deployment** holds its machine for as long as it runs, because the model stays loaded in memory. Stop deployments you don't use to free the GPU.
* **Activity** in the main sidebar shows everything running and waiting across the workspace.

## Related pages

* [Self-hosted installation](/administration/self-hosting) — the full picture of an installation.
* [Platform configuration](/administration/platform-configuration) — the Executor tab among the other settings.
* [Models and deployments](/concepts/models#deployments) — what a deployment is.