elixir_unikernel

Boot straight into Elixir on a Rust kernel.

A mix release becomes a 15 MB machine image: a memory-safe kernel, one 300-line init program, and the Erlang VM. No shell, no init system, no dynamic loader. TCP, UDP and TLS 1.3 work on the first boot.

Serial console, Asterinas kernel, QEMU. Unedited apart from the kernel's ASCII logo.
[kernel] initramfs is ready
[kernel] running /init as the init process
[init] elixir_unikernel init, uptime 0.560 s
[init] net: eth0 10.0.2.15/24 gw 10.0.2.2
[init] exec /rel/erts-17.1/bin/beam.smp (iex mode)
Erlang/OTP 29 [erts-17.1] [source] [64-bit] [smp:2:2] [ds:2:2:10] [async-threads:1] [jit:ns]
13:01:08.809 [info] uniapp starting (otp 29, elixir 1.20.4, uptime 3.80s)
Interactive Elixir (1.20.4) - press Ctrl+C to exit (type h() ENTER for help)
iex(1)> 

Build and boot it git clone, make check, make run-m1. About 15 minutes on a cold cache.

What is in the image

Three statically linked ELF files and the .beam files of your release. The build fails if anything else turns up.

PathSizeRole
/init40 KBPID 1. Mounts /proc, configures the NIC from the kernel command line, writes inetrc and resolv.conf, execs the VM.
/rel/erts-17.1/bin/beam.smp9.9 MBErlang/OTP 29.1.1, JIT, musl. OpenSSL 3.5, zlib, zstd and the crypto and asn1 NIFs linked in.
/rel/erts-17.1/bin/erl_child_setup59 KBThe helper ERTS starts for Ports. Present because ERTS insists; idle.
/rel/lib/*/ebin11 MBkernel, stdlib, ssl, elixir, iex, your application.
/etc/ssl/cacert.pem230 KBCA bundle, so :ssl.connect with verify_peer works.

Compressed, that is a 9.4 MB initramfs.cpio.gz. The Asterinas kernel adds 5.8 MB. The same initramfs also boots on a stock Linux kernel, which is how kernel bugs get told apart from application bugs.

How it boots

  1. QEMU loads the kernel ELF and the initramfs. The kernel unpacks the cpio into a RAM file system and runs /init.
  2. /init does what a release's bin/myapp script and erlexec would do, in C with the arguments fixed at build time, then execvs beam.smp. It stays PID 1.
  3. ERTS reads the release boot script, starts the applications and, in iex mode, the shell on the serial port. In app mode there is no shell at all.

Name resolution goes through inet_res, OTP's resolver written in Erlang, so the inet_gethost port program is not needed. Everything configurable arrives on the kernel command line: uniapp.mode=app, uniapp.ip=10.0.2.15/24, uniapp.dns=10.0.2.3. Details in the command-line reference.

Measured

QEMU 11 with TCG on an Apple M-series laptop, so x86-64 is emulated instruction by instruction. KVM will be faster; those numbers are not in yet.

AsterinasLinux 6.1 (reference)
Kernel entry to /init0.45 s1.0 s
Application started3 to 4 s4 s
Minimum guest RAM, all tests passing144 MB128 MB
beam.smp RSS, idle sample app62 MB62 MB
Image + kernel15.2 MB9.4 MB + 44 MB vmlinux
TCP, UDP echo from the hostpasspass
TLS 1.3 server (openssl s_client)passpass
TLS 1.3 client, verify_peer, www.erlang.orgpasspass
DNS via inet_respasspass

These are the assertions of make smoke, which rebuilds everything from pinned git tags (OTP-29.1.1, v1.20.4, Asterinas 3d85cb4) and runs in about twelve minutes. All measurements and limits.

About the kernel

Asterinas is a Linux-ABI compatible kernel written in Rust, with unsafe code confined to a small core. Because the ABI is Linux's, an unmodified OTP runs on it; this project changes nothing in ERTS. Two kernel bugs were found by running the BEAM on it and are carried as patches until they land upstream: wildcard bind() failed with EADDRNOTAVAIL, and a disarmed timerfd stayed readable, which made ERTS's poll thread spin and looked exactly like a kernel livelock. Both patches are a few lines; the second one is a one-liner with a thirty-line C reproducer.

This is not a unikernel in the strict sense: kernel and VM are two binaries with a syscall boundary. Everything else a Linux system ships is gone. A single-binary build on Hermit is the stretch goal; the kernel notes say what that would take.

What it does not do

Phoenix works within those limits: pure-BEAM dependencies, assets built in the Docker stage, configuration from the command line. There is a chapter on it.

Try it

git clone https://github.com/bmalum/elixir_unikernel.git
cd elixir_unikernel
make check        # docker buildx, qemu-system-x86_64, coreutils
make run-m1       # build, then boot into IEx. Ctrl-a x leaves QEMU.
make smoke        # everything, both kernels, with assertions

macOS on Apple Silicon works with Colima; the build runs natively on arm64 and cross-compiles only the ERTS binaries. Linux hosts with /dev/kvm get hardware acceleration automatically. The manual covers prerequisites, shipping your own release, troubleshooting, and the internals.