Skip to content
The compose and resolve steps are executed on every build of this site; the build, run and deploy steps need a builder VM and a connected board, so no gate runs them.

Port a Linux app

Run an existing OCI image on SpaceOS as a Linux service. Describe where it runs in build.yaml and run.yaml, try it locally, then deploy it to a connected target.

Linux services run under runc and share the partition’s kernel. The isolation layers chapter covers the boundary this gives you and how it compares with a unikernel.

Prerequisites

  • An OCI image for your application. Any image that runs under docker run works here.
  • The Space CLI (space), installed from the Parsimoni tap. Run brew tap parsimoni-labs/space, then brew install space.

Check the image

Run the image with your usual OCI tool first:

Terminal window
docker run --rm ghcr.io/<your-org>/<your-app>:latest

Space CLI resolves service images by OCI reference. Push the image to a registry that both your workstation and your target can reach:

Terminal window
docker push ghcr.io/<your-org>/<your-app>:latest

A private registry needs credentials on the target for the reference you name in build.yaml.

Describe the service

Create a project with a build.yaml that bundles the service image:

schema: space.build
version: "1"
kernel: linuxkit/kernel:6.6.13
output: _build/spaceos
partitions:
payload:
init: pid1
disk: true
services:
- name: my-app
image: ghcr.io/<your-org>/<your-app>:latest
runtime: oci
isolation: runc
healthcheck:
type: command
argv: ["/bin/sh", "-lc", "test -f /tmp/ready"]

build.yaml names what goes into the composition, and it neither picks a target nor claims target resources.

Then create run.yaml for local execution:

schema: space.run
version: "1"
image: _build/spaceos
partitions:
payload:
memory: 512
apids: [0x010, 0x01F]
services:
- name: my-app
isolation: runc
cpus: [0]
relay:
from: payload
to: [ground]
ground:
port: 8080
socket: /tmp/spaceos/ground.sock

run.yaml places the service at runtime: memory, APID access, relay topology, and the ground endpoint. Set isolation: runc for a Linux service.

Check both before a build: space sbom decodes the composition, and space run --dry-run resolves the placement and prints it.

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 512 MB apids 0x010, 0x01f
services:
my-app runc cpus 0

Run locally

Build and run the composition. The first build creates a development signing identity and prints its fingerprint:

Terminal window
space build . -t my-app:dev
space run my-app:dev --runtime run.yaml

To put the workload under the runner, give it a name. The engine starts on first use:

Terminal window
space run --name my-app my-app:dev --runtime run.yaml
space logs my-app
space stop my-app

The runner tracks lifecycle, logs, stats, and status for named workloads.

Deploy to a target

Build a release composition. --release needs a release identity that already exists: it refuses the development identity, and refuses to create the profile you name. Generate one first for bench work. Generate a real release identity where the release is authorized, and name it here.

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:...
Terminal window
space build . -t ghcr.io/<your-org>/my-app:release --release \
--signing-profile release --rollback-index 1

Connect the target once, with its content-addressed profile and its uplink transport. --endpoint and --cla-address carry the node’s delay-tolerant network name and the address that reaches it. Claim --always-reachable for a bench board on your network. A node reached over real passes names its orbit with --tle, a two-line element set, and its ground station with --station:

Terminal window
$ space target connect sat-42 --profile cm5-dev \
> --signing-key ./keys/sat-42.pem \
> --endpoint ipn:7.1 --cla-address sat-42.local:4556 --always-reachable
Generated acknowledgement key: /tmp/space-docs/config/keys/target-sat-42-ack.pem
target/sat-42 connected

Inspect the deployment plan before enqueueing it:

Terminal window
space deploy ghcr.io/<your-org>/my-app:release --target sat-42 --plan
space deploy ghcr.io/<your-org>/my-app:release --target sat-42

space deploy assembles the per-board release and binds the composition to the target’s board and admission policy. Omit --target when the project declares a single target.

Unikernel migration criteria

Stay on Linux services while your workload needs a full Linux userland or a large dependency tree. Move to a unikernel when the mission needs:

  • A smaller trusted computing base, so less code has to be correct for the system to be secure.
  • A faster cold start.
  • A stronger isolation boundary between services.