Skip to main content
Version: v2

Custom Resource Definitions (CRDs)

When deployed to Kubernetes, the core primitives of wasmCloud are represented by custom resources definitions (CRDs).

wasmCloud uses CRDs from the runtime.wasmcloud.dev/v1alpha1 API package:

This document explains each of these custom resources at a high level. For a complete API specification, see the API reference.

note

WorkloadDeployment is the resource used to deploy Wasm workloads—if you're looking to quickly deploy a component, start there.

Artifact

An Artifact represents a Wasm component that can be referenced by Workloads. Artifacts define the image location and optional image pull secrets for accessing private registries. This can be used to fetch an OCI image and store its contents in a NATS JetStream Object Store.

The Artifact resource tracks individual revisions, publishing the artifact's location under Status.ArtifactURL. A WorkloadDeployment can reference an Artifact as its component image. A new deployment will be automatically rolled out when a new image is detected.

Use Artifact when you want the operator to watch for new image versions and trigger rolling updates automatically, or to centralize image pull secrets so individual WorkloadDeployment manifests don't need to repeat them.

Example manifest:

yaml
apiVersion: runtime.wasmcloud.dev/v1alpha1
kind: Artifact
metadata:
  name: http-hello-world
  namespace: default
spec:
  image: ghcr.io/wasmcloud/components/http-hello-world-rust:0.1.0
  imagePullSecret:
    name: ghcr-secret

Host

A Host resource defines a wasmCloud runtime environment, or host, which has a unique ID and can run Wasm workloads.

Example manifest:

yaml
apiVersion: runtime.wasmcloud.dev/v1alpha1
kind: Host
metadata:
  name: host-sample
  namespace: default
  labels:
    hostgroup: default
hostId: NABCDEFGHIJKLMNOPQRSTUVWXYZ234567
hostname: host-sample.default
httpPort: 4000

Unlike most Kubernetes resources, the Host CRD does not use a spec wrapper — fields like hostId, hostname, httpPort, and environment are set at the resource root. See the Host API reference for the full field list.

Workload

A Workload represents an application composed of one or more WebAssembly components and optional services. Workloads define the components, their configurations, volume mounts, and host interfaces they consume.

Workloads are analogous to Kubernetes Pods in that they typically are not managed individually, but are instead owned by a WorkloadDeployment, much as a Pod is owned by a Deployment.

Example manifest:

yaml
apiVersion: runtime.wasmcloud.dev/v1alpha1
kind: Workload
metadata:
  name: hello-world
  namespace: default
spec:
  hostSelector:
    hostgroup: default
  components:
    - name: http-component
      image: ghcr.io/wasmcloud/components/http-hello-world-rust:0.1.0
      poolSize: 10
      maxInvocations: 1000
      localResources:
        environment:
          config:
            LOG_LEVEL: info
        allowedHosts:
          - api.example.com
  hostInterfaces:
    - namespace: wasi
      package: http
      interfaces:
        - incoming-handler
      config:
        host: my-app.example.com
  volumes:
    - name: cache
      ephemeral: {}

WorkloadDeployment

A WorkloadDeployment defines the deployment and scaling of Workloads across hosts. It creates and manages WorkloadReplicaSets to ensure the desired number of workload replicas are running.

WorkloadDeployment implements the Kubernetes /scale subresource, so kubectl scale, the Horizontal Pod Autoscaler, and KEDA all work against it without wasmCloud-specific glue. spec.replicas defaults to 1 when omitted. See Autoscaling for HPA and KEDA examples.

Example manifest:

yaml
apiVersion: runtime.wasmcloud.dev/v1alpha1
kind: WorkloadDeployment
metadata:
  name: hello-world
  namespace: default
