Tyn - A Rust microkernel that runs BEAM on bare metal (no Linux)

This has been quite a few nights and weekends, and I’m at the point where OTP 27 boots, Elixir runs, and TCP works — so I figured it was time to share it.

Tyn is a minimal Rust kernel that does one thing: host the BEAM. It boots directly on KVM/QEMU, loads the real unmodified OTP 27 ERTS, and runs your Erlang/Elixir code. No Linux, no containers, no POSIX — just BEAM on bare metal.

$ echo "hello" | nc localhost 5555
Hello from OTP 27 on Tyn!

Why?

The thought that kept bugging me: BEAM already has its own scheduler, its own memory management, its own process isolation, its own supervision, its own distribution protocol. It’s basically an operating system for your application. So why are we running it on top of another operating system that duplicates all of that?

Tyn is the experiment: what if you strip away everything the BEAM doesn’t need and give it a purpose-built host? The entire kernel is about 5,000 lines of Rust. It handles memory, interrupts, virtio drivers, a TCP/IP stack (smoltcp), and an in-memory VFS serving .beam files from a cpio archive baked into the image. BEAM handles everything else.

How it works

ERTS is compiled against musl (statically linked, same as Alpine Linux). musl translates libc calls into Linux syscalls. Tyn traps those syscalls and handles them in Rust. ERTS genuinely doesn’t know it’s not running on Linux — it’s the same binary you’d run on Alpine.

┌─────────────────────────────────────────┐
│  Your Elixir / Erlang / Gleam code      │
├─────────────────────────────────────────┤
│  OTP 27 (unmodified ERTS, SMP)          │
├─────────────────────────────────────────┤
│  ~50 Linux syscalls implemented (Rust)  │
├─────────────────────────────────────────┤
│  Tyn Kernel — 5,000 LOC Rust            │
│  SMP · Memory · TCP/IP · VFS · Drivers  │
├─────────────────────────────────────────┤
│  KVM / QEMU                             │
└─────────────────────────────────────────┘

What actually works

  • Unmodified OTP 27 — no source patches. Same ERTS you’d build for Alpine.

  • 8-way SMP — APIC timers, per-CPU scheduling, proper blocking futex. All the threading machinery ERTS expects.

  • TCP networkinggen_tcp:listen/accept/send/close end-to-end through virtio-net.

  • Elixir 1.18.3IO.puts("Hello from Elixir on Tyn!") works. System.version returns "1.18.3".

  • Stable — last stability run was 1 hour, 153 TCP connections, 41 OTP processes, zero faults.

What doesn’t work yet

  • JIT is disabled. Bytecode interpreter only. I/O-bound workloads are fine; compute-heavy code takes a hit. The kernel needs mprotect with executable page support — known fix, just haven’t done it yet.

  • No interactive shell. Code runs via -eval at boot. Interactive IEx needs terminal I/O handling I haven’t built. Remote shell over distributed Erlang is probably the better path anyway.

  • No SSL/TLS. This is deliberate — OpenSSL would be the single largest component in the system. TLS termination at the load balancer is the expected model.

  • No Phoenix yet. OTP 27 + Elixir + TCP all work, so the pieces are there. Cowboy/Bandit testing is next on the list.

Some context

This isn’t the first attempt at BEAM-on-bare-metal. LING (Erlang on Xen) proved the concept around 2013 but died because it reimplemented the BEAM VM and couldn’t keep up with OTP releases. That was the lesson I took to heart — Tyn runs the real ERTS. When OTP 28 ships, it should just work.

Nerves and GRiSP are doing great work in the embedded space. Tyn is targeting a different niche — cloud deployments on KVM, where each BEAM node gets its own tiny VM instead of a container on shared Linux.

The pitch (if you need one)

  • ~5,000 lines of Rust kernel vs a general-purpose OS. Smaller attack surface.

  • Boots in milliseconds. Good for elastic scaling.

  • Images are megabytes, not gigabytes.

  • VM-level isolation (hardware-enforced) instead of container isolation.

  • Scale by adding more Tyn VMs and connecting them via distributed Erlang — which is what BEAM was designed for.

Code & contributing

Everything is at github.com/tyn-os/kernel — MIT/Apache-2.0.

