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.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
policy: network: replay reproducible: true
outputs: flight: target: hvt-arm64 path: dist/my-service.hvt kind: unikernelUnikraft differs only in the toolchain fields:
schema: space.appversion: "1"name: my-servicesource: .kind: unikraftkraftkit_image: kraftkit.sh/base@sha256:<digest>targets: - platform: qemu arch: arm64policy: network: replay reproducible: trueoutputs: flight: target: qemu-arm64 path: .unikraft/build/app_qemu kind: unikernelBuild it:
space build app.yamlspace 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:
space run my-service:dev --headlessCompose it into a system
A multi-service system references the artifact from build.yaml:
schema: space.buildversion: "1"kernel: linuxkit/kernel:6.6.13output: _build/spaceos
artifacts: my-service: manifest: _build/oci/my-service/artifacts.yaml
partitions: payload: init: pid1 services: - name: my-service artifact: my-service#flightAn artifact reference takes the runtime and variants from the manifest, so
you write no runtime field. Then place it in run.yaml:
schema: space.runversion: "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: 8080Neither file needs a build to be checked. space sbom decodes the composition
and space run --dry-run resolves the placement:
$ 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 0Use 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:
space build . -t my-service:devspace run my-service:dev --runtime run.yamlPackaging 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 scratchCOPY 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:
$ space keys generate releaseGenerated 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:
space build . -t my-service:release --release --signing-profile release \ --rollback-index 1space deploy my-service:release --target cm5-demo --planspace deploy my-service:release --target cm5-demoLinux-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.