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.
1. The files
Section titled “1. The files”Make an empty folder and work in it. Every block on this page starts in that folder.
mkdir cloudoak-work && cd cloudoak-workgh 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.
tar -xzf cloudoak-compose_*.tar.gztar -xzf cloudoak_*_linux_*.tar.gz cloudoaktar -xzf cloudoak-starter.tar.gzmv starter my-app./cloudoak version2. A node to develop against
Section titled “2. A node to develop against”The compose bundle, set up for development: plain http on localhost,
unsigned apps accepted, anyone may register. Not a node for real
users.
cd cloudoak-composeIMAGE=$(awk '$1 == "image:" && $NF ~ /cloudoakhq\/cloudoak:/ {print $NF}' compose.yaml)docker run --rm "$IMAGE" init --write-env --dev --out - --owner-email you@example.com > .envchmod 600 .envdocker compose up -d --waitdocker compose exec -T cloudoak cloudoak setup-linkOpen 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.
3. The app
Section titled “3. The app”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.
cp cloudoak-sdk-*.tgz my-app/cd my-appnpm installnpm run buildcloudoak.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:
./cloudoak app validate my-app/cloudoak.app.jsonA problem is reported with the field and the line it is on.
4. Run it
Section titled “4. Run it”Install the manifest on your node. It is unsigned, which a development node accepts:
docker compose -f cloudoak-compose/compose.yaml exec -T cloudoak cloudoak apps install - < my-app/cloudoak.app.jsonStart 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.
cd my-app && npm run devOpen 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 -).
5. A publisher key
Section titled “5. A publisher key”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.
./cloudoak app keygen --out publisher.key | tee publisher.txtThe 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.
6. Publish
Section titled “6. Publish”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:
cd cloudoak-composedocker compose --profile registry-dev up -d --wait registry-devdocker compose exec -T registry-dev cloudoak registry root --dir /home/nonroot/registry-devThe 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:
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:
docker compose -f cloudoak-compose/compose.yaml exec -T registry-dev cloudoak registry review --dir /home/nonroot/registry-dev listdocker 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.
7. What a user and a node’s owner see
Section titled “7. What a user and a node’s owner see”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.
cd cloudoak-composedocker compose exec -T cloudoak cloudoak apps remove com.example.starter --yesROOT_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/|" .envsed -i "s|^CLOUDOAK_REGISTRY_KEYS=.*|CLOUDOAK_REGISTRY_KEYS=$ROOT_KEY|" .envdocker compose --profile registry-dev up -d --waitConnect 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:
docker compose -f cloudoak-compose/compose.yaml exec -T cloudoak cloudoak apps pendingdocker 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.
8. A new version
Section titled “8. A new version”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:
cp my-app/cloudoak.app.json app-1.0.0.jsonsed -i 's|"version": "1.0.0"|"version": "1.1.0"|' my-app/cloudoak.app.jsonsed -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.jsonsed -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.jsonAsk what the upgrade will do on a node, before any node sees it:
./cloudoak app plan --from app-1.0.0.json --to my-app/cloudoak.app.jsonAdding 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:
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.jsonThe owner of a node sees the new version in the registry, and upgrades:
docker compose -f cloudoak-compose/compose.yaml exec -T cloudoak cloudoak apps upgrade --app com.example.starter --version 1.1.0 --plandocker compose -f cloudoak-compose/compose.yaml exec -T cloudoak cloudoak apps upgrade --app com.example.starter --version 1.1.0docker compose -f cloudoak-compose/compose.yaml exec -T cloudoak cloudoak apps listCopies 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.
9. Stop
Section titled “9. Stop”cd cloudoak-composedocker compose --profile registry-dev down -v-v deletes the node’s data, and the stand-in’s, along with it.
Where to go from here
Section titled “Where to go from here”- The SDK guide: accounts, data, files and sync, with
examples/notesin 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.