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:
- Artifact -
runtime.wasmcloud.dev/v1alpha1 - Host -
runtime.wasmcloud.dev/v1alpha1 - Workload -
runtime.wasmcloud.dev/v1alpha1 - WorkloadDeployment -
runtime.wasmcloud.dev/v1alpha1 - WorkloadReplicaSet -
runtime.wasmcloud.dev/v1alpha1
This document explains each of these custom resources at a high level. For a complete API specification, see the API reference.
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:
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-secretHost
A Host resource defines a wasmCloud runtime environment, or host, which has a unique ID and can run Wasm workloads.
Example manifest:
apiVersion: runtime.wasmcloud.dev/v1alpha1
kind: Host
metadata:
name: host-sample
namespace: default
labels:
hostgroup: default
hostId: NABCDEFGHIJKLMNOPQRSTUVWXYZ234567
hostname: host-sample.default
httpPort: 4000Unlike 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:
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:
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.comHost 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:
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.
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.
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.comNaming rules:
nameis 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-emptyname. - Names must be unique within a workload's
hostInterfacesfor the samenamespace+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'swasmcloud:messaging/handlerexport 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 valuebroadcastto 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 like45s,2m, or bare seconds. Default30s, maximum600s.
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):
| Flag | Environment variable | Default |
|---|---|---|
--wasmcloud-messaging-max-in-flight | WASH_WASMCLOUD_MESSAGING_MAX_IN_FLIGHT | Derived from the instance pool (133 on a stock host) |
--wasmcloud-messaging-max-in-flight-per-component | WASH_WASMCLOUD_MESSAGING_MAX_IN_FLIGHT_PER_COMPONENT | A 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.
localResources:
environment:
config:
some_key: some_valueEnvironmental values may also come from ConfigMaps or Secrets. The following approaches are also valid:
localResources:
environment:
configFrom:
- name: my-configmap
secretFrom:
- name: my-secret
config:
literal_key: literal_valueComponent 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 underpoolSize, 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 haspoolSizeset: 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 alongsidepoolSize). Unset means1: 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 topoolSize × maxConcurrencyin-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.) When0, 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 explicitallowedHosts: ["*"]for unrestricted egress. See Workload Security for details and usage guidance.allowedIpNameLookups: A list of names this component is permitted to resolve viawasi:socketsname lookup (added in wasmCloud 2.6.0 asallowIpNameLookup; renamed to its current form in 2.6.1). Entries may be exact hostnames,*.suffixwildcards,*(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 namehost.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.1keeps 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:
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