Skip to content

WebAssembly for Infrastructure - Street-Level Ops

Quick Diagnosis Commands

# Check wasmtime installation and version
wasmtime --version

# Run a WASM binary with wasmtime
wasmtime my-app.wasm

# Run with WASI capabilities (filesystem, network)
wasmtime --dir=. --env="KEY=value" my-app.wasm

# Inspect a WASM module (exports, imports, memory)
wasmtime explore my-app.wasm

# Check what a WASM module imports (dependencies)
wasm-tools dump my-app.wasm | grep -i "import\|export"

# Validate a WASM binary
wasmtime validate my-app.wasm

# Check wasmer installation
wasmer --version
wasmer run my-app.wasm
# Spin CLI (Fermyon's serverless WASM framework)
spin --version

# New Spin application
spin new

# Build Spin application
spin build

# Run locally
spin up

# Deploy to Fermyon Cloud
spin deploy

# Check application status
spin apps list

# View application logs
spin logs my-app
# Kubernetes + WASM (via containerd shim)
# Check if WASM runtime class is available
kubectl get runtimeclasses

# Deploy a WASM workload
kubectl apply -f wasm-pod.yaml

# Check node's WASM runtime support
kubectl get nodes -o json | jq '.items[].metadata.annotations | to_entries[] | select(.key | contains("runtimeclass"))'

# List pods using WASM runtime
kubectl get pods -A -o json | jq '.items[] | select(.spec.runtimeClassName != null) | {name: .metadata.name, runtime: .spec.runtimeClassName}'

Common Scenarios

Scenario 1: Running a WASM Serverless Function Locally with Spin

Build and test a Spin application locally before deploying.

# Install Spin
curl -fsSL https://developer.fermyon.com/downloads/install.sh | bash

# Create a new HTTP trigger application in Rust
spin new http-rust my-service
cd my-service

# Build (compiles Rust to wasm32-wasi target)
spin build

# Run locally on port 3000
spin up

# Test the endpoint
curl http://localhost:3000/

# Add environment variables for local testing
# spin.toml:
# [component.my-service.environment]
# DATABASE_URL = "postgres://..."

# Check spin application manifest
cat spin.toml

# Build with verbose output to debug compilation issues
spin build --up  # build and run in one step

Scenario 2: Running WASM Workloads in Kubernetes

Deploy a WASM-based workload to a Kubernetes cluster that has containerd + wasm runtime shim installed.

# wasm-pod.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: wasm-service
spec:
  replicas: 2
  selector:
    matchLabels:
      app: wasm-service
  template:
    metadata:
      labels:
        app: wasm-service
    spec:
      runtimeClassName: wasmtime   # or spin, wasmedge, etc.
      containers:
        - name: wasm-service
          image: ghcr.io/myorg/my-wasm-app:latest
          resources:
            limits:
              memory: "64Mi"
              cpu: "100m"
# Apply the workload
kubectl apply -f wasm-pod.yaml

# Check pod status
kubectl get pods -l app=wasm-service

# If pod fails to start, check runtime class
kubectl describe pod wasm-service-xxx | grep -A5 "Events:"
kubectl get runtimeclass wasmtime -o yaml

# View WASM application logs
kubectl logs -l app=wasm-service --follow

# Check node taints for WASM nodes
kubectl get nodes -o json | jq '.items[] | {name: .metadata.name, taints: .spec.taints}'

Scenario 3: Debugging a WASM Module That Fails at Runtime

Your WASM binary crashes or returns unexpected results. Need to diagnose without a traditional debugger.

# Run with increased verbosity
WASMTIME_LOG=wasmtime=debug wasmtime my-app.wasm

# Check WASI capabilities — missing capability = trap at runtime
# Common error: "failed to find preopened dir for /etc/config"
# Fix: add --dir=/etc/config to grant filesystem access
wasmtime --dir=/etc/config my-app.wasm

# Run with fuel limit to detect infinite loops (wasmtime extension)
wasmtime --fuel 1000000 my-app.wasm

# Check if the trap is from OOM (memory limit exceeded)
# Default WASM linear memory limit is 4GB but modules often set lower limits
wasmtime --max-wasm-stack=8388608 my-app.wasm  # 8MB stack

# Use wasm-tools to inspect the module
wasm-tools print my-app.wasm | head -100

# Check memory sections
wasm-tools dump my-app.wasm | grep "Memory\|memory"

# If built with Rust, use wasm-pack with debug symbols
wasm-pack build --dev  # includes DWARF debug info

Scenario 4: Building and Publishing a WASM Component

Build a WASM component from Rust/Go/Python and push to OCI registry.