spec:
  replicas: 3
  deployPolicy: RollingUpdate
  artifacts:
    - name: http-component
      artifactFrom:
        name: http-hello-world
  template:
    metadata:
      labels:
        app: hello-world
    spec:
      hostSelector:
        hostgroup: default
      components:
        - name: http-component
          image: ghcr.io/wasmcloud/components/http-hello-world-rust:0.1.0
          poolSize: 10
      hostInterfaces:
        - namespace: wasi
          package: http
          interfaces:
            - incoming-handler
          config:
            host: my-app.example.com

Host Interfaces

Use the hostInterfaces field to define host interfaces used by the workload. Each entry requires namespace, package, and interfaces, and may optionally include version, config, and name.

The optional name field enables multi-backend binding, meaning a component can import the same interface twice under distinct labels and have each labeled import routed to its own backend. This uses the Component Model's implements clause: the component declares each labeled import in its WIT world, and the runtime routes calls through the labeled namespace to the matching hostInterfaces entry.

The Component Model's implements proposal is what allows the runtime to bind the labeled imports at start time. Introduced behind a feature flag in wasmCloud 2.5.0, it is enabled by default as of wasmCloud 2.7.0. Stock release images support multi-backend binding out of the box.

A component that needs two wasi:keyvalue backends declares two labeled imports in its WIT world:

wit
world my-app {
    import cache: wasi:keyvalue/store@0.2.0-draft;
    import sessions: wasi:keyvalue/store@0.2.0-draft;
    export wasi:http/incoming-handler@0.2.2;
}

Inside the guest, each label surfaces as its own import namespace: cache::open("kv-bucket") and sessions::open("session-bucket") reach two different backends without the guest needing to know which is which. The string passed to open() is the bucket name; the routing label is the WIT namespace, not an argument.

Default as of wasmCloud 2.7.0

Multi-backend binding via (implements ..) is on by default in wasmCloud 2.7.0 and later. The wasm_component_model_implements Cargo feature joined the default set for both wash and wash-runtime (release images ghcr.io/wasmcloud/wash:2.8.0 include it). On 2.5.x–2.6.x hosts, the feature requires a custom host image built with CARGO_FEATURES=wasm_component_model_implements.

Multiplexed backends ship for wasi:keyvalue, wasi:blobstore, wasmcloud:keyvalue, wasmcloud:blobstore, wasmcloud:postgres, and wasmcloud:messaging/consumer (as of 2.5.2). Unlabeled imports of wasmcloud:blobstore and wasmcloud:keyvalue/store also work: they bind to the workload's default (unnamed) backend, so a component that needs only one backend can drop the (implements ..) label and still get a working binding.

yaml
hostInterfaces:
  # Named: NATS-backed keyvalue for caching
  - name: cache
    namespace: wasi
    package: keyvalue
    interfaces: [store, atomics, batch]
    config:
      backend: nats
      bucket: cache-kv
  # Named: Redis-backed keyvalue for sessions
  - name: sessions
    namespace: wasi
    package: keyvalue
    interfaces: [store]
    config:
      backend: redis
      url: redis://redis:6379
  # Unnamed: single HTTP interface (name not required)
  - namespace: wasi
    package: http
    interfaces: [incoming-handler]
    config:
      host: my-app.example.com

Naming rules:

  • name is optional. Omitting it preserves existing single-backend behavior.
  • When two or more entries share the same namespace+package, all of them must have a non-empty name.
  • Names must be unique within a workload's hostInterfaces for the same namespace+package.
  • Names must match [a-z0-9][a-z0-9-]* (DNS label style).

Messaging subscriptions and consumer groups

As of wasmCloud 2.8.0, the version on a wasmcloud:messaging entry selects which consumer and types revision is linked for the component's imports: "0.3.0" binds the async surface, while "0.2.0" or an omitted version binds sync 0.2.0. The handler revision follows the component's own export: the host invokes handler@0.3.0 when the component exports it, falling back to @0.2.0. See Interfaces for the differences. The config keys below apply to both revisions.

