| name | startos-packaging |
| description | Complete guide for packaging services for StartOS (.s9pk), including manifest, Dockerfile, scripting, config, dependencies, testing, and submission |
| metadata | {"version":"1.0.0","tags":["startos","packaging","s9pk","start-sdk","docker"],"source":"https://docs.start9.com/latest/developer-docs"} |
StartOS Service Packaging Skill
You are a StartOS service packaging assistant. Help users create, build, test, and submit .s9pk packages for StartOS.
Key Concepts
- Service โ An application that runs on StartOS (server-side software)
- Package (.s9pk) โ The bundled artifact installed on StartOS, containing a Docker image, manifest, instructions, icon, and license
- Wrapper โ The repository that "wraps" an upstream project with the metadata files needed to build a
.s9pk
- start-sdk โ The CLI tool used to pack and verify services
- start-cli โ The CLI tool for interacting with a running StartOS instance
Software Compatibility
Suitable services should meet these criteria:
- Has a web UI, REST API, or accepts TCP connections (no SSH/CLI-only tools)
- Can compile for
arm64v8 (aarch64) and/or amd64 (x86_64)
- Can be served over Tor
- Docker image optimized for size (under 1GB preferred)
Development Environment
Required Dependencies
-
Docker + Buildx (cross-arch builds):
curl -fsSL https://get.docker.com | bash
sudo usermod -aG docker "$USER"
exec sudo su -l $USER
docker run --privileged --rm linuxkit/binfmt:v0.8
docker buildx install
docker buildx create --use
-
Rust & Cargo:
curl https://sh.rustup.rs -sSf | sh
source $HOME/.cargo/env
-
Start SDK:
git clone https://github.com/Start9Labs/start-os.git && \
cd start-os && git submodule update --init --recursive && \
make sdk
start-sdk init
start-sdk --version
-
Deno (optional, for JS scripting):
curl -fsSL https://deno.land/x/install/install.sh | sh
Recommended Dependencies
- Build essentials:
sudo apt-get install -y build-essential openssl libssl-dev libc6-dev clang libclang-dev ca-certificates
- Git:
sudo apt install git
- yq:
sudo snap install yq
Wrapper Repository Structure
โโโ Dockerfile
โโโ LICENSE
โโโ Makefile
โโโ README.md
โโโ assets/
โ โโโ compat/
โ โโโ config_rules.yaml
โ โโโ config_spec.yaml
โโโ docker_entrypoint.sh
โโโ <submodule-project>/
โโโ icon.png
โโโ instructions.md
โโโ manifest.yaml
โโโ scripts/
โโโ procedures/
Required Files
| File | Purpose |
|---|
manifest.yaml | Package metadata, versions, ports, volumes, dependencies, health checks |
instructions.md | User-facing instructions rendered in the StartOS UI |
LICENSE | Open source license |
icon.png | Service icon (< 100KB) |
Dockerfile | Docker image build recipe |
docker_entrypoint.sh | Container startup script, handles SIGTERMs gracefully |
Optional Files
| File | Purpose |
|---|
Makefile | Build automation: Docker build โ start-sdk pack โ start-sdk verify |
prepare.sh | Sets up Debian build environment for Start9 reproducible builds |
scripts/ | TypeScript procedures for config, migrations, health checks |
assets/compat/ | Config spec and config rules files |
Manifest Specification
The manifest.yaml (or .toml/.json) defines all service metadata. Key fields:
id: my-service
title: My Service
version: 1.0.0
release-notes: "Initial release"
license: mit
wrapper-repo: "https://github.com/user/my-service-wrapper"
upstream-repo: "https://github.com/upstream/project"
support-site: "https://github.com/user/my-service-wrapper/issues"
marketing-site: "https://example.com"
build: ["make"]
min-os-version: 0.3.0
description:
short: "A brief description"
long: "A detailed description for the marketplace page"
assets:
license: LICENSE
icon: icon.png
instructions: instructions.md
docker-images: image.tar
main:
type: docker
image: main
entrypoint: "docker_entrypoint.sh"
args: []
mounts:
main: /root
io-format: yaml
health-checks:
main:
name: Web UI
description: "Checks that the web UI is accessible"
type: docker
image: main
entrypoint: "check-web.sh"
args: []
inject: true
io-format: yaml
volumes:
main:
type: data
compat:
type: assets
interfaces:
main:
name: Web UI
description: "The main web interface"
tor-config:
port-mapping:
80: "80"
lan-config:
443:
ssl: true
internal: 80
ui: true
protocols:
- tcp
- http
dependencies: {}
alerts:
install-alert: "Custom install message"
uninstall-alert: "Custom uninstall message"
restore-alert: "Custom restore message"
start-alert: "Custom start message"
backup:
create:
type: docker
image: compat
system: true
entrypoint: compat
args:
- duplicity
- my-service
- /mnt/backup
- /root/data
mounts:
BACKUP: /mnt/backup
main: /root/data
restore:
type: docker
image: compat
system: true
entrypoint: compat
args:
- duplicity
- my-service
- /mnt/backup
- /root/data
mounts:
BACKUP: /mnt/backup
main: /root/data
actions: {}
Dockerfile Guidelines
docker_entrypoint.sh
- Complete environment setup (create directories, set env vars)
- Execute the service run command
- Handle SIGTERMs for graceful shutdown
- Optionally generate
stats.yaml for Properties display
Config Specification
The config spec defines the UI-driven configuration form. ValueSpec types:
| Type | Description | UI Element |
|---|
boolean | True/false toggle | Toggle switch |
enum | Selection from a set | Dropdown |
list | Array of values | List editor |
number | Numeric value with optional range | Number input |
object | Nested config group | Collapsible section |
string | Text, optionally masked/copyable | Text input |
union | Multiple variant configs | Variant selector |
pointer | Reference to another service's config | Auto-linked |
Example config spec:
enable-feature:
type: boolean
name: Enable Feature
description: "Toggle this feature on or off"
default: true
port:
type: number
name: Port
description: "The port to listen on"
nullable: false
integral: true
range: "[1024, 65535]"
default: 8080
password:
type: string
name: Password
description: "Service password"
nullable: false
copyable: true
masked: true
default:
charset: "a-z,A-Z,0-9"
len: 22
JS Scripting API
For advanced features (config, migrations, health checks, properties, dependencies), create scripts/embassy.ts:
import { types as T } from "https://deno.land/x/embassyd_sdk@v0.3.3.0.5/mod.ts";
export const getConfig: T.ExpectedExports.getConfig = ;
export const setConfig: T.ExpectedExports.setConfig = ;
export const properties: T.ExpectedExports.properties = ;
export const dependencies: T.ExpectedExports.dependencies = ;
export const health: T.ExpectedExports.health = ;
export const migration: T.ExpectedExports.migration = ;
Build step (must output to scripts/embassy.js):
deno bundle scripts/embassy.ts scripts/embassy.js
Set type: script in manifest config stanza to use JS procedures instead of Docker.
Dependencies
Define in manifest.yaml under dependencies:
dependencies:
bitcoind:
version: "^0.21.1.2"
critical: false
requirement:
type: "opt-in"
how: "Can configure an external node instead"
description: "Used to fetch validated blocks"
config:
check:
type: docker
image: compat
system: true
entrypoint: compat
args: [dependency, check, my-service, bitcoind, /datadir, /mnt/assets/bitcoind_config_rules.yaml]
mounts:
main: /datadir
compat: /mnt/assets
io-format: yaml
auto-configure:
type: docker
image: compat
system: true
entrypoint: compat
args: [dependency, auto-configure, my-service, bitcoind, /datadir, /mnt/assets/bitcoind_config_rules.yaml]
mounts:
main: /datadir
compat: /mnt/assets
io-format: yaml
Config rules file example:
- rule: "advanced.peers.listen?"
description: "Peer port must be listening"
suggestions:
- SET:
var: advanced.peers.listen
to-value: true
Properties (stats.yaml)
Display runtime information in the service Properties panel:
version: 2
data:
"Admin URL":
type: string
value: "http://my-service.local:8080"
description: "Web admin interface"
copyable: true
qr: false
masked: false
Build & Pack Commands
Build the Docker image
docker buildx build --tag start9/$(PKG_ID)/main:$(PKG_VERSION) \
--platform linux/arm64 -o type=docker,dest=image.tar .
Pack into .s9pk
start-sdk pack
Verify the package
start-sdk verify s9pk <package-id>.s9pk
Inspect package contents
start-sdk inspect manifest <package-id>.s9pk
start-sdk inspect instructions <package-id>.s9pk
start-sdk inspect icon <package-id>.s9pk
start-sdk inspect docker-images <package-id>.s9pk
Testing
- Build:
make (or run Docker build + start-sdk pack manually)
- Sideload via UI: System โ Sideload Service โ drag and drop
.s9pk
- Or install via CLI:
echo "host: <STARTOS_IP>" > /etc/embassy/config.yaml
start-cli auth login
start-cli package install <package-id>.s9pk
- Verify:
- Service starts and health checks pass
- UI launches correctly (if applicable)
- All Properties display accurate information
- Config UI renders without errors
- Service stops and restarts cleanly
- Backup and restore work correctly
Community Submission Checklist
- Source code is public (wrapper + upstream)
prepare.sh produces a clean build on Debian
make <package-id>.s9pk builds without errors
- Submit wrapper repo link to
submissions@start9.com
Start9 Review Criteria
Packaging Checklist (Quick Reference)
Reference Examples
Support