Skip to content
No end-to-end test boots a workload under either Solo5 tender, so the launch steps here are unexercised; Port a Linux app is the container path every scenario covers.

Build a unikernel

Build a MirageOS or Unikraft service with space build and run it on SpaceOS. The library operating systems chapter covers what a unikernel is and how it differs from a Linux service.

Pick the family that matches your source:

  • 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.

space build runs the matching toolchain inside the disposable builder VM. Your workstation needs no OCaml, mirage, or kraft install, and no Docker daemon.

Prerequisites

  • The Space CLI on your computer.

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:
target: hvt-arm64
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:
target: qemu-arm64
path: .unikraft/build/app_qemu
kind: unikernel

Build it:

Terminal window
space build app.yaml

space build pins the build and runs it in the builder VM. It writes an OCI artifact with its manifest. The output key (flight) is architecture-independent, and the manifest carries the per-architecture variants.

Run it directly

A single unikernel image needs no manifests. space run IMAGE spots 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. This launch path is implemented and no end-to-end test covers it:

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

Compose it into a system

A multi-service system references 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 takes the runtime and variants from the manifest, so you write no runtime field. Then place it in run.yaml:

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

Neither file needs a build to be checked. space sbom decodes the composition and space run --dry-run resolves the placement:

Terminal window
$ space sbom build.yaml | sed -n '1,4p'
SpaceOS Software Bill of Materials
kernel linuxkit/kernel:6.6.13
payload pid1
$ space run _build/spaceos --runtime run.yaml --dry-run | sed -n '4,7p'
partitions:
payload 64 MB apids 0x010, 0x01f
services:
my-service hvt cpus 0

Use isolation: hvt where the target has KVM hardware virtualization, and spt where it does not. The isolation backends a target supports vary by board, and a target admits only what its profile declares. Of the five shipped profiles, cm5 and cm5-dev declare hvt and runc, and the other three declare runc alone. No shipped profile declares spt, so isolation: spt resolves against no target today. 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. If you already build unikernels with your own toolchain, wrap the binary yourself and push it with any standard OCI client:

FROM scratch
COPY my-service.hvt /

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

Deploy

Build a release-signed image and target a connected board. --release names a release identity that already exists; it refuses the development identity and refuses to create the profile you name, so generate one before the build if you have none:

Terminal window
$ space keys generate release
Generated signing identity release
ed25519-key: /tmp/space-docs/config/keys/release.ed25519.pem
mldsa65-key: /tmp/space-docs/config/keys/release.mldsa65.pem
fingerprint: sha256:...

Then build against that identity and deploy:

Terminal window
space build . -t my-service:release --release --signing-profile release \
--rollback-index 1
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 needs any of these:

  • A broad Linux userland or a large dependency tree.
  • Plugins loaded at runtime.
  • Kernel-specific features.

Port a Linux app has the steps.