NubleStation

Orbit hosts your app’s frontend. You build a static SPA locally, run nuble deploy, and seconds later it’s live at http://{app}.{org}.local for every device on the network. You interact with Orbit exclusively through the CLI — there is no JavaScript SDK.

Get up and running

  1. Create the app in the Console. This reserves {app}.{org}.local and issues your API key (nbl_…).
  2. Connect the CLI:
    npm install -g @nublestation/cli
    nuble init   # prompts for gateway URL, API key, app slug
  3. Build and deploy:
    npm run build
    nuble deploy
  4. Open it. Visit http://{app}.{org}.local from any device on the LAN. No restart, no pipeline — the new files are served immediately.

Everyday usage

Deploy

nuble deploy                # zips and uploads ./dist
nuble deploy --dist build   # custom build output directory

Build and deploy in one step

nuble deploy --build

Runs your project’s build script first, loading .env automatically so build-time variables (like VITE_…) are baked into the bundle. Remember: changing a VITE_ variable requires a rebuild and redeploy — it’s compiled in, not read at runtime.

Roll back

Orbit keeps the previous deployment on disk and can swap back to it. There’s no CLI wrapper yet (coming soon), so call the endpoint directly:

curl -X POST http://api.clinic.local/v1/orbit/rollback \
  -H "Authorization: Bearer nbl_<key_id>.<secret>"

Or simply redeploy the older build artifact: nuble deploy --dist ./path/to/previous-dist.

Check connectivity

nuble status    # verifies the gateway is reachable for each profile

Reference

All endpoints are reached through the Gateway at api.{org}.local with your API key. The CLI handles this for you.

MethodPathOne-liner
POST/v1/orbit/deployUpload a zipped bundle as multipart/form-data (field name: bundle)
POST/v1/orbit/rollbackSwap back to the previous deployment (filesystem rename, milliseconds)
GET/healthzLiveness probe
GET/readyzReadiness probe

Error codes

StatuserrorMeaning
400invalid_content_type / missing_bundle_fieldRequest isn’t multipart, or the bundle field is missing
401unauthorizedBad or missing API key (returned by the Gateway)
409no_previous_versionRollback requested but no previous deployment exists
413bundle_too_largeBundle exceeds the 50 MB limit
422missing_index_htmlThe zip has no index.html at its root — check your build output
500deploy_failed / rollback_failedServer-side failure — check nuble server logs orbit

CLI flags (nuble deploy)

FlagDescription
--dist <path>Built frontend directory (default: dist)
--buildRun the project build script before deploying
--profile <name>Config profile to use (default: default)

See CLI Commands for the full reference.

Limits & gotchas

  • One previous version only. Each deploy overwrites the old previous/ — there is no full deployment history, and only one rollback step.
  • Static SPAs only. Pre-built files are served as-is; server-side rendering is not supported.
  • 50 MB max bundle. Larger uploads are rejected with 413.
  • index.html must be at the zip root. Deploying a zip that wraps everything in a subfolder fails with 422.
  • No deploy hooks. Post-deploy webhooks or notifications are not yet implemented.
  • No streaming deploys. The entire bundle uploads before extraction begins.

Tip

For large bundles, strip source maps from your build (--no-sourcemap in Vite). Smaller zips upload faster, especially on slow LAN links.

Go deeper

How the upload is authenticated and atomically swapped on disk (HMAC-signed forwarding, current//previous/ layout under /var/nuble/, Caddy’s wildcard serving) is covered in the concepts and security sections: