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
- The Space CLI, installed as the documentation home shows.
Describe the app
An app.yaml declares the kind of app and pins its
toolchain. For MirageOS:
schema: space.appversion: "1"name: my-servicesource: .kind: mirage
mirage_version: "4.9.0"ocaml_version: "5.4.1"opam_repo: https://github.com/ocaml/opam-repository.gitopam_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: unikernelplatform: 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:
space build app.yamlspace 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:
space run my-service:dev --headlessCompose it into a system
A project places the artifact from space.yaml:
schema: space.projectversion: "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: 8080An 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: pid1names 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: 64is 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:
$ 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 0isolation: 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:
space build . -t my-service:devspace run my-service:devPackage 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 scratchCOPY 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:
space build . -t my-service:release --release --signing-key release \ --rollback-index 1space deploy my-service:release --target cm5-demo --planspace deploy my-service:release --target cm5-demo