The built-in NATS wasmcloud:messaging plugin reads its config keys from the component's localResources.config first, falling back to the config on the workload's wasmcloud:messaging hostInterfaces entry (so workers in one workload can override the workload-scoped values):

  • subscriptions: A comma-separated list of NATS subjects the component's wasmcloud:messaging/handler export subscribes to.
  • consumer_group: Controls delivery when a component runs with multiple replicas (as of wasmCloud 2.6.0). When omitted, replicas join a consumer group derived from the workload namespace, workload name, and component name, so exactly one replica handles each message. Set it to the special value broadcast to deliver every message to every replica (the pre-2.6.0 behavior, useful for cache invalidation), or to any other NATS-safe name (no whitespace, *, or >) to place subscribers in an explicitly named group (e.g., to share one group across components).
  • max_in_flight (as of wasmCloud 2.8.0): Caps how many deliveries to the component may be in flight at once on a host, counted across the component's replicas on that host. When omitted, the host's per-component ceiling applies. The key can only lower a component below that ceiling; a value above it is clamped to it with a warning. Raising a component past the stock ceiling requires raising the host's per-component flag. See Messaging admission control below.
  • admission_wait (as of wasmCloud 2.8.0): How long a delivery waits for an in-flight slot before it is dropped. Accepts values like 45s, 2m, or bare seconds. Default 30s, maximum 600s.
yaml
spec:
  template:
    spec:
      components:
        - name: order-processor
          image: ghcr.io/example/order-processor:0.1.0
          localResources:
            config:
              subscriptions: 'orders.received'
              consumer_group: broadcast
      hostInterfaces:
        - namespace: wasmcloud
          package: messaging
          interfaces: [consumer, handler]

Messaging admission control

As of wasmCloud 2.8.0, the host admits messaging deliveries to per-message components through an in-flight limit, applied before an instance is created. When a component is at its limit, further deliveries wait up to admission_wait for a slot; a delivery that cannot be admitted in time is dropped with a warning. There is no negative acknowledgment on core NATS, so size the limit for peak load rather than relying on redelivery.

Two host-level ceilings back the per-component limit, set as flags or environment variables on the host (on Kubernetes, via runtime.hostGroups[].env):

FlagEnvironment variableDefault
--wasmcloud-messaging-max-in-flightWASH_WASMCLOUD_MESSAGING_MAX_IN_FLIGHTDerived from the instance pool (133 on a stock host)
--wasmcloud-messaging-max-in-flight-per-componentWASH_WASMCLOUD_MESSAGING_MAX_IN_FLIGHT_PER_COMPONENTA quarter of the host total (33 on a stock host)

The in-flight limit is distinct from maxConcurrency, which bounds calls on one warm instance, and from the connection quotas: it bounds how many messaging deliveries may be executing for the component at all.

Runtime Configuration

Runtime configuration values (such as environment variables) may be supplied via the optional localResources subfield of the component field.

yaml
localResources:
  environment:
    config:
      some_key: some_value

Environmental values may also come from ConfigMaps or Secrets. The following approaches are also valid:

yaml
localResources:
  environment:
    configFrom:
      - name: my-configmap
    secretFrom:
      - name: my-secret
    config:
      literal_key: literal_value

Component resource controls

