Skip to content

Build a unikernel

A unikernel links one application with only the operating-system libraries it needs. There is no general-purpose guest OS inside the service image. Compared with a Linux service, this usually means a smaller boot path, faster startup, and a narrower isolation boundary.

The Platform supports two unikernel families:

  • MirageOS for OCaml services where memory safety and a small trusted computing base (less code that has to be correct for the system to be secure) are the main design goals.
  • Unikraft for C, C++, Rust, and POSIX-oriented services closer to an existing Linux application.

You do not need a local OCaml, mirage, or kraft toolchain, and no Docker daemon is involved: space build runs the matching toolchain inside the disposable builder VM.

Prerequisites

  • A local SpaceOS environment from the quickstart.

Describe the app

An app.yaml declares the kind and pins the toolchain. MirageOS:

schema: space.app
version: "1"
name: my-service
source: .
kind: mirage
mirage_version: "4.9.0"
ocaml_version: "5.4.1"
opam_repo: https://github.com/ocaml/opam-repository.git
opam_repo_rev: "<full-git-commit>"
targets:
- platform: hvt
arch: arm64
policy:
network: replay
reproducible: true
outputs:
flight:
platform: hvt
path: dist/my-service.hvt
kind: unikernel

Unikraft differs only in the toolchain fields:

schema: space.app
version: "1"
name: my-service
source: .
kind: unikraft
kraftkit_image: kraftkit.sh/base@sha256:<digest>
targets:
- platform: qemu
arch: arm64
policy:
network: replay
reproducible: true
outputs:
flight:
platform: qemu
path: .unikraft/build/app_qemu
kind: unikernel

Build it:

Terminal window
space build app.yaml

space build generates a pinned build, runs it in the builder VM, and emits an OCI artifact plus an artifact manifest. The output key (flight) is architecture-independent; the manifest carries the per-architecture variants.

Run it directly

A single unikernel image needs no manifests at all. space run IMAGE recognizes an image whose root filesystem is a single .hvt or .spt binary and boots it under the matching Solo5 tender, the small host program that sandboxes and runs a unikernel:

Terminal window
space run my-service:dev --headless

Compose it into a system

For a multi-service system, reference the artifact from build.yaml:

schema: space.build
version: "1"
kernel: linuxkit/kernel:6.6.13
output: _build/spaceos
artifacts:
my-service:
manifest: _build/oci/my-service/artifacts.yaml
partitions:
payload:
init: pid1
services:
- name: my-service
artifact: my-service#flight

An artifact reference derives the runtime and variants from the manifest, so no runtime field is needed. Then place it in run.yaml:

schema: space.run
version: "1"
image: _build/spaceos
partitions:
payload:
memory: 64
apids: [0x010, 0x01F]
send_to: [ground]
services:
- name: my-service
isolation: hvt
cpus: [0]
relay:
from: payload
to: [ground]
ground:
port: 8080

Use isolation: hvt where the target provides KVM hardware virtualization, and spt where it does not. Supported isolation backends vary by target. Build and run the composition:

Terminal window
space build . -t my-service:dev
space run my-service:dev --runtime run.yaml

Packaging by hand

The OCI image is a distribution format, not a build requirement. If you already build unikernels with your own toolchain, wrap the binary yourself and any standard OCI client can push it:

FROM scratch
COPY my-service.hvt /

An image with that shape is detected and launched as a unikernel exactly like an image built by space build.

Deploy

Build a release-signed image and target a connected board:

Terminal window
space build . -t my-service:release --release --signing-profile release
space deploy my-service:release --target cm5-demo --plan
space deploy my-service:release --target cm5-demo

Linux-container use cases

Use the Linux service path when your workload depends on a broad Linux userland, runtime-loaded plugins, kernel-specific features, or a dependency tree that is not worth porting.