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.
[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.
| Path | Size | Role |
|---|---|---|
/init | 40 KB | PID 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.smp | 9.9 MB | Erlang/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_setup | 59 KB | The helper ERTS starts for Ports. Present because ERTS insists; idle. |
/rel/lib/*/ebin | 11 MB | kernel, stdlib, ssl, elixir, iex, your application. |
/etc/ssl/cacert.pem | 230 KB | CA 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
- QEMU loads the kernel ELF and the initramfs. The kernel unpacks the cpio into a RAM file system and runs
/init. /initdoes what a release'sbin/myappscript anderlexecwould do, in C with the arguments fixed at build time, thenexecvsbeam.smp. It stays PID 1.- ERTS reads the release boot script, starts the applications and, in
iexmode, the shell on the serial port. Inappmode 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.
| Asterinas | Linux 6.1 (reference) | |
|---|---|---|
Kernel entry to /init | 0.45 s | 1.0 s |
| Application started | 3 to 4 s | 4 s |
| Minimum guest RAM, all tests passing | 144 MB | 128 MB |
beam.smp RSS, idle sample app | 62 MB | 62 MB |
| Image + kernel | 15.2 MB | 9.4 MB + 44 MB vmlinux |
| TCP, UDP echo from the host | pass | pass |
TLS 1.3 server (openssl s_client) | pass | pass |
TLS 1.3 client, verify_peer, www.erlang.org | pass | pass |
DNS via inet_res | pass | pass |
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
- No persistent storage. The root is read-only,
/tmpis RAM. - No external programs.
System.cmd/3has nothing to run. - No NIFs loaded at runtime. OTP's own are linked in; yours would have to be too.
- No Erlang distribution, no
epmd, no DHCP, no IPv6 testing yet. - On Asterinas, the IP address is still compiled into the kernel; the command-line value is honoured on Linux only.
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.