Skip to content
The SDK and node releases can be downloaded only by accounts invited to their repositories, while Cloudoak is in its private alpha.

From zero to an app on a node

An app, built and shipped: a node on your machine to develop against, the starter app running on it, then signed, published, connected by a user, approved by the node’s owner, and upgraded. About half an hour.

Every command on this page is run by CI against each build (deploy/compose/e2e/e2e-developer.sh reads this file), so the page cannot drift from what works.

What you need: Linux, Docker with the compose plugin, Node 24 with npm, three files of a Cloudoak release, and the SDK from its own release. Your node reads the central app registry from its first start, as every node does, but publishing to it opens only at its public launch, so this page publishes to a stand-in that runs the same rules beside your node (see Registries). What is not here yet: sign-in from a native (non-browser) app is implemented but has not been tested end to end.

Make an empty folder and work in it. Every block on this page starts in that folder.

Terminal window
mkdir cloudoak-work && cd cloudoak-work
gh release download v0.6.0-alpha.1 --repo cloudoakhq/cloudoak \
--pattern 'cloudoak-compose_*.tar.gz' \
--pattern 'cloudoak_*_linux_amd64.tar.gz' \
--pattern 'cloudoak-starter.tar.gz'
gh release download v2.0.0 --repo cloudoakhq/cloudoak-sdk \
--pattern 'cloudoak-sdk-*.tgz'

While Cloudoak is in private alpha this needs the GitHub account that was invited to both repositories, and docker login ghcr.io with a token that can read packages; the quick start has both, and how to verify what you downloaded. On an arm64 machine take the _linux_arm64 archive.

Unpack them. cloudoak is the node and the developer’s tool in one binary; the commands on this page that start with ./cloudoak run on your machine and talk to nothing.

Terminal window
tar -xzf cloudoak-compose_*.tar.gz
tar -xzf cloudoak_*_linux_*.tar.gz cloudoak
tar -xzf cloudoak-starter.tar.gz
mv starter my-app
./cloudoak version

The compose bundle, set up for development: plain http on localhost, unsigned apps accepted, anyone may register. Not a node for real users.

Terminal window
cd cloudoak-compose
IMAGE=$(awk '$1 == "image:" && $NF ~ /cloudoakhq\/cloudoak:/ {print $NF}' compose.yaml)
docker run --rm "$IMAGE" init --write-env --dev --out - --owner-email [email protected] > .env
chmod 600 .env
docker compose up -d --wait
docker compose exec -T cloudoak cloudoak setup-link

Open the link it prints and choose a username and password: that is the node’s owner, and the account you will sign in with below. The node is at http://localhost:8080.

my-app is the starter: one page, one table, about a hundred lines in src/main.ts. It depends on the SDK as a package, which for now is a file in the SDK’s release and not on npm.

Terminal window
cp cloudoak-sdk-*.tgz my-app/
cd my-app
npm install
npm run build

cloudoak.app.json is the app’s manifest: its id, its version, the addresses it may be served from, and the tables it keeps on a node. Check it the way a node will:

Terminal window
./cloudoak app validate my-app/cloudoak.app.json

A problem is reported with the field and the line it is on.

Install the manifest on your node. It is unsigned, which a development node accepts:

Terminal window
docker compose -f cloudoak-compose/compose.yaml exec -T cloudoak cloudoak apps install - < my-app/cloudoak.app.json

Start the app. The manifest names http://localhost:5175 as its address, and a node refuses any other, so the dev server stays on that port.

Terminal window
cd my-app && npm run dev

Open http://localhost:5175, enter http://localhost:8080 and press Connect. The node shows its own sign-in page; sign in and approve, and you are back in the app. Add an item. The app never saw your password, and the item is in a table on your node.

Edit src/main.ts and the page reloads. Change the manifest (a new column, say) and install it again with a higher app.version; a node takes an unsigned upgrade the same way it took the install (cloudoak apps upgrade --app com.example.starter --file -).

Other people’s nodes do not take unsigned apps. Make a signing key, once, and keep it: a node pins your app to the first key that signs it and refuses a version signed with any other.

Terminal window
./cloudoak app keygen --out publisher.key | tee publisher.txt

The file publisher.key is the secret. The public key and its key id (64 hex characters), which the command printed, are what you give to people. The 16-character short id beside it is only for reading.

Apps are published to a registry: a catalogue that every node reads, where each version is signed by its publisher and nothing changes without a signed request. The node bundle can run a stand-in for the central registry beside your node, at http://localhost:8790, on this machine’s loopback only. It runs the same rules, and you are its operator:

Terminal window
cd cloudoak-compose
docker compose --profile registry-dev up -d --wait registry-dev
docker compose exec -T registry-dev cloudoak registry root --dir /home/nonroot/registry-dev

The last line is the stand-in’s root key. A node given it trusts what the stand-in signs, as nodes trust the central registry’s built-in key.

app publish signs the manifest with your key, on your machine, and sends it; the key never leaves this folder. --registry-key is the stand-in’s root key, which the CLI checks its answers against:

Terminal window
ROOT_KEY=$(docker compose -f cloudoak-compose/compose.yaml exec -T registry-dev cloudoak registry root --dir /home/nonroot/registry-dev)
./cloudoak app publish --registry http://localhost:8790/ --registry-key "$ROOT_KEY" --accept-terms --wait 0 --key-file publisher.key my-app/cloudoak.app.json

