glowvm
Run Gleam serverlessly via AtomVM compiled to WebAssembly
GlowVM compiles AtomVM to wasm32-wasi, with WASI bindings and a few shims
and NIFs for compatibility, so Gleam’s Erlang target can run inside a
Cloudflare Worker (or any WASI-compliant host). Gleam modules are compiled
and packed into an AtomVM PackBeam (.avm) file, loaded by AtomVM at request
time.
Docs — a Giolt project.
Status: experimental, not ready for production use. GlowVM inherits AtomVM’s constraints — 64-bit signed integers only, no hot code swapping, no distributed Erlang, no C-based NIFs, limited non-byte-aligned bitstring matching, partial OTP stdlib coverage, and a stateless, request-scoped execution model. Prefer pure-Gleam dependencies and test early on GlowVM rather than assuming a successful build implies runtime correctness.
Known gaps beyond that:
crypto— onlycrypto:strong_rand_bytes/1exists (a platform NIF);otp_crypto.cis excluded from the WASM build, sogleam_cryptoand wisp’s signed cookies/sessions (crypto:hash/2,crypto:mac/4) don’t work.- Uncaught crashes report opaquely. AtomVM’s own error reporting needs
init, butinitis never called from Erlang bytecode, so packbeam’s pruning always strips it — any unhandled exception in a handler surfaces asFailed load module: init.beaminstead of the real reason. Wrap handler logic intry/catchif you need the actual error client-side. gleam/string’s grapheme functions treat graphemes as codepoints (src/shims/string.erl), so combining marks, emoji ZWJ sequences and Hangul jamo don’t cluster correctly. Case mapping (uppercase/lowercase) covers ASCII, Latin-1, Latin Extended-A, Greek and Cyrillic only.
Setup
Requires macOS or Linux.
| Tool | Version | Used for |
|---|---|---|
| cmake | >= 3.20 | building native/ |
| Erlang/OTP | >= 28 | erlc, and Gleam’s Erlang target |
| Gleam | >= 1.18 | building this library |
| Binaryen | any | wasm-opt / wasm-dis / wasm-as, used to repack the WASM output into a form workerd accepts |
| wasi-sdk | 34 | the WASI clang toolchain |
| just | any | The runner |
On macOS, the first four are a brew install cmake erlang gleam binaryen just
away.
wasi-sdk has no Homebrew formula: download the release archive for your
platform from the v34 release
page and
extract it to ~/.wasi-sdk. scripts/build-atomvm.sh looks for it at
~/.wasi-sdk, /opt/wasi-sdk, /usr/local/wasi-sdk, and ./wasi-sdk in
this repo, or at $WASI_SDK_PATH if set.
Build
just build
Clones the vendor/AtomVM submodule on first run if it isn’t present, builds
native/ with cmake, repacks and optimizes the WASM with Binaryen, and
writes priv/glowvm.wasm plus the Erlang stdlib subset into priv/stdlib/.
gleam build # compile this library on its own
Building an app
glowvm is the runtime only. The giolt CLI compiles
your project, checks that your entry module exports handle, packs it into an
AtomVM PackBeam (app.avm) and copies the runtime files below into your output
directory. Your project does not depend on glowvm directly.
pub fn handle(req: wisp.Request) -> wisp.Response {
wisp.ok() |> wisp.string_body("hello from glowvm")
}
giolt generates a start/0 that calls glowvm.serve(fn(req) { your.handle(req) }).
The handler has to be called through a lambda: passing your.handle by reference
makes the packer drop your modules.
Runtime contract
What giolt relies on, and what a change here has to keep stable:
| Path | Purpose |
|---|---|
glowvm.serve/1 | Guest-side bridge: reads one request (Erlang external term format) from stdin, calls the handler, writes the response to stdout. One VM run per request |
priv/glowvm.wasm | AtomVM compiled to wasm32-wasi |
priv/index.js, priv/index.min.js | Worker entry that loads ./glowvm.wasm and ./app.avm and answers fetch |
priv/stdlib/*.beam | Curated Erlang stdlib subset, packed into every app |
The protocol between the host JavaScript and the guest bridge is versioned with
this package: a change to it is a minor version bump while glowvm is 0.x, and
giolt declares the range of glowvm versions it supports.
Issues
Found a bug or have a request? File it in withgiolt/issues, the central issue tracker for all Giolt projects.
Attribution
This project is inspired by Jaketdaniels/elixir-workers. The code has been heavily modified to remove the 2 pass system, use latest AtomVM with OTP >=28, add support for environment variables, Gleam support and more.