Containers revolutionized deployment. But they carry baggage: a full Linux userspace, slow cold starts, and megabytes of image data. For some workloads—especially serverless functions and edge computing—this overhead matters.
WebAssembly (Wasm) offers an alternative: millisecond cold starts, tiny binaries, and a sandboxed execution model. And now it runs natively on Kubernetes. This post explains how.
Why Wasm? ¶
The Container Overhead Problem ¶
When you start a container, a lot happens:
1. Pull image (if not cached): 100MB-1GB, seconds to minutes
2. Create container: mount layers, set up namespaces
3. Start process: load binaries, initialize runtime
4. Ready to serve: 500ms-5s cold start typical
For a long-running web server, this doesn’t matter. For a serverless function that runs for 50ms, a 2-second cold start is unacceptable.
Wasm: A Different Model ¶
WebAssembly is a binary instruction format designed for:
- Fast startup: No OS to boot, no libraries to load
- Small size: Compact binary format, often <1MB
- Sandboxed: Capabilities must be explicitly granted
- Portable: Same binary runs anywhere with a Wasm runtime
Container cold start: 500ms - 5s
Wasm cold start: 1ms - 50ms
Container image: 50MB - 1GB
Wasm module: 100KB - 10MB
The Famous Quote ¶
Solomon Hykes (Docker co-founder), 2019:
“If WASM+WASI existed in 2008, we wouldn’t have needed to create Docker. That’s how important it is. WebAssembly on the server is the future of computing.”
Wasm Fundamentals ¶
What Wasm Is ¶
Wasm is a compilation target. You write code in Rust, Go, C, Python, JavaScript, or many other languages, and compile it to .wasm:
// Rust code
fn main() {
println!("Hello from Wasm!");
}
# Compile to Wasm
cargo build --target wasm32-wasi --release
# Output: target/wasm32-wasi/release/hello.wasm (few hundred KB)
WASI: The System Interface ¶
Wasm in browsers has no system access. For servers, we need WASI (WebAssembly System Interface)—a standardized API for:
- File system access
- Environment variables
- Command-line arguments
- Random numbers
- Clocks
- Network (emerging)
WASI is capability-based: a Wasm module can only access what the runtime explicitly grants.
# Run with wasmtime, granting file access
wasmtime --dir=/data hello.wasm
The Security Model ¶
Containers rely on Linux namespaces and cgroups for isolation. A container escape = host access.
Wasm is sandboxed at the instruction level:
Wasm module
|
| Can only call WASI functions
| Memory is bounds-checked
| No raw syscalls
v
WASI Runtime (wasmtime, wasmer, etc.)
|
| Grants specific capabilities
v
Host OS
A bug in a Wasm module can’t escape the sandbox without a bug in the runtime itself. The attack surface is much smaller than a container.
Wasm Runtimes ¶
Several runtimes execute Wasm:
| Runtime | Focus | Used By |
|---|---|---|
| wasmtime | Standards compliance, security | Bytecode Alliance, Fermyon |
| wasmer | Performance, versatility | Wasmer Inc |
| WasmEdge | Edge/cloud native | CNCF, second-state |
| wazero | Pure Go, no CGO | Go ecosystem |
For Kubernetes, the runtime is embedded in a containerd shim.
Running Wasm on Kubernetes ¶
The Architecture ¶
Kubernetes doesn’t run Wasm directly. The trick: teach containerd to run Wasm modules as if they were containers.
kubectl create pod
|
v
API Server
|
v
Scheduler → selects Wasm-capable node
|
v
kubelet
|
v
containerd
|
| (RuntimeClass: wasmtime)
v
containerd-shim-wasmtime
|
v
wasmtime runs .wasm module
The containerd shim is the key component. It implements containerd’s runtime interface but executes Wasm instead of Linux containers.
RuntimeClass ¶
Kubernetes uses RuntimeClass to select different container runtimes:
apiVersion: node.k8s.io/v1
kind: RuntimeClass
metadata:
name: wasmtime
handler: wasmtime # Matches containerd config
scheduling:
nodeSelector:
kubernetes.io/wasm: "true"
Pods specify which runtime to use:
apiVersion: v1
kind: Pod
metadata:
name: wasm-pod
spec:
runtimeClassName: wasmtime # Use Wasm runtime
containers:
- name: hello
image: ghcr.io/example/hello-wasm:latest
command: ["/hello.wasm"]
containerd Shims ¶
The runwasi project provides containerd shims for various Wasm runtimes:
# containerd config.toml
[plugins."io.containerd.grpc.v1.cri".containerd.runtimes.wasmtime]
runtime_type = "io.containerd.wasmtime.v1"
[plugins."io.containerd.grpc.v1.cri".containerd.runtimes.wasmedge]
runtime_type = "io.containerd.wasmedge.v1"
Available shims:
containerd-shim-wasmtime-v1containerd-shim-wasmedge-v1containerd-shim-wasmer-v1containerd-shim-spin-v2(for Spin framework)
Installing Wasm Support ¶
Option 1: kwasm-operator (easiest)
# Install kwasm operator
helm install kwasm-operator kwasm/kwasm-operator \
--namespace kwasm \
--create-namespace
# Label nodes to install Wasm shims
kubectl label node worker-1 kwasm.sh/kwasm-node=true
Option 2: Manual installation
# On each node
# Download and install shim
curl -LO https://github.com/containerd/runwasi/releases/download/v0.3.0/containerd-shim-wasmtime-v1-linux-amd64.tar.gz
tar xzf containerd-shim-wasmtime-v1-linux-amd64.tar.gz
mv containerd-shim-wasmtime-v1 /usr/local/bin/
# Update containerd config
cat >> /etc/containerd/config.toml << EOF
[plugins."io.containerd.grpc.v1.cri".containerd.runtimes.wasmtime]
runtime_type = "io.containerd.wasmtime.v1"
EOF
# Restart containerd
systemctl restart containerd
SpinKube: The Serverless Wasm Platform ¶
SpinKube combines the Spin framework with Kubernetes for serverless Wasm workloads.
What is Spin? ¶
Spin is a framework for building serverless Wasm applications:
use spin_sdk::http::{Request, Response};
use spin_sdk::http_component;
#[http_component]
fn handle_request(req: Request) -> Response {
Response::builder()
.status(200)
.body(Some("Hello from Spin!".into()))
.build()
}
# Build and run locally
spin build
spin up
# HTTP server on port 3000
SpinKube Architecture ¶
┌─────────────────────────────┐
│ spin-operator │
│ (manages SpinApp CRDs) │
└─────────────┬───────────────┘
│
┌─────────────────────────────────┼────────────────────────────────┐
│ │ │
│ ┌──────────────┐ ┌───────────▼──────────┐ ┌──────────────┐ │
│ │ SpinApp │ │ SpinApp │ │ SpinApp │ │
│ │ CRD │ │ (Deployment-like) │ │ CRD │ │
│ └──────────────┘ └──────────────────────┘ └──────────────┘ │
│ │
│ Kubernetes Cluster │
│ │
│ ┌────────────────────────────────────────────────────────────┐ │
│ │ containerd-shim-spin │ │
│ │ (runs Spin apps as Wasm) │ │
│ └────────────────────────────────────────────────────────────┘ │
└──────────────────────────────────────────────────────────────────┘
Installing SpinKube ¶
# Install cert-manager (required)
kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.14.0/cert-manager.yaml
# Install SpinKube (runtime class, shim, operator)
kubectl apply -f https://github.com/spinkube/spin-operator/releases/download/v0.2.0/spin-operator.crds.yaml
kubectl apply -f https://github.com/spinkube/spin-operator/releases/download/v0.2.0/spin-operator.runtime-class.yaml
kubectl apply -f https://github.com/spinkube/spin-operator/releases/download/v0.2.0/spin-operator.shim-executor.yaml
helm install spin-operator oci://ghcr.io/spinkube/charts/spin-operator \
--namespace spin-operator --create-namespace
Deploying a Spin App ¶
apiVersion: core.spinoperator.dev/v1alpha1
kind: SpinApp
metadata:
name: hello-spin
spec:
image: ghcr.io/spinkube/spin-operator/hello-world:latest
replicas: 2
executor: containerd-shim-spin
kubectl apply -f spinapp.yaml
# Service is automatically created
kubectl get svc hello-spin
Spin App Features ¶
Autoscaling:
apiVersion: core.spinoperator.dev/v1alpha1
kind: SpinApp
metadata:
name: autoscaled-app
spec:
image: ghcr.io/example/my-app:latest
enableAutoscaling: true
resources:
limits:
cpu: 100m
memory: 128Mi
Variables and secrets:
spec:
variables:
- name: API_KEY
valueFrom:
secretKeyRef:
name: my-secret
key: api-key
Building Wasm Applications ¶
Rust (Best Support) ¶
// src/lib.rs
use spin_sdk::http::{Request, Response};
use spin_sdk::http_component;
#[http_component]
fn handle(req: Request) -> anyhow::Result<Response> {
let path = req.uri().path();
Ok(Response::builder()
.status(200)
.header("content-type", "application/json")
.body(format!(r#"{{"path": "{}"}}"#, path))
.build())
}
# spin.toml
spin_manifest_version = 2
[application]
name = "my-app"
version = "0.1.0"
[[trigger.http]]
route = "/..."
component = "my-app"
[component.my-app]
source = "target/wasm32-wasi/release/my_app.wasm"
[component.my-app.build]
command = "cargo build --target wasm32-wasi --release"
Go ¶
package main
import (
"fmt"
"net/http"
spinhttp "github.com/fermyon/spin/sdk/go/v2/http"
)
func main() {
spinhttp.Handle(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
fmt.Fprintf(w, `{"message": "Hello from Go!"}`)
})
}
tinygo build -target=wasi -o main.wasm main.go
JavaScript/TypeScript ¶
// src/index.js
export async function handler(request, context) {
return {
status: 200,
headers: { "content-type": "application/json" },
body: JSON.stringify({ message: "Hello from JS!" })
};
}
Uses the ComponentizeJS toolchain to compile JS to Wasm.
Python ¶
from spin_sdk.http import simple
class IncomingHandler(simple.IncomingHandler):
def handle_request(self, request):
return simple.Response(
200,
{"content-type": "application/json"},
b'{"message": "Hello from Python!"}'
)
Uses componentize-py under the hood.
Use Cases ¶
1. Serverless Functions ¶
The canonical use case. Wasm cold starts are fast enough for per-request scaling:
Request arrives → Spin starts Wasm module → Execute → Respond → Scale to zero
| |
| | ~1-10ms
| |
+-------- Total latency: ~15ms including execution
Compare to container-based serverless: 100ms-2s cold start.
2. Edge Computing ¶
Wasm’s small footprint suits edge nodes:
| Resource | Container | Wasm |
|---|---|---|
| Memory | 50MB+ | 1-10MB |
| Image size | 100MB+ | 100KB-5MB |
| Startup | 500ms+ | 1-10ms |
Edge nodes might have 1GB RAM. You can run many more Wasm instances than containers.
3. Multi-Tenant Platforms ¶
Wasm’s sandbox is smaller and safer than containers:
- No kernel vulnerabilities to escape to
- Memory bounds-checked
- Capabilities explicitly granted
Running untrusted user code? Wasm is more defensible than containers.
4. Plugin Systems ¶
Extend applications safely:
Application (host)
|
| Loads plugin.wasm
| Grants limited capabilities
v
Plugin (Wasm)
|
| Can only call host-provided functions
v
Limited, safe extension
Envoy, Vector, OPA, and others use Wasm for plugins.
5. AI Inference at Edge ¶
Small models running in Wasm at the edge:
apiVersion: core.spinoperator.dev/v1alpha1
kind: SpinApp
metadata:
name: inference
spec:
image: ghcr.io/example/llm-inference:latest
resources:
limits:
memory: 512Mi # Small model fits in Wasm
No GPU needed for small models; CPU inference with fast cold starts.
Wasm vs Containers: When to Use Which ¶
Use Wasm When: ¶
| Scenario | Why Wasm |
|---|---|
| Serverless/FaaS | Cold start matters, per-request scaling |
| Edge computing | Resource constrained, many small workloads |
| Untrusted code | Smaller attack surface, better sandbox |
| Short-lived tasks | Don’t pay container overhead for 50ms work |
| Plugins/extensions | Safe, portable, language-agnostic |
Use Containers When: ¶
| Scenario | Why Containers |
|---|---|
| Long-running services | Cold start doesn’t matter |
| Full OS needed | Shell, package managers, debugging tools |
| Complex dependencies | Native libraries, databases, etc. |
| Existing workloads | Already containerized, not worth rewriting |
| GPU/hardware access | Wasm hardware support is limited |
| Network-heavy | Wasm networking is still evolving |
The Hybrid Future ¶
Most clusters will run both:
┌────────────────────────────────────────────────────────────┐
│ Kubernetes Cluster │
│ │
│ ┌──────────────────────┐ ┌──────────────────────────┐ │
│ │ Traditional Nodes │ │ Wasm-Capable Nodes │ │
│ │ │ │ │ │
│ │ - Web servers │ │ - Serverless functions │ │
│ │ - Databases │ │ - Edge processors │ │
│ │ - Stateful apps │ │ - Event handlers │ │
│ │ - ML training │ │ - Plugins │ │
│ │ │ │ │ │
│ │ RuntimeClass: │ │ RuntimeClass: │ │
│ │ containerd (runc) │ │ wasmtime/spin │ │
│ └──────────────────────┘ └──────────────────────────┘ │
│ │
└────────────────────────────────────────────────────────────┘
Limitations and Challenges ¶
WASI is Still Evolving ¶
WASI 0.2 (current) has limited APIs:
- ✅ Filesystem, environment, clocks
- ✅ HTTP (via wasi-http)
- ⚠️ Sockets (preview)
- ❌ Full POSIX compatibility
Some things don’t compile to Wasm yet.
Language Support Varies ¶
| Language | Support Level |
|---|---|
| Rust | Excellent |
| Go (TinyGo) | Good, some stdlib missing |
| C/C++ | Good |
| JavaScript | Good (ComponentizeJS) |
| Python | Improving (componentize-py) |
| Java | Experimental |
| .NET | Experimental |
Debugging is Harder ¶
No shell to exec into. Debugging options:
- Print statements (captured as logs)
- Remote debugging (limited)
- Local testing with Spin
Ecosystem Maturity ¶
Container ecosystem: millions of images, decades of tooling.
Wasm ecosystem: growing but young. You might need to build things that already exist for containers.
Getting Started ¶
Local Development ¶
# Install Spin
curl -fsSL https://developer.fermyon.com/downloads/install.sh | bash
# Create new app
spin new -t http-rust hello-wasm
cd hello-wasm
# Build and run locally
spin build
spin up
# Visit http://localhost:3000
Deploy to Kubernetes ¶
# Build and push OCI image
spin registry push ghcr.io/myuser/hello-wasm:latest
# Deploy
cat <<EOF | kubectl apply -f -
apiVersion: core.spinoperator.dev/v1alpha1
kind: SpinApp
metadata:
name: hello-wasm
spec:
image: ghcr.io/myuser/hello-wasm:latest
replicas: 1
executor: containerd-shim-spin
EOF
Summary ¶
WebAssembly on Kubernetes offers:
| Benefit | Impact |
|---|---|
| Millisecond cold starts | True scale-to-zero, per-request scaling |
| Tiny binaries | Less storage, faster pulls |
| Strong sandbox | Safer than containers for untrusted code |
| Language flexibility | Compile once, run anywhere |
The stack:
Your Code (Rust, Go, JS, Python, ...)
|
v
Spin Framework (optional, for serverless)
|
v
Wasm Module (.wasm binary)
|
v
containerd-shim-spin/wasmtime
|
v
Kubernetes (via RuntimeClass)
Wasm won’t replace containers—but for the right workloads (serverless, edge, plugins, multi-tenant), it’s a compelling alternative with fundamentally better characteristics.
Containers are VMs done right. Wasm is processes done right. Both have their place.