Pavan Rangani

HomeBlogWebAssembly Containers in Kubernetes: Running Wasm Workloads in Production

WebAssembly Containers in Kubernetes: Running Wasm Workloads in Production

By Pavan Rangani · March 16, 2026 · DevOps & Cloud

WebAssembly Containers in Kubernetes: Running Wasm Workloads in Production

WebAssembly Containers in Kubernetes

WebAssembly containers Kubernetes integration represents the next evolution in cloud-native computing. Wasm containers start in under 1 millisecond, consume a fraction of the memory of Linux containers, and provide stronger sandboxing guarantees. With projects like SpinKube and runwasi reaching production readiness in 2026, teams can now run Wasm workloads alongside traditional containers in the same Kubernetes cluster. Crucially, this is not a rip-and-replace migration — the same kubelet, scheduler, and CNI continue to operate while a per-pod RuntimeClass selects the execution engine.

This guide walks you through the complete setup — from configuring containerd with Wasm shims to deploying production workloads. Whether you are evaluating Wasm for edge computing, serverless functions, or plugin systems, you will find practical patterns here. We will also cover the parts that rarely make it into demos: WASI capability wiring, observability gaps, and the workloads where Wasm is the wrong tool.

Why Wasm Containers Matter

Traditional Linux containers package an entire OS userspace — libraries, shells, package managers. A typical Node.js container image is 200MB+. A Wasm module doing the same work is often under 5MB. Moreover, Wasm modules start in microseconds, not seconds, making them ideal for scale-to-zero serverless patterns. Because the artifact is a single architecture-neutral binary, the same module runs unchanged on amd64 and arm64 nodes, which simplifies multi-arch build pipelines considerably.

WebAssembly containers Kubernetes architecture
Comparing traditional containers with WebAssembly modules in Kubernetes

The security model is fundamentally different too. Wasm modules run in a capability-based sandbox — they cannot access the filesystem, network, or environment variables unless explicitly granted permission. This is deny-by-default security, unlike Linux containers which start with broad access and restrict via seccomp/AppArmor profiles. In practice this means a compromised Wasm handler cannot read a mounted secret or open an outbound socket it was never granted, which shrinks the blast radius significantly.

Comparison: Linux Container vs Wasm Container

┌─────────────────────┬────────────────┬────────────────┐
│ Metric              │ Linux Container│ Wasm Container │
├─────────────────────┼────────────────┼────────────────┤
│ Cold Start          │ 500ms - 5s     │ < 1ms          │
│ Image Size          │ 50MB - 500MB   │ 1MB - 10MB     │
│ Memory Overhead     │ 20MB - 100MB   │ 1MB - 5MB      │
│ Security Model      │ Allow-default  │ Deny-default   │
│ Language Support     │ Any            │ Rust/Go/C/JS   │
│ CPU Architecture     │ Platform-bound │ Universal      │
│ Networking          │ Full stack      │ WASI sockets   │
│ Filesystem          │ Full (chroot)   │ Capability-based│
└─────────────────────┴────────────────┴────────────────┘

Setting Up Kubernetes for Wasm

The architecture uses containerd shims to run Wasm modules alongside traditional containers. The key component is runwasi — a containerd shim that delegates Wasm execution to runtimes like Wasmtime or Spin. Because the integration lives at the containerd layer, the Kubernetes control plane needs no patches; it simply schedules a pod whose RuntimeClass points at a handler that containerd knows how to invoke.

Installing the Wasm Runtime Shim

# Install runwasi shim for containerd
# On each Kubernetes node:
curl -LO https://github.com/containerd/runwasi/releases/latest/download/containerd-shim-wasmtime-v1
chmod +x containerd-shim-wasmtime-v1
sudo mv containerd-shim-wasmtime-v1 /usr/local/bin/

# Install SpinKube shim for Spin framework apps
curl -LO https://github.com/spinkube/containerd-shim-spin/releases/latest/download/containerd-shim-spin-v2
chmod +x containerd-shim-spin-v2
sudo mv containerd-shim-spin-v2 /usr/local/bin/

