Setting Up WebAssembly Runtime (Spin/Fermyon) for Cloud Microservices

Services in modular isolation start predictably even under load, but without proper environment configuration they lose their advantages. We configure WebAssembly runtime turnkey: audit, placement scheme, module isolation, access policies, and documentation handover. Placement density grows several times, and downtime during updates is reduced.

Our competencies:

Frequently Asked Questions

Latest works

  • Development of a web application for FEEDME
    Development of a web application for FEEDME
    1365
  • Development of an online store for the company FURNORO
    Development of an online store for the company FURNORO
    1330
  • B2B Advance company logo design
    B2B Advance company logo design
    776
  • Development of a web application for Enviok
    Development of a web application for Enviok
    1077
  • AIDER company logo development
    AIDER company logo development
    1022
  • CRM development for Chasseurs
    CRM development for Chasseurs
    1113

WebAssembly runtime in the cloud: where services break

A WebAssembly runtime removes some container problems: a module comes up as a separate instance, memory is limited by a sandbox, and the kernel does not reload at startup.

But under real load, resilience is lost in three predictable places — cold start, service co-location, and access to external resources. Each of these causes tail latencies or failure of an entire group of services at once.

Cold start is the first bottleneck. An instance is created for the first request, and on a spike the environment brings up dozens at once, and response time grows nonlinearly.

Warm-up and a pool of ready instances help, but they work only when coordinated with the number of service replicas and the initialization queue.

Service co-location breaks isolation differently: the sandbox separates code into modules, but does not partition resources. A service with a memory leak or a tight loop consumes the host CPU, and everyone next to it degrades.

This is a typical infrastructure bottleneck for microservices in the cloud — from the metrics of an individual module it is almost unreadable and shows up only on the shared host.

Access to external resources is the third failure point. Outgoing calls go through host functions, the connection pool is not inherited between instances, so at peak timeouts and retry storms begin.

Limits on network descriptors and a unified retry policy are needed, otherwise one slow external service consumes the resources of the entire environment.

Next we will break down how this is closed by configuration: where warm-up is needed, what quotas we set per instance and per host, and how we check behavior when an external dependency fails.

What changes after migration: startup, memory, density

Cold start stops being a bottleneck: a module comes up in fractions of a second, whereas a container with a full guest environment spends seconds on initialization.

The difference shows up not in the average but on a spike — when dozens of instances need to be added in a short window and the request queue does not have time to accumulate.

That is why migrating services to WebAssembly with a modular runtime gives predictable response time under peak load as well.

Memory consumption per node drops for the same reason: a module carries only its own code and declared dependencies, rather than a separate copy of system libraries for each instance.

Many times more services fit on the same node — density grows, and the number of nodes for the same load profile decreases.

A class of failures related to the shared runtime and leaks in neighboring processes is also closed. A module is memory-limited and isolated from neighbors, so degradation of one service does not drag down the rest: a failure is localized rather than turning into a cascade.

We confirm this with measurements before and after on the same load profile: startup time, memory consumption per node, number of instances on the same configuration. The decision is made based on numbers, not expectations.

Comparison of WebAssembly runtime operating modes

The launch mode is chosen not by taste but by the load profile: how long the handler lives, whether it needs access to the host network and files, how often new instances come up under a surge of requests.

Below are the options that are actually chosen between, and the boundaries beyond which each stops working.

Launch option When it fits Where the boundaries are
Lightweight module in a Spin-compatible runtime Short-lived handlers, burst traffic, high density per node No arbitrary system calls; access to network and files only through explicitly granted capabilities; some libraries require rebuilding
Container (OCI image) Familiar dependencies, system packages, background processes, debugging as in prod Longer startup, larger image, lower density, isolation entirely on the host kernel
Classic virtual node Long-running processes, third-party agents, non-standard stack Long provisioning and updates, lowest density, a lot of manual work

