Port a Linux app
You do not need to rewrite a Linux container as a unikernel before testing it
with SpaceOS. Start with the existing OCI image, describe where it runs in
build.yaml and run.yaml, then move from local execution to a named target.
Linux services share the partition’s Linux kernel, so they provide weaker isolation than a unikernel. They are still the fastest path for existing software, especially when the application depends on a standard Linux userland or a large dependency tree.
Prerequisites
- An OCI image for your application. Images that run with
docker runcan use the Linux service path. - The Space CLI (
space), installed from the Parsimoni Homebrew tap:brew install parsimoni/tap/space. - A local SpaceOS environment from the quickstart.
Check the image
Run the image with your usual OCI tool first:
docker run --rm ghcr.io/<your-org>/<your-app>:latestSpace CLI resolves service images by OCI reference. Push the image to a registry that your local environment and deployment target can reach:
docker push ghcr.io/<your-org>/<your-app>:latestPrivate registries work, but the target environment must have credentials for
the reference you use in build.yaml.
Describe the service
Create a project with a build.yaml that bundles the service image:
schema: space.buildversion: "1"kernel: linuxkit/kernel:6.6.13output: _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 defines what goes into the composition without choosing a target
or allocating target resources.
Then create run.yaml for local execution:
schema: space.runversion: "1"image: _build/spaceos
partitions: payload: memory: 512 apids: [0x010, 0x01F] send_to: [ground] services: - name: my-app isolation: runc cpus: [0]
relay: from: payload to: [ground]
ground: port: 8080 socket: /tmp/spaceos/ground.sockrun.yaml defines runtime memory,
APID access, relay topology,
ground endpoint, and service isolation. For Linux services, use isolation: runc.
Run locally
Build and run the composition. The first build creates and selects a development signing identity automatically and prints its fingerprint:
space build . -t my-app:devspace run my-app:dev --runtime run.yamlFor a runner-supervised local deployment, give the workload a name. The engine starts on first use:
space run --name my-app my-app:dev --runtime run.yamlspace logs my-appspace stop my-appThe runner tracks lifecycle, logs, stats, and status for named workloads.
Deploy to a target
First build a release composition. The --release flag requires a
non-development signing profile:
space build . -t ghcr.io/<your-org>/my-app:release --release --signing-profile releaseConnect the target once, with its
content-addressed profile and its
uplink transport. --endpoint and --cla-address are the node’s
delay-tolerant network name and the address that reaches
it. A bench board on
your network claims --always-reachable; a node reached over real passes
names its orbit with --tle, a
two-line element set, and --station:
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-reachableInspect the deployment plan before enqueueing it:
space deploy ghcr.io/<your-org>/my-app:release --target sat-42 --planspace deploy ghcr.io/<your-org>/my-app:release --target sat-42space deploy assembles the per-board release for the named target. Local
space run executions exercise the runtime path; deployments bind the
composition to a registered board and its admission policy.
Unikernel migration criteria
Stay on Linux services when you need a normal Linux userland, fast porting, or existing container images. Move to a unikernel when the mission requires a smaller trusted computing base (less code that has to be correct for the system to be secure), faster cold starts, or stronger service isolation.