# Add RuntimeClass to containerd config
cat >> /etc/containerd/config.toml << 'EOF'
[plugins."io.containerd.grpc.v1.cri".containerd.runtimes.spin]
  runtime_type = "io.containerd.spin.v2"

[plugins."io.containerd.grpc.v1.cri".containerd.runtimes.wasmtime]
  runtime_type = "io.containerd.wasmtime.v1"
EOF

sudo systemctl restart containerd

Configuring RuntimeClasses

# runtime-classes.yaml
apiVersion: node.k8s.io/v1
kind: RuntimeClass
metadata:
  name: wasmtime
handler: wasmtime
scheduling:
  nodeSelector:
    kubernetes.io/wasm: "true"
---
apiVersion: node.k8s.io/v1
kind: RuntimeClass
metadata:
  name: spin
handler: spin
scheduling:
  nodeSelector:
    kubernetes.io/wasm: "true"
Kubernetes cluster with WebAssembly runtime
Kubernetes nodes configured with Wasm runtime shims

Deploying Wasm Workloads

With the runtime configured, deploying Wasm workloads uses standard Kubernetes manifests. The only difference is specifying the RuntimeClass:

# wasm-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: wasm-api
spec:
  replicas: 3
  selector:
    matchLabels:
      app: wasm-api
  template:
    metadata:
      labels:
        app: wasm-api
    spec:
      runtimeClassName: spin  # Use Wasm runtime
      containers:
        - name: api
          image: ghcr.io/myorg/api-service:latest
          ports:
            - containerPort: 80
          resources:
            requests:
              memory: "10Mi"   # Wasm needs very little memory
              cpu: "50m"
            limits:
              memory: "32Mi"
              cpu: "200m"
---
apiVersion: v1
kind: Service
metadata:
  name: wasm-api
spec:
  selector:
    app: wasm-api
  ports:
    - port: 80
      targetPort: 80

Building Wasm Container Images

// src/lib.rs — A simple Spin HTTP component in Rust
use spin_sdk::http::{IntoResponse, Request, Response};
use spin_sdk::http_component;

#[http_component]
fn handle_request(req: Request) -> anyhow::Result {
    let path = req.uri().path();
    match path {
        "/health" => Ok(Response::builder()
            .status(200)
            .body("OK")
            .build()),
        "/api/data" => {
            let data = serde_json::json!({
                "message": "Hello from WebAssembly",
                "runtime": "Spin on Kubernetes"
            });
            Ok(Response::builder()
                .status(200)
                .header("Content-Type", "application/json")
                .body(data.to_string())
                .build())
        }
        _ => Ok(Response::builder()
            .status(404)
            .body("Not Found")
            .build()),
    }
}
# Build and push Wasm OCI image
spin build
spin registry push ghcr.io/myorg/api-service:latest

Granting Capabilities the WASI Way

Because the sandbox denies everything by default, your component will fail the moment it tries to reach a database or call an external API unless you grant that capability explicitly. With Spin this is declarative — the manifest lists allowed outbound hosts, and the runtime rejects any request to a host that is not on the list. This is a meaningful behavioral difference from Linux containers, where an application can dial any reachable address unless a NetworkPolicy blocks it.

# spin.toml — explicit capability grants
[[trigger.http]]
route = "/api/..."
component = "api-service"

[component.api-service]
source = "target/wasm32-wasi/release/api_service.wasm"
allowed_outbound_hosts = [
  "https://payments.internal:443",
  "postgres://db.internal:5432"
]
# Filesystem access is also opt-in:
files = [{ source = "assets", destination = "/assets" }]

The practical takeaway is that least-privilege stops being a NetworkPolicy you might forget to write and becomes a compile-time-adjacent property of the artifact. When you review a Wasm component, the manifest tells you exactly what it can touch — there is no implicit ambient authority to audit.

Production Patterns with SpinKube

SpinKube provides a Kubernetes operator that manages Wasm applications with custom resources. Therefore, it handles scaling, networking, and lifecycle management automatically:

