> ## Documentation Index
> Fetch the complete documentation index at: https://docs.novacula.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Provision an executor on Kubernetes

> Install the Novacula Operator with Helm and connect it to your organization

This guide takes you from a fresh Kubernetes cluster to a registered, online Operator executor. End-state: a single `nvcl-operator` `Deployment` runs in the `nvcl-system` namespace, the Hub sees the executor as `online`, and you can deploy nodes from the UI.

The **Operator** is the Kubernetes executor: it runs each node as a `StatefulSet` of pods and reports status back to the Hub. Choose it when you already run Kubernetes, want pod-level isolation, or plan to scale fleets. For the executor concept and the Agent-vs-Operator choice, see [Executors](/docs/executors/overview).

## Prerequisites

* **Kubernetes** — a cluster serving `networking.k8s.io/v1` (Ingress, NetworkPolicy). The chart declares no `kubeVersion` floor.
* **Cluster-admin at install time** — the chart creates a namespaced `Role`/`RoleBinding` and, for cluster telemetry, a small cluster-scoped `ClusterRole` that reads node capacity.
* **A storage class** suitable for blockchain workloads — typically a CSI provider with `WaitForFirstConsumer` binding, fast NVMe-backed for heavy chains.
* **Outbound HTTPS** from the Operator pod to the Hub (`https://hub.novacula.io`). No inbound ports are needed.
* **An executor API key**, created via **Connect Executor** (Operator kind) — see [Connect an executor](/docs/executors/connect-an-executor).
* **(Optional) cert-manager** with a ClusterIssuer — only if you'll expose node RPC via **proxy** with automatic TLS. See [RPC exposure](/docs/nodes/rpc-exposure).

## Steps

<Steps>
  <Step title="Run the Helm command">
    From the **Connect Executor** screen in the UI, copy the exact Helm command. There's no `helm repo add` — the chart is an OCI ref. It installs the Operator in **hub mode** (it syncs to the Hub each tick) and renders the Hub token into a `Secret`. It looks like this:

    ```bash theme={null}
    helm upgrade --install nvcl-operator oci://ghcr.io/rsquad/nvcl/charts/nvcl-operator \
      --namespace nvcl-system --create-namespace \
      --set image.tag="<version>" \
      --set gateway.tag="<version>" \
      --set hub.url="https://hub.novacula.io" \
      --set-string hub.token="<executor-api-key>" \
      --set hub.name="<executor-name>"
    ```

    * `image.tag` pins the Operator version; `gateway.tag` pins the matching per-node RPC-auth gateway sidecar (same version) — required once any node uses RPC-key auth.
    * `hub.url` and `hub.token` are the Hub connection; `hub.name` is the executor's identity in the Hub (defaults to the Helm release name).
    * `--set source=crd` switches to standalone/GitOps mode (reconcile `Node` CRs in-namespace, no Hub); the default is `hub`.

    The chart deploys, all in the release namespace:

    * A single-replica `Deployment` `nvcl-operator` (+ `ServiceAccount`), running the operator with `run --config /etc/nvcl/operator.toml`.
    * A `Secret` `nvcl-operator-config` holding `operator.toml` (with the Hub token) — mounted at `/etc/nvcl/operator.toml`.
    * A namespaced `Role` + `RoleBinding` `nvcl-operator` covering `statefulsets`, `configmaps`/`secrets`/`services`/`persistentvolumeclaims`, `ingresses`/`networkpolicies`, `pods`/`pods/log`, and `novacula.io` `nodes` (+ `/status`); with self-update on, it also patches this Deployment (via `resourceNames`).
    * A small cluster-scoped `ClusterRole` + binding that reads node capacity for cluster telemetry (disable with `--set clusterNodeRead=false`).
  </Step>

  <Step title="Confirm online in the UI">
    ```bash theme={null}
    kubectl -n nvcl-system rollout status deploy/nvcl-operator
    kubectl -n nvcl-system logs deploy/nvcl-operator -f
    ```

    The first log lines show the Operator loading its config, syncing with the Hub, and reporting its capabilities. The executor row in the UI flips to `online` within seconds.

    If it stays offline:

    * Check Operator logs for auth errors.
    * Confirm outbound HTTPS to the Hub URL.
    * Verify `hub.token` is the `exc_` key from the **Connect Executor** step.
  </Step>
</Steps>

## What lives in the cluster

Static (chart-managed, in the release namespace):

