Skip to main content
Version: v2

Helm Values Reference

This page documents the configuration values you are most likely to override when installing the runtime-operator Helm chart. For the authoritative list of every value the chart supports, run:

shell
helm show values oci://ghcr.io/wasmcloud/charts/runtime-operator --version <version>

Top-level structure

The chart's values.yaml is organized into five top-level sections:

SectionPurpose
globalSettings that apply across all components (image registry, TLS, image pull secrets)
natsThe bundled NATS server — set enabled: false to connect an external NATS cluster instead
operatorThe wasmCloud runtime-operator deployment
gatewayDeprecated in 2.0.3. Legacy runtime-gateway. Set enabled: false to skip installing it
runtimeHost group deployments (pods running the wash host binary)

global

global.image.registry

Override the container image registry for all components at once. Useful for air-gapped or mirrored deployments.

yaml
global:
  image:
    registry: myregistry.example.com

See Private Registries and Air-Gapped Deployments for the full mirroring workflow.

global.tls.enabled

Set to false to disable TLS for NATS connections and skip certificate generation. Intended for clusters where a service mesh (e.g. Istio, Linkerd) provides mTLS between pods.

yaml
global:
  tls:
    enabled: false

When global.tls.enabled is false, the chart ignores global.certificates.generate — no self-signed certs are created and NATS runs plaintext.

global.certificates.generate

Controls whether the chart generates self-signed TLS certificates for NATS and the control plane. Set to false when bringing your own certificate secrets. See the TLS: bring your own certificates recipe for the full BYOC flow.

global.nats.schedulerUrl and global.nats.dataUrl

The chart exposes two NATS URLs separately:

  • global.nats.schedulerUrl — the control-plane URL the operator (-nats-url) and the host runtime (--scheduler-nats-url) connect to for workload scheduling and host heartbeats.
  • global.nats.dataUrl — the data-plane URL the host runtime (--data-nats-url) uses for Wasm workload messaging, key-value, and blobstore backends.

Both default to nats://nats.<release-namespace>.svc.cluster.local:4222 when left empty. Splitting them lets workloads target a separate NATS cluster from the scheduler — useful when application traffic and operator coordination live on different brokers.

yaml
global:
  nats:
    schedulerUrl: "nats://control-plane.example.internal:4222"
    dataUrl: "nats://data-plane.example.internal:4222"

Per-host-group overrides are also available via runtime.hostGroups[].schedulerNatsUrl and runtime.hostGroups[].dataNatsUrl.

operator, nats, runtime — pod labels and annotations

Each deployment accepts podLabels and podAnnotations that are merged into the pod template. This is most commonly used for service mesh injection:

yaml
operator:
  podLabels:
    sidecar.istio.io/inject: "true"
  podAnnotations:
    proxy.istio.io/config: '{"holdApplicationUntilProxyStarts": true}'

nats:
  podLabels:
    sidecar.istio.io/inject: "true"

runtime:
  podLabels:
    sidecar.istio.io/inject: "true"

operator

operator.watchNamespaces

By default, the operator watches every namespace in the cluster. Set watchNamespaces to a list of namespace names to scope it down:

yaml
operator:
  watchNamespaces:
    - team-a
    - team-b

When watchNamespaces is populated, the chart drops the operator's cluster-wide ClusterRole and ClusterRoleBinding entirely and renders a set of Role + RoleBinding pairs in each watched namespace instead. Per watched namespace:

  • <release>-workload-crd covers the runtime.wasmcloud.dev workload resources — artifacts, workloads, workloadreplicasets, workloaddeployments, plus their /status and /finalizers subresources
  • <release>-workload-namespace covers per-workload core resources — configmaps, secrets, events, services (plus services/finalizers)
  • <release>-endpointslice covers endpointslices for Kubernetes-native traffic routing

Host CRD grants stay on the namespaced Role in the operator's own namespace (host pods aren't tenant-scoped), and <release>-leader-election continues to live there too. The net effect: an operator.watchNamespaces install holds no cluster-wide permissions for workload resources and can be deployed with namespace-admin RBAC alone for those namespaces.

info

