Skip to main content

Serve a Static Website as a Unikernel

· 6 min read
Brian Graham
Consultant
What are Unikernels and Rump Kernels?

Unikernels are specialized, single-purpose operating systems that package your application with only the minimal OS components it needs to run. Think of them as extremely lightweight VMs that boot in milliseconds and use a fraction of traditional server resources.

Rump Kernels take a unique approach: they let you extract specific drivers and components from existing operating systems (like NetBSD) and run them in userspace or as unikernels. This means you can use proven, mature OS components without the overhead of a full operating system. Your application gets direct access to high-performance networking, file systems, and other kernel services while maintaining strong isolation.

The result is applications that start faster, use less memory, have smaller attack surfaces, and can be deployed more efficiently than traditional containerized or VM-based approaches.

You'll learn how to use the Rumprun toolchain to build a unikernel that hosts your static website served by Nginx. This tutorial focuses on giving application and web developers practical steps to get running with Rump Kernel-based unikernels.

This assumes you're on a GNU/Linux or BSD system and familiar with command line basics.

What We'll Build​

We'll create a complete unikernel-based web server by:

  1. Setting up the Rumprun toolchain for building Rump Kernels
  2. Compiling Nginx to run as a unikernel on QEMU
  3. Configuring networking so you can access your site
  4. Adding your static content to the unikernel
  5. Setting up HTTPS support

This is experimental technology, so some troubleshooting may be required. We'll explain the tools in enough detail that you can debug issues if they arise.

1. Setting Up Rumprun​

The Rumprun repository provides everything needed to develop unikernels based on Rump Kernel: debugging scripts, compiler tools, and NetBSD drivers as dependencies.

First, download the repository and its dependencies:

git clone https://github.com/rumpkernel/rumprun
cd rumprun
git submodule update --init

You'll need GCC installed (Rumprun doesn't yet support Clang). The exact package varies by distribution.

Choose your target platform. We'll use "hw" (hardware) rather than "xen" because it's easier to get started with. Even though we're building for hardware, we can test using virtual machines.

Build the toolchain:

./build-rr.sh hw

If this fails, you may need to install binutils on your system.

This compilation takes time as you're building both C and C++ components. When complete (it will say "build-rr ran successfully"), add the tools to your PATH:

export PATH=${PATH}:$(pwd)/rumprun/bin

Note: This PATH assignment is temporary. You'll lose it when you close the terminal or open new windows.

2. Building Nginx as a Unikernel​

Now we'll build a Nginx unikernel using an existing Rump package:

git clone https://github.com/rumpkernel/rumprun-packages
cd rumprun-packages/

Configure the package builder to use your Rumprun toolchain. Copy the configuration template and update it:

cp config.mk.dist config.mk

Edit config.mk and set:

RUMPRUN_TOOLCHAIN_TUPLE=x86_64-rumprun-netbsd

(Don't add -gcc or other suffixes)

Now build Nginx:

cd nginx
make

You'll need genisoimage installed (often in a package called cdrkit) to build ISO files for bare metal deployment.

After the build completes, "bake" the unikernel. First, see available platforms:

rumprun-bake list

Use hw_virtio for cloud deployment. Bake your unikernel:

rumprun-bake hw_virtio ./nginx.bin bin/nginx

Test the basic functionality:

rumprun qemu -M 128 -i \
-b images/data.iso,/data \
-- nginx.bin -c /data/conf/nginx.conf

You should see QEMU launch with debugging text. Success means no "panic" messages (though "rumprun: call to sigaction ignored" is normal). Rump Kernel has a different understanding of signals than you would expect on an operating system.

QEMU running nginx unikernel

3. Setting Up Network Access​

To access your web server, we'll create a TAP network device. This provides more flexibility than simple port forwarding for complex applications.

Create and configure the TAP device (may require sudo):

ip tuntap add tap0 mode tap
ip addr add 10.0.120.100/24 dev tap0
ip link set dev tap0 up

Choose a network range that doesn't conflict with your existing network. Verify the device works:

ping 10.0.120.100

Now launch Nginx with network access:

rumprun qemu -i -M 128 \
-I if,vioif,'-net tap,script=no,ifname=tap0'\
-W if,inet,static,10.0.120.101/24 \
-b images/data.iso,/data \
-- ./nginx.bin -c /data/conf/nginx.conf

This command:

  • Runs QEMU as the emulator
  • (-i) Attaches guest console on startup
  • (-M) Sets memory limit to 128MB
  • (-I) Creates guest network interface with iftag
  • (-W) Configures VM network address (different from TAP interface)
  • (-b) Mounts data.iso as block device on /data
  • (--) Starts nginx with specified config

If successful, you can visit http://10.0.120.101 in your browser to see the default page.

Nginx running in browser

4. Adding Your Static Content​

Your website files go in ./images/data/www and Nginx configuration in ./images/data/conf. Simply replace the www directory contents with your static site.

For static site generation, consider tools like Jekyll, Hugo, or Sculpin.

To see new content:

  1. Stop the running unikernel
  2. Rerun make in the nginx directory
  3. Re-bake with rumprun-bake
  4. Launch with rumprun qemu

Important: Don't modify the filesystem image while the guest is running. Use different image names when building new versions.

If you have connection issues, verify your tap0 device hasn't lost its IP address or gone down when you killed the emulator.

5. Adding HTTPS Support​

Configure SSL in your nginx.conf under the server { section:

listen 443 ssl;
listen [::]:443 ssl;
ssl_certificate /data/conf/ssl-cert.crt;
ssl_certificate_key /data/conf/ssl-cert.key;

Generate a self-signed certificate or use your production certificates. Place the certificate files in the conf directory.

After updating the configuration:

  1. Remove build artifacts in ./build/
  2. Run make
  3. Re-bake with rumprun-bake
  4. Launch with rumprun qemu

Your site will now be available over HTTPS.

6. What You've Accomplished​

You now have the fundamentals to host unikernel applications based on Rump Kernel. This approach offers several advantages:

  • Fast scaling: Unikernels boot in milliseconds, enabling rapid scale-up and scale-down
  • Resource efficiency: Minimal memory and CPU overhead compared to traditional VMs
  • Security: Reduced attack surface with only necessary OS components
  • Deployment flexibility: Can run on cloud hypervisors or directly on hardware

You can build on these basics to create more interactive applications, run PHP applications in immutable images, or deploy applications directly on hardware without a traditional operating system.

Resources​

Lost Blog Post

I previously wrote a blog post explaining how to host Symfony 2 PHP applications on a Rump Kernel, but I've lost the original content. If anyone has a copy or link to this post, please send it my way! It covered more advanced topics around running dynamic web applications as unikernels.