# Rust to WASM (using wasm-pack for web, cargo for WASI)
# For WASI target:
rustup target add wasm32-wasi
cargo build --target wasm32-wasi --release
# Output: target/wasm32-wasi/release/my-app.wasm

# Optimize the binary size (wasm-opt is from the Binaryen toolkit)
wasm-opt -O3 -o optimized.wasm my-app.wasm

# Check binary size
ls -lh target/wasm32-wasi/release/my-app.wasm
wasm-tools print my-app.wasm | wc -l  # instructions count

# Go to WASM (TinyGo for smaller binaries)
tinygo build -target=wasi -o my-app.wasm main.go
# Standard Go also supports wasm32-wasi (Go 1.21+):
GOOS=wasip1 GOARCH=wasm go build -o my-app.wasm .

# Default trap: standard Go produces much larger WASM binaries (~10-20MB)
# than TinyGo (~500KB-2MB) because it includes the full Go runtime and GC.

# Push WASM module to OCI registry (for K8s deployment)
# WASM modules can be stored as OCI artifacts
oras push ghcr.io/myorg/my-wasm-app:latest \
  --artifact-type application/vnd.wasm.content.layer.v1+wasm \
  my-app.wasm:application/vnd.wasm.content.layer.v1+wasm

Key Patterns

WASI Capability Model

# WASI (WebAssembly System Interface) uses a capability-based security model
# WASM modules start with NO access to anything
# Capabilities are explicitly granted at runtime

# Grant filesystem access (preopened directories)
wasmtime --dir=/data --dir=/tmp my-app.wasm
# Module can only see /data and /tmp, not the full filesystem

# Grant network access (WASI Preview 2 / component model)
wasmtime --wasi-threads my-app.wasm  # only if needed

# Inherit environment variables selectively
wasmtime --env=DATABASE_URL=$DATABASE_URL --env=PORT=8080 my-app.wasm
# NOT: wasmtime --inherit-env (exposes all host env vars)

# Check what a module is requesting
wasm-tools print my-app.wasm | grep "import.*wasi"
# Shows which WASI interfaces the module requires

WASM Component Model

# The component model allows WASM modules to import/export typed interfaces
# WIT (WebAssembly Interface Types) defines the interface

# Example WIT interface definition
cat interface.wit
# package myorg:my-service@0.1.0;
# interface handler {
#   handle: func(req: request) -> response;
# }

# Build a component (rather than a core WASM module)
cargo component build --release

# Compose components (link multiple modules together)
wasm-tools compose my-app.wasm \
  -d ./deps/http-handler.wasm \
  -o composed.wasm

# Convert a core WASM module to a component
wasm-tools component new my-app.wasm \
  --adapt wasi_snapshot_preview1.wasm \
  -o my-component.wasm

Security Boundaries

# WASM provides strong sandboxing:
# - Memory isolated: can't read host memory outside linear memory
# - No system calls directly: all via WASI interface (controllable)
# - No filesystem access without explicit grant
# - No network access without explicit grant (varies by runtime)

# Verify sandbox is working — try to access a path not granted
wasmtime --dir=/tmp my-app.wasm  # if app tries /etc, it gets ENOTCAPABLE

# Check module doesn't have unsafe host bindings
wasm-tools validate my-app.wasm --features all

# For K8s: WASM workloads can run without privileged context
# They don't need CAP_SYS_ADMIN, no root required by WASM itself

When WASM Makes Sense vs Containers

Use WASM when:
> **One-liner:** WASM's security pitch in one sentence: a container says "I promise not to escape," while WASM says "I literally cannot access anything you didn't hand me."

- Startup time must be <1ms (WASM: microseconds, containers: 100ms+)
- Polyglot functions from same binary: WASM handles multiple languages
- Untrusted code execution with strong sandboxing needed
- Edge/CDN execution (Cloudflare Workers, Fastly Compute@Edge)
- Very small resource footprint required (WASM binaries: KBs-MBs)
- Plugin system that needs language-agnostic extensibility

Use containers when:
- Full POSIX compliance needed (WASM POSIX support is still incomplete)
- Existing Linux binaries without source modification
- Rich OS interaction (raw sockets, mounts, device access)
- Language runtimes with GC (JVM, .NET — work in WASM but with caveats)
- Full filesystem access patterns expected
- Debugging with standard Linux tools needed

Cloudflare Workers (Edge WASM)

# Cloudflare Workers: WASM at the edge (CDN PoPs)
# Deploy Rust/C/Go functions as Cloudflare Workers

# Install Wrangler CLI
npm install -g wrangler

# Deploy a WASM worker (Rust)
wrangler publish

# View worker logs
wrangler tail my-worker

# Test locally
wrangler dev

# Check worker metrics
wrangler metrics my-worker