--accept-terms accepts the registry’s publisher terms; without it the command shows their address and asks. Your first submission claims your publisher name, so it waits for the registry’s operators. Here that is you. List what waits and approve it; the id is in the first column:

Terminal window
docker compose -f cloudoak-compose/compose.yaml exec -T registry-dev cloudoak registry review --dir /home/nonroot/registry-dev list
docker compose -f cloudoak-compose/compose.yaml exec -T registry-dev cloudoak registry review --dir /home/nonroot/registry-dev approve <id>

Later versions of a listed app go straight through, unless they add an address the sign-in page may return to.

Make your node behave like someone else’s: take the development copy of the app off it, and tell it to read the stand-in in place of the central registry, and trust the stand-in’s key.

Terminal window
cd cloudoak-compose
docker compose exec -T cloudoak cloudoak apps remove com.example.starter --yes
ROOT_KEY=$(docker compose exec -T registry-dev cloudoak registry root --dir /home/nonroot/registry-dev)
sed -i "s|^#*CLOUDOAK_REGISTRY_URLS=.*|CLOUDOAK_REGISTRY_URLS=http://localhost:8790/|" .env
sed -i "s|^CLOUDOAK_REGISTRY_KEYS=.*|CLOUDOAK_REGISTRY_KEYS=$ROOT_KEY|" .env
docker compose --profile registry-dev up -d --wait

Connect from the app again (http://localhost:5175, the node’s address, sign in). This time the node does not have the app. It finds it in the registry, shows who published it, and after you sign in sends you back with the answer that the app is waiting for the node’s owner. Your users will see that message the first time anyone on their node connects your app; src/main.ts shows how the starter words it.

As the owner, look at the queue and approve. The id is in the first column of the list:

Terminal window
docker compose -f cloudoak-compose/compose.yaml exec -T cloudoak cloudoak apps pending
docker compose -f cloudoak-compose/compose.yaml exec -T cloudoak cloudoak apps pending approve <id>

Connect once more and you are in. An owner can also enable an app before anyone asks for it, from the store in the terminal: cloudoak apps search, apps show com.example.starter (what it stores and who can read it), then apps enable com.example.starter. An owner who trusts you can skip the queue for all your apps by listing your key’s full key id in CLOUDOAK_TRUSTED_PUBLISHERS.

Keep the manifest that is out there, then change the app. This adds a column and raises the version, in the manifest and in the app:

Terminal window
cp my-app/cloudoak.app.json app-1.0.0.json
sed -i 's|"version": "1.0.0"|"version": "1.1.0"|' my-app/cloudoak.app.json
sed -i 's|"title": { "type": "text", "required": true, "max": 200 }|"title": { "type": "text", "required": true, "max": 200 },\n "done": { "type": "bool", "default": false }|' my-app/cloudoak.app.json
sed -i "s|const APP_VERSION = '1.0.0';|const APP_VERSION = '1.1.0';|" my-app/src/main.ts
./cloudoak app validate my-app/cloudoak.app.json

Ask what the upgrade will do on a node, before any node sees it:

Terminal window
./cloudoak app plan --from app-1.0.0.json --to my-app/cloudoak.app.json

Adding a column applies by itself. Dropping or renaming one, widening who may read a table, or adding an address the sign-in page may return to, needs the owner of each node to approve, and the plan says so. A node refuses a change that would lose data without a migration step that says what happens to it; plan refuses it the same way, here, first, and so does the registry. App versions has the rules, including how to retire a column without breaking copies of the app that are still out there.

Publish it. app publish shows the same plan against the version that is out there, and says that it goes straight through:

Terminal window
ROOT_KEY=$(docker compose -f cloudoak-compose/compose.yaml exec -T registry-dev cloudoak registry root --dir /home/nonroot/registry-dev)
./cloudoak app publish --registry http://localhost:8790/ --registry-key "$ROOT_KEY" --wait 0 --key-file publisher.key my-app/cloudoak.app.json

The owner of a node sees the new version in the registry, and upgrades:

Terminal window
docker compose -f cloudoak-compose/compose.yaml exec -T cloudoak cloudoak apps upgrade --app com.example.starter --version 1.1.0 --plan
docker compose -f cloudoak-compose/compose.yaml exec -T cloudoak cloudoak apps upgrade --app com.example.starter --version 1.1.0
docker compose -f cloudoak-compose/compose.yaml exec -T cloudoak cloudoak apps list

Copies of the app at 1.0.0 keep working against the upgraded node, because the manifest’s min_client still says 1.0.0. Raise it when a version of the app can no longer be served, and the node tells people on older copies to update.

Terminal window
cd cloudoak-compose
docker compose --profile registry-dev down -v

-v deletes the node’s data, and the stand-in’s, along with it.

  • The SDK guide: accounts, data, files and sync, with examples/notes in the repository.
  • Registries: the registry, a registry in a folder, yanking a version, changing your key.
  • The quick start: the same node with TLS, for real users.

Built from spec 2.1.0, SDK 2.0.0 and the node's guide at v0.6.0-alpha.1.