A JVM-based wasmCloud host built on the Endive WebAssembly
runtime (a fork of Chicory). Speaks the wasmCloud runtime-operator NATS API:
heartbeats on runtime.operator.heartbeat.<host-id>, RPC on
runtime.host.<host-id>.workload.{start,status,stop}.
What works today, end-to-end:
- Boots, registers as a
HostCR with the runtime-operator. - Accepts
WorkloadStartRequests dispatched by the operator (or by hand over NATS). - Pulls wasm bytes from an OCI registry (
Service.imageorWitWorld.components[0].image), anonymous bearer-token auth, OCI image-index and Docker manifest-list both supported, plain-HTTP support for local/dev registries viaENDIVE_INSECURE_REGISTRIES. - Binds
wasi:http/incoming-handlerhost interfaces to an Undertow HTTP route (path fromconfig.path, default/<workload-id>). - Also supports running modules from a YAML config file with HTTP, cron, or NATS triggers (independent of the operator).
What this host does not do:
- Run WASI 0.2 / component-model components. The wasm engine wraps Endive's
Instance.builderand calls_start, so modules must be WASI Preview 1 command modules. AWorkloadDeploymentdeclaringwasi:http/incoming-handlerwill route to the right wasm and serve it over HTTP, but the body of the call is a JSON-over-stdio shim — not a realwasi:http/incoming-handlerhost binding. Components built againstwasi:httpwon't actually be invoked. - OCI auth beyond anonymous bearer-token. Private registries (most
ghcr.iopaths) will return 401. Adding basic auth or a credential env var is straightforward. - Integrate with the wasmCloud runtime-gateway. The host serves HTTP triggers directly on its own port; the gateway sees the workload via the operator but does not route requests through.
- Docker (for the compose stack)
- Maven 3.9+ and JDK 21+ (for building this repo)
- Endive
999-SNAPSHOTin your local Maven cache (see next section) - Optional, for the operator-driven demo:
oras,kubectl,natsCLIs
Endive is not yet published to Maven Central, so the run.endive:* artifacts
this project depends on have to be built locally and dropped into
~/.m2/repository/run/endive/. One-time setup:
git clone https://github.com/bytecodealliance/endive ~/repos/bytecodealliance/endive
cd ~/repos/bytecodealliance/endive
./mvnw install -DskipTestsmvn install is the important verb — package alone leaves the jars in
Endive's own target/ and our build won't see them.
Verify the install landed:
ls ~/.m2/repository/run/endive/runtime/999-SNAPSHOT/
# runtime-999-SNAPSHOT.jar runtime-999-SNAPSHOT.pom ...
ls ~/.m2/repository/run/endive/wasi/999-SNAPSHOT/
# wasi-999-SNAPSHOT.jar wasi-999-SNAPSHOT.pom ...If both directories exist with .jar files (not just .lastUpdated markers),
Maven will resolve run.endive:runtime and run.endive:wasi for our build.
The Endive version this repo expects is pinned in pom.xml:
<endive.version>999-SNAPSHOT</endive.version>When Endive starts publishing real releases, bump that property and re-run
mvn install against the matching Endive tag. If you see a build failure like
Could not find artifact run.endive:runtime:jar:<X> in central and the
artifacts exist in ~/.m2, run mvn -U to force a re-resolve — Maven caches
the previous miss.
make demo # build, compose up, push hello.wasm, apply Workload, curl /hi
# ... record what you need ...
make down # docker compose down -v + wipe deploy/k3s/tmpmake demo does the full end-to-end loop in ~30 seconds. The individual
steps it runs are listed in the Deploying a wasm module
section below, so you can run them by hand for a recording.
That brings up k3s (with the wasmCloud CRDs already loaded), NATS, an OCI registry, the wasmCloud runtime-operator, runtime-gateway, and the endive-host itself. Confirm the host registered:
export KUBECONFIG=$PWD/tmp/kubeconfig.yaml
kubectl --insecure-skip-tls-verify get hosts.runtime.wasmcloud.dev
# NAMESPACE NAME HOSTID HOSTGROUP READY
# default endive-host ac346f7d-5230-4728-9116-6f6bdcdabff1 default TrueThe host accepts WASI Preview 1 command modules (anything exporting _start).
A 166-byte hello world example lives in examples/hello.wasm (copied from
endive's wasm-corpus).
Push it to the local registry:
cd examples
oras push --plain-http localhost:5050/hello:demo \
hello.wasm:application/vnd.wasm.content.layer.v1+wasmApply a Workload that points at it:
kubectl --insecure-skip-tls-verify apply -f examples/workload.yaml
# workload.runtime.wasmcloud.dev/hello created
kubectl --insecure-skip-tls-verify get workload hello \
-o jsonpath='{.status.conditions[?(@.type=="Ready")].status}'
# True
curl http://localhost:8081/hi
# hello worldThe operator picks a Ready host, NATS-publishes WorkloadStartRequest on
runtime.host.<host-id>.workload.start, and the host pulls the image and
registers the HTTP trigger.
| flag | default | notes |
|---|---|---|
--config, -c <path> |
— | YAML config file (see below); CLI flags override |
--nats-url <url> |
nats://localhost:4222 |
|
--http-port <port> |
8080 |
|
--host-group <group> |
default |
sets the hostgroup label |
--host-id <id> |
random UUID | |
--host-name <name> |
random <adjective>-<noun>-<nnnn> |
| var | notes |
|---|---|
ENDIVE_INSECURE_REGISTRIES |
comma-separated list of host:port entries the OCI fetcher should use plain HTTP for (default empty → HTTPS for everything) |
host:
friendly-name: demo-host
host-group: default
labels:
zone: dev
nats:
url: nats://nats:4222
http:
port: 8080
bind-address: 0.0.0.0
heartbeat:
interval-seconds: 15
workloads: # optional — pre-configured workloads
- id: hello
wasm-path: examples/hello.wasm # or an OCI ref like registry:5000/hello:demo
trigger:
type: http # http | cron | nats
path: /hello # http: path; cron: schedule; nats: subject
env:
FOO: barThe YAML path runs alongside the operator-driven path. Both register triggers
through the same HttpServer / TriggerRegistry.
+--------------------+
| runtime-operator |
+---------+----------+
| NATS: runtime.host.<id>.workload.start
v
+--------+ heartbeats +---+------------+ OCI pull +-----------+
| NATS | <------------- | endive-host | -----------> | registry |
+--------+ workload | (this repo) | +-----------+
RPC +---+------------+
| Undertow trigger
v
+--------------------+
| wasm module |
| (WASI Preview 1) |
+--------------------+
The host process is one JVM with five long-lived pieces:
NatsControlPlane— subscribes toruntime.host.<id>.>, dispatches toHostApimethods; publishesHostHeartbeatevery 15s.HttpServer— single Undertow listener with a dynamicpath → TriggerCallbackmap.WorkloadManager/TriggerRegistry— register/teardown workloads and their associated triggers.OciFetcher— Docker Registry v2 client overjava.net.http.EndiveWasmEngine/EndiveWasmModule— wraps Endive'sParser+Instancefor WASI Preview 1 execution.
endive-host/
├── endive-host-core/ # library: host runtime, OCI fetch, triggers, NATS
│ └── src/main/proto/ # wasmcloud.runtime.v2 protobuf (host_service,
│ # workload_service, host_heartbeat, etc.)
├── endive-host-app/ # executable: CLI parsing + main()
├── deploy/k3s/ # docker-compose stack (k3s + NATS + registry +
│ ├── docker-compose.yml # operator + gateway + endive-host)
│ └── kubernetes/ # k3s server scripts, healthcheck
├── charts/runtime-operator/crds/ # synced from upstream chart, pinned in
│ # .version (see scripts/sync-crds.sh)
├── examples/
│ ├── hello.wasm # WASI Preview 1 "hello world" demo module
│ ├── host.yaml # YAML config example for the standalone path
│ ├── workload.yaml # Kubernetes Workload CR example
│ └── vertx-demo/ # Vert.x app embedding endive-host alongside
│ # pure-Java handler functions
├── scripts/sync-crds.sh # pulls CRDs from the published Helm chart
└── Dockerfile # eclipse-temurin:21-jre-alpine + shaded jar
The committed CRDs in charts/runtime-operator/crds/ are sourced from the
published Helm chart at oci://ghcr.io/wasmcloud/charts/runtime-operator.
Update with:
scripts/sync-crds.sh # uses the version pinned in the script
scripts/sync-crds.sh --version 2.3.0 # bumpThe synced version is recorded in charts/runtime-operator/crds/.version.
| target | what it does |
|---|---|
make build |
mvn -DskipTests package for the whole reactor (host + vertx-demo). |
make demo |
make build, then docker compose up -d --build, push examples/hello.wasm to the local registry, apply examples/workload.yaml, kubectl wait, and curl /hi. |
make down |
docker compose down -v and rm -rf deploy/k3s/tmp. |
GATEWAY_PORT, ENDIVE_HOST_PORT, and REGISTRY_PORT overrides are
respected (defaults 8000 / 8081 / 5050).
make build
# endive-host-app/target/endive-host-app-0.1.0-SNAPSHOT.jar (shaded fat jar)
# rebuild just the host container in the running stack:
cd deploy/k3s && docker compose up -d --build endive-hostmvn -pl endive-host-core test
# Tests run: 21, Failures: 0, Errors: 0Coverage today: OCI reference parsing (including the registry-port edge case),
WWW-Authenticate challenge parsing, and the WorkloadManager state machine.