Containers versus modules differ in what exactly they get from the host. A container brings its own user environment and a full set of system calls; a module executes inside the engine and sees only what the rights manifest has allowed.

Hence lightweight isolation: an instance comes up an order of magnitude faster, and more of them fit on a node.

The transition is convenient to make with a mixed scheme: requests are routed by handler type, some go to modules, some to containers, and the calling code sees a single interface.

This is how you migrate one service at a time and retain the ability to roll back an individual route.

The applicability boundaries are hard: privileged operations, background daemons, or someone else's binary cannot be packed into the lightweight format — workarounds will cost more.

Work order: audit, design, configuration, validation, handover

Implementing a WebAssembly runtime for cloud microservices starts not with installation but with an analysis of the current environment.

First we answer which services are even worth moving to the new format and which are cheaper to leave in containers — the entire subsequent design depends on this.

  1. Infrastructure audit. We inventory the orchestrator, image registry, load balancing, network policies, secret handling, and observability. We record constraints: where the managed perimeter ends and what will have to be improved in-house.
  2. Load profile by service. We look at the share of short calls, isolation requirements, and sensitivity to cold start. This shows where modules provide gains in startup time and density per node, and where there will be no difference.
  3. Deployment design. We describe the target environment: where the runtime lives (Spin), how traffic is routed, how modules are published to the registry, how versions are rolled out, and how rollback to the previous one is arranged.
  4. Environment configuration. We specify the configuration, per-instance limits, access to external services, secret binding, and metric and log channels. Separately — module naming rules and network isolation boundaries.
  5. Load validation. We run scenarios: peak, sustained load, node failure. We compare cold startup and resource consumption with the current environment and adjust limits based on facts, not calculations.
  6. Handover of procedures. We deliver the design, configurations, rollout and rollback procedures, and the on-call checklist. After that the team works independently, without being tied to us.

The order is strict: without an audit you cannot choose the model, without a design you cannot validate load, without validation there is nothing to hand over into procedures.

Configurations, designs, and access policies in the output

The package is handed over together with the code and lives in the same repository, so after launch the team retains working files and procedures, not verbal agreements. Below is what exactly is included in the handover and how to use it without us.

  • Service manifests — one per module: entry points, routes, memory and execution time limits, and environment variable composition separately for staging and production.
  • Interface contracts — type descriptions at component boundaries: what the module accepts and what it returns. Editing the contract breaks the build, not production.
  • Deployment designs — which environments exist, how traffic reaches WebAssembly modules, how the artifact registry and runtime are connected, and where the failure points and workarounds are.
  • Access policies — roles in the cluster, rollout permissions, lists of allowed outbound addresses, secret issuance rules: a reference in the manifest, the value only in the vault.
  • Pipeline checks — module build, signature verification, contract test run, publication to the registry only after a green run.
  • Observability — metrics and logs by which the on-call engineer distinguishes module degradation from runtime failure, and the alerts tied to them.
  • Rollback procedure — returning the previous component version in one operation, how long it takes, who makes the decision, and how the result is verified.
  • On-call documentation — service map, typical failures, procedure, and escalation line.

Case: migrating services to isolated modules

The client came with an environment of twelve internal services that ran on a shared runtime in several large instances.

The service migration case began with an analysis of incidents: one hung handler or leak in a module knocked out its neighbors in the process, deployment stopped traffic for the whole group, and response time under peaks went beyond a second.

What was unsatisfactory and what was changed

Measurements showed that the services almost did not share data, but shared the execution environment — hence the "noisy neighbor", shared dependency pool, and shared release cycle.

They were moved to a WebAssembly runtime: each service became an independent module with explicitly declared rights — access only to its own routes, keys, and tables, without the host file system.

Spin was chosen as the launch environment: an instance comes up per request, hot routes keep a warmed pool, and the module version switches without affecting neighbors.

Metrics before and after

