WASM microservices
WebAssembly microservices are portable services compiled to WASM: sandboxed, runtime-agnostic, and deployable across browser, edge, cloud, and AI-native environments without recompilation.
What a WASM microservice is
A WASM microservice is a service whose business logic is compiled to a WebAssembly module rather than packaged as a container image or deployed as a native process. The module is a self-contained binary: it imports only the capabilities it explicitly declares, exposes a typed interface via the WASM Component Model, and executes inside a sandboxed virtual machine hosted by a WASM runtime.
This is different from a traditional container-based microservice in three concrete ways:
- Isolation model. A container process shares the host OS kernel and has ambient access to the network, filesystem, and environment unless explicitly restricted. A WASM module has no ambient authority. It can only use the capabilities the host explicitly passes at startup. The permission surface is opt-in, not opt-out.
- Portability scope. A container image is tied to an OS and CPU architecture combination. The same WASM module runs without recompilation in a browser, a Cloudflare Worker, a wasmtime process on Linux, and a WasmEdge instance at the edge. The execution semantics are defined by the WASM specification, not by the host OS.
- Artifact size and startup time. A minimal WASM module is measured in kilobytes. A container image is typically measured in tens to hundreds of megabytes. Cold start times for WASM are sub-millisecond at the edge. Container cold starts are seconds. This difference matters for high-frequency invocations, edge deployments, and cost-sensitive workloads.
The service contract is expressed using WIT (WebAssembly Interface Types), which supports typed records, variants, streams, and resources. The interface is machine-readable and embedded in the artifact itself, so a runtime can inspect what a module expects and produces before running it.
Why WASM is the right execution boundary
The case for WASM as a service execution boundary is not about performance alone, though near-native execution speed and sub-millisecond cold starts are real properties. The stronger argument is the combination of sandboxing, portability, and interface determinism in a single binary format.
Sandboxing. The WASM security model is capability-based by specification, not by convention. A module compiled to WASM cannot open a network socket, read a file, or access environment variables unless the host runtime explicitly provides those capabilities at startup. This is not a policy layer bolted on top. It is the default behavior of the execution model. The permission surface of a running service is auditable by inspecting what capabilities the host grants, not by auditing the service's source code for ambient calls.
Portability. The same binary runs across runtimes. In practice this means: the same module that handles a Cloudflare Worker HTTP request handles a wasmtime process invocation and a browser WebAssembly call. Behavior is deterministic: the same module, same inputs, same outputs regardless of host OS or CPU architecture. This is a stronger guarantee than "same Dockerfile on different machines."
Size. A Rust-compiled WASM module for a stateless business logic service is typically 200–800 KB. A comparable container image with a language runtime is 50–200 MB. The size difference affects distribution time, cold start time, and cost at scale. At the edge, where modules may be distributed to hundreds of points of presence, this is not a marginal concern.
Interface determinism. WIT interfaces are embedded in the component binary. The type system covers records, variants, options, lists, resources, and streams, which is enough to express real service contracts. A WASM runtime can verify interface compatibility before executing. This is structurally different from REST APIs or gRPC where the contract lives in a separate file that may drift from the implementation.
WASM microservices vs Docker and Kubernetes
WASM and containers solve overlapping but distinct problems. Choosing between them depends on the workload, the deployment target, and the operational constraints in place.
Use WASM when:
- The service is stateless and compute-oriented: request handling, transformation, validation, inference.
- You need sub-millisecond cold starts for edge deployments, event-driven invocations, or high-frequency functions.
- You are deploying to multiple runtimes (browser, edge, server) and want a single binary artifact.
- The service's permission surface must be explicit and auditable by default.
- Binary distribution size and startup latency are operational constraints.
Use containers when:
- The service depends on full Linux userspace tooling, mature language runtimes that do not compile well to WASM, or shared filesystem semantics.
- You need to run existing workloads without modifying them. Containerizing an existing binary is lower friction than recompiling to WASM.
- Stateful services with complex I/O patterns that exceed current WASI capability coverage.
- Your team's operational tooling (observability, debugging, incident response) is built around container primitives.
In realistic architectures, WASM and containers coexist. The edge and serverless layers increasingly run WASM. The server-side coordination, stateful storage, and legacy integration layers run in containers. The question is not which replaces the other but which execution boundary fits each service's requirements.
Kubernetes does not directly support WASM workloads today. Runwasi (a containerd shim for WASM) allows Kubernetes to schedule WASM modules alongside containers, but it is still maturing. For pure WASM deployments at scale, purpose-built WASM platforms (Cloudflare Workers, Fastly Compute, Fermyon Spin) are the current production-grade option.
How to build a WASM microservice
Two toolchain paths cover most production use cases: Rust and TypeScript via AssemblyScript. Both compile to WASM modules compatible with WASI 0.2 and the Component Model.
Rust path. Rust has the most complete WASM toolchain. The wasm32-wasip2 compilation target (available from Rust 1.78+) produces WASI 0.2 components. The cargo component toolchain handles WIT interface generation and component bundling. Output binaries are small (typically under 1 MB for non-trivial services) because Rust has no garbage collector and compiles to minimal WASM. The tradeoff is Rust's learning curve: ownership and borrow checking add friction for teams new to the language.
TypeScript path. AssemblyScript is a strict subset of TypeScript that compiles to WASM. Teams already writing TypeScript can adopt AssemblyScript with moderate friction. Binary sizes are larger than Rust but smaller than GC-based languages. WASI 0.2 support in AssemblyScript is maturing. As of 2024, WASI 0.1 is the stable target. WASI 0.2 support is in active development.
For step-by-step walkthroughs, see the linked tutorials below.
WASM microservices in Universal Microservices Architecture (UMA)
UMA uses WASM as the execution boundary for portable business logic. The WASM module provides the portability guarantee: compile once, run anywhere the WASM spec is implemented. UMA's active descriptors and runtime governance layer provide what WASM alone does not: contract definition, placement policy, version constraints, and audit evidence.
In UMA, a WASM microservice is not just a module. It is a module paired with a descriptor that declares:
- The input and output schemas the module expects and produces.
- The deployment targets where the module is allowed to run (browser, edge, server, or a specific runtime class).
- The version constraints governing compatibility with other services in the workflow.
- The evidence requirements a successful run must satisfy to be considered compliant.
The runtime evaluates the descriptor before scheduling execution. If the current environment does not satisfy the declared constraints, the module is not placed there. This is runtime governance: policy enforcement at execution time, not at deployment time as a one-off check.
The same WASM module runs without recompilation across the three UMA deployment contexts: browser (via the browser's native WASM API), edge (via Cloudflare Workers or Fastly Compute), and server (via wasmtime or WasmEdge inside a container). What changes between contexts is the capability surface the host provides. The module's behavior is invariant.
WASM portability plus descriptor-driven governance is what makes WASM microservices production-ready in complex distributed systems rather than just in toy examples. WASM provides the mechanism. UMA provides the model.