Introduction
WebAssembly started as a way to run C++ in browsers. In 2026, it’s a universal compute substrate for:
- Edge functions that start in microseconds (vs seconds for containers)
- Plugin systems that safely run untrusted third-party code
- Serverless with near-zero cold starts
- Portable binaries that run on any OS/CPU without recompilation
The key insight: Wasm + WASI gives you a sandboxed, portable binary that’s smaller than a container image and starts faster than a process.
Why Wasm for Server-Side?
Container cold start: 2-10 seconds (pull image, start runtime, init app)
Process cold start: 100-500ms (fork, exec, init)
Wasm cold start: 1-10ms (load module, JIT compile, execute)
Container size: 50MB-1GB
Wasm module size: 1-20MB (no OS, no runtime bundled)
Container isolation: OS-level namespaces
Wasm isolation: Capability-based sandbox (no syscalls without explicit grant)
WASI: System Access for Wasm
WASI (WebAssembly System Interface) gives Wasm modules controlled access to the host system:
Without WASI: Wasm can only do pure computation (no files, no network)
With WASI: Wasm can access files, network, env vars โ but only what you grant
// Rust program compiled to Wasm with WASI
use std::fs;
use std::env;
fn main() {
// These work in Wasm+WASI if the host grants access
let args: Vec<String> = env::args().collect();
let content = fs::read_to_string(&args[1]).unwrap();
println!("File contents: {}", content);
}
# Compile Rust to Wasm
rustup target add wasm32-wasi
cargo build --target wasm32-wasi --release
# Run with Wasmtime (only grants access to /tmp)
wasmtime --dir /tmp target/wasm32-wasi/release/myapp.wasm /tmp/test.txt
# The module CANNOT access /etc/passwd even if it tries
# โ capability-based security
Wasmtime: The Production Runtime
Wasmtime is the reference Wasm runtime from the Bytecode Alliance:
# Install
curl https://wasmtime.dev/install.sh -sSf | bash
# Run a Wasm module
wasmtime hello.wasm
# Grant filesystem access
wasmtime --dir /data myapp.wasm
# Grant network access (WASI preview2)
wasmtime --wasi-modules=experimental-wasi-sockets myapp.wasm
# Set environment variables
wasmtime --env KEY=value myapp.wasm
# Limit memory (in pages, 1 page = 64KB)
wasmtime --max-wasm-stack 1048576 myapp.wasm
Embedding Wasmtime in Go
package main
import (
"context"
"fmt"
"os"
"github.com/bytecodealliance/wasmtime-go/v14"
)
func main() {
// Load Wasm module
wasmBytes, _ := os.ReadFile("plugin.wasm")
engine := wasmtime.NewEngine()
store := wasmtime.NewStore(engine)
module, _ := wasmtime.NewModule(engine, wasmBytes)
// Create instance with WASI
wasiConfig := wasmtime.NewWasiConfig()
wasiConfig.InheritStdout()
wasiConfig.InheritStderr()
store.SetWasi(wasiConfig)
linker := wasmtime.NewLinker(engine)
linker.DefineWasi()
instance, _ := linker.Instantiate(store, module)
// Call exported function
fn := instance.GetFunc(store, "process")
result, _ := fn.Call(store, int32(42))
fmt.Println("Result:", result)
}
Embedding Wasmtime in Python
from wasmtime import Store, Module, Instance, WasiConfig, Linker, Engine
def run_wasm_plugin(wasm_path: str, input_data: bytes) -> bytes:
engine = Engine()
store = Store(engine)
# Configure WASI โ no filesystem access, no network
wasi = WasiConfig()
wasi.inherit_stdout()
store.set_wasi(wasi)
module = Module.from_file(engine, wasm_path)
linker = Linker(engine)
linker.define_wasi()
instance = linker.instantiate(store, module)
# Call the plugin's process function
process = instance.exports(store)["process"]
memory = instance.exports(store)["memory"]
# Write input to Wasm memory
ptr = instance.exports(store)["alloc"](store, len(input_data))
memory.data_ptr(store)[ptr:ptr+len(input_data)] = input_data
# Call process
result_ptr = process(store, ptr, len(input_data))
# Read result from Wasm memory
# ... (read length-prefixed bytes from result_ptr)
return result
Spin: Serverless Wasm Functions
Spin by Fermyon is the easiest way to build serverless Wasm functions:
# Install Spin
curl -fsSL https://developer.fermyon.com/downloads/install.sh | bash
# Create a new Spin app
spin new http-rust my-api
cd my-api
# Build and run locally
spin build
spin up
# Listening on http://127.0.0.1:3000
// src/lib.rs โ Spin HTTP handler in Rust
use spin_sdk::http::{IntoResponse, Request, Response};
use spin_sdk::http_component;
#[http_component]
fn handle_request(req: Request) -> anyhow::Result<impl IntoResponse> {
let body = format!("Hello from Wasm! Path: {}", req.uri().path());
Ok(Response::builder()
.status(200)
.header("content-type", "text/plain")
.body(body)
.build())
}
# spin.toml
spin_manifest_version = 2
[application]
name = "my-api"
version = "0.1.0"
[[trigger.http]]
route = "/..."
component = "my-api"
[component.my-api]
source = "target/wasm32-wasi/release/my_api.wasm"
allowed_outbound_hosts = ["https://api.example.com"]
[component.my-api.build]
command = "cargo build --target wasm32-wasi --release"
Spin with TypeScript
// src/index.ts
import { HandleRequest, HttpRequest, HttpResponse } from "@fermyon/spin-sdk"
export const handleRequest: HandleRequest = async (request: HttpRequest): Promise<HttpResponse> => {
const name = new URL(request.uri).searchParams.get("name") ?? "World"
return {
status: 200,
headers: { "content-type": "application/json" },
body: JSON.stringify({ message: `Hello, ${name}!` })
}
}
Plugin Systems with Wasm Sandboxing
Wasm is ideal for running untrusted third-party plugins safely:
// plugin_host.go โ host application that runs Wasm plugins
package main
import (
"context"
"fmt"
"time"
"github.com/bytecodealliance/wasmtime-go/v14"
)
type PluginHost struct {
engine *wasmtime.Engine
}
type PluginResult struct {
Output []byte
Error error
}
func (h *PluginHost) RunPlugin(pluginPath string, input []byte, timeout time.Duration) PluginResult {
store := wasmtime.NewStore(h.engine)
// Resource limits โ prevent runaway plugins
store.SetFuel(1_000_000) // limit CPU instructions
store.Limiter(
1024*1024*10, // 10MB max memory
-1, // unlimited tables
-1, // unlimited instances
-1, // unlimited memories
-1, // unlimited globals
)
// No filesystem, no network โ pure computation only
wasi := wasmtime.NewWasiConfig()
store.SetWasi(wasi)
module, err := wasmtime.NewModuleFromFile(h.engine, pluginPath)
if err != nil {
return PluginResult{Error: fmt.Errorf("load plugin: %w", err)}
}
linker := wasmtime.NewLinker(h.engine)
linker.DefineWasi()
// Timeout via context
ctx, cancel := context.WithTimeout(context.Background(), timeout)
defer cancel()
instance, err := linker.Instantiate(store, module)
if err != nil {
return PluginResult{Error: fmt.Errorf("instantiate: %w", err)}
}
// Call plugin's transform function
transform := instance.GetFunc(store, "transform")
if transform == nil {
return PluginResult{Error: fmt.Errorf("plugin missing 'transform' export")}
}
// ... write input to memory, call function, read output
return PluginResult{Output: []byte("result")}
}
// Usage: safely run untrusted plugins
func main() {
host := &PluginHost{engine: wasmtime.NewEngine()}
// Run user-provided plugin โ sandboxed, can't harm the host
result := host.RunPlugin(
"user-plugin.wasm",
[]byte(`{"data": "process this"}`),
5*time.Second,
)
if result.Error != nil {
fmt.Println("Plugin error:", result.Error)
} else {
fmt.Println("Plugin output:", string(result.Output))
}
}
Wasm on Kubernetes with Krustlet / WASM Node
# Deploy a Wasm workload to Kubernetes
# (requires a Wasm-capable node runtime like containerd-wasm-shims)
apiVersion: apps/v1
kind: Deployment
metadata:
name: wasm-app
spec:
replicas: 3
selector:
matchLabels:
app: wasm-app
template:
metadata:
labels:
app: wasm-app
spec:
# Schedule on Wasm-capable nodes
nodeSelector:
kubernetes.io/arch: wasm32
runtimeClassName: wasmtime # Wasm runtime class
containers:
- name: app
image: ghcr.io/myorg/myapp:latest # OCI image containing .wasm
resources:
limits:
memory: "64Mi" # Much smaller than container workloads
cpu: "100m"
Language Support
| Language | Wasm Support | WASI Support | Notes |
|---|---|---|---|
| Rust | Excellent | Excellent | Best ecosystem, smallest binaries |
| C/C++ | Excellent | Good | Via Emscripten or wasi-sdk |
| Go | Good | Good | GOOS=wasip1 GOARCH=wasm |
| Python | Good | Good | Via Pyodide or py2wasm |
| TypeScript/JS | Good | Good | Via Javy or QuickJS |
| Java | Experimental | Experimental | Via TeaVM or GraalVM |
# Compile Go to Wasm
GOOS=wasip1 GOARCH=wasm go build -o main.wasm .
wasmtime main.wasm
# Compile C to Wasm
/opt/wasi-sdk/bin/clang --sysroot=/opt/wasi-sdk/share/wasi-sysroot \
-o hello.wasm hello.c
wasmtime hello.wasm
When to Use Wasm
โ Edge functions needing <10ms cold start
โ Plugin systems requiring sandboxed untrusted code
โ Serverless with very high invocation rates (cold starts matter)
โ Portable binaries across OS/CPU architectures
โ Multi-tenant compute where isolation is critical
โ Long-running services (containers are better)
โ GPU workloads (limited GPU support)
โ Applications needing full POSIX compatibility
โ When your language has poor Wasm support
Resources
- WebAssembly Official Site
- WASI Specification
- Wasmtime Runtime
- Spin Framework
- Bytecode Alliance
- Wasm by Example
Comments