This namespaced-by-default behavior for the runtime.wasmcloud.dev apiGroup landed in 2.3.0 (#5208). Earlier releases bound the workload CRD verbs cluster-wide even when watchNamespaces was set.

operator.hostNamespaces

List of namespaces where host pods run. The operator's pod informer cache and per-namespace pod RBAC cover this set so the host-pod controller can manage finalizers on host pods. Leave empty when host pods only run in the operator's own namespace (the chart's default).

yaml
operator:
  hostNamespaces:
    - team-a
    - team-b

When you set runtime.hostGroups[].namespace to deploy host pods outside the operator's namespace, also include those namespaces here — otherwise the operator can't observe or finalize the host pods running there.

operator.allowSharedHosts

Default: true. Controls whether WorkloadDeployments can schedule onto hosts whose Host.environment differs from the workload's own namespace, via spec.template.spec.environment.

yaml
operator:
  allowSharedHosts: false

The default (true) lets workloads with no environment set schedule onto any matching host, regardless of which tenant namespace the host runs in. This is permissive: in a multi-tenant cluster where each tenant has its own namespace and host pods, a workload in team-a can target hosts in team-b simply by setting spec.template.spec.environment: team-b.

Set to false when namespace boundaries are part of your tenant isolation model. With allowSharedHosts: false:

  • Scheduling is locked to the workload's own namespace.
  • Any cross-namespace environment value is rejected with a CrossEnvironmentSchedulingDenied Warning Event and a HostSelection=False condition on the Workload.

See Troubleshooting: Workload stays unscheduled with allowSharedHosts: false for the symptom and resolution patterns.

operator.env, operator.envFrom, and operator.extraArgs

Three passthrough fields that let chart users wire operator-side configuration without forking the chart:

  • operator.env — additional environment variables for the operator container, appended after the chart-managed vars. Standard Kubernetes env shape (supports valueFrom with secretKeyRef / configMapKeyRef).
  • operator.envFrom — populate the operator container's environment from ConfigMaps or Secrets, using the standard Kubernetes envFrom shape.
  • operator.extraArgs — additional CLI args appended verbatim to the operator container, for operator flags the chart does not template (e.g. -leader-elect, -cpu-backpressure-threshold=75).
yaml
operator:
  env:
    - name: LOG_LEVEL
      value: debug
  envFrom:
    - secretRef:
        name: operator-extra-config
  extraArgs:
    - "-leader-elect"

operator.probes

Probe timings for the operator deployment (values-driven since 2.9.0), each individually disableable. Liveness fails only on a permanently closed NATS connection (since 2.4.0): the operator stays healthy while reconnecting, so a NATS rolling restart does not restart it. The startup probe (new in 2.9.0) covers the initial NATS connect window, since the operator binds its health port only after connecting.

yaml
operator:
  probes:
    startup:
      periodSeconds: 5
      failureThreshold: 24
    liveness:
      initialDelaySeconds: 15
      periodSeconds: 20
      failureThreshold: 3
    readiness:
      initialDelaySeconds: 5
      periodSeconds: 10
      failureThreshold: 3

operator.image.tag

Defaults to the chart's appVersion. Override only when you need to pin to a specific operator build that differs from the chart release:

yaml
operator:
  image:
    tag: "2.9.0"

The same pattern applies to gateway.image.tag and runtime.image.tag.

gateway (deprecated)

Deprecated

The runtime-gateway is deprecated as of 2.0.3. HTTP routing is now handled by the runtime-operator via EndpointSlices tied to user-defined Kubernetes Services. See Expose a Workload via Kubernetes Service for the replacement pattern.

To skip installing the gateway, set gateway.enabled: false.

yaml
gateway:
  enabled: false

runtime

Pinning an older host image

Three 2.9.0 values render host flags that a pre-2.9.0 host image refuses, crash-looping the pod: probes.endpoint.enabled (--probe-addr), runtime.drainDelaySeconds (--drain-delay), and runtime.natsConnectTimeoutSeconds (--nats-connect-timeout). probes.endpoint.enabled: false can be set per host group, but the other two are chart-wide with no per-group override, so a release containing a pinned older host image must clear both for every group, or split the pinned hosts into their own release.

runtime.env, runtime.envFrom, and runtime.extraArgs

Chart-wide host configuration applied to every host group's container. Per-host-group values (see runtime.hostGroups[].env etc.) are appended after these, letting one group extend the chart-wide defaults without redefining them.

  • runtime.env — additional environment variables applied to every host group container, appended after the chart-managed vars (WASMCLOUD_HOST_IP, WASMCLOUD_HOST_ENVIRONMENT) and before any per-host-group env.
  • runtime.envFrom — populate every host group container's environment from ConfigMaps or Secrets.
  • runtime.extraArgs — additional CLI args appended to every host group container, for host flags the chart does not template. Merged with per-host-group extraArgs.
yaml
runtime:
  env:
    - name: RUST_LOG
      value: info
  envFrom:
    - configMapRef:
        name: shared-host-config
  extraArgs:
    - "--max-concurrent-workloads=64"

runtime.hostGroups

A host group is a Deployment of pods running the wash host. You can define multiple groups to isolate workloads or provide specialized capabilities (e.g. WebGPU-enabled hosts):

yaml
runtime:
  hostGroups:
    - name: default
      replicas: 3
      http:
        enabled: true
        port: 80
      resources:
        requests:
          memory: "64Mi"
          cpu: "250m"
        limits:
          memory: "512Mi"
          cpu: "500m"
    - name: gpu
      replicas: 1
      webgpu:
        enabled: true

WorkloadDeployment manifests target a group via spec.template.spec.hostSelector.hostgroup.

runtime.hostGroups[].namespace

Namespace to deploy this host group's Deployment, Service, generated TLS Secret, and ServiceAccount into. Empty (default) deploys to the chart release's namespace.

yaml
runtime:
  hostGroups:
    - name: team-a
      namespace: team-a
      replicas: 2

When you override this, ensure the namespace exists and is included in operator.hostNamespaces so the operator has the pod RBAC and informer cache access it needs to manage host pod lifecycle there. Each host's Host.environment will reflect the namespace where its pod runs, which is what allowSharedHosts: false matches against for namespace-scoped scheduling.

runtime.hostGroups[].schedulerNatsUrl and runtime.hostGroups[].dataNatsUrl

Per-host-group overrides for the NATS URLs the host runtime connects to. Both fall back to the chart-wide global.nats.schedulerUrl / global.nats.dataUrl when empty, which in turn fall back to the in-cluster NATS service.

yaml
runtime:
  hostGroups:
    - name: edge
      replicas: 2
      dataNatsUrl: "nats://edge-data.example.internal:4222"

Use this when a single chart release runs host groups against different data-plane brokers (for example, regional NATS clusters for an edge group while the default group stays on the in-cluster broker).

runtime.hostGroups[].env, envFrom, and extraArgs

Per-host-group versions of the chart-wide runtime.env / envFrom / extraArgs fields. These are appended after the chart-wide values, so a group can extend the shared defaults without redeclaring them.

yaml
runtime:
  env:
    - name: RUST_LOG
      value: info
  hostGroups:
    - name: gpu
      replicas: 1
      env:
        - name: RUST_LOG
          value: debug   # overrides the chart-wide value for this group only
      extraArgs:
        - "--wasi-webgpu-debug"

runtime.hostGroups[].volumes, volumeMounts, and ports

Optional passthrough fields rendered directly into the host group's pod and container spec, letting chart users mount ConfigMaps / Secrets / persistent volumes and expose extra container ports without forking the chart.

  • runtime.hostGroups[].volumes — appended to the pod's spec.volumes. Standard Kubernetes volume shape.
  • runtime.hostGroups[].volumeMounts — appended to the host container's volumeMounts. Pair each entry with a matching volumes entry above.
  • runtime.hostGroups[].ports — appended to the host container's ports. Typically used to expose a metrics scrape port for Prometheus.
yaml
runtime:
  hostGroups:
    - name: default
      replicas: 3
      volumes:
        - name: app-config
          configMap:
            name: my-host-config
      volumeMounts:
        - name: app-config
          mountPath: /etc/wasmcloud/config
          readOnly: true
      ports:
        - name: metrics
          containerPort: 9090
          protocol: TCP

All three fields render unconditionally, so they survive a global.tls.enabled: false install. Do not re-list the HTTP port in ports — the chart already renders it from .http.port, and a duplicate containerPort fails Deployment validation. See Filesystems and Volumes for the broader volume story.

runtime.hostGroups[].wasmProposals

A per-host-group list of top-level Wasm proposals to enable on the engine. The chart renders each entry as a --wasm-proposal argument on the host container.

yaml
runtime:
  hostGroups:
    - name: default
      replicas: 3
      # Enable the async component model + garbage collection proposals for this group.
      wasmProposals:
        - component-model-async
        - gc

Recognized values are component-model-async, component-model-map (added in 2.7.0), gc, exception-handling, wide-arithmetic, threads, and tail-call. WASI 0.3 always brings the component-model-async proposal along with it, and as of wasmCloud 2.7.0 the engine also enables the Component Model map type proposal unconditionally, so neither needs to be listed explicitly on 2.7.0-and-later hosts.

runtime.hostGroups[].http.port

The port the host's HTTP server listens on inside the pod, and the port the operator populates into each managed EndpointSlice. The upstream chart default is 9191; the values.local.yaml overlay overrides it to 80 for local development.

runtime.hostGroups[].webgpu.enabled

Enables the WebGPU plugin on hosts in the group. Requires a host image built with the wasi-webgpu feature.

runtime.ociCaPaths and runtime.hostGroups[].ociCaPaths

Introduced in 2.7.0. PEM bundles of additional CA certificates the host trusts when pulling artifacts (e.g., workload components, host component plugins, or washlet artifacts) from OCI registries. runtime.ociCaPaths applies chart-wide; the per-host-group form is additive. The chart renders entries as --oci-ca-path arguments (the paths must be mounted into the pod via volumes/volumeMounts).

For an in-cluster registry signed by the chart's own CA (with global.tls.enabled), the CA bundle is already mounted:

yaml
runtime:
  ociCaPaths:
    - /runtime-cert/ca.crt

Prefer this over --allow-insecure-registries, which switches every registry to plain HTTP. Credentials travel in the clear and no certificate is checked at all.

For trusting private CAs on components' outbound HTTPS (a separate mechanism from OCI pulls), pass --http-client-ca-path/--http-client-trust-roots via runtime.hostGroups[].extraArgs with the bundle mounted the same way.

runtime.resources.defaultHeapMemory and runtime.resources.coreInstances

Introduced in 2.8.0. Wasmtime engine sizing for hosts, set inside the resources block alongside the Kubernetes requests and limits (the chart strips them out before rendering the container resources). Both are passed to the host as environment variables, so older host images ignore them.

  • defaultHeapMemory: Ceiling on any single guest linear memory. Defaults to wasmtime's 4 GiB.
  • coreInstances: Number of instance slots in wasmtime's pooling allocator. Defaults to 1000.

Sizes accept Kubernetes quantity suffixes (Gi/GiB binary, G/GB decimal, bare values are bytes):

yaml
runtime:
  resources:
    limits:
      memory: "2Gi"
    defaultHeapMemory: "512Mi"
    coreInstances: "500"

The chart also forwards resources.limits.memory to the host as its guest memory budget, set as the WASH_HOST_MAX_GUEST_MEMORY environment variable rather than the --max-guest-memory flag so an older host image ignores it (since 2.8.0). When no limit is set, the host derives the budget as three quarters of the cgroup or physical memory limit, clamped between 256 MiB and 1 TiB. Since 2.9.0 the host counts guest memory use against the budget and can enforce it; see guestMemoryMode below.

These values size the engine for the whole host; they are not per-workload limits. A host group with its own resources block replaces the chart-wide runtime.resources wholesale, so repeat these keys per group if you use both.

runtime.resources.guestMemoryMode

How the host treats the guest memory budget (since 2.9.0): count, the default when unset, records what enforcement would refuse without refusing anything; enforce refuses guest memory growth past the budget. Set inside the resources block like the sizing values above; passed to the host as the WASH_GUEST_MEMORY_MODE environment variable.

Under enforce, a guest whose memory.grow would cross the budget sees the growth fail (the same result as hitting its own heap ceiling), not a trap. A refusal during instantiation surfaces as a workload start error. The host publishes guest_memory.in_use, guest_memory.high_water, guest_memory.limit, guest_memory.refused, and guest_memory.would_refuse metrics whenever an OpenTelemetry exporter is configured, so the intended rollout is: run in count, watch high_water and would_refuse, then switch to enforce. (See metrics you can scale on for the host's guest metrics generally.)

Leave headroom. The chart forwards resources.limits.memory verbatim, so an enforced budget equal to the pod limit can be OOM-killed before a refusal ever fires; the host warns at startup when an enforced budget exceeds 90% of the detected memory limit. Set limits.memory above the guest budget you want, or set a lower budget explicitly with WASH_HOST_MAX_GUEST_MEMORY in runtime.env.

runtime.resources.maxConcurrentStarts

How many workload starts, the image pull plus the compile, a host admits at once (since 2.9.0); further start commands queue. Set inside the resources block; passed as WASH_MAX_CONCURRENT_STARTS. When unset, the host sizes it to one fewer than the CPUs it can see, clamped between 1 and 4, and logs the effective value on its startup line. A literal 0 is preserved by the chart and clamps to 1.

Compilation runs off the host's serving runtime (since 2.9.0), so a host keeps heartbeating and serving traffic through a burst of starts, and a stop command can overtake a queued start. Each admitted compile spreads across every core the process can see, so this value bounds how many compiles contend with serving traffic, not how many threads a compile uses. To make each compile single-threaded instead, set WASMTIME_PARALLEL_COMPILATION=false in runtime.env.

runtime.probes and runtime.hostGroups[].probes

Host pods expose HTTP health endpoints on a dedicated probes port (since 2.9.0; default 8081, rendered as --probe-addr):

  • /livez means "restart me": it fails when the host's command loop has stalled or its HTTP ingress has stopped permanently.
  • /readyz means "stop sending me work": it fails while the host is starting or draining, and while the HTTP ingress connection ceiling is reached.

The failure body names the failing condition, so kubectl describe pod shows why a probe failed. Chart defaults:

yaml
runtime:
  probes:
    endpoint:
      enabled: true              # false restores the pre-2.9.0 TCP probe on the http port
      port: 8081
      startupFailureThreshold: 60 # 60 failures at the fixed 5s startup period, a 5 minute budget
    readiness:
      enabled: true
      initialDelaySeconds: 5
      periodSeconds: 10
      timeoutSeconds: 1
      failureThreshold: 3
    liveness:
      enabled: true              # also gates the startup probe
      initialDelaySeconds: 10
      periodSeconds: 30
      timeoutSeconds: 5
      failureThreshold: 5

Liveness is deliberately slower than readiness: a restart loses every workload on the host. Unlike resources, a per-group hostGroups[].probes block overlays runtime.probes field by field, so a group can retune one timing without redeclaring the rest. The chart refuses to render when the probe port collides with the group's HTTP port or a declared container port.

runtime.drainDelaySeconds and runtime.natsConnectTimeoutSeconds

Host pods drain on termination instead of exiting immediately (since 2.9.0):

  1. On SIGTERM the host reports draining: readiness fails and the pod leaves its Service endpoints while the host is still serving.
  2. The host keeps serving for runtime.drainDelaySeconds (default 5, rendered as --drain-delay).
  3. In-flight commands get 5 seconds to finish, and each plugin's stop is capped by WASH_PLUGIN_STOP_TIMEOUT_SECS (default 5 seconds) plus a 1 second grace.

The chart now sets terminationGracePeriodSeconds on every pod (previously hardcoded to 0): runtime: 15, operator: 45, gateway: 45, nats: 30. Rendering fails if the runtime grace is less than drainDelaySeconds + 5. A second signal exits immediately rather than waiting out the drain.

runtime.natsConnectTimeoutSeconds (default 60, rendered as --nats-connect-timeout) lets a starting host wait for a NATS server that is not up yet instead of exiting and burning pod restarts.

runtime.hostGroups[].networking

Maps to the host's socket policy and connection quota flags. The chart renders these keys as host flags since 2.9.0; the block existed in the 2.8.0 values file but was not wired to the host, so on earlier charts set these through extraArgs:

KeyDefaultHost flag
allowHostLoopbackfalse--allow-host-loopback
socketEgresscount--socket-egress
denySpecialRangestrue--deny-special-ranges
denyPrivateRangesfalse--deny-private-ranges
maxConnectionsempty (derived from the descriptor limit)--max-connections
maxOutboundHttpConnectionsPerWorkload128--max-outbound-http-connections-per-workload
maxOutboundSocketConnectionsPerWorkload256--max-outbound-socket-connections-per-workload
maxInboundSocketConnectionsPerWorkload256--max-inbound-socket-connections-per-workload
maxHttpIngressConnectionsempty (a quarter of the descriptor limit, floor 256; since 2.9.0)--max-http-ingress-connections

allowHostLoopback and denyPrivateRanges render as presence flags, so setting them to false is the same as omitting them; only denySpecialRanges renders an explicit value. See Concurrency and connections and Workload security for what these bound. publishPorts and publishPortRange appear in the values file but are not yet wired to the host; setting them has no effect.

runtime.hostGroups[].plugins

One declaration block configures host plugins of both kinds (since 2.9.0): native plugins built into the host (an entry with only an id) and host component plugins (an entry with an image or file source). A native entry, and any entry carrying config, secrets, allowlists, or binding fields, is rendered into the host's config file rather than CLI arguments, so credentials never appear in the pod spec; a bare component entry (id plus a source) still renders as a --host-plugin argument. Each entry accepts:

  • config / configFrom / secretFrom: entry-wide configuration, layered under each binding's own.
  • workloadConfig: deny (the default), warn, or allow. Under deny, host-owned keys (connection and credential settings) come only from this declaration: a workload manifest that sets one or widens a grant ceiling is refused at deploy, and once any binding is declared for a plugin, so is a manifest naming a binding the declaration does not carry. warn behaves like allow but logs everything deny would refuse, for rehearsing a lockdown.
  • hostOwnedKeys: additional keys to claim for the host under deny.
  • bindings: a map of label to {config, configFrom, secretFrom}. A component imports the plugin's interface under that label (the Component Model's implements clause), and the labeled import resolves against the binding's config layered over the entry's.
  • Component-plugin-only fields: maxRestarts, digest (an OCI digest pin), allowedHosts, allowedIpNameLookups. (A ports field appears in the values file but is not yet wired to the host.)
yaml
runtime:
  hostGroups:
    - name: default
      plugins:
        - id: wasmcloud-nats
          workloadConfig: deny
          config:
            servers: nats://nats.example.com:4222
            subject-allow: 'orders.>'
          secretFrom:
            - name: nats-creds

runtime.hostGroups[].hostPlugins remains as a deprecated alias: its entries are concatenated onto plugins and rendered identically, so prefer plugins for new declarations. The chart refuses to render the removed wasmcloudNats and wasmcloudNatsWorkloadConfig keys with a message pointing at plugins.

runtime.hostGroups[].wasmcloudNatsUrl

The default NATS address for the wasmcloud:nats plugin's per-workload connections (since 2.9.0; rendered as --wasmcloud-nats-url). Empty means the group's data NATS URL and its TLS settings; when set, the data plane's TLS does not carry over. The inherited bundle is address and TLS only, never credentials; provide credentials through the plugin's plugins entry.

runtime.hostGroups[].http.tls.certificate.generate.ipAddresses

Introduced in 2.7.0. Additional IP SANs for the host group's generated TLS certificate; needed when clients reach the host group by IP rather than name. Note that the chart reuses an existing hostgroup-<name>-http-tls Secret if one exists, so adding ipAddresses to a live release takes effect only after that Secret is deleted and the certificate regenerated.

runtime.image.tag

Defaults to the chart's appVersion; leave unset to track the chart release.