space.yaml reference
space.yaml is the one file you write beside your Dockerfile. It declares where
the built image lands, which partitions the system boots, what runs inside each
one, and how frames reach the ground. space build reads it when you name no
source, space run boots what it describes, and space show --sbom lists what it
contains without building anything.
The tables below list every member the space.project schema decodes, with its
default. A member the schema does not declare is refused by name, so a mistyped
field fails the load. A row with a WIP badge is a member the decoder accepts
and the rest of the product does not act on, and the row says what does not
happen.
The file has two sections. build: says where the image lands, which artifact
manifests it imports, and what disk geometry it is baked to. run: describes
the application itself: its partitions, the services inside them, and the frame
topology that carries their output. Everything that differs between machines,
such as the board, the keys, and how the machine is reached, belongs to the
target and is written in targets/NAME.yaml. One space.yaml therefore runs on
every target, and each target completes it from its own profile.
Example
schema: space.projectversion: "1"
build: output: _build/spaceos storage: blocks: 256 params: - id: 1 value: boot_count=0 - id: 2 value: mode=nominal
run: partitions: bus-relay: memory: 128 apids: [0x001, 0x00F] payload: init: pid1 disk: true memory: 256 apids: [0x010, 0x01F] services: - name: eo-classifier image: ghcr.io/<your-org>/eo-classifier:latest runtime: oci isolation: runc cpus: [0, 1] interfaces: - type: ip bridges: [payload-net] - type: frame_bus apids: [0x010, 0x01F] send_to: [sdls-downlink] 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] relay: from: bus-relay to: [payload, ground] ground: port: 8080 socket: /tmp/spaceos/ground.sockThis example spells out several members that would otherwise default, so that one document shows them together.
bus-relay declares no services. It owns the APID range 0x001 to 0x00F and is
the relay's source, run.relay.from, so its frames are the ones the relay
forwards to payload and to the ground. A partition that is not the relay's
source originates frames to no other partition and to no ground. payload
runs the classifier and the sdls-downlink service beside it, which the
classifier's send_to names.
space show --sbom reads the project without building it, so it is the shortest way to
check that a space.yaml resolves. The header names the kernel each partition
boots and the init the supervisor runs:
$ space show --sbom | sed -n '1,4p'No signed SBOM at _build/spaceos/spaceos.spdx.json yet; printing the SBOM computed from space.yamlSpaceOS Software Bill of Materials
kernel ghcr.io/parsimoni-labs/uniboot-kernel/node:6.18.39-62332cf6bcc6@sha256:21e9b2669e2347dd0a9b7703a248bf191ef91e412ae6b720f3d3768f6cfbbd8fThe package inventory follows, one row per OCI layer and OCaml package the
project carries. A member the schema does not declare is refused by space show --sbom, naming the member.
space run --dry-run resolves the placement and prints the boot plan without
starting anything:
$ space run --dry-runLocal target: macos-vmm@sha256:4c549a833d4e9801bdc6f6e55485e64b23d5d33e0190ba713a20131f114a65da+vzBoot image: not validated (not in the local store: ghcr.io/parsimoni-labs/uniboot-kernel/node:6.18.39-62332cf6bcc6@sha256:21e9b2669e2347dd0a9b7703a248bf191ef91e412ae6b720f3d3768f6cfbbd8f); build firstimage: _build/spaceospartitions: bus-relay 128 MB apids 0x001, 0x00f payload 256 MB apids 0x010, 0x01f services: eo-classifier runc cpus 0, 1 sdls-downlink runcrelay: bus-relay -> payload, ground (sdls)ground: port 8080 socket /tmp/spaceos/ground.sock
2 partitions, 384 MB totalDry run complete; no workload started.Boot image: not validated means the image is not in the local store yet. Run
space build before starting the workload.
Top-level fields
| Field | Type | Required | Description |
|---|---|---|---|
schema | string | yes | Must be space.project. Another schema name is refused. |
version | string | yes | Schema version, "1". |
build | map | no | Where the image lands and what it imports. Every member defaults. |
run | map | yes | The application: its partitions, their services, and the frame topology. |
Unknown fields are rejected.
Build fields
The Dockerfile says how each service image is built. build: says only where
the system image lands, what it imports, and what shape it is baked to.
| Field | Type | Required | Description |
|---|---|---|---|
build.output | path | no; default _build/spaceos | Directory where build artifacts are written. space run boots what this directory holds, so the project states the image once. |
build.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>. |
build.storage.blocks | int | no; default 256 | Persistent storage, counted in 512-byte disk sectors. The partition is rounded up to a whole mebibyte and is never smaller than one, so the default asks for 256 × 512 bytes, 128 KiB, and gets 1 MiB. A signed capability grants storage in 4 KiB blocks instead, so one granted block is eight of these sectors. |
build.storage.params | list | no | Parameter store entries that persist across reboots. |
build.storage.ab_slots | boolean | no; default false | WIP Reserves storage for a second bootable system release. The decoder accepts it and the project records it, and image assembly bakes no second slot: a node trials a new system release through its content store. |
build.kernel and build.target_profile are refused by name, and so is a
kernel under a partition. A kernel and a target profile describe a machine, so
a project that named one would run on exactly one target. The refusal says which
member it was and sends you to targets/NAME.yaml, where the
target manifest carries the board.
Each item under build.storage.params:
| Field | Type | Required | Description |
|---|---|---|---|
id | int | yes | Integer identifier. Must be unique within build.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. |
Run fields
| Field | Type | Required | Description |
|---|---|---|---|
run.partitions | map | yes | The partitions this project boots, keyed by name. At least one is required. |
run.relay | map | no | Frame route, queue limits, and authentication settings. |
run.ground | map | no | Local ground-service endpoint. |
run.bindings | map | no | WIP What this project supplies for each accelerator the application requires, under bindings.accelerators, keyed by the role the app declared: a content-addressed model or bitstream, and requested_limits. The decoder takes it and checks it against the app's requirements; realizing the flow through to the device is next-release work. |
run.acquisition | map | no | The imagery this project wants, and when: area as west, south, east and north in degrees, from as an RFC 3339 instant, and hours of window (default 24). A run on the local engine ignores it; a run bought from an operator that owns spacecraft is where it is read. |
run.results | string | no | Directory a run writes its results into. Naming it empty is refused; omit it instead. It is spelled results because build.output already names where the build writes. |
Partition fields
| Field | Type | Required | Description |
|---|---|---|---|
run.partitions.<name>.init | string | no; default pid1 | Partition init and supervisor. It starts services, mediates frame-bus access, and reports lifecycle and health. Override only when a workload brings its own supervision. |
run.partitions.<name>.disk | boolean | no; default enabled when the partition declares services | Persistent block storage for this partition, backed by the build.storage section. |
run.partitions.<name>.memory | int | no; default 256 | Memory assigned to the partition, in MiB. |
run.partitions.<name>.apids | two-item int list | no; default [] | Inclusive CCSDS APID range, from 0x000 to 0x7FF. Example: [0x010, 0x01F]. |
run.partitions.<name>.files | list | no | Local files or directories embedded into the partition initramfs. Each entry has source and image-root path. |
run.partitions.<name>.requires.frame_bus | list of string | no; default [] | Bus endpoints this partition needs the node to provide. A service's send_to names either a service of this partition or an endpoint listed here; a name in neither is refused as a misspelling. |
run.partitions.<name>.services | list | no; default [] | The workloads the partition supervisor manages. |
Service fields
Each item under run.partitions.<name>.services:
| Field | Type | Required | Description |
|---|---|---|---|
name | string | yes | Unique identifier within the partition. |
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 | string | no; default auto | The isolation backend the service runs behind. auto resolves from the runtime: runc for oci, host for host. |
cpus | list of int | no; default [] | CPU IDs the service is pinned to. An empty list leaves the placement to the runner. |
memory_mb | int | no; default 128 | 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, oneshot, 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 run.relay. |
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), the defaults of Docker's
HEALTHCHECK. The runtime probes from outside the guest every interval
seconds, and a probe that takes longer than timeout counts as failed.
Failures during the first start_period seconds do not count. After that,
retries consecutive failures mark the service unhealthy. A service that stops
answering is therefore marked unhealthy after about retries × interval
seconds, 30 s with the defaults.
Relay fields
If run.relay is omitted, from is the first partition and to contains the
remaining partitions plus ground.
| Field | Type | Required | Description |
|---|---|---|---|
run.relay.from | string | no | Source partition. ground is not valid here. |
run.relay.to | list of string | no | Destination partitions and ground. A name may appear once. Telecommands use the reverse route. |
run.relay.limits.rate | int | no; default 1000 | Frames admitted per second from the source. 0 disables the rate limit. |
run.relay.limits.burst | int | no; default 64 | Capacity, in frames, of the source's token bucket: the largest burst admitted at once. |
run.relay.limits.queue_depth | int | no; default 256 | Frames held in each egress queue. |
run.relay.limits.drop_policy | enum | no; default drop-oldest | Behavior when an egress queue is full: block pauses the source, drop-new discards the arriving frame, and drop-oldest discards the frame at the head of the queue. |
The limits are a token bucket counted in 256-byte frames. The bucket holds
burst tokens and refills at rate tokens per second, and each admitted frame
spends one. A source therefore sends at most rate × 256 bytes per second
sustained, 256,000 bytes per second with the defaults, and at most burst
frames, 16 KiB, at once. Every relay target shares the one source, so block
on any full queue pauses delivery to every target. Every discarded frame is
recorded in the relay's security log.
Key material is provisioned at runtime and is never stored in space.yaml. The
fields under run.relay.security select the association it is provisioned for:
| Field | Type | Required | Description |
|---|---|---|---|
spi | int | no; default 1 | SDLS Security Parameter Index, from 0 to 65535. Each frame's security header carries it, and the receiver uses it to select the security association: the key, the cipher suite, and the sequence counter. |
scid | int | no; default 0 | Spacecraft ID. |
vcid | int | no; default 0 | Virtual channel ID. |
acs | string | no; default aes-256-cmac | Authentication cipher suite: one of aes-256-cmac, hmac-sha-256, hmac-sha-384 or hmac-sha-512. Any other name is refused. |
sn_len | int | no; default 4 | Sequence-number length in bytes. |
arsnw | int | no; default 128 | Anti-replay window size: how far below the highest sequence number received a frame's sequence number may be and still be accepted, once. |
authentication | enum | no; default sdls | sdls is the only value, and the relay authenticates every frame. |
Ground fields
The run.ground block configures local execution. space deploy uses the
ground recorded for the connected target.
| Field | Type | Required | Description |
|---|---|---|---|
run.ground.port | int | no; default 8080 | Local ground-service TCP port. |
run.ground.socket | string | no | Local ground-service Unix socket. The path is bound as written, so it must be absolute. A project that names none gets one under the run's own runtime directory. |
Partition supervisor
Each partition runs a single supervisor process as PID 1, the init field,
pid1 by default. 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 project's artifacts and an SBOM to the build.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)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 thatspace.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 project.
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 project for one architecture. Run it once per
architecture you intend to ship, and publish each result under its own tag.
Validation rules
- Partition and service names are free-form identifiers.
groundis reserved and cannot be used as a partition name.run.relayis the only place to define the frame route. A partition declares no egress list of its own.- A
send_tomust name a service of the same partition or an endpoint the partition lists underrequires.frame_bus. - OCI images are pulled once and cached locally. When you need a fresh artifact, use an immutable digest or publish a new tag, then build again.
Files space no longer reads
A project directory that holds build.yaml or run.yaml is refused by every
command that reads a project. space run in a directory holding build.yaml
prints space run: build.yaml is not read any more. This project's build and run settings live in one space.yaml. and stops.
To move such a project across, write both documents into one space.yaml and
delete the old files:
- What
build.yamldeclared becomes thebuild:section, which says where the image lands and what it imports. - What
run.yamldeclared becomes therun:section.partitions,relay, andgroundkeep their members and defaults, and each service carries its build source and its placement in one entry. - A
kernel:or atarget_profile:moves to the target manifest,targets/NAME.yaml, described in Supported targets. The board, the keys, and the route to a machine belong to the target. - An image path in
run.yamlgoes away:space runboots whatbuild.outputholds, andspace run IMAGEnames another image.
space run --dry-run then prints the placement the new file resolves to, and
space show --sbom lists what a build of it contains.