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