Skip to main content
Version: v2

Deploy a Wasm Workload

In our previous tutorials, we started a developer loop and added two interfaces (wasi:logging and wasi:keyvalue) to our application. The wash dev process satisfied our application's capability requirements automatically, so we could move quickly and focus on code.

Now we'll learn how to:

  • Compile your application to a WebAssembly component binary
  • Publish your Wasm binary to an OCI registry
  • Set up a wasmCloud environment on Kubernetes and deploy your Wasm workload
Prerequisites

This tutorial assumes you're following directly from the previous steps. Make sure to complete Quickstart and Develop a WebAssembly Component first.

Build Wasm binary

Use the wash build command from the root of a project directory to compile the component into a .wasm binary:

shell
wash build

By default, the compiled .wasm binary for a Rust project is generated at /target/wasm32-wasip2/debug/. The output path for the compiled .wasm binary can be configured via wash's config.json file.

Publish image

Now we can publish a Wasm binary image to any OCI compliant registry that supports OCI artifacts.

What is this image?

The wasmCloud ecosystem uses the OCI image specification to package Wasm components—these images are not container images, but conform to OCI standards and may be stored on any OCI-compatible registry. You can learn more about wasmCloud packaging on the Packaging page.

Authenticate to registry

wash uses Docker credentials for OCI registry authentication. For GitHub Packages (GHCR), you'll need a GitHub Personal Access Token (PAT) with write:packages scope. Authenticate with the docker CLI:

shell
docker login ghcr.io -u <username>

Docker will prompt you for your PAT as the password.

note

Your GitHub username (and the namespace in image paths) must be all lowercase.

Push image to registry

Now we'll push a Wasm component image to a registry. The example below uses GitHub Packages, but you can use any OCI compliant registry that supports OCI artifacts.

If you're following along using GitHub Packages, make sure to replace <namespace> in the command below with your own GitHub namespace (lowercase).

shell
wash oci push ghcr.io/<namespace>/http-hello-world:0.1.0 ./target/wasm32-wasip2/debug/http_hello_world.wasm
  • The registry address (including name and tag) is specified for the first option with wash oci push.
  • The second option defines the target path for the component binary to push.
warning

If you're using GitHub Packages, remember to make the image public in order to follow the steps below.

Install wasmCloud on Kubernetes

Installation requires the following tools:

For more information on running wasmCloud on Kubernetes, see Kubernetes Operator.

Select your Kubernetes environment:

If you already have a Kubernetes cluster, skip cluster creation. Verify your kubectl context is pointing to the right cluster:

shell
kubectl cluster-info

Use Helm to install the wasmCloud operator:

shell
helm install wasmcloud --version 2.0.1 oci://ghcr.io/wasmcloud/charts/runtime-operator \
  -f https://raw.githubusercontent.com/wasmCloud/wasmCloud/refs/heads/main/charts/runtime-operator/values.local.yaml \
  --set gateway.service.type=LoadBalancer

Wait for the deployment to be ready:

shell
kubectl rollout status deploy -l app.kubernetes.io/instance=wasmcloud -n default

Deploy Wasm workload

Now we'll deploy our Wasm application using a WorkloadDeploment manifest.

wasmCloud security model

wasmCloud components are deny-by-default: a component cannot use any capability—HTTP, key-value storage, logging, or any other—unless the host is explicitly told to allow it. The hostInterfaces field in a workload manifest is the allowlist. Each entry names an interface package that the host will make available to the component. Any interface not listed is silently unavailable, regardless of what the component's WIT file imports.

Run the following script in your terminal to create a file called deployment.yaml containing a manifest.

shell
cat > deployment.yaml << 'EOF'
  apiVersion: runtime.wasmcloud.dev/v1alpha1
  kind: WorkloadDeployment
  metadata:
    name: http-hello-world
  spec:
    replicas: 1
    template:
      spec:
        hostSelector:
          hostgroup: default
        components:
          - name: http-hello-world
            image: ghcr.io/<namespace>/http-hello-world:0.1.0
        hostInterfaces:
          - namespace: wasi
            package: http
            interfaces:
              - incoming-handler
            config:
              host: localhost
          - namespace: wasi
            package: keyvalue
            interfaces:
              - atomics
              - store
EOF