# SpinKube SpinApp custom resource
apiVersion: core.spinoperator.dev/v1alpha1
kind: SpinApp
metadata:
  name: my-api
spec:
  image: ghcr.io/myorg/api-service:latest
  replicas: 2
  executor: containerd-shim-spin
  resources:
    limits:
      memory: 32Mi
      cpu: 100m
  enableAutoscaling: true
  targetCPUUtilization: 60
Cloud infrastructure for Wasm containers
SpinKube managing WebAssembly workloads in production Kubernetes

Observability and the Tooling Gaps

The honest reality in 2026 is that the operational ecosystem around Wasm trails the runtime itself. Standard Linux debugging — exec into the pod, attach a profiler, inspect /proc — does not apply, because there is no shell and no conventional process to attach to. Teams should plan for structured logging from inside the component and lean on the host runtime for metrics rather than expecting kubectl exec to work.

The encouraging trend is that WASI is converging on host-provided observability. Spin, for example, can export OpenTelemetry traces for each HTTP trigger, which means request-level latency and error data flow into the same backend as the rest of your services. Even so, you should treat memory profiling, heap dumps, and live debugging as immature, and validate that your incident runbooks do not depend on tools that simply are not available in this execution model.

# Emit OTel traces from a Spin app to your collector
export OTEL_EXPORTER_OTLP_ENDPOINT="http://otel-collector:4318"
export OTEL_SERVICE_NAME="wasm-api"
spin up --otel
# Traces appear alongside your Linux-container services in
# Jaeger / Tempo, correlated by the same trace IDs.

Comparing WebAssembly Containers Kubernetes Runtimes

Not every Wasm shim targets the same use case, and choosing the wrong one creates friction later. Wasmtime via runwasi is the general-purpose, low-level choice — you get a raw WASI runtime and bring your own application structure. Spin layers an HTTP-and-events framework on top, which is the fastest path for request/response services. WasmEdge leans toward edge and AI-inference scenarios with extended host functions, while wasmCloud takes an actor-and-lattice approach better suited to distributed application meshes than to drop-in Kubernetes pods.

As a rule of thumb, teams building stateless HTTP APIs reach for Spin and SpinKube because the operator handles the Kubernetes wiring. Teams embedding Wasm as a plugin host inside an existing Rust or Go service tend to use Wasmtime directly. Picking based on the workload shape, rather than on benchmarks alone, avoids painful migrations once the platform is in production.

When NOT to Use Wasm Containers

Despite the advantages, Wasm containers are not a universal replacement. Avoid them when you need full Linux system access, complex filesystem operations, established language ecosystems (Python ML libraries), or long-running stateful processes. The mismatch is sharpest for CPU-and-memory-heavy data work: the Wasm linear-memory model and the lack of mature threading support make it a poor fit for in-process machine learning or large in-memory caches. As a result, most teams adopt a hybrid approach — Wasm for lightweight API handlers and Linux containers for heavy workloads.

There are subtler trade-offs too. Cold-start wins evaporate if your component must establish a fresh database connection on every invocation, so connection reuse patterns matter more than the raw runtime numbers suggest. Languages compile to Wasm with varying maturity — Rust and Go are solid, but garbage-collected runtimes can carry surprising binary-size overhead. Benchmarks show the resource savings are real, yet they are most pronounced for short-lived, bursty traffic rather than steady high-throughput services where a warm Linux container already amortizes its startup cost.

Key Takeaways

WebAssembly containers in Kubernetes are production-ready for specific use cases. They offer sub-millisecond cold starts, minimal resource consumption, and superior security isolation. Start with edge computing or serverless API endpoints and expand as the ecosystem matures. Pair the rollout with explicit capability grants and host-level observability from day one, and keep Linux containers for the heavy, stateful, or library-dependent workloads where they still win.

Related Reading

External Resources

In conclusion, WebAssembly containers Kubernetes is an essential topic for modern software development. By applying the patterns and practices covered in this guide, you can build more robust, scalable, and maintainable systems. Start with the fundamentals, iterate on your implementation, and continuously measure results to ensure you are getting the most value from these approaches.

← Back to all articles