A minimal Rust microkernel purpose-built for BEAM.
No Linux. No POSIX. Just your Erlang/Elixir/Gleam code on bare metal.
Tyn is a unikernel β a single-purpose operating system kernel that hosts one thing: the BEAM virtual machine. It replaces the entire Linux stack with ~6,300 lines of Rust, targeting KVM/QEMU cloud deployments.
The BEAM already has its own process model, scheduler, memory management, and distribution protocol. A general-purpose OS kernel underneath duplicates much of what the BEAM provides natively. Tyn explores what happens when you remove that redundancy and give BEAM a purpose-built host.
Security. A general-purpose kernel includes subsystems for hardware a cloud BEAM workload never uses β USB, GPUs, dozens of filesystems, thousands of device drivers. Tyn includes only what BEAM needs, reducing the attack surface to a few thousand lines of Rust.
Simplicity. A Tyn image contains only BEAM bytecode and the Rust kernel. No general-purpose OS services, no package management, no user accounts β just your application and its runtime.
Boot speed. Tyn boots in milliseconds, not seconds. For elastic cloud deployments where BEAM nodes scale up and down, this matters.
Density. Tyn images are megabytes, not gigabytes. More BEAM nodes per host, lower cloud costs.
βββββββββββββββββββββββββββββββββββββββββββ
β Applications (Elixir / Erlang / Gleam) β
βββββββββββββββββββββββββββββββββββββββββββ€
β OTP / Supervision Trees β
βββββββββββββββββββββββββββββββββββββββββββ€
β ERTS / BEAM VM (unmodified, SMP) β
βββββββββββββββββββββββββββββββββββββββββββ€
β BEAM Host Interface (Rust) β
β ~50 Linux syscalls emulated β
βββββββββββββββββββββββββββββββββββββββββββ€
β Tyn Kernel (Rust, ~6,300 LOC) β
β SMP Β· Memory Β· Networking Β· VFS Β· I/O β
βββββββββββββββββββββββββββββββββββββββββββ€
β KVM / QEMU / Cloud Hypervisor β
βββββββββββββββββββββββββββββββββββββββββββ
Tyn runs the real, unmodified ERTS/BEAM β not a reimplementation. When OTP ships a new version, it should just work. This is the critical lesson from LING (Erlang on Xen), which died because it reimplemented the VM and couldn't keep pace with upstream changes.
OTP 27 BEAM running on bare metal with SMP, TCP, Phoenix + Bandit + Plug, and Elixir.
defmodule TynHelloWeb.HelloController do
use TynHelloWeb, :controller
def index(conn, _params) do
conn
|> put_resp_content_type("text/plain")
|> send_resp(200, "Hello from Phoenix on Tyn!\n")
end
end
defmodule TynHelloWeb.Router do
use TynHelloWeb, :router
pipeline :api, do: plug :accepts, ["json", "html"]
scope "/", TynHelloWeb do
pipe_through :api
get "/", HelloController, :index
end
end
{:ok, _} = Bandit.start_link(plug: TynHelloWeb.Router, port: 8080)$ curl http://localhost:5566/
Hello from Phoenix on Tyn!
- OTP 27 ERTS boots with up to 8 CPUs, loads 200+ .beam files from in-memory VFS
- Full OTP kernel application starts β supervision trees, code_server, logger
- Phoenix 1.8 routes through
use TynHelloWeb, :router(Bandit fronts the Phoenix Router directly; the fullPhoenix.Endpointmiddleware stack would also work givensecret_key_baseetc., but the Router is the minimum demo) - Bandit runs unmodified on top of ThousandIsland with the default
num_acceptors: 100configuration β the fullDynamicSupervisorβConnection.startβ handler-spawn chain works - Plug pipeline:
Plug.Connβput_resp_content_typeβsend_respβ host gets the response - Elixir 1.18.3 runs:
IO.puts,System.version,Kernel.inspectall work
Where things stand on KVM (host: AWS Xeon 6975P-C):
- Image size: 49 MB bootable image β ~4Γ smaller than Alpine + Elixir + Phoenix (~190 MB)
- Cold boot to serving HTTP: ~7 s on KVM (kernel β BEAM handoff in ~430 ms; rest is OTP startup)
- Reliability: 30/32 cold-boot trials, each routing a curl request through Phoenix.Router + Bandit + Plug. The two failures are a long-known ERTS thread-progress registration race (
managed=4/5) where one of the 5 startup threads doesn't register and schedulers wait forever β kernel-side futex/scheduler bug, not Phoenix-specific. Phoenix is no less reliable than Bandit-only; the original 14/16 number was a measurement artifact (the readiness markerphoenix_listening\ngets interleaved with kernel debug output and a strictgrep -qmissed it on otherwise-successful boots) - Runtime memory: ~400 MB host RSS β ~6Γ an Alpine container due to ERTS allocator pool defaults (demand paging landed; allocator tuning is next)
- 8-way SMP β ACPI/MADT CPU discovery, APIC timer calibration, AP trampoline (16β64 bit), per-CPU GDT/TSS/IST, GS_BASE per-CPU syscall data, IPI wakeup, preemptive user-mode scheduling
- TCP networking β
gen_tcp:listen/accept/send/closeend-to-end, POSIX socket layer β smoltcp TCP/IP β virtio-net PCI β QEMU β host - Elixir β Elixir 1.18.3 .beam files load and execute on OTP 27
- ~50 Linux syscalls β mmap, read, write, open, stat, pipe, ppoll, futex, clone, epoll, select, readv, ...
- VFS β cpio newc archive with OTP kernel/stdlib .beam files + optional Elixir
- Boot β Multiboot1, identity-mapped 4 GiB, ELF loader for static musl binaries
- Threading β up to 16 CPUs, per-thread kernel stacks, atomic futex, preemptive + deferred scheduling
- I/O β COM1 serial (stdin/stdout/stderr), PCI ECAM, virtio-net
ERTS is built from unmodified OTP 27 source β no patches, no special defines. The only non-default configure flags are --disable-jit and --without-* for unused applications.
Tyn uses a hybrid futex strategy:
- During ERTS init (~first 2 seconds):
futex_waitreturns immediately (spin-yield) to avoid a thread-progress registration deadlock where blocked threads prevent other threads from registering with the progress system. - After init:
futex_waitblocks properly β threads sleep and consume zero CPU until woken byfutex_wake. Idle CPUs enter HLT.
The switch happens automatically after ERTS finishes loading boot modules. Normal operation uses real blocking semantics with proper sleep/wake.
- Phoenix β Bandit + Plug works; Phoenix on top should "just work" subject to compiling its dependency tree against the same OTP/Elixir as the cpio
- Concurrent-burst load β sequential request handling is rock-solid; under a 5-simultaneous-curl burst, 2/5 succeed and 3 get
Connection reset by peer. This is a smoltcp backpressure / connection-sup limit, not a kernel bug β see MESSAGE_DELIVERY.md Β§B2.22 - BEAM JIT β BeamAsm support (requires IST-safe preemption for clone child stacks)
- Interactive shell β IEx/Erlang shell with full stdin support
- Rust nightly toolchain with
rust-srccomponent - QEMU with KVM support (
qemu-system-x86_64) - A statically-linked
beam.smpand the OTP/Elixir rootfs cpio. Both are committed atsrc/beam.smp.elfandsrc/otp-rootfs.cpioso the kernel builds out of the box. To rebuild them yourself, see "Building ERTS + VFS" below.
cargo build --release --target x86_64-tyn.json \
-Zbuild-std=core,alloc,compiler_builtins \
-Zbuild-std-features=compiler-builtins-memqemu-system-x86_64 \
-kernel target/x86_64-tyn/release/tyn-kernel \
-m 2560M -machine q35 -cpu host -enable-kvm -smp 8 \
-nographic -no-reboot -serial mon:stdio \
-device virtio-net-pci,netdev=net0,disable-legacy=on,disable-modern=off \
-netdev user,id=net0,hostfwd=tcp::5555-:8080# In another terminal while QEMU is running, after Tyn prints
# "bandit_listening" on the serial console (~12s after boot):
curl http://localhost:5555/
# β Hello from Bandit on Tyn!Tyn embeds a statically-linked ERTS binary and a cpio archive of .beam files directly in the kernel image. Here's how to build them.
# On an x86_64 Linux host with musl-gcc installed:
git clone --branch OTP-27.3.4.2 https://github.com/erlang/otp.git otp27
cd otp27
# Configure for static musl (no JIT, minimal dependencies)
./configure --disable-jit --without-javac --without-odbc --without-wx \
--without-termcap --without-ssl --without-ssh --without-megaco \
--without-diameter --without-observer --without-debugger \
--without-et --without-reltool --without-common-test --without-eunit \
--without-edoc --without-eldap --without-ftp --without-tftp \
--without-snmp --without-docs --without-mnesia \
CC=musl-gcc CFLAGS="-O2 -static" LDFLAGS=-static
# Build
make -j$(nproc)
# The static beam.smp binary:
ls bin/x86_64-pc-linux-musl/beam.smp
# β ~9 MB statically linked ELF# Create an OTP release directory with .beam files
mkdir -p staging/otp/bin
cp otp27/bin/start.boot staging/otp/bin/
# Copy kernel and stdlib .beam files (with versioned paths for boot script)
for d in otp27/lib/kernel-*/ebin otp27/lib/stdlib-*/ebin; do
versioned=$(basename $(dirname $d))
mkdir -p staging/otp/lib/$versioned/ebin
cp $d/*.beam staging/otp/lib/$versioned/ebin/
done
# Copy .beam files to root for code_server fallback loading
cp otp27/lib/kernel-*/ebin/*.beam staging/
cp otp27/lib/stdlib-*/ebin/*.beam staging/
# Create the cpio archive
cd staging
find . -type f | sed 's|^\./||' | cpio -o -H newc > ../src/otp-rootfs.cpio
# Copy the ERTS binary
cp otp27/bin/x86_64-pc-linux-musl/beam.smp ../src/beam.smp.elf# Download prebuilt Elixir for OTP 27
curl -L -o elixir.zip \
https://github.com/elixir-lang/elixir/releases/download/v1.18.3/elixir-otp-27.zip
unzip elixir.zip -d elixir
# Add Elixir .beam files to the staging root
cp elixir/lib/elixir/ebin/*.beam staging/
cp elixir/lib/iex/ebin/*.beam staging/
# Rebuild cpio with Elixir included
cd staging && find . -type f | sed 's|^\./||' | cpio -o -H newc > ../src/otp-rootfs.cpio- Module structure β source file dependencies and line counts
- Boot flow β from power-on to Erlang shell, syscall sequence
- Runtime architecture β CPU layout, futex strategy, memory map
These document the bug-class hunts that got Tyn from "ERTS boots" to "Bandit + Plug serves real traffic":
- BOOT_RELIABILITY.md β failure modes, stack-layout trace through preemption + syscall, what fixes worked and why
- MESSAGE_DELIVERY.md β scheduler-wake / process-scheduling races, the watchdog-rescue fix, and the
sys_acceptrace that was blocking ThousandIsland's concurrent-acceptor pattern (now fixed)
Run the real BEAM. Not a reimplementation β the actual ERTS, cross-compiled for Tyn's host interface.
Purpose-built for BEAM. The kernel hosts one runtime and nothing else. This constraint enables a small trusted computing base and a clean verification story.
Minimal kernel, maximal BEAM. The kernel provides only what BEAM needs β memory, interrupts, device access, network. BEAM handles its own scheduling, memory management, code loading, and supervision.
Target KVM/virtio. Standardized virtual hardware means the kernel only needs a handful of drivers. The entire device layer is a few hundred lines of Rust.
Designed for verification. The kernel is structured for future formal verification with Verus. Minimal unsafe code, explicit invariants, small trusted computing base.
- LING β Erlang on Xen. Proved the concept. Died because it reimplemented BEAM and targeted only Xen.
- Nerves β Elixir on embedded Linux. Complementary β Nerves owns embedded, Tyn targets cloud.
- GRiSP β BEAM on RTEMS for IoT hardware. Different niche.
- Asterinas β Rust Linux-compatible kernel. Architectural reference.
- rcore-os/virtio-drivers β VirtIO drivers used by Tyn.
- smoltcp β TCP/IP stack used by Tyn.
Tyn is part of a broader ecosystem:
- Vor β A BEAM-native language with compile-time verification
- VorDB β A CRDT-based distributed database built on Vor
MIT OR Apache-2.0