Unikernels
Technical Overview
A unikernel is a single-address-space operating system image in which an application and the minimal OS libraries it needs are compiled together into a single executable that runs directly on hardware or a hypervisor. There is no user space, no kernel space — a single privilege level, a single address space, a single purpose.
The unikernel proposition: if you only run one application per VM, the OS abstraction (multiple users, processes, protection domains) is unnecessary overhead. Strip it out, link only the OS components you actually need, and deploy a ~1-10MB binary that boots in under 10ms with a minimal attack surface.
Prerequisites
- Virtual machine architecture (hypervisor, guest OS, ring 0/3)
- Operating system structure (kernel vs userspace split)
- Library OS concept (from exokernel material,
04-exokernels.md) - Basic understanding of a typed functional language is helpful (for MirageOS)
- Container and VM isolation models
Core Concepts
The Fundamental Design
Traditional VM Stack Unikernel Stack
====================== ==================
+-------------------+ +-------------------+
| App (user space) | | App + libOS |
+-------------------+ | (single image, |
| OS (kernel+libs) | | single address |
+-------------------+ | space) |
| Hypervisor (Type1)| +-------------------+
+-------------------+ | Hypervisor (Type1)|
| Hardware | +-------------------+
+-------------------+ | Hardware |
+-------------------+
Traditional: Unikernel:
OS: 100-500 MB Image: 1-20 MB
Boot: 30-60 seconds Boot: 10-100 ms
Idle RAM: 50-200 MB Idle RAM: 4-64 MB
Processes: many Processes: 1 (application)
Users: many Users: none
Attack surface: full OS Attack surface: minimal
Memory Layout
Unikernel Memory Layout (MirageOS on Xen example)
===================================================
+--------------------------------------------------+
| 0x000000: Application code (OCaml compiled) |
| ... |
| Stack region (single stack, no per-thread) |
| Heap region (OCaml GC heap) |
| ... |
| Mini-OS stubs (Xen hypercall interface) |
| Network driver (mirage-net-xen library) |
| Block driver (mirage-block-xen library) |
| TCP/IP stack (mirage-tcpip library) |
| ... |
| [ENTIRE IMAGE IS SINGLE ADDRESS SPACE] |
| [NO KERNEL/USER SPLIT. NO RING 3/RING 0 SPLIT] |
+--------------------------------------------------+
| Xen Hypervisor (provides virtualized hardware) |
+--------------------------------------------------+
| Physical Hardware |
+--------------------------------------------------+
Total binary size: typically 2-15 MB for typical apps
Because there's no user/kernel boundary, there's no syscall overhead. An application function call to the "network send" operation is just a function call to a library. The actual hypervisor interaction (hypercall to Xen, or VirtIO notification to KVM) happens only when hardware I/O is needed.
MirageOS (OCaml, Cambridge 2013)
MirageOS is the canonical unikernel implementation, developed at Cambridge University by Anil Madhavapeddy and team. It uses OCaml as the primary language, which provides:
- Type safety: Memory safety without garbage collection overhead (mostly)
- Modular functor-based design: OS components are OCaml module functors — swappable based on target
- Cooperative threading via Lwt: Lightweight threads without OS scheduler involvement
(* MirageOS network stack application example *)
open Mirage
(* Define what this unikernel needs *)
let main =
let packages = [
package "mirage-protocols";
package "mirage-net";
package "tcpip";
] in
foreign ~packages "Dispatch.Main"
(console @-> stackv4v6 @-> job)
(* config.ml - compiled to specific target *)
let () =
let stack = generic_stackv4v6 default_network in
register "dispatch" [
main $ default_console $ stack
]
(* dispatch.ml - the actual application *)
module Main (C: Mirage_console.S) (S: Tcpip.Stack.V4V6) = struct
module TCP = S.TCP
let start _console stack =
(* Start TCP listener — no OS involvement, just library calls *)
TCP.listen (S.tcp stack) ~port:80 (fun flow ->
TCP.read flow >>= function
| Ok (`Data buf) ->
let response = "HTTP/1.1 200 OK\r\n\r\nHello" in
TCP.write flow (Cstruct.of_string response) >>= fun _ ->
TCP.close flow
| _ -> Lwt.return_unit
);
S.listen stack
end
The same OCaml code, with a different config.ml, compiles to:
- A Xen VM image (boots on Xen hypervisor)
- A KVM/QEMU image (boots on Linux KVM)
- A Unix process (for development, runs on Linux)
HalVM (Haskell)
Galois Inc.'s HalVM (Haskell Lightweight Virtual Machine) applies the same concept to Haskell. Haskell's strong type system and purity guarantees provide additional assurance properties. HalVM targets Xen and is used for security research applications where type-level invariants can be proved.
IncludeOS (C++)
IncludeOS allows existing C++ applications to be compiled as unikernels. It provides: - A partial C++ standard library implementation - VirtIO/Xen drivers - A simple TCP/IP stack - POSIX-like API stubs
IncludeOS is notable for demonstrating that unikernels are not limited to functional languages — existing C++ codebases can be ported with varying effort.
// IncludeOS HTTP server (simplified)
#include <net/interfaces.hpp>
#include <net/http/server.hpp>
void Service::start() {
auto& inet = net::Interfaces::get(0);
inet.network_config(
{10, 0, 0, 42}, // IP
{255, 255, 255, 0}, // Mask
{10, 0, 0, 1} // Gateway
);
// Start HTTP server — no fork(), no accept() syscall
// Just library calls in single address space
auto server = std::make_shared<http::Server>(inet.tcp());
server->on_request([](http::Request_ptr req, http::Response_writer_ptr rw) {
rw->write_header(http::OK);
rw->write("Hello from unikernel\r\n");
rw->end();
});
server->listen(8080);
}
Unikraft (Modular Unikernel Framework)
Unikraft (2021, NEC Research + University of Manchester) addresses a major unikernel limitation: lack of POSIX compatibility. Unikraft provides: - A microlibrary architecture: each OS component is a minimal, configurable library - POSIX compatibility layer: existing applications can run with minimal source modification - Multiple platform targets: Xen, KVM, bare metal - Automated build system: select only required components
Unikraft Component Selection
==============================
Application needs:
- TCP server
- File I/O (in-memory)
- JSON parsing
Unikraft selects:
[x] lib/uknetdev (network device abstraction)
[x] lib/lwip (TCP/IP stack)
[x] lib/ramfs (in-memory filesystem)
[x] lib/posix-socket (POSIX socket API)
[x] lib/posix-stdio (printf, etc.)
[ ] lib/ext4fs (NOT included — not needed)
[ ] lib/9pfs (NOT included — not needed)
[ ] lib/vfscore (NOT included — not needed)
Result: ~5 MB image vs. ~100 MB Ubuntu minimal
Unikernel vs Container vs VM Comparison
Comparison: Unikernel vs Container vs VM
==========================================
Property | VM (Ubuntu) | Container | Unikernel
------------------|-------------|------------|----------
Image size | 500MB-2GB | 10-200MB | 1-20MB
Boot time | 30-60s | 100-500ms | 10-100ms
Idle memory | 100-300MB | 10-50MB | 4-64MB
Isolation level | Strong (HW) | Weak (NS) | Strong (HW)
Attack surface | Full OS | Full OS | Minimal
POSIX compat | Full | Full | Partial (Unikraft: good)
Processes/users | Many | Container | None
Live debugging | Yes | Yes | Very Hard
Language freedom | Any | Any | Limited*
Shared kernel | No | Yes (host) | No
Per-app kernel | No | No | Yes (LibOS)
Production maturity| Very High | Very High | Low-Medium
*MirageOS: OCaml; HalVM: Haskell; IncludeOS/Unikraft: C/C++;
Unikraft adds limited Python, Go, Lua support
Historical Context
Research Lineage
The unikernel concept descends directly from: 1. Exokernel LibOS (MIT 1995): OS as library in application address space 2. Nemesis (Cambridge 1997): Single-process OS for multimedia, motivated latency guarantees 3. Singularity (Microsoft Research 2003-2012): .NET CLR as OS — type-safe single address space 4. MirageOS (Cambridge 2013): First practical unikernel for cloud deployment
The Microsoft Singularity project is particularly relevant — it demonstrated that a type-safe language (rather than hardware protection rings) could enforce OS isolation guarantees. A process-isolated Singularity program couldn't corrupt another process's memory because the language's type system guaranteed it, not the CPU's ring protection.
Cloud Timing
MirageOS appeared in 2013, coinciding with the explosion of cloud microservices and serverless computing. The unikernel's properties (tiny, fast boot, minimal overhead) aligned perfectly with the "function-as-a-service" deployment model where each function could be a separate unikernel image.
Anil Madhavapeddy's 2013 blog post and paper created significant buzz. Docker had just been released. The question was whether unikernels or containers would win for microservices.
Production Examples
Cloudius Systems / ScyllaDB
Cloudius Systems built OSv, a unikernel targeting JVM and general application compatibility. OSv includes a JVM without a host OS. ScyllaDB (a Cassandra-compatible NoSQL database) was originally developed for OSv before being ported to run on Linux. The ScyllaDB port retains unikernel-style architecture: user-space networking (DPDK), user-space I/O, custom memory management — a "unikernel philosophy" without requiring actual unikernel deployment.
Amazon Firecracker
AWS Firecracker (2018) is not a unikernel itself but is the hypervisor designed to host microVMs efficiently. Firecracker enables unikernel-like properties (fast boot, small memory footprint) for standard Linux guests: - Boot time: ~125ms for a minimal Linux guest - Memory overhead: ~5MB per microVM baseline - Deployed at: AWS Lambda, AWS Fargate (millions of instances)
The Firecracker team explicitly cited unikernel research as motivation for their approach to minimizing VM overhead.
NEC / Unikraft Production Use
NEC Research has deployed Unikraft-based services for internal applications. The combination of POSIX compatibility (allowing existing software to run) with unikernel isolation and boot speed makes Unikraft the most production-viable unikernel framework as of 2024.
Debugging Notes
Unikernel debugging is a known weak point of the architecture:
# MirageOS: run as Unix process for development debugging
mirage configure -t unix
mirage build
./_build/default/main.exe # runs as normal Unix process, debuggable with gdb
# Production Xen unikernel: limited to serial console output
xl console unikernel-domain
# KVM unikernel via QEMU: gdb stub support
qemu-system-x86_64 \
-kernel unikernel.elf \
-s -S # start paused, gdb server on port 1234
# then: gdb unikernel.elf -ex 'target remote :1234'
# Unikraft: GDB debugging (if compiled with debug symbols)
qemu-system-x86_64 -kernel app.elf -s -S -nographic
# gdb: target remote :1234
# break main
# continue
# Core issue: no /proc, no strace, no ptrace in production unikernel
# Debugging requires instrumentation at compile time
The standard debugging tools (strace, perf, gdb attach to running process) don't apply to unikernels. You must:
1. Debug in the Unix/development mode
2. Add extensive logging before deployment
3. Use hypervisor-level tracing (Xen's xenctx, QEMU's monitor)
Security Implications
Minimal Attack Surface
The compelling security argument for unikernels: if the image doesn't contain a shell, sshd, cron, or any other service, those cannot be exploited. A typical Linux OS contains hundreds of potentially exploitable services, configurations, and binaries. A unikernel contains exactly what was compiled in.
Attack Surface Comparison
==========================
Ubuntu 22.04 minimal server:
Processes running: ~30 (systemd, journald, sshd, cron, ...)
Syscalls exposed: ~400
Binaries in /usr/bin: ~1000
Third-party packages: varies
MirageOS unikernel:
"Processes" running: 1 (the application)
Syscalls: N/A (no syscall interface — single address space)
External attack paths: only open network ports
"Packages": exactly what was linked
No User/Kernel Separation: Security Risk
The single-address-space design is also a security risk: if the application is exploited (buffer overflow, use-after-free), the attacker has full memory access — there's no kernel space that's protected from a compromised user process. In a traditional OS, a compromised application still needs a local privilege escalation to reach kernel memory.
Mitigations: - Type-safe languages (OCaml, Haskell) prevent most memory safety bugs - No writable/executable memory (NX enforced at the only privilege level) - External isolation via hypervisor
No Dynamic Code Loading
Most unikernel frameworks don't support dynamic library loading (dlopen). This eliminates entire classes of attacks (DLL injection, LD_PRELOAD hijacking) but also limits application flexibility.
Performance Implications
Boot Time
| System | Cold Boot Time | Notes |
|---|---|---|
| EC2 t3.micro (Amazon Linux) | ~40 seconds | Full Linux boot |
| Docker container | ~100-500 ms | Linux already running |
| Firecracker microVM | ~125 ms | Minimal Linux kernel |
| MirageOS on Xen | ~10-30 ms | Unikernel boot |
| Unikraft on KVM | ~10-50 ms | Modular unikernel |
| IncludeOS on KVM | ~5-15 ms | Minimal unikernel |
For serverless-style functions that run for <1 second, the difference between 40s boot and 10ms boot is the difference between viable and impractical.
Runtime Performance
Network benchmark (HTTP GET, small response, MirageOS vs Linux): - MirageOS: ~50,000 req/s on 1 core - nginx on Linux (same hardware): ~100,000 req/s on 1 core
MirageOS underperforms nginx in raw throughput — the OCaml runtime and cooperative threading model have overhead. But the comparison with a full Linux OS is closer than expected, and for I/O-bound microservices, the difference may be acceptable given the security and size benefits.
Memory
A typical MirageOS unikernel using ~4MB baseline RAM vs. ~50MB for a minimal Ubuntu container running the equivalent process. For cloud billing, this can be significant at scale.
Failure Modes and Real Incidents
The Debugging Deficit in Production
A recurring theme in unikernel deployments: when something goes wrong in production, diagnosis is extremely difficult. There's no way to attach a debugger, no shell to exec into, no /proc to inspect. The only output is what was explicitly logged before deployment.
Teams that deployed MirageOS/IncludeOS for production services consistently report that the debugging deficit was the primary operational challenge, often outweighing the security and performance benefits.
Upgrade Complexity
Updating a unikernel requires recompiling and redeploying the entire image. There's no apt-get update equivalent. For security patches to library dependencies, the entire image must be rebuilt. This is theoretically solvable with CI/CD automation but requires more mature tooling than patching a running container.
Memory Safety in Non-Type-Safe Unikernels
IncludeOS and Unikraft use C/C++. A buffer overflow in application code has no kernel protection boundary to stop it. The single-address-space model means a compromised application owns the entire VM. This is arguably worse than running in a container where the container's kernel namespace at least limits the blast radius.
Modern Usage
Unikraft (2021+): Most production-viable. POSIX compatibility, active development, NEC/university backing.
OSv: Running JVM and containerized apps as unikernels on Xen/KVM. Used by some organizations for Java microservices.
MirageOS: Used for DNS-over-HTTPS resolvers (Cloudflare experimented with it), network function virtualization, and security research.
Nanos/Ops (2019): A Linux-compatible unikernel targeting running existing Linux binaries without modification. The most "drop-in" replacement approach.
Cloud Provider Support: AWS, GCP, and Azure do not have native unikernel support as of 2024. All require wrapping unikernels in hypervisors (Xen/KVM/Hyper-V). Firecracker brings the most unikernel-like properties to standard Lambda/Fargate.
Future Directions
WebAssembly as Unikernel Alternative: WASM with WASI (WebAssembly System Interface) provides many unikernel benefits — tiny, fast-starting, minimal attack surface — with better toolchain support and language compatibility. The debate about whether WASM/WASI replaces unikernels for cloud functions is active.
Unikernel Linux (UKL): Research at Boston University on running the Linux kernel in a unikernel configuration — single address space, compiled with the application, no user/kernel split. Maintains Linux's vast driver and API ecosystem while gaining unikernel boot time and overhead benefits.
CrosVM / Virtio-fs: Google's CrosVM (Chrome OS's VM monitor) and virtio-fs allow efficient host filesystem access from minimal VMs, addressing one of the unikernel deployment practicalities.
Exercises
-
MirageOS HTTP Server: Install MirageOS and OCaml. Build and run the
mirage-skeleton/applications/httpexample as a Unix process. Measure response latency vs. a standard nginx container. Then build the same code for Xen or KVM target and compare image sizes. -
Unikraft Hello-World: Build a minimal Unikraft application (HTTP server or key-value store) using the provided tutorials. Measure boot time and idle memory usage. Compare to the Docker equivalent.
-
Attack Surface Analysis: Take a standard Ubuntu 22.04 minimal cloud image and a Unikraft unikernel running the same application. Use
nmapandstraceto enumerate the attack surface of each. Count: open ports, running processes, exposed syscalls, writable binaries. Quantify the difference. -
Debugging a Production Failure: Introduce a deliberate bug in a MirageOS application (e.g., incorrect HTTP response handling for a specific content type). Debug it first in Unix mode (where debuggers work), then reproduce the failure in a KVM target where only serial output is available. Document the debugging methodology difference.
-
Performance Profiling: Write a unikernel (Unikraft or IncludeOS) that performs a tight compute loop and a I/O-intensive workload. Profile using hypervisor-level performance counters (QEMU's
perfintegration). Compare the cycle profile to the same workload in a Linux process. Identify where unikernel overhead appears.
References
- Madhavapeddy, A., et al. "Unikernels: Library Operating Systems for the Cloud." ASPLOS '13. 2013. [The founding paper]
- Madhavapeddy, A. and Scott, D. "Unikernels: Rise of the Virtual Library Operating System." Queue, ACM, 2013.
- Kuenzer, S., et al. "Unikraft: Fast, Specialized Unikernels the Easy Way." EuroSys '21. 2021.
- Engler, D., et al. "Exokernel: An OS Architecture for Application-Level Resource Management." SOSP '95. 1995. [LibOS foundation]
- Hunt, G. and Larus, J. "Singularity: Rethinking the Software Stack." ACM SIGOPS Operating Systems Review, 2007.
- MirageOS documentation: https://mirage.io/docs/
- Unikraft documentation: https://unikraft.org/docs/
- OSv project: https://github.com/cloudius-systems/osv
- Nanos/Ops: https://nanos.org/
- Firecracker design: https://github.com/firecracker-microvm/firecracker/blob/main/docs/design.md