This is a deployment manifest for a Kubernetes custom resource. For the purposes of this quickstart, the most important fields to highlight in the specification are:

  • hostSelector.hostgroup: Selects which pool of wasmCloud hosts runs this workload. The Helm chart installs three host pods labelled hostgroup: default—this is the correct target for most deployments. Custom host groups can be created to isolate workloads or provide specialized capabilities.
  • components.image: This defines the registry address we will use to fetch our Wasm image.
  • hostInterfaces: These declare which capability interfaces the host will make available to the component. Without an explicit entry here, the component cannot access that capability. Since the HTTP and key-value capabilities use well-known built-in interfaces, all we have to do is list them.

Open the file and update the image name with the correct registry address. If you're using GitHub Packages, that means replacing <namespace> with your GitHub namespace.

yaml
apiVersion: runtime.wasmcloud.dev/v1alpha1
kind: WorkloadDeployment
metadata:
  name: hello-world
spec:
  replicas: 1
  template:
    spec:
      hostSelector:
        hostgroup: public-ingress
      components:
        - name: http-hello-world
          image: ghcr.io/<namespace>/http-hello-world:0.1.0
      hostInterfaces:
        - namespace: wasi
          package: http
          interfaces:
            - incoming-handler
          config:
            host: localhost

Now use kubectl to apply the manifest:

shell
kubectl apply -f deployment.yaml

Verify that the Wasm component is successfully deployed:

shell
kubectl get workloaddeployments

Use curl to invoke the Wasm workload with an HTTP request:

macOS with OrbStack

OrbStack manages port 80 on macOS, intercepting traffic before it reaches the cluster. Run a port-forward in a separate terminal:

shell
kubectl port-forward svc/runtime-gateway 8080:80

Then substitute localhost:8080 for localhost in the hello-world command, and use -H "Host: blobby.localhost.direct" http://localhost:8080/... in place of http://blobby.localhost.direct/... in the Blobby commands further below.

shell
curl localhost
text
Hello x1, World!

Deploy the Blobby file server

Now let's deploy a more feature-rich example: Blobby ("Little Blobby Tables"), a simple file server that demonstrates the wasi:blobstore capability alongside wasi:http.

The manifest below uses host: blobby.localhost.direct to route HTTP traffic — blobby.localhost.direct is a public DNS alias for 127.0.0.1, so no /etc/hosts changes are needed. Apply it to deploy the blobby-ui component from a wasmCloud-hosted OCI image:

shell
kubectl apply -f - <<EOF
apiVersion: runtime.wasmcloud.dev/v1alpha1
kind: WorkloadDeployment
metadata:
  name: blobby
spec:
  replicas: 1
  template:
    spec:
      hostInterfaces:
        - namespace: wasi
          package: http
          interfaces:
            - incoming-handler
          config:
            host: blobby.localhost.direct
        - namespace: wasi
          package: blobstore
          version: 0.2.0-draft
          interfaces:
            - blobstore
          config:
            buckets: blobby
        - namespace: wasi
          package: logging
          version: 0.1.0-draft
          interfaces:
            - logging
      components:
        - name: blobby
          image: ghcr.io/wasmcloud/components/blobby-ui:0.5.2
EOF

Verify the deployment:

shell
kubectl get workloaddeployments

Once running, open http://blobby.localhost.direct in your browser to use the file manager UI:

Blobby Storage Manager UI showing an uploaded file with Copy Link, Download, and Delete buttons

You can also interact with the file server directly from the command line:

Upload a file:

shell
echo "Hello, wasmCloud!" > hello.txt
curl -H "Content-Type: text/plain" http://blobby.localhost.direct/hello.txt --data-binary @hello.txt

List all files:

shell
curl -X POST http://blobby.localhost.direct/

Download a file:

shell
curl http://blobby.localhost.direct/hello.txt

Delete a file:

shell
curl -X DELETE http://blobby.localhost.direct/hello.txt

Clean up

To remove the deployments created in this tutorial:

shell
kubectl delete workloaddeployment http-hello-world blobby

To tear down the cluster entirely:

Remove the wasmCloud Helm release:

shell
helm uninstall wasmcloud

What's next?

  • Explore the interfaces: Learn how WASI interfaces work and browse the full list of built-in capabilities your components can use.
  • Go deeper with components: The Developer Guide covers building, publishing, debugging, and composing components.
  • Deploy to production: The Operator Manual covers production topics: RBAC, private registries, and CI/CD pipelines.