π This is a bonus reading, paired with Lab 12. Optional, for students chasing the frontier of portable runtimes.
Containers solved "ships across machines". But they did not solve:
- π Cold-start time β a Go container starts in ~1-2 s. A WASM module starts in ~1 ms
- π¦ Size β even with multi-stage, a Go container is ~15 MB. A WASM module is ~1-3 MB
- ποΈ Isolation β containers share the host kernel (Lecture 5). WASM modules run in a sandbox with no system calls by default β stronger isolation than containers, lighter than VMs
- π CPU portability β
linux/amd64vslinux/arm64vs Apple Silicon. One WASM module runs on all of them, unchanged
For high-fan-out, latency-sensitive workloads (edge functions, plugin systems, multi-tenant SaaS), these wins matter.
π€ Think: Cloud Run cold-started QuickNotes in ~1.5 s in Lab 10. What workloads would benefit from booting in 1 ms instead?
- π 2015 β Mozilla, Google, Microsoft, Apple announce WebAssembly as a successor to asm.js
- π March 2017 β WebAssembly 1.0 specification published
- πͺ 2017-2019 β Adopted by all four major browsers as a stable feature
- π₯οΈ 2019 β WASI (WebAssembly System Interface) proposed β a POSIX-like ABI for running WASM outside the browser (CLI tools, server-side modules)
- π 2020 β Fermyon founded; Spin framework launches the "WASM containers" pattern
- π§© 2022-2023 β containerd gains WASM shims; Kubernetes can schedule a
crun-wasmtime"container" that's actually a WASM module - π 2024-2026 β Cloudflare Workers, Fastly Compute@Edge, Fermyon Cloud β mainstream edge-compute platforms ship WASM-first
graph LR
S["π source<br/>Go / Rust / C / AssemblyScript"] -- "compile" --> W["π¦ .wasm binary<br/>(stack-based VM bytecode)"]
W -- "run" --> R1["π Browser"]
W -- "run" --> R2["π₯οΈ wasmtime CLI"]
W -- "run" --> R3["π³ containerd-wasm"]
W -- "run" --> R4["βοΈ Spin / Cloudflare Worker"]
- π§ WebAssembly is a stack-based virtual machine instruction set β like a portable, compact CPU
- π Capability-based sandbox: by default no filesystem, no network, no clock. You grant only what's needed
- π¦ Multiple source languages compile to it: Rust (best support), C/C++, Go (via TinyGo), AssemblyScript
.wasm = bytecode + module imports/exports + type signatures
In the browser, WASM accesses the world through JavaScript bindings. On the server, WASI is the standard ABI:
| WASI API | What it gives |
|---|---|
wasi:cli/stdio |
stdin/stdout/stderr |
wasi:filesystem |
Pre-opened directory handles only (no /) |
wasi:sockets |
TCP/UDP (limited) |
wasi:clocks |
Monotonic + wall clocks |
wasi:random |
Entropy source |
- π Capability model: the runtime mounts a directory like
--dir=.::./dataβ the module sees./databut cannot see/etc/passwdor/ - πͺΆ No syscalls smuggled in β every interaction with the OS is an explicit WASI import
Spin (created by Fermyon, donated to the CNCF in 2024 β SDK now lives under the spinframework org) is the easiest entry to server-side WASM. You write a normal-looking Go HTTP handler; the Spin SDK adapts it to a wasi-http component:
// main.go β Spin Go SDK
package main
import (
"fmt"
"net/http"
spinhttp "github.com/spinframework/spin-go-sdk/v2/http"
)
func init() {
spinhttp.Handle(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
fmt.Fprintln(w, `{"status":"ok"}`)
})
}
func main() {}# spin.toml
spin_manifest_version = 2
[application]
name = "quicknotes-wasm"
version = "0.1.0"
[[trigger.http]]
route = "/time"
component = "moscow-time"
[component.moscow-time]
source = "main.wasm"
allowed_outbound_hosts = []
[component.moscow-time.build]
command = "tinygo build -target=wasip1 -buildmode=c-shared -no-debug -o main.wasm ."$ spin new -t http-go moscow-time # scaffold the CURRENT template
$ spin build # runs tinygo under the hood
$ spin up # serves on :3000
$ spin deploy # β Fermyon Cloud (free tier)- β‘ Spin instantiates a fresh WASM instance per request β true serverless. Cold starts in single-digit milliseconds
- π Each component carries its own capability list β it cannot reach code or hosts it wasn't granted
- π
-buildmode=c-sharedis required: it makes TinyGo export the handler symbols the Spin host calls (omit it β HTTP 500)
β οΈ Tooling churn warning. WASM server tooling moves fast. The SDK import path changed fromgithub.com/fermyon/spin/sdk/go/v2togithub.com/spinframework/spin-go-sdk/v2with the CNCF donation. Alwaysspin new -t http-goto get the current template rather than copy-pasting an oldspin.toml.
Before the wasi-http Component Model matured, Spin offered a WAGI executor that mapped HTTP onto stdin/stdout, CGI-style: the module read request info from environment variables, wrote the response to stdout. It was the simplest possible Go-to-WASM port.
# β DEPRECATED β removed in Spin 3.x
[component.quicknotes]
source = "main.wasm"
executor = { type = "wagi" } # `spin up` rejects this field in 3.x- πͺ¦ Spin 3.x removed the inline WAGI executor. Modern Spin uses the
wasi-httpcomponent model (Section 5) - π§° The WAGI pattern still lives on in bare
wasmtime run: a standalone WASI module reads env + stdin, writes stdout. Lab 12's Bonus uses exactly this to contrast the two execution models β Spin's persistentwasi-httpserver vs wasmtime's per-invocation CLI
| Dimension | Docker container (Lab 6) | WASM module (Lab 12) |
|---|---|---|
| Cold start | ~200 ms β 2 s | ~1 ms |
| Size | 15-200 MB | 1-3 MB |
| CPU portability | Per-arch image | Single artifact |
| Isolation | Linux kernel namespaces | Capability sandbox (stronger than namespaces, weaker than VM) |
| Mature ecosystem | β massive | π‘ growing fast, still rough edges |
| Multi-tenant safety | OK | Excellent |
| Long-running, stateful workloads | β | |
apt install arbitrary OS deps |
β | β (no shell, no syscalls beyond WASI) |
- π― Sweet spot for WASM: edge, plugin systems, multi-tenant SaaS request handlers, IoT, browser companion code
- πͺ€ Bad fit for WASM (today): heavy DB clients, persistent processes, anything needing arbitrary syscalls
graph TB
K["βΈοΈ Kubernetes Pod"] --> CR["π³ containerd"]
CR --> R1["π₯ͺ runc (Linux containers)"]
CR --> R2["π§ͺ containerd-shim-wasmtime<br/>or -wasmedge -spin"]
R2 --> W["π¦ .wasm artifact"]
- π§© A
RuntimeClass: wasmtime(Kubernetes feature) lets a pod's containers run as WASM modules instead of Linux containers - π Major cloud K8s providers (GKE, AKS, EKS) support this in preview as of 2024-2025
- π K8s scheduling + WASM runtime = put microservice plugins next to their users at the edge, with strong isolation
These platforms run WASM modules at 300+ POPs worldwide:
-
π Your request hits the nearest POP (typically <30 ms RTT)
-
β‘ A fresh WASM instance starts per request β measured in microseconds
-
πΈ Pricing is per-request, not per-instance-hour β perfect for spiky traffic
-
π‘οΈ Each tenant's WASM is sandboxed from every other tenant's β multi-tenant by design
-
π― This is where WASM-on-server has decisively won β edge platforms, where Cold-Start latency dominates the user experience
| β WASM wins | |
|---|---|
| Cold start, size, portability | Library ecosystem (esp. databases) |
| Multi-tenant isolation | Long-running stateful workloads |
| Browser + server with same artifact | Debugging tools (still maturing) |
| Sandbox is "deny by default" | TinyGo (the Go-to-WASM compiler) doesn't support all of Go's stdlib |
| Standard ABI (WASI) growing | Cgo, reflection, large goroutine fleets |
π‘ The Go-specific gotcha: standard
go build -o main.wasmproduces a WASM module that runs only in browsers (with the Go runtime + JS glue). For server-side WASM you typically want TinyGo (tinygo build -target=wasi), which produces a smaller, WASI-compliant binary with a stripped-down stdlib.
Lab 12 is the WASM bonus lab β itself worth 10 pts, structured 4+4+2:
- π¨ Task 1 (4 pts): Build a minimal
timeendpoint (returns current Moscow time as JSON) in Go β WASM via TinyGo β packaged as a Spin SDKwasi-httpcomponent, served byspin up. Compare deployed size vs the QuickNotes Docker image - ποΈ Task 2 (4 pts): Benchmark request latency: warm Lab 6 Docker container vs warm
spin up. Then compare cold-starts. Explain what dominates each curve - π Bonus (2 pts): Rebuild the same logic as a standalone WASI CLI module, run under bare
wasmtime run, and contrast the two execution models (Spin's persistentwasi-httpserver vs wasmtime's per-invocation CLI) - π Deliverable:
submissions/lab12.mdβ spin.toml, build sizes, perf numbers, written reflection
- π WebAssembly: The Definitive Guide β Brian Sletten (O'Reilly, 2022)
- π WebAssembly Spec β the actual spec; surprisingly readable
- π WASI documentation β capability-based system interface
- π Fermyon Spin docs β quickest start to server-side WASM
- π₯ Lin Clark β A cartoon introduction to WebAssembly β the canonical visual primer
- π Cloudflare Workers β WASM at the edge β production case study
- π WASI Preview 2 update (2024) β the latest milestone
π― Remember: Containers won the 2014-2020 era by making "ship anywhere on Linux" trivial. WebAssembly is making a credible bid for the 2025-2030 era by adding "ship to any CPU, with capability-based safety, in 1 millisecond". Whether it dethrones containers or coexists with them is the open question of the decade.