You’ll need an x86_64 Linux host with KVM, Rust nightly, and QEMU. The README walks through building ERTS against musl and packaging the VFS.

If any of this sounds interesting, I’d welcome questions, feedback, and ideas !

42 Likes

I’m very excited to use this on some of my own systems. Was the 153 tcp connections just ‘test’ connections, not a benchmark ?

3 Likes

Very cool! Yeah, those were just functional tests over a stability run. Benchmarking is on the list!

3 Likes

Very interesting and cool project. It will be great to have pre-built images.

3 Likes

very interresting project. Would you be interrested by a patch adding UEFI and bhyve (freebsd) support?

3 Likes

Thanks! I’d definitely be interested in UEFI and bhyve support. The kernel currently uses multiboot1 with BIOS boot, so UEFI would broaden the deployment targets significantly. bhyve is interesting too.

3 Likes

Quick update — Bandit + Plug now serve HTTP on Tyn:

$ curl http://localhost:5566/
Hello from Bandit on Tyn!

The full chain runs end to end: ThousandIsland accepts the connection, DynamicSupervisor spawns the handler, Bandit parses the HTTP request, Plug builds the response, gen_tcp sends it back through smoltcp and virtio-net to the host.

Since the original post: OTP 27 with 8-way SMP, Elixir 1.18.3, ~6,300 lines of Rust kernel. Boot takes about 8 seconds on KVM.

Next up: demand paging (memory optimization), JIT, and Phoenix.

8 Likes

Just got it to work, nice :slight_smile:

Had a few minor hickups on the way, but I wrote down exactly what I did to get it working in case anyone would find it useful:

4 Likes

Very cool ! Thanks for sharing your notes ! JIT is now enabled, there’s an interactive TCP shell, and profiling and network optimization are coming along. After that, hopefully some public cloud images to make it more accessible.

8 Likes

Update — Tyn now runs on real AWS EC2. There’s a public AMI you can try:

aws ec2 run-instances --image-id ami-093863e8a76caecc5 \
    --instance-type c5.large --region us-east-1

Open port 8080 in your security group, wait 10 seconds, then:

$ curl http://<public-ip>:8080/hello
Hello from Phoenix on Tyn!

$ curl http://<public-ip>:8080/json
{"status":"ok","beam":"27","jit":"jit","procs":352,"mem":18124768,"server":"Tyn"}

Since the last update: ENA NIC driver written from scratch in Rust, DHCP networking, an interactive eval shell via EC2 Serial Console, and a one-command deploy from source. Full instructions in the README.

Would love feedback!

6 Likes

Update — you can now deploy your own Phoenix app on Tyn.

Last time it was our demo app baked into the image. There’s now a packaging tool that takes a Mix release and produces a bootable image, so you don’t need Rust or a kernel rebuild.

A stock mix phx.new app runs unmodified — static assets, LiveView (interactive, not just server-rendered), signed sessions, runtime.exs. Getting there needed :crypto, which is a from-scratch Rust NIF statically linked into ERTS, since a unikernel has no dynamic linker. It’s fed by a kernel CSPRNG. Outbound TCP/UDP and DNS work now too, so an app can actually reach a database or an API.

New AMI, running a stock Phoenix app:

aws ec2 run-instances --image-id ami-09619e2d139f2a57d \
    --instance-type c5.large --region us-east-1

Open 8080, wait ~10s, curl it. LiveView counter is at /counter if you want to click something.

Still missing: no TLS in the guest (terminate at a load balancer), no writable filesystem, no distributed Erlang, and the wall clock sits at 1970. The crypto is unreviewed — don’t trust it with real sessions. Full list in the README.

Would love to hear if it breaks for anyone.

1 Like

Does the syscall interception overhead actually matter in practice, or is it pretty negligible?

There’s no interception layer in the usual sense. ERTS makes real syscalls and the kernel handles them in-process, so there’s no VM exit or trap, just a privilege-boundary crossing in the same address space.

The BEAM barely touches the kernel on the hot path. It runs its own scheduler, its own memory management, its own I/O polling, and multiplexes everything onto a few OS threads, so syscalls are rare relative to the work between them. For a Phoenix workload the request path is dominated by BEAM-side work, not kernel crossings, so negligible. There could be some syscall-heavy path, but so far this hasn’t caused any issues.

1 Like