```
namespace/nvcl-system
├── deployment/nvcl-operator
├── serviceaccount/nvcl-operator
├── role + rolebinding/nvcl-operator            ← namespaced ops
├── clusterrole + binding (node-capacity read)  ← cluster telemetry (optional)
└── secret/nvcl-operator-config                 ← operator.toml (holds the Hub token)
```

Dynamic (Operator-managed, per deployed node):

```
statefulset/<node>          ← node pods
configmap/<node>            ← rendered config
secret/<node>               ← per-node secrets (e.g. Ethereum JWT, RPC-key hash)
service/<node>…             ← headless + client services
persistentvolumeclaim/…     ← node data volumes
ingress/<node>…             ← for proxy-exposed ports
networkpolicy/<node>        ← for RPC-key-gated nodes (which also get a gateway sidecar in the pod)
```

The Operator manages these with server-side apply; deleting a node removes its objects (including its `persistentvolumeclaim`s).

## Exposing node RPC

Each node's RPC exposure is set per node (see [RPC exposure](/docs/nodes/rpc-exposure)); the Operator materializes it:

* **Direct** → a `NodePort` or `LoadBalancer` Service (your choice).
* **Proxy** → an `Ingress` at your domain, with TLS via cert-manager when you set a ClusterIssuer, and optional RPC-key auth through the gateway sidecar.
* **Closed** → internal `ClusterIP` only.

For proxy TLS, set the cert-manager ClusterIssuer and IngressClass via the chart's `proxy.*` values (see below).

## Operating

```bash theme={null}
kubectl -n nvcl-system get pods
kubectl -n nvcl-system logs deploy/nvcl-operator -f
kubectl -n nvcl-system logs <node-pod> -c <role> -f

helm upgrade nvcl-operator oci://ghcr.io/rsquad/nvcl/charts/nvcl-operator -n nvcl-system --reuse-values
helm -n nvcl-system uninstall nvcl-operator
```

The Operator's own upgrade is driven from the UI/Hub — a dispatched release makes the Operator patch its own `Deployment` image. Run `helm upgrade` only for chart-level changes (RBAC, resources, values).

## Configuration (Helm values)

The chart composes `operator.toml` from its values and renders it into the `Secret`. Common values:

```yaml theme={null}
image:
  tag: ""            # required: the released operator version

gateway:
  tag: ""            # required for RPC-key nodes: matches the operator version

source: hub          # "hub" (sync with the Hub) or "crd" (standalone Node CRs, no Hub)

hub:
  url: ""            # required in hub mode; your Hub URL
  token: ""          # required in hub mode; your exc_ executor key (stored in the Secret)
  name: ""           # executor identity; defaults to the Helm release name

selfUpdate:
  enabled: true      # let the Hub patch the Operator's Deployment image

logging:
  level: info        # trace | debug | info | warn | error

proxy:                            # TLS for proxy-exposed node RPC (cert-manager must already exist)
  clusterIssuer: letsencrypt-prod # cert-manager ClusterIssuer; "" disables the annotation
  ingressClass: nginx             # IngressClass; "" uses the cluster default

clusterNodeRead: true  # ClusterRole reading node capacity for cluster telemetry

resources:
  requests: { cpu: 100m, memory: 512Mi }
  limits:   { memory: 1Gi }

# Advanced sections appended verbatim to operator.toml, for what the chart doesn't template
# (e.g. [telemetry]). Must be valid TOML; the operator parses strictly:
extraConfig: |
  [telemetry]
  # ...
```

* The namespace the Operator watches and deploys nodes into is the **Helm release namespace** (`--namespace`).
* Per-node settings (resources, config/CLI overrides, RPC exposure) aren't Helm values — set them per node in the UI. See [Edit node configuration](/docs/nodes/edit-configuration).

## Hardening

* **Keep the Hub token secret** — it lives in the `nvcl-operator-config` `Secret`; restrict who can read Secrets in `nvcl-system`.
* **Let the Hub own the image** when self-update is on — pinning `image.tag` and then `helm upgrade --reuse-values` re-applies the pin and can undo a self-update. Either let self-update own the image, or manage it only via Helm.
* **Restrict egress** from `nvcl-system` to the Hub URL (and per-chain image/binary sources) if you run NetworkPolicies.

## Next steps

* [Deploy a node](/docs/nodes/deploy-a-node) onto your fresh executor.
