ToolHive Operator Helm Chart
July 22, 2026 ยท View on GitHub
A Helm chart for deploying the ToolHive Operator into Kubernetes.
TL;DR
helm upgrade -i toolhive-operator oci://ghcr.io/stacklok/toolhive/toolhive-operator -n toolhive-system --create-namespace
Prerequisites
- Kubernetes 1.25+
- Helm 3.10+ minimum, 3.14+ recommended
Usage
Installing from the Chart
Install one of the available versions:
helm upgrade -i <release_name> oci://ghcr.io/stacklok/toolhive/toolhive-operator --version=<version> -n toolhive-system --create-namespace
Tip: List all releases using
helm list
Uninstalling the Chart
To uninstall/delete the toolhive-operator deployment:
helm uninstall <release_name>
The command removes all the Kubernetes components associated with the chart and deletes the release. You will have to delete the namespace manually if you used Helm to create it.
Values
| Key | Type | Default | Description |
|---|---|---|---|
| fullnameOverride | string | "toolhive-operator" | Provide a fully-qualified name override for resources |
| nameOverride | string | "" | Override the name of the chart |
| operator | object | {"affinity":{},"autoscaling":{"enabled":false,"maxReplicas":100,"minReplicas":1,"targetCPUUtilizationPercentage":80},"containerSecurityContext":{"allowPrivilegeEscalation":false,"capabilities":{"drop":["ALL"]},"readOnlyRootFilesystem":true,"runAsNonRoot":true,"runAsUser":1000,"seccompProfile":{"type":"RuntimeDefault"}},"defaultImagePullSecrets":[],"defaultRedis":{"addr":"","existingSecret":"","existingSecretKey":""},"env":[],"features":{"experimental":false,"storageVersionMigrator":true},"gc":{"gogc":75,"gomemlimit":"110MiB"},"image":"ghcr.io/stacklok/toolhive/operator:v0.40.1","imagePullPolicy":"IfNotPresent","imagePullSecrets":[],"leaderElectionRole":{"binding":{"name":"toolhive-operator-leader-election-rolebinding"},"name":"toolhive-operator-leader-election-role","rules":[{"apiGroups":[""],"resources":["configmaps"],"verbs":["get","list","watch","create","update","patch","delete"]},{"apiGroups":["coordination.k8s.io"],"resources":["leases"],"verbs":["get","list","watch","create","update","patch","delete"]},{"apiGroups":["events.k8s.io"],"resources":["events"],"verbs":["create","patch"]}]},"livenessProbe":{"httpGet":{"path":"/healthz","port":"health"},"initialDelaySeconds":15,"periodSeconds":20},"nodeSelector":{},"podAnnotations":{},"podLabels":{},"podSecurityContext":{"runAsNonRoot":true},"ports":[{"containerPort":8080,"name":"metrics","protocol":"TCP"},{"containerPort":8081,"name":"health","protocol":"TCP"}],"proxyHost":"0.0.0.0","rbac":{"allowedNamespaces":[],"scope":"cluster"},"readinessProbe":{"httpGet":{"path":"/readyz","port":"health"},"initialDelaySeconds":5,"periodSeconds":10},"replicaCount":1,"resources":{"limits":{"cpu":"500m","memory":"128Mi"},"requests":{"cpu":"10m","memory":"64Mi"}},"serviceAccount":{"annotations":{},"automountServiceAccountToken":true,"create":true,"labels":{},"name":"toolhive-operator"},"tolerations":[],"toolhiveRunnerImage":"ghcr.io/stacklok/toolhive/proxyrunner:v0.40.1","vmcpImage":"ghcr.io/stacklok/toolhive/vmcp:v0.40.1","volumeMounts":[],"volumes":[]} | All values for the operator deployment and associated resources |
| operator.affinity | object | {} | Affinity settings for the operator pod |
| operator.autoscaling | object | {"enabled":false,"maxReplicas":100,"minReplicas":1,"targetCPUUtilizationPercentage":80} | Configuration for horizontal pod autoscaling |
| operator.autoscaling.enabled | bool | false | Enable autoscaling for the operator |
| operator.autoscaling.maxReplicas | int | 100 | Maximum number of replicas |
| operator.autoscaling.minReplicas | int | 1 | Minimum number of replicas |
| operator.autoscaling.targetCPUUtilizationPercentage | int | 80 | Target CPU utilization percentage for autoscaling |
| operator.containerSecurityContext | object | {"allowPrivilegeEscalation":false,"capabilities":{"drop":["ALL"]},"readOnlyRootFilesystem":true,"runAsNonRoot":true,"runAsUser":1000,"seccompProfile":{"type":"RuntimeDefault"}} | Container security context settings for the operator |
| operator.defaultImagePullSecrets | list | [] | List of image pull secrets that the operator applies as defaults to every workload it spawns (proxy runners, vMCP servers, registry API, etc.). Per-CR imagePullSecrets take precedence on name collisions; chart-level entries are appended additively. The operator parses these once at startup from the TOOLHIVE_DEFAULT_IMAGE_PULL_SECRETS environment variable. The Secrets must exist in the namespace where each workload is created. Each entry may be either a plain string (the Secret name) or an object with a name field, e.g.: defaultImagePullSecrets: - regcred - name: otherscred The two shapes are equivalent; the object form matches operator.imagePullSecrets above for convenience. |
| operator.defaultRedis | object | {"addr":"","existingSecret":"","existingSecretKey":""} | Default Redis/Valkey address used by the operator when a workload CR has no sessionStorage configured. The operator injects this as TOOLHIVE_DEFAULT_REDIS_ADDR on pods it creates. When empty, no global Redis default is active. Override per-CR with spec.sessionStorage. global.redis.host (from a parent umbrella chart) is used as fallback when addr is empty here. Both express the same addr concept; addr takes precedence. For the password, reference a pre-existing Kubernetes Secret: defaultRedis: addr: "myredis.svc:6379" existingSecret: "redis-credentials" existingSecretKey: "password" |
| operator.defaultRedis.addr | string | "" | addr is the Redis/Valkey address (host:port). |
| operator.defaultRedis.existingSecret | string | "" | existingSecret is the name of a Secret containing the Redis password. |
| operator.defaultRedis.existingSecretKey | string | "" | existingSecretKey is the key within existingSecret that holds the password. Empty means use global.redis.existingSecretKey or fall back to "redis-password". |
| operator.env | list | [] | Environment variables to set in the operator container. Supported toolhive-specific variables include: - TOOLHIVE_SKIP_UPDATE_CHECK: set to "true" to disable the operator's periodic update check against the ToolHive update API. Also disables the usage-metrics collection that is gated on the same check. |
| operator.features.experimental | bool | false | Enable experimental features |
| operator.features.storageVersionMigrator | bool | true | Enable the StorageVersionMigrator controller, which auto-cleans status.storedVersions on opted-in toolhive.stacklok.dev CRDs so a future release can drop deprecated versions (e.g. v1alpha1) without orphaning etcd objects in the cluster. Enabled by default; set to false to opt out and handle storage-version cleanup yourself. Sets TOOLHIVE_ENABLE_STORAGE_VERSION_MIGRATOR in the operator deployment. Requires operator.rbac.scope=cluster โ the controller watches cluster-scoped CRDs and re-stores resources across all namespaces, so the chart rejects this being true when scope is namespace. |
| operator.gc | object | {"gogc":75,"gomemlimit":"110MiB"} | Go memory limits and garbage collection percentage for the operator container |
| operator.gc.gogc | int | 75 | Go garbage collection percentage for the operator container |
| operator.gc.gomemlimit | string | "110MiB" | Go memory limits for the operator container |
| operator.image | string | "ghcr.io/stacklok/toolhive/operator:v0.40.1" | Container image for the operator |
| operator.imagePullPolicy | string | "IfNotPresent" | Image pull policy for the operator container |
| operator.imagePullSecrets | list | [] | List of image pull secrets to use |
| operator.leaderElectionRole | object | {"binding":{"name":"toolhive-operator-leader-election-rolebinding"},"name":"toolhive-operator-leader-election-role","rules":[{"apiGroups":[""],"resources":["configmaps"],"verbs":["get","list","watch","create","update","patch","delete"]},{"apiGroups":["coordination.k8s.io"],"resources":["leases"],"verbs":["get","list","watch","create","update","patch","delete"]},{"apiGroups":["events.k8s.io"],"resources":["events"],"verbs":["create","patch"]}]} | Leader election role configuration |
| operator.leaderElectionRole.binding.name | string | "toolhive-operator-leader-election-rolebinding" | Name of the role binding for leader election |
| operator.leaderElectionRole.name | string | "toolhive-operator-leader-election-role" | Name of the role for leader election |
| operator.leaderElectionRole.rules | list | [{"apiGroups":[""],"resources":["configmaps"],"verbs":["get","list","watch","create","update","patch","delete"]},{"apiGroups":["coordination.k8s.io"],"resources":["leases"],"verbs":["get","list","watch","create","update","patch","delete"]},{"apiGroups":["events.k8s.io"],"resources":["events"],"verbs":["create","patch"]}] | Rules for the leader election role |
| operator.livenessProbe | object | {"httpGet":{"path":"/healthz","port":"health"},"initialDelaySeconds":15,"periodSeconds":20} | Liveness probe configuration for the operator |
| operator.nodeSelector | object | {} | Node selector for the operator pod |
| operator.podAnnotations | object | {} | Annotations to add to the operator pod |
| operator.podLabels | object | {} | Labels to add to the operator pod |
| operator.podSecurityContext | object | {"runAsNonRoot":true} | Pod security context settings |
| operator.ports | list | [{"containerPort":8080,"name":"metrics","protocol":"TCP"},{"containerPort":8081,"name":"health","protocol":"TCP"}] | List of ports to expose from the operator container |
| operator.proxyHost | string | "0.0.0.0" | Host for the proxy deployed by the operator |
| operator.rbac | object | {"allowedNamespaces":[],"scope":"cluster"} | RBAC configuration for the operator |
| operator.rbac.allowedNamespaces | list | [] | List of namespaces that the operator is allowed to have permissions to manage. Only used if scope is set to "namespace". |
| operator.rbac.scope | string | "cluster" | Scope of the RBAC configuration. - cluster: The operator will have cluster-wide permissions via ClusterRole and ClusterRoleBinding. - namespace: The operator will have permissions to manage resources in the namespaces specified in allowedNamespaces. The operator will have a ClusterRole and RoleBinding for each namespace in allowedNamespaces. |
| operator.readinessProbe | object | {"httpGet":{"path":"/readyz","port":"health"},"initialDelaySeconds":5,"periodSeconds":10} | Readiness probe configuration for the operator |
| operator.replicaCount | int | 1 | Number of replicas for the operator deployment |
| operator.resources | object | {"limits":{"cpu":"500m","memory":"128Mi"},"requests":{"cpu":"10m","memory":"64Mi"}} | Resource requests and limits for the operator container |
| operator.serviceAccount | object | {"annotations":{},"automountServiceAccountToken":true,"create":true,"labels":{},"name":"toolhive-operator"} | Service account configuration for the operator |
| operator.serviceAccount.annotations | object | {} | Annotations to add to the service account |
| operator.serviceAccount.automountServiceAccountToken | bool | true | Automatically mount a ServiceAccount's API credentials |
| operator.serviceAccount.create | bool | true | Specifies whether a service account should be created |
| operator.serviceAccount.labels | object | {} | Labels to add to the service account |
| operator.serviceAccount.name | string | "toolhive-operator" | The name of the service account to use. If not set and create is true, a name is generated. |
| operator.tolerations | list | [] | Tolerations for the operator pod |
| operator.toolhiveRunnerImage | string | "ghcr.io/stacklok/toolhive/proxyrunner:v0.40.1" | Image to use for Toolhive runners |
| operator.vmcpImage | string | "ghcr.io/stacklok/toolhive/vmcp:v0.40.1" | Image to use for Virtual MCP Server (vMCP) deployments |
| operator.volumeMounts | list | [] | Additional volume mounts on the operator container |
| operator.volumes | list | [] | Additional volumes to mount on the operator pod |
| registryAPI | object | {"image":"ghcr.io/stacklok/thv-registry-api:v1.3.0"} | All values for the registry API deployment and associated resources |
| registryAPI.image | string | "ghcr.io/stacklok/thv-registry-api:v1.3.0" | Container image for the registry API |
Contributing
See CONTRIBUTING.md for guidelines on contributing to this chart.