Guides

Deploy

Upload the project file tree — schema, APIs, and frontend — to Starbase.

From a linked project folder, or from an unlinked folder that already has robodev/database.ts (or database.ts):

terminal

robodev deploy

The CLI reads robodev/.robodev, collects the allowed file tree (robodev/database.ts, robodev/api/**/*.ts, robodev/jobs/**/*.ts, robodev/sockets/**/*.ts, robodev/mcp/**/*.ts, optional robodev/mcp.ts, robodev/hooks/**/*.ts, optional robodev/package.json, index.html, src/**, and config files), and POSTs them to /v1/projects/:id/deploy. If the folder has no robodev/.robodev, a signed-in TTY deploy asks for an organization (when you admin two or more) and a project name, creates a name-only hosted project, writes the link, then deploys the local files. Non-TTY unlinked deploy needs --name (and --org when the default org is wrong). A collect failure exits before create. If create succeeds and deploy fails, the link stays so a retry does not create a second project. When robodev/.robodev has a frontend object, it also runs the local package.json script (default build) in that root and uploads the static output as index.html + public/** (including fonts, images, video, audio and PDFs under public/). frontend: false skips that build. Starbase compiles APIs, jobs, sockets, hooks, and the hosted UI, syncs the schema, and serves the app on the project host. The dashboard Code page Save uses the same API and does not run Vite. The official empty starter, Space, and other full starters come from robodev create. Every deploy attempt (CLI, robodev-be, dashboard Save, agent publish) is listed on the project Deployments page with a durable transcript. A deploy shows as Running while it builds (the page refreshes every 5 s), then Succeeded or Failed; one that never finished because Starbase restarted, or that ran past 15 minutes, shows as Interrupted. Failures that stop before the build are listed too and tagged: Local (the CLI or robodev-be failed on your machine before upload, e.g. a Vite build error or an invalid file path, with the captured output), Auth (a revoked deploy key of this project), and Request (an invalid deploy body). Deploys from GitHub Actions (robodev 0.54+ / robodev-be 0.5.1+) are labelled "· GitHub Actions" and link to the run.

Source and API files up to 1 MB each; built frontend files (index.html, public/) up to 8 MB each; images incl. SVG, fonts, video, audio and PDF up to 50 MB each, uploaded separately and only when changed. Lockfiles (package-lock.json, pnpm-lock.yaml, yarn.lock, npm-shrinkwrap.json, bun.lock) at the project root, apps/fe/ or packages/be/ are exempt from the 1 MB and 8 MB caps; the same names anywhere else get the normal caps. A file over its cap fails with "File must be under 1MB: <path>", "File must be under 8MB: <path>" or "File must be under 50MB: <path>". There is no total size or file count limit. robodev checks every file before uploading anything, and Starbase enforces the same limits. The source snapshot (the files robodev load and the dashboard Code page read back) keeps the flat pre-0.59 limit: 1 MB per file wherever it lives, public/ text and SVG included; lockfiles at the collected locations are exempt, and binaries are never part of it. The deploy credential is checked before any body is read.

Video, audio, PDFs, images and fonts

Binary files are accepted only under public/ (a built dist's output lands there): .mp4 .webm .mov video, .mp3 .wav .ogg audio, .pdf, .png .jpg .jpeg .gif .webp .ico images, and .woff .woff2 .ttf .otf fonts. Anywhere else (src/, the project root) they fail the deploy, and they are never stored in the source snapshot that robodev load reads. Built SVGs under public/ take the same path (they are text, but builds can emit multi-MB SVGs). robodev 0.69+ uploads each one as a content-addressed blob before the deploy (HEAD then PUT /v1/projects/:id/deploy-blobs/:sha256, 4 at a time) and prints "Uploaded N files (X MB), skipped M unchanged" — a file whose bytes did not change since an earlier deploy is not sent again, so a 300MB site with a few edited pages redeploys in seconds. Older CLIs, MCP publish_frontend_files and Starbases without blob storage send binaries inline as base64 in the deploy body, which still works under the same 50MB per-file cap. The project host serves them with their real Content-Type (video/mp4, video/webm, video/quicktime, audio/mpeg, audio/wav, audio/ogg, application/pdf) and HTTP Range requests (206 Partial Content), so videos seek and play in Safari.

The hosted MCP publish_frontend_files tool accepts the same types as base64 but keeps its 30MB per call cap; ship large media with robodev deploy and a deploy key instead. Tiny projects: do not pin an old robodev in package.json — use bunx robodev@latest deploy or pin robodev 0.70.0 or newer. robodev older than 0.59 fails locally with "File must be under 8MB" before anything reaches Starbase, and 0.59–0.68 refuse video, audio and PDF files under public/.

Put apple-app-site-association (no extension) and assetlinks.json in the frontend public/.well-known/ folder, so the build writes dist/.well-known/. robodev 0.57+ uploads them and the project host serves /.well-known/apple-app-site-association (and the legacy /apple-app-site-association) and /.well-known/assetlinks.json as application/json, status 200, with no redirect and Cache-Control: public, max-age=3600. A missing file returns a JSON 404, never the SPA index.html. Only those exact extensionless names are allowed; any other extensionless file under public/ still fails the deploy.

Deploy keys and CI

A project deploy key (rdb_…) authorizes the same full POST /v1/projects/:id/deploy payload as a signed-in user: schema, APIs, jobs, sockets, hooks, and optional hosted frontend. Any project member can create one on the project's Connect page (/projects/:id/connect) → Deploy keys → Create deploy key, from the Lovable → Deploy from GitHub Actions instead step, or through the hosted MCP connector's create_deploy_key tool. The key is shown once and cannot be retrieved again. For GitHub Actions — the preferred CI path — store the rdb_ key as the repo secret ROBODEV_DEPLOY_KEY. rdc_… claim codes are different: single-use, 30-minute codes that robodev-be auth redeems in Bolt's terminal for a new key, not something to put in CI. List and revoke keys on the Connect page or with the MCP tools list_deploy_keys and delete_deploy_key; a deploy with a revoked key fails with 401 "Invalid or revoked deploy key". A deploy key can also read its own project's request logs and deploy logs (GET /v1/projects/:id/logs, /deploy-logs, /deploy-logs/:logId — what robodev logs uses); a key for a different project gets 403. A deploy key can also restart its project's worker to apply secret changes (POST /v1/projects/:id/worker/restart — what robodev restart uses), and list, set and delete its project's secrets (GET / PUT /v1/projects/:id/secrets, DELETE /v1/projects/:id/secrets/:name — what robodev secrets uses), each change audit-logged with the key's prefix. It can never read a secret value back. A leaked key can publish, read those logs, and change or delete secrets, but cannot read secret values, list or delete auth users, robodev load, or GET workspace / deployment files; revoke a leaked key on the Connect page.

robodev deploy resolves credentials in this order: (1) ROBODEV_DEPLOY_KEY (trimmed, non-empty env), (2) deployToken on robodev/.robodev or legacy ./.robodev (written by robodev-be auth), (3) the user JWT from robodev auth. A key (1 or 2) wins over a home JWT for that deploy. Key deploy requires an existing link (projectId + apiUrl). It does not call /v1/auth/me, create a project, or persist the env secret into the link file. Unlinked + key exits 1 with no POST /v1/projects — link first, or provide a redacted robodev/.robodev (no token required when the env supplies the key).

  • robodev deploy — serious / local / GitHub Actions full-stack. Auth: JWT or ROBODEV_DEPLOY_KEY or file deployToken. Payload: schema, APIs, jobs, sockets, hooks, optional FE build + source. Creates a project only on JWT + unlinked.
  • robodev-be deploy — Lovable (from GitHub Actions), Bolt, and other web containers. Auth: ROBODEV_DEPLOY_KEY (env) wins over file deployToken (robodev-be auth rdc_… or rdb_…); with the env key, robodev/.robodev only needs projectId + apiUrl. Payload: backend tree only (no Vite, no apps/fe, no root index.html + src/). Never creates a project.

Starters gitignore .robodev, so robodev/.robodev is not in a fresh clone. CI must still have projectId + apiUrl (plus projectApiUrl / frontend as today): force-add a redacted link with no deployToken, or write that JSON in the workflow from repo variables. Never commit deployToken or rdb_….

GitHub Actions: create and link the hosted project once, mint a key, store it as repo secret ROBODEV_DEPLOY_KEY. Runner needs Node 22 and the same FE build tools as local robodev deploy. Use --force so a destructive schema plan does not hang on a TTY prompt; omit --force if the team wants CI to fail closed on 409. Optional STARBASE_URL only for a non-prod control plane. Projects created from a GitHub template with a deployment-ready provider get this workflow, key and secret automatically; setup waits up to 15 minutes for GitHub to publish a new repository, and a platform admin — or an admin of the project's organization when that organization has the provider attached — can re-run it on an existing project from Settings → Repository → Repair deployment.

GitHub Actions

- uses: actions/setup-node@v4
  with:
    node-version: 22
- run: npm i -g robodev
- run: robodev deploy --force
  env:
    ROBODEV_DEPLOY_KEY: ${{ secrets.ROBODEV_DEPLOY_KEY }}

API packages

Optional runtime dependencies for APIs and jobs live in robodev/package.json (not the root package.json used by hosted frontend / local CLI). Only these packages are allowed: date-fns, dayjs, nanoid, uuid, ms, slugify, decimal.js, lodash, lodash-es, ts-pattern, jose, bcryptjs, stripe, zod, superjson. Each version must be an exact semver (1.2.3 or 1.2.3-rc.1). scripts must be absent or {}. @robodev-ai/sdk may be listed for local DX but is not installed for the tenant. Native addons, git/file URLs, and ranges fail deploy; the previous runtime stays. Starbase installs with pnpm --ignore-scripts during compile and bundles the result..

Destructive changes

If the plan would drop tables or columns, deploy returns 409. The CLI prints the SQL and asks for confirmation. Skip the prompt with --force. CI is non-TTY; use --force so the prompt does not hang or cancel, or omit it to fail closed on 409.

terminal

robodev deploy --force

Worker boot check and rollback

Before a deploy reports success, Starbase boots the new version's API worker and waits for it to load every route module. The deploy log shows -----> Booting worker (generation N) and then -----> Worker ready in <ms>ms. A project that was serving keeps serving throughout: the new worker takes over only once it is ready, and the old one finishes its in-flight requests. A project with no running worker stays cold; the check worker is stopped again once it is ready.

A worker gets 30 seconds to become ready (WORKER_BOOT_MS, default 30000, on self-hosted Starbase), counted after the worker binary is built. Building that binary now happens during the deploy rather than on the first request after it, so deploys take a little longer and the first request is faster. Code shared between routes, jobs, sockets, and hooks is bundled once rather than into every module, which keeps boot well inside that budget.

If the new worker cannot boot, the deploy fails with HTTP 422 and the previous version keeps serving. The deploy log lists -----> Worker failed to boot: <reason>, the last lines the worker wrote to stderr, and -----> Kept generation <N>; the previous version is still serving. robodev deploy prints the same reason and stderr. The error code says what went wrong:

  • worker_boot_failed — the worker exited or crashed before it was ready, for example a route module that throws at the top level. The reason names the exit code or signal.
  • worker_boot_timeout — the worker did not become ready within WORKER_BOOT_MS, for example top-level code that never finishes.
  • worker_compile_failed — the worker binary could not be built; the reason carries the build output.

On a boot failure nothing live changes: the previous files on disk, live generation, worker, and deployment record all stay as they were, so a Starbase restart and robodev load also use the previous version. Schema changes the deploy already applied stay applied, as for any deploy that fails after the schema step. The new generation only becomes live once its worker is ready; a failed one is marked failed, its number is never reused, and it is never chosen for a rollback. While a deploy is checking the new worker, requests keep going to a running worker, and a request that would have to start one waits for the deploy to finish. Deploys to the same project run one at a time, in the order they arrive. Successful deploys now keep earlier compiled generations on disk (up to WORKER_KEEP_GENERATIONS), so a worker rollback has a target.

If a worker fails to start when a request arrives (for example after an idle shutdown), the project API answers 503 with error worker_boot_failed or worker_boot_timeout and the message "The project worker failed to start. See the deploy logs." The public response never includes stderr. GET /_robodev/ready never starts a worker: it answers 503 project_unhealthy for a crash-looping project, 503 with the boot code after a failed start while no worker is running, and otherwise 200 {"ok":true} once the database answers.

Request ids

Every Starbase response, on the control plane and on project hosts, carries an x-request-id header (req_ followed by 32 hex characters). Starbase generates it and ignores any x-request-id the client sends. JSON error bodies from the platform (the /v1 API, auth, deploy errors, 404s) also include "requestId" with the same value, and the project's request log stores it, so a failing call can be matched to its log row. Your own API handlers' response bodies are never changed; they only gain the header. When you report a problem, include the request id.

When Starbase itself is redeployed, it stops accepting new connections and lets in-flight requests, including running deploys, finish before it exits, instead of cutting them off.

Project hosts

  • https://{projectId}.robodev.povio.dev.

Wait until live

After Starbase accepts the deploy, the CLI waits until the public project host is reachable and the tenant database accepts a connection (up to 30 seconds). Then it prints the App URL, Docs URL (/_robodev/docs), schema operations, and registered routes.

If the host does not become reachable in time, the command exits with an error. The deploy was accepted, but the CLI does not print Deployed to ….