Compiling Go to WebAssembly: Embedding NATS in wash
The April 16, 2025 wasmCloud community call is a study in Go WebAssembly done in anger. Brooks Townsend demos compiling the Go-based NATS server to a WebAssembly module and embedding it directly in the wash CLI — no separate binary, no Docker, no process forked off — so that wash dev can stand up a full local wasmCloud platform from a single static binary. Before that, maintainer Masoud (ossfellow) walks through his provenance-and-attestation RFC — SLSA levels, SBOMs, and the cost of bolting supply-chain security onto a complex monorepo CI pipeline — and the call closes with a heads-up that Q2 roadmap planning lands next week.
Key Takeaways
- Go compiles to WebAssembly today. Brooks compiled the upstream NATS server (written in Go) to a WASI Preview 1 Wasm module using big Go's
wasip1target, then embedded it inwashvia Wasmtime — the same runtimewashalready uses for plugins - The only source change to NATS was swapping the TCP listener for a pre-opened socket file descriptor (WASI P1 cannot open its own sockets); everything else compiled and ran unchanged
- The result:
wash devruns the messaging backbone with nonats-serverprocess in the process list — the 28 MB Wasm module (vs. ~16 MB native) handled ~15k messages/sec in the local NATS benchmark, plenty for development - Componentizing the module hit known duplicate-import issues in Go's
wasip1output; full WASI P2 sockets would let NATS just callnet.Listen— but that path depends on big Go's P2 support and TinyGo'snetpackage maturing (Bailey Hayes offered to connect contributors with upstream maintainers) - Brooks leans toward one
washwith embedded NATS as the standard experience (with flags to bring your own NATS), rather than shipping multiple binary flavors - On supply-chain security: Masoud's RFC showed that producing SBOMs and SLSA attestations across Rust, Go, and Python means juggling three different toolchains, and folding it into wasmCloud's monorepo CI would add heavy boilerplate — the team agreed to park the RFC and revisit it as part of a future CI redesign
- Some pieces of that original RFC (SPIFFE/SPIRE workload identity, NATS auth) have already landed in the host
- Q2 roadmap planning runs next week — leave priorities on the GitHub discussion
Chapters
- 3:47 — Welcome to wasmCloud Wednesday, April 16
- 5:53 — Provenance and attestation RFC: background
- 7:17 — SLSA, SBOMs, and supply-chain attestation
- 15:11 — Why CI complexity makes this a heavy lift
- 17:48 — Community feedback and a redesigned pipeline
- 24:19 — Next steps: park the RFC pending roadmap
- 27:33 — Demo intro: a single static binary for wash
- 31:00 — wash dev with no NATS process running
- 33:30 — Compiling the NATS server to a WASI P1 Wasm module
- 37:19 — Benchmarking the embedded NATS module
- 43:09 — One wash or two? Compile flags and flavors
- 50:41 — Embedding wasmCloud and wadm: the path forward
- 52:15 — TinyGo, big Go, and the road to WASI P2
- 54:35 — Q2 roadmap planning and how to give feedback
Meeting Notes
Provenance and Attestation: Research and Findings
Maintainer Masoud (ossfellow) opened with a background on his provenance-and-attestation RFC. The motivation is supply-chain security: after the SolarWinds incident and the resulting executive order, the industry coalesced around SLSA (Supply-chain Levels for Software Artifacts) and a rich set of tooling for generating SBOMs and build attestations. An attestation certifies what built an artifact and how — not the nature of the content itself — and the SLSA levels ladder up from manual builds (L1) to automated builds (L2) to attestations produced inside hardened, reusable workflows (L3+).
Masoud's investigation surfaced the practical friction: he needed three different tools to produce SBOMs across Rust, Go, and Python because no single tool covered every language. GitHub's official attestation actions help, but to reach SLSA Level 3 the attestation has to be encapsulated in an isolated, reusable workflow — something wasmCloud's current pipeline isn't built around. He built a working proof of concept (SBOMs, certificates, and attestation records all visible in the job output), but the cost is many insertion points across the build, and a meaningfully more complex pipeline.
Why This Is a Heavy Lift Right Now
Brooks, Joonas Bergius, and Masoud agreed: wasmCloud's CI is already complex because it's a monorepo building many languages and architectures into OCI images. Bolting SBOM and attestation generation onto that — across every build and OCI packaging step — would add a lot of boilerplate without an urgent, named consumer asking for it today. Joonas framed it as organic growth that would need a deliberate redesign (common, maintained "build-an-artifact" actions) before this work could land cleanly. Brooks noted some parallel upstream effort under the Bytecode Alliance's SIG Packaging (wackage and related Wasm tooling) taking a different approach to embedding this metadata.
The consensus: save all of Masoud's work, give the community a window to weigh in on the RFC issue, and if it isn't prioritized by the next roadmap planning, archive it for a future CI overhaul. Brooks also noted that pieces of Masoud's original RFC — SPIFFE/SPIRE workload identity and NATS authentication — have already made their way into the host, demoed at the recent KubeCon.
Demo: Embedding NATS in wash
Brooks then ran a demo aimed at a long-standing goal: making wash up and wash dev download fewer binaries. Today wash bootstraps a local platform by fetching three binaries from GitHub — wasmCloud (the host), wadm (application orchestration), and NATS (the messaging, RPC, and control backbone). The host and wadm are Rust crates, so wash can already embed them in-memory as libraries. The sticking point is NATS, which is written in Go — you can't simply link a Go binary into a Rust program.
So Brooks tried something else. He ran wash dev on a simple HTTP key-value component (curl an endpoint, increment a counter) — and showed that no nats-server process appears in the process list. No dynamic library, no Docker, no forked binary. Instead, he had compiled the NATS server to WebAssembly: using big Go's wasip1 target, go build produced a 28 MB WASI Preview 1 module (the native binary is ~16 MB; the delta is the Go runtime). The single required change to NATS source: instead of opening a TCP listener on 4222, the server accepts a pre-opened socket file descriptor and turns it into a net.Listener — because WASI P1 modules can't open their own sockets (that arrives in WASI P2).
Inside wash, Brooks reused the existing Wasmtime embedding (already there for plugins): a fresh engine and linker for the NATS module, a pre-opened JetStream directory for config and storage, the pre-opened socket bound to 4222, and a background task running the module with inherited stdout/stderr. Running the NATS benchmark through the single-threaded module pushed ~15k messages/sec — not production-grade, but more than enough for local development.
One wash, or Two? And the Road to WASI P2
Florian asked whether wasmCloud would ship two flavors of wash, one with embedded NATS and one without. Brooks and Lucas converged on a cleaner answer: keep the hooks so advanced users can point wash at their own NATS (or run it as a container), but default to one binary with the standard, no-config local experience — too many options is itself a developer-experience tax. Masoud suggested a compile flag for building from source, which Brooks was happy to support.
Bailey Hayes explained the componentization snags: Go's wasip1 output currently has duplicate imports (e.g. fd_write), and TinyGo's net package isn't yet where it needs to be — the TinyGo maintainers want it to mirror big Go. Both are tractable, and big Go's WASI P2 support (the GOOS/GOARCH for wasip2) would eventually let NATS just call net.Listen and be turned into a real component, opening the door to leaf-node and cluster modes. Bailey offered to connect interested contributors with the upstream maintainers, and Brooks signaled a PR upstreaming this into wash is likely soon.
Up Next: Q2 Roadmap Planning
The call closed with a reminder that Q2 roadmap planning happens next week as a special edition of wasmCloud Wednesday. Brooks encouraged everyone to leave priorities on the GitHub discussion ahead of time, so the session can focus on committing to a handful of goals for the next three months rather than parsing feature requests on the spot.
WebAssembly News and Updates
The headline from this call is a vivid demonstration of WebAssembly's original promise: a language-agnostic, platform-agnostic compilation target. By compiling the Go-based NATS server to a WebAssembly module, wasmCloud took software written in one language and embedded it, sandboxed, inside a tool written in another — no native cross-compilation, no per-OS binaries. The embedding reuses Wasmtime, the same standardized runtime wash already uses for plugins. The current limits (WASI P1's lack of socket support, Go's duplicate-import quirks, TinyGo's net package) are exactly the rough edges that WASI P2 and the component model are closing. For the broader landscape, the WASI P2 transition for Go is tracked across big Go and TinyGo; follow the Bytecode Alliance and the WASI subgroup for the socket and component-model milestones that will turn this hack into a first-class capability.
What is wasmCloud?
wasmCloud is a CNCF project that lets you build applications using WebAssembly components and deploy them anywhere — cloud, edge, or Kubernetes clusters. It uses the WebAssembly component model to let you write business logic in any supported language (Rust, Go, Python, TypeScript, C#) while the platform handles capabilities like HTTP, messaging, and key-value storage through a pluggable provider architecture. wasmCloud's reference host is built on Wasmtime, and as this call shows, that same embedding model lets wash run an entire local platform — host, orchestration, and even the NATS messaging backbone — from a single tool. With built-in OpenTelemetry observability and Kubernetes integration, wasmCloud bridges WebAssembly's portable, sandboxed execution model and production cloud-native infrastructure.
Topic Deep Dive: Compiling Go to WebAssembly
Compiling Go to WebAssembly is one of the most common on-ramps into the ecosystem, and this call shows both its power and its current boundaries. Go has two routes: TinyGo, a size-optimized compiler that historically led on the Wasm targets, and big Go (the standard toolchain), which now ships a GOOS=wasip1 target that compiled the entire NATS server out of the box. The trade-off is size — the big-Go module pulls in the full Go runtime (28 MB vs. ~16 MB native) — but it required only a single source change because the Go standard library is more complete than TinyGo's for a project like NATS.
The hard boundary today is WASI Preview 1: P1 modules can't open their own network sockets, which is why Brooks had to hand NATS a pre-opened file descriptor. WASI Preview 2 introduces a real socket API, so a P2-targeting NATS could simply call net.Listen, be compiled to a proper WebAssembly component, and gain typed interfaces over the component model — unlocking leaf-node and cluster configurations that need multiple sockets. If you're building Go components for wasmCloud today, the Go language support guide and the useful WebAssembly tools reference are the right starting points; network and socket behavior is covered under network access and socket isolation.
Who Should Watch This
This call is especially valuable for Go developers curious about compiling real-world Go services to WebAssembly and the big-Go-vs-TinyGo trade-offs (jump to the demo at 27:33), platform and tooling engineers thinking about static, dependency-free CLIs that embed their own runtime (the NATS-in-wash reveal at 31:00), and security and supply-chain teams interested in SBOMs, SLSA, and the realities of attesting builds across a polyglot monorepo (Masoud's RFC walkthrough at 7:17).
Up Next
The next community call is the Q2 roadmap planning session — a special edition where the team prioritizes the open-source roadmap for the coming quarter. Watch for follow-up on the embedded-NATS PR into wash, the decision on whether to park the provenance/attestation RFC, and continued progress on SPIFFE/SPIRE workload identity in the host. Leave your priorities on the GitHub discussion before the meeting.
Get Involved
wasmCloud is a CNCF project and contributions are welcome. Join the community:
- GitHub — star the repo and check out open issues
- Slack — join the conversation
- Community Meetings — every Wednesday at 1:00 PM ET
- wasmCloud Blog — latest news and releases
Full Transcript
Read the complete transcript with speaker labels and timestamps: