build.yaml reference
build.yaml declares a target-agnostic system composition:
partitions, their init processes, services, storage, and service interfaces.
space build assembles that composition, signs it, and writes artifacts under
output. It also registers a local OCI index that space run and
space deploy resolve later.
build.yaml holds no board configuration, and space build produces no
per-board bootable image. space run assembles that image for local
execution. space deploy assembles it for a connected target from the board
kernel, device tree, and firmware in the target’s profile.
Example
schema: space.buildversion: "1"kernel: linuxkit/kernel:6.6.13output: _build/spaceos
partitions: payload: init: pid1 disk: true services: - name: eo-classifier image: ghcr.io/<your-org>/eo-classifier:latest runtime: oci isolation: runc interfaces: - type: ip bridges: [payload-net] healthcheck: type: command argv: [/usr/local/bin/healthcheck] - name: sdls-downlink image: ghcr.io/parsimoni-labs/sdls-downlink:latest runtime: oci isolation: runc depends_on: [eo-classifier]
storage: blocks: 256 params: - id: 1 value: boot_count=0 - id: 2 value: mode=nominalThis example spells out several members that would otherwise default, so that
one document shows them together. kernel is the clearest case: a composition
boots the product’s own kernel unless it names another, and the line above is
that override.
space sbom reads a composition without building it, so it is the shortest
way to check that a build.yaml resolves. The header names the kernel each
partition boots and the init the supervisor runs:
$ space sbom build.yaml | sed -n '1,4p'SpaceOS Software Bill of Materials
kernel linuxkit/kernel:6.6.13 payload pid1The package inventory follows, one row per OCI layer and OCaml package the
composition carries. A member the schema does not declare is refused by
space sbom, naming the member.
Fields
| Field | Type | Required | Description |
|---|---|---|---|
schema | string | yes | Must be space.build. The CLI rejects a wrong-schema document with a clear error. |
version | string | yes | Manifest schema version. Currently "1". |
kernel | OCI ref or path | no (default: the product’s own kernel, pinned by digest) | Partition kernel for local and dev-VM execution. Name one only to boot a kernel of your own. A deploy target’s board kernel is a target fact recorded with space target connect, not this field. |
output | path | no (default: _build/spaceos) | Directory where build artifacts are written. |
artifacts | map | no | Imported app artifact manifests, each with a manifest path and optional digest. A service references one imported output with artifact: <name>#<output>. |
target_profile | catalog name or NAME@sha256:DIGEST | no | Target profile this composition is assembled against. The build copies its network section into the launch plan. A digest pins the exact profile; a bare name resolves through the target catalog. |
partitions | map | yes | The partitions this composition boots, keyed by name. |
partitions.<name>.init | string | no (default: pid1) | Partition init / supervisor. It starts services, mediates frame-bus access, and reports lifecycle and health. Override only when a workload needs custom supervision. |
partitions.<name>.kernel | OCI ref or path | no | Per-partition kernel override. When unset, the partition inherits the top-level kernel. |
partitions.<name>.disk | boolean | no (default: enabled when the partition has services) | Persistent block storage for this partition, backed by the storage section. |
partitions.<name>.files[] | list | no | Local files or directories embedded into the partition initramfs. Each entry has source and image-root path. |
partitions.<name>.services[] | list | no | Workloads managed by the partition supervisor. |
storage.blocks | int | no (default: 256) | Persistent storage, counted in 512-byte blocks. The partition is rounded up to a whole mebibyte and is never smaller than one, so the default asks for 128 KiB and gets 1 MiB. |
storage.params[] | list | no | Parameter store entries that persist across reboots. |
storage.ab_slots | boolean | no (default: false) | WIP It is meant to reserve storage for a second bootable system release, so an update can stage the inactive slot. The decoder accepts it and the composition records it; image assembly does not bake a second slot today, and on-node updates now trial a release through a content store. |
Service entry
Each item under partitions.<name>.services[]:
| Field | Type | Required | Description |
|---|---|---|---|
name | string | yes | Unique identifier within the partition. Cross-referenced by run.yaml to attach per-service runtime configuration. |
image | OCI ref or Dockerfile path | conditional | A prebuilt service image, or a local Dockerfile (or a directory holding one) that the build resolves under the project root and builds for you. Use this or artifact, not both. |
artifact | <manifest>#<output> | conditional | Reference to an imported app artifact output. Use this or image, not both. |
runtime | oci, mirage, unikraft, host, or auto | required with image | Service payload runtime. Artifact references derive this from the artifact manifest. |
isolation | auto, runc, spt, hvt, qemu, host, or firecracker | no (default: auto) | Build-side isolation default; auto resolves from the runtime (runc for oci, host for host). run.yaml may choose a concrete placement. |
cpus | list of int | no | Default CPU set. run.yaml placement can override. |
memory_mb | int | no (default: 128) | Default memory cap for the service. |
interfaces[] | list | no | Typed service interfaces: type: ip with bridges; type: frame_bus with apids and send_to; type: host_network with bind_tcp; type: data_link with link, mbps and mode; type: supervision_page. See service interfaces. |
bridges | list of string | no | Shorthand for interfaces: [{type: ip, bridges: [...]}]. A bridge is a named virtual network segment. |
lifecycle | process, bgprocess, scripted, or internal | no (default: process) | How the supervisor runs the service: long-running, self-daemonizing, one-shot, or a grouping node. |
environment | map of string | no | Process environment for the service. Entries override the ones the OCI image declares in its own Config.Env. |
config | map of string | no | Runtime configuration read by the supervisor, not passed to the process. The space. prefix belongs to the product: it carries the entrypoint, working directory, data paths, credential bindings, and settings the runner lowers into a launch. |
depends_on | list of string | no | Service names that must launch before this one. Launch-order only: this is not a health gate. Circular dependencies are rejected at build time. |
healthcheck | map | no | App-declared liveness check the runner evaluates from outside the guest. Omit for services without a health requirement. |
Service interfaces
interfaces[] is a list of tagged objects. The type field selects the arm,
and there are five:
| Type | Fields | Grants |
|---|---|---|
ip | bridges | Addresses on each named bridge, and connections to the peers the signed release lists. |
frame_bus | apids, send_to | A tenant socket on the frame bus, the APID range the service owns, and the peer services it may send frames to. ground is reserved and refused here: ground is reached through relay in run.yaml. |
host_network | bind_tcp | TCP ports published from the node into this service. It changes the container’s network isolation, so a service may declare it once, only under runc, and never alongside an ip arm. |
data_link | link, mbps, mode | Attachment to one named high-throughput link, admitted against the links the signed release grants. mbps is the reserved rate; mode is stream or rmap. All three are required. |
supervision_page | none | The shared page carrying heartbeat, mission time, health, and the safe-mode word. |
bridges at the top of a service entry is shorthand for a single ip arm.
Healthcheck
healthcheck is a tagged object. The type field selects the probe:
| Type | Required fields | Description |
|---|---|---|
tcp | port | Healthy when the guest accepts a TCP connection on the port. |
http | port, optional path (default /) | Healthy when an HTTP GET returns a 2xx response. |
command | argv | Healthy when the command exits successfully. argv must not be empty. |
All probe types also accept interval (default 10 s), timeout (5 s),
retries (3), and start_period (0 s).
Parameter entry
Each item under storage.params[]:
| Field | Type | Required | Description |
|---|---|---|---|
id | int | yes | Integer identifier. Must be unique within storage.params. |
value | string | yes | String in key=value format. The supervisor reads this as the wire-format representation; parsing is the workload’s responsibility. |
Partition supervisor
Each partition runs a single supervisor process as PID 1 (the init field,
pid1 by default; you do not need to declare it). Its responsibilities:
- Start the partition’s services in order, honoring
depends_on. - Mediate each service’s access to the frame bus.
- Surface the declared per-service healthchecks.
- Own the guest half of the supervision page.
Set init to a custom binary only when a workload brings its own supervision.
Custom supervisors must implement the partition supervisor contract.
Build output
space build writes the composition artifacts and an SBOM to the output
directory, then registers the composition in the local OCI store under the
tag passed with -t:
_build/spaceos/ kernel/ # resolved partition kernels initramfs/ # partition initramfs images disks/ # rootfs / storage artifacts spaceos.spdx.json # software bill of materials (SPDX)Use space sbom <build.yaml> to inspect or write SBOM data. Every build also
appends one provenance row to the host build record, giving the tag, image
digest, source, completion time, status, and signing fingerprint; a failed
build is recorded there too.
Service image sources
Every service is an OCI image. There are two ways to produce one:
- An app manifest. Write an
app.yaml(kind: mirage,unikraft, orhost) and runspace build app.yaml. The build is pinned and runs in the disposable builder VM. It writes the OCI artifact and an artifact manifest thatbuild.yamlimports withartifact:. The build needs no local toolchain and no Docker daemon. - Any OCI image. A service
image:is a plain OCI reference. An image built and pushed with standard tools (docker build,buildah) works. A unikernel binary packaged asFROM scratchplus the binary is a valid unikernel service image.
While assembling the system image, space build detects each service’s
runtime, records it in the image annotations, and signs the composition.
Multi-architecture images
A service image may be a standard OCI image index carrying more than one architecture. Build one with standard tooling:
docker buildx build \ --platform linux/amd64,linux/arm64 \ -t ghcr.io/<your-org>/cloud-mask:latest \ --push \ .One space build assembles a composition for one architecture. Run it once per
architecture you intend to ship, and publish each result under its own tag.
Notes
- Partition names (e.g.,
bus-relay,payload) are free-form identifiers. They must match betweenbuild.yamlandrun.yaml. - OCI images are pulled once and cached locally. When you need a fresh artifact, use an immutable digest or publish a new tag, then rebuild the composition.
- The
kernelfield accepts two formats: an OCI image reference (pulled from a registry) or a local file path. Values starting with./or/are always local paths; other values containing/or:are OCI references; a bare filename is a local path.