Indicator Before After
Response at the 95th percentile under peak 1.4 s 0.3 s
Service downtime during deployment 12–18 minutes per group without traffic stoppage
Rollback to the previous version repeated group deploy, 20+ minutes module version switch, seconds
"Neighbor crashed" incidents 2–3 per week 1 per quarter
Memory per service shared pool, unbounded growth per-module limit, predictable ceiling

Isolation delivered the main thing — predictability: a failure of one module no longer goes beyond its boundaries, and deployment stopped being an event with a maintenance window.

The environment handles peak load without response degradation, and this is confirmed not by a one-off measurement but by behavior under real traffic.

How modular isolation differs from containers

A container isolates the process entirely: it carries the user environment and relies on shared kernel mechanisms.

A WebAssembly module is structured differently — it is a portable artifact with code and dependencies that by default has neither a file system nor a network: only what is explicitly granted is available. Hence the difference in instance overhead, and it determines what is worth migrating.

In practice the choice sounds not like "containers or modules" but like "what to do if the container environment is already running".

The answer is a hybrid environment: next to the orchestrator, a module runtime is deployed (Fermyon Spin and compatible implementations), routing remains shared, and services are migrated one at a time.

Migration without downtime looks like this: a module with the same contract receives a small share of traffic, metrics are compared, then the share grows; rollback — return the route to the container, without rebuilding.

The coexistence of the approaches is justified: state, long-running background tasks, and heavy native dependencies remain in containers, while short request handlers go into modules.

The shared layer of routing, logs, and metrics preserves a single observability picture for the team.

Common technical questions about module isolation

Engineers' doubts about isolation come down to four topics: what a module sees in the file system, where it gets secrets from, how it updates without downtime, and where services with persistent state should write.

Below are short answers; detailed breakdowns and configuration examples are in the following sections.

Does a module get access to disk?

No, until access is explicitly declared. A WebAssembly module runs in a sandbox and does not see the node's file system; directories are mounted by name in the configuration.

We serve static content read-only; writes go to a narrow set of paths: a module with a vulnerability cannot reach system files and neighboring services.

How does a module get secrets?

Secrets are not hardcoded into the artifact: they arrive at startup — via environment variables or a connection to the vault. We grant rights on a least-privilege basis, revoking access does not require rebuilding, and key rotation does not trigger a new release.

How does update without downtime work?

A module is an immutable artifact, so the version is swapped atomically: a new copy comes up alongside, traffic is switched after successful validation, and the previous one remains for rollback. Downtime is zero, and rollback takes as long as the switch.

What should services with persistent state do?

The runtime does not preserve state between restarts, so we move sessions, carts, counters, and queues to external storage with explicit persistence. Any module copy can be shut down without losing data.

Let's discuss the task: what to send for an estimate

To come back with a project estimate rather than a range of "roughly this much", we need source data about your infrastructure: the more precise the inputs, the fewer assumptions in the estimate and timeline. Below is the minimum set that covers most questions. We will clarify the rest in correspondence or on a call with your engineer.

  • Service map: what is already containerized, which calls are latency-critical, where the bottlenecks are now.
  • Current WebAssembly runtime and module build method: what you build with, how artifacts reach the registry, who is responsible for the release.
  • Load profile: peak requests, share of short calls, requirements for cold start time and isolation between tenants.
  • Access requirements: secrets, network policies, roles, action audit — what is already configured and what cannot be changed.
  • Platform constraints: cluster, edge nodes, or own servers, support for the required interfaces and state stores.
  • Build pipeline and observability: how pre-rollout checks are arranged, log and metric collection, whether there is a proven rollback to the previous version.
  • Acceptance criteria: by which indicators you consider the implementation successful — latency, density per node, share of automated rollouts.

Send what already exists: a list of services, an environment diagram, a link to the repository, notes on previous attempts.

Based on this data, we will come back with an estimate, scope of work, and a phased implementation plan — with the order of steps and the metrics that will confirm the result.