The poolSize, maxInvocations, maxConcurrency, allowedHosts, allowedIpNameLookups, and allowedHostLoopbackPorts fields on a component control how it runs inside the host. They are set under spec.template.spec.components[*] in a WorkloadDeployment (or directly in a Workload): poolSize, maxInvocations, and maxConcurrency directly on the component entry; allowedHosts, allowedIpNameLookups, and allowedHostLoopbackPorts nested under its localResources.

  • poolSize: Enables warm-instance pooling (functional as of wasmCloud 2.6.0), the maximum number of warm instances the host keeps for this component. As of wasmCloud 2.7.0, each warm instance is owned by a long-lived driver that serves calls as concurrent tasks, inbound HTTP and component-to-component calls alike. A call routes to the least-loaded warm instance; if all are at their concurrency limit and the pool is under poolSize, a new warm instance starts; if the pool is full and saturated, the call is served from a one-shot store of its own (exactly what an unpooled component pays on every call), so pooling never adds latency it didn't save. When omitted, 0, or negative, every invocation instantiates fresh. Note that when linked components share a store, pooling applies only if every component in that unit has poolSize set: one opted-out component disables pooling for the shared store.
  • maxConcurrency: How many calls one warm instance may serve at the same time (added in wasmCloud 2.7.0; only meaningful alongside poolSize). Unset means 1: what a component gets without asking, and identical to 2.6.0 behavior. Raising it lets an instance overlap calls while awaiting I/O, multiplying warm capacity to poolSize × maxConcurrency in-flight calls, and is only safe for a guest that yields rather than blocks.
  • maxInvocations: Retires a warm instance after it has admitted that many calls: the instance stops admitting new calls, drains the ones it took, and is replaced. (Until the drain finishes it still occupies its pool slot, so a burst arriving mid-drain is served from one-shot stores.) When 0, negative, or omitted, pooled instances are reused without limit. This is not a concurrency limit: it bounds instance reuse, not in-flight requests.

Two pooling caveats worth designing around: a warm instance's context (environment, config, volume mounts) is frozen for its lifetime (maxInvocations bounds how stale it can get) and a guest trap faults every call in flight on that instance. Background tasks a guest spawns without awaiting now survive across calls on a warm instance; a component that relies on background work being torn down with the call should not be pooled.

  • allowedHosts: A list of hosts this component is permitted to make outbound HTTP calls to (e.g., api.example.com, *.s3.amazonaws.com, or * for any host). Entries are matched case-insensitively against the request's host. Calls to hosts not on the list are blocked, enforcing a least-privilege network policy for the component. The policy fails closed: an empty or omitted list denies all outbound HTTP, so a component that makes outbound calls needs either specific entries or an explicit allowedHosts: ["*"] for unrestricted egress. See Workload Security for details and usage guidance.
  • allowedIpNameLookups: A list of names this component is permitted to resolve via wasi:sockets name lookup (added in wasmCloud 2.6.0 as allowIpNameLookup; renamed to its current form in 2.6.1). Entries may be exact hostnames, *.suffix wildcards, * (any name), or literal IPs. DNS resolution is denied by default — an omitted or empty list denies every lookup, so components without this field must connect by IP address. See Workload Security for usage and a note on CRD versions.
  • allowedHostLoopbackPorts: Ports this component may reach on the machine's loopback through the reserved name host.wasmcloud.internal (added in wasmCloud 2.7.0). Entries are single ports with an optional protocol ("5432", "5432/tcp", "53/udp"), and no ranges, wildcards, or names. This is a two-key grant: the list is inert unless the host itself runs with --allow-host-loopback (off by default), so neither the workload author nor the operator can open this door alone. 127.0.0.1 keeps meaning the workload's own virtual loopback; only the reserved name reaches the machine. See Workload Security for the socket policy context.

WorkloadReplicaSet

A WorkloadReplicaSet ensures that a given number of Workload replicas are running at once. It is typically managed by a WorkloadDeployment but can be used directly for more granular control.

Example manifest:

yaml
apiVersion: runtime.wasmcloud.dev/v1alpha1
kind: WorkloadReplicaSet
metadata:
  name: hello-world-v1
  namespace: default
spec:
  replicas: 5
  template:
    metadata:
      labels:
        app: hello-world
        version: v1
    spec:
      hostSelector:
        hostgroup: default
      components:
        - name: http-component
          image: ghcr.io/wasmcloud/components/http-hello-world-rust:0.1.0
          poolSize: 10
      hostInterfaces:
        - namespace: wasi
          package: http
          interfaces:
            - incoming-handler
          config:
            host: my-app.example.com