WebAssembly Beyond the Browser: Edge Computing and Serverless
WasmHub Team
February 26, 2025 · 6 min read
The browser is no longer WebAssembly's primary home. A growing number of cloud platforms have adopted Wasm as their preferred execution model for serverless and edge computing — and for good reasons that go beyond raw performance. This post explores why Wasm is a compelling fit for these environments and how the major platforms are using it today.
Why Wasm Works So Well at the Edge
Serverless and edge computing have a fundamental tension: you want to run arbitrary user code close to end users, with strong isolation between tenants, with startup times measured in microseconds rather than seconds, and at a cost that makes running thousands of tiny functions economical.
Traditional containers solve isolation but carry a Linux kernel's worth of overhead. Language-specific runtimes (Node.js, Python, JVM) solve developer experience but leak abstractions across tenant boundaries. Wasm solves all three:
- Isolation: each module has its own linear memory space with no access to the host filesystem, network, or other modules' memory unless explicitly granted
- Cold start: a Wasm module can be compiled and instantiated in under a millisecond — orders of magnitude faster than spinning up a container or even a Node.js process
- Size: Wasm binaries are typically 100 KB–5 MB, not hundreds of megabytes; they distribute and cache trivially
The WASI capability model adds a fourth property that's crucial for multi-tenant platforms: principle of least privilege by default. A module that doesn't need network access simply doesn't receive a socket capability — no firewall rules required.
Cloudflare Workers
Cloudflare Workers was one of the first platforms to bet heavily on Wasm. It runs on V8 isolates across Cloudflare's 300+ edge locations, and every Worker can execute Wasm modules at the same edge node that serves the HTTP request.
Workers are written in JavaScript or TypeScript, but you can load Wasm modules alongside the JS glue:
// worker.js — loaded at the edge node nearest to the request
import wasmBytes from './image-resizer.wasm'
const { instance } = await WebAssembly.instantiate(wasmBytes)
const { resize, alloc, dealloc, memory } = instance.exports
export default {
async fetch(request) {
if (request.method !== 'POST') {
return new Response('Expected POST', { status: 405 })
}
const buffer = await request.arrayBuffer()
const input = new Uint8Array(buffer)
const ptr = alloc(input.byteLength)
new Uint8Array(memory.buffer, ptr, input.byteLength).set(input)
const outPtr = resize(ptr, input.byteLength, 400, 300)
const outLen = /* read from Wasm */ instance.exports.last_output_size()
const output = new Uint8Array(memory.buffer, outPtr, outLen).slice()
dealloc(ptr, input.byteLength)
return new Response(output, {
headers: { 'Content-Type': 'image/webp' },
})
},
}Cloudflare also ships Workers written in Rust using the worker crate, which compiles to Wasm under the hood. Their internal Wasm-native products — like Cloudflare R2's storage layer — are built entirely this way.
Fastly Compute
Fastly's Compute platform takes a more opinionated approach: your entire function is a Wasm module, not a Wasm module called from JavaScript. You write in Rust, Go, AssemblyScript, or any WASI-compatible language, compile to Wasm, and deploy via the Fastly CLI.
// src/main.rs — a Fastly Compute handler in Rust
use fastly::http::{HeaderValue, Method, StatusCode};
use fastly::{Error, Request, Response};
#[fastly::main]
fn main(req: Request) -> Result<Response, Error> {
match req.get_method() {
&Method::GET => {
let mut resp = Response::from_status(StatusCode::OK);
resp.set_body("Hello from the edge!");
Ok(resp)
}
_ => Ok(Response::from_status(StatusCode::METHOD_NOT_ALLOWED)),
}
}fastly compute build # compiles to .wasm
fastly compute deploy # ships to Fastly's edgeFastly's viceroy tool lets you run the same Wasm binary locally for development — same runtime, same WASI environment, same behavior. No "works on my machine" problems. The cold-start time in production is consistently under 100 µs.
Fermyon Spin
Spin from Fermyon is the most Wasm-native of the major frameworks. It's built entirely around the WASI Component Model: your application is a set of composable Wasm components wired together by a Spin manifest.
# spin.toml
spin_manifest_version = 2
[application]
name = "api-service"
version = "1.0.0"
[[trigger.http]]
route = "/api/..."
component = "handler"
[component.handler]
source = "target/wasm32-wasip2/release/handler.wasm"
allowed_outbound_hosts = ["https://api.example.com"]
[component.handler.build]
command = "cargo component build --release"The allowed_outbound_hosts line is a perfect example of the capability model in action: the component can only make HTTP requests to the listed hosts — nothing else. You add capabilities in the manifest; you don't subtract them with firewall rules.
Spin runs on Fermyon Cloud, on any Kubernetes cluster (via SpinKube), on bare metal with Wasmtime, and locally with spin up. The same .wasm binary works everywhere.
Docker + Wasm
Docker shipped native Wasm support in Docker Desktop 4.15 (late 2023) and has continued expanding it. The key piece is a containerd shim — containerd-shim-wasmtime-v1 — that lets the container runtime launch a Wasm module instead of a Linux process when the image's platform is wasi/wasm.
# Build stage — compile Rust to WASI
FROM rust:1.78 AS builder
WORKDIR /app
COPY . .
RUN rustup target add wasm32-wasip1
RUN cargo build --release --target wasm32-wasip1
# Runtime — a scratch image with the Wasm binary only
FROM scratch
COPY --from=builder /app/target/wasm32-wasip1/release/myapp.wasm /myapp.wasm
ENTRYPOINT ["/myapp.wasm"]docker buildx build --platform wasi/wasm -t myapp .
docker run --runtime=io.containerd.wasmtime.v1 --platform=wasi/wasm myappThe resulting image is tiny (often under 5 MB), starts instantly, and runs on any architecture — the Wasm binary is the same on x86-64 and ARM64. Kubernetes clusters running the WasmEdge or Wasmtime shim can schedule Wasm workloads alongside regular containers with no special node configuration beyond installing the shim.
AWS Lambda and Spin-based Serverless
AWS Lambda doesn't natively run Wasm today, but several deployment patterns exist. The most common is running a Wasm runtime (Wasmtime or Wasmer) as the Lambda function itself, which then loads and executes Wasm modules from S3 or bundled in the Lambda package. This gives you the isolation and cold-start benefits of Wasm inside Lambda's billing model.
Fermyon Cloud's managed offering is the most turnkey option for Spin-based serverless: deploy a component, get an HTTPS endpoint, pay per request. No infrastructure to manage.
The Emerging Pattern
Across all these platforms, a common pattern is emerging: Wasm as the universal unit of deployment. One binary, compiled once, runs on Cloudflare's V8 isolates, Fastly's AOT-compiled edge nodes, Fermyon's Spin runtime, a Kubernetes pod via the WasmEdge shim, or a developer's laptop via wasmtime run. The same isolation guarantees, the same security model, the same binary.
This is the promise that containers made in 2013 — "build once, run anywhere" — but delivered at a finer granularity, with stronger security boundaries, and with startup times that make even the smallest functions economically viable. If you're building the next generation of serverless infrastructure, Wasm deserves a serious look.