Skip to content
No end-to-end test boots a workload under a Solo5 tender, so the run steps on this page are unexercised. Port a Linux app is the path every test covers.

Build a unikernel

Build a MirageOS or Unikraft service with space build and place it in a SpaceOS image. A unikernel links your service with only the operating-system code it uses and runs in its own hardware virtual machine: a guest of KVM, the Linux kernel module that exposes the processor's virtualization extensions through /dev/kvm. The library operating systems chapter compares it with a Linux service.

Pick the family that matches your source:

  • MirageOS for OCaml services, where memory safety and a small trusted computing base are the main goals. The trusted computing base is the code that must be correct for the system to be secure.
  • Unikraft for C, C++, Rust, and POSIX services closer to an existing Linux application.

space build runs the matching toolchain inside the builder VM, so your computer needs no OCaml, mirage, or kraft install and no Docker daemon.

Prerequisites

Describe the app

An app.yaml declares the kind of app and pins its toolchain. For 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
image: ocaml/opam:debian-ocaml-5.4
policy:
network: replay
reproducible: true
outputs:
flight:
target: hvt-arm64
path: dist/my-service.hvt
kind: unikernel

platform: hvt builds for Solo5's hvt tender, which runs the unikernel as a KVM guest; spt would run it instead as an ordinary Linux process that seccomp restricts to the few system calls Solo5 needs. image names the toolchain image the build runs in. network: replay builds with networking off, from inputs already in the build caches; today it behaves exactly as none. reproducible: true asks for the builder's reproducibility check, and the build policy says which builds run it.

A Unikraft app sets kind: unikraft, names a kraftkit_image, and targets the qemu platform. The app.yaml reference has the full example. No shipped target profile declares the qemu backend, so start from MirageOS.

Build the app:

Terminal window
space build app.yaml

space build pins the toolchain and runs the build in the builder VM, the disposable Linux VM the Space CLI starts for builds. It stores the unikernel binary as an OCI image, the Open Container Initiative format Docker uses: a JSON manifest that lists content-addressed layer archives. It also writes an artifact manifest, artifacts.yaml, that names each declared output. The output key, flight, does not depend on the architecture; the artifact manifest lists one variant per architecture.

Run the image directly

A single unikernel needs no project file. space run IMAGE recognizes an image whose root filesystem is one .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

A project places the artifact from space.yaml:

schema: space.project
version: "1"
build:
output: _build/spaceos
artifacts:
my-service:
manifest: _build/oci/my-service/artifacts.yaml
run:
partitions:
payload:
init: pid1
memory: 64
apids: [0x010, 0x01F]
services:
- name: my-service
artifact: my-service#flight
isolation: hvt
cpus: [0]
relay:
from: payload
to: [ground]
ground:
port: 8080

An artifact reference takes the runtime and the variants from the manifest, so the service has no runtime field. The partition fields read as follows:

  • init: pid1 names the partition supervisor, the first process in the partition's machine. It starts the services in dependency order, carries their frame-bus traffic, and reports their health.
  • apids: [0x010, 0x01F] is the inclusive range of APIDs the partition owns. An APID is the 11-bit Application Process Identifier in a CCSDS packet header that labels which data stream a message belongs to, and the partition's services send only from APIDs in this range.
  • memory: 64 is the partition's memory in MB.

Check the file before you build. space show --sbom decodes the project, and space run --dry-run resolves the placement:

Terminal window
$ space show --sbom | sed -n '1p;4p'
No signed SBOM at _build/spaceos/spaceos.spdx.json yet; printing the SBOM computed from space.yaml
kernel ghcr.io/parsimoni-labs/uniboot-kernel/node:6.18.39-62332cf6bcc6@sha256:21e9b2669e2347dd0a9b7703a248bf191ef91e412ae6b720f3d3768f6cfbbd8f
$ space run --dry-run | sed -n '4,7p'
partitions:
payload 64 MB apids 0x010, 0x01f
services:
my-service hvt cpus 0

isolation: hvt needs KVM on the target, and a target admits only the isolation backends its profile declares. The target reference lists which profiles declare hvt; the Raspberry Pi CM5 profiles do.

Build and run the project:

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

Package a unikernel you built yourself

The OCI image is a distribution format. If you build unikernels with your own toolchain, wrap the binary in an image and push it with any OCI client:

FROM scratch
COPY my-service.hvt /

SpaceOS launches an image of that shape as a unikernel, the same way it launches one built by space build.

Deploy

A release build names a release signing identity with --signing-key, and Port a Linux app shows how to generate one and connect a target. It also sets --rollback-index, the release's security version: a target refuses a release whose index is below the highest it has already admitted. Index 0 is reserved for development builds, so a --release build needs 1 or more. Then build and deploy:

Terminal window
space build . -t my-service:release --release --signing-key release \
--rollback-index 1
space deploy my-service:release --target cm5-demo --plan
space deploy my-service:release --target cm5-demo