Github|...

Deploying

One command builds your images, provisions infrastructure, runs migrations and cuts over DNS.

Deploy

spky deploy

The deploy command performs these steps automatically:

  1. Validates your sp00ky.yml configuration
  2. Builds Docker images for each app (linux/amd64)
  3. Pushes images to your team’s private build registry on Sp00ky Cloud: only the layers it does not have yet, so an unchanged image costs seconds. Apps on a machine pool or a dedicated machine upload an image archive instead, which their machines download.
  4. Provisions infrastructure (SurrealDB, Scheduler, SSP instances)
  5. Runs migrations against the cloud database
  6. Deploys your backend and frontend containers
  7. Configures DNS and SSL certificates

You’ll see real-time progress as each step completes. These are the steps of a VM project; a Cloudflare project has no containers, see Cloudflare projects.

Note

Run spky lint before deploying to catch config issues early.

Flags

FlagEffect
--upgradeAlso move the SSP and Scheduler to the newest published build. Only for a project whose version is any or unset: a pinned version stays where sp00ky.yml put it, and newest already rolls on every deploy. The swap is blue/green, see Zero-downtime upgrades. On pay as you go it also moves a SurrealDB 3 database to SurrealDB 4 after a backup, whatever version says, see SurrealDB version.
--only api,webBuild and deploy just these apps. Everything else keeps running untouched.
--cleanWipe the scheduler’s persistent volume first. Last resort: the scheduler self-heals stale snapshot state on startup, and spky verify --fix (or POST /admin/resync on the scheduler) repairs it without a wipe. Your SurrealDB data is not touched either way.
--force-schemaRe-apply the internal schema and remote functions even when the hash says nothing changed. Drift recovery.
--cache-bustClients reloading onto this version clear service-worker caches first.
--mandatoryClients reload immediately instead of being offered a notification. Broken-build kill switch.
spky deploy --upgrade

--only is the one to reach for during normal iteration: a frontend-only change doesn’t need the backend rebuilt.

A backend on a dedicated machine shows up in the deploy table as machine/<name> with its public address, moving through creating, booting and serving. A deploy of such a backend takes a few minutes longer: a fresh VM boots beside the old one and only takes over once it answers its healthcheck.

Zero-downtime upgrades

Upgrading or restarting the Scheduler and SSP never takes sync down. Sp00ky Cloud starts the new container first and stops the old one only after the new one has taken over:

  • Scheduler. The new scheduler starts on the same volume and asks the old one to hand over. Requests that arrive during the swap wait a moment (typically well under a second) instead of failing, writes keep succeeding, and SSPs keep their registration.
  • SSP. The new SSP loads its rows from the old one’s checkpoints and follows every change as a standby, without writing anything. Once it has caught up it takes over the views; only views whose results differ are republished. Then the old SSP is stopped.

If the new container never becomes ready, the old one keeps serving and the restart or deploy reports the failure. --clean (which wipes the scheduler volume) and SurrealDB upgrades still stop the old container first.

Failed deploys and rollbacks

A deploy that fails does not take the project down: what serves keeps serving, custom domains included, until new containers are up. When a deploy fails before it ever served (its migrations failed, it never left the queue, its apps never came up healthy), Sp00ky Cloud falls back by itself within seconds: it deploys again what served last, as a new version marked auto rollback in the history. A failed rollback is not rolled back again, and a deployment that served and failed later is left to autoheal.

To go back by hand, open Deployments in the console and choose Roll back on a version that served. Any of the newest ten deployments can be brought back; the images of older ones may be gone. A rollback deploys that version’s apps (backends and frontends, their images and settings) as a new version. The scheduler, the SSPs and SurrealDB stay on the versions that run now, and no migrations run, since migrations only go forward. Agents can do the same with the deploy_rollback tool of the Sp00ky Cloud MCP server, which takes the version.

Cloudflare projects

A Cloudflare project (plan payg, pay as you go) has no VMs and no containers of its own except its database. spky deploy puts each part of it here:

PartWhere it runs
Web appStatic assets on Workers, built from deploy.static. No Docker image.
SSPA Durable Object in the project’s SSP Worker. It also runs your outbox jobs; there is no separate scheduler.
Worker backendsDynamic Workers inside the SSP Worker. spky deploy bundles each worker.entry with esbuild and uploads it with the SSP Worker. Their expose paths are served on https://<slug>.<domain>.
DatabaseWith surrealdb.hosting: cloud, a SurrealDB of the project’s own (1 GiB), run by Sp00ky Cloud at {slug}-db.<domain> like a VM project’s; spky deploy bakes its endpoint into the web app. With hosting: external your own database.
SchemaPushed to the SSP after the deploy, with your schedules, workflows and job policies. spky push retries it.

Jobs, schedules and workflows on Cloudflare

They behave as on a VM: the same outbox, retries, leases, schedules and workflows. The SSP’s Durable Object runs them on its alarm, and only sets one when something is due, so an idle project costs nothing and the object is evicted between jobs.

  • Wakes follow what is due. A delayed job, a step.sleep, a retry’s backoff and a schedule’s next fire each wake the SSP at that moment (a job about a second late), not on a polling cadence. A delay of one day is one wake, not one a minute.
  • Long waits live in the database. A delay or backoff longer than 30 seconds is not a timer in memory: the job row waits, and an eviction cannot lose it. Shorter ones wait in memory.
  • Background work does not load your views. A cron fire or a job wake runs on the SSP without rebuilding its live queries first, and keeps running while the SSP is still starting (or failing to start) after a deploy. A job created during that time runs at once.
  • At least once. A job in flight when the object is evicted runs again when its lease runs out. Make job routes safe to repeat.

Docker backends do not run on a Cloudflare project. Make the backend a Worker backend, or host it yourself (hosting: external) at a public baseUrl the SSP Worker can reach. --only works as on a VM: spky deploy --only api bundles and ships just that Worker backend, and the others keep their last version.

A Cloudflare project has no migrations: every deploy pushes the whole schema as the desired state. Tables, fields, events, functions, params, analyzers and buckets are replaced with what your files say. Two kinds keep their first definition: DEFINE ACCESS (overwriting a record access that has no WITH JWT key gives it a new random key, which signs every user out) and DEFINE INDEX (an overwrite rebuilds the index row by row). To change one of those, write OVERWRITE into that statement yourself for one deploy.

Your own VMs (Pro)

A Pro project runs on two VMs of its own. The project host carries the scheduler, the SSPs, your backends and frontends and the proxy; the database VM carries SurrealDB, with its data on a volume at the provider. The shared host that Sp00ky Cloud runs on keeps only the control plane. The infrastructure is included in the Pro price.

VMDefault typeMemory
Database VMcax21 (ARM)8 GB, of which the database may use 6 GB
Project hostcx338 GB

If cx33 cannot be had when the project is set up, the project host starts on cx23 (4 GB), or on cpx22 for a new signup, and upgrades to cx33 by itself in a quiet window, 03:30 to 05:30 UTC. A project admin can ask for it sooner. spky host status shows each VM and any upgrade waiting, and the console shows the same under Settings, Machines.

# The project's VMs: kind, type, location, status, any upgrade waiting
spky host status

# Upgrade the project host now instead of in its quiet window
spky host upgrade

# Move a pro project that still runs on the shared host onto VMs of its own
spky project move
Warning

An upgrade restarts the project host: web, API and live sync are down for about two minutes. The database VM is not touched. spky host upgrade asks before it starts, and refuses while a backup, a database upgrade, a move or a deploy is running, saying which.

A Pro project that still runs on the shared host moves onto its own VMs with spky project move. The data is copied across while the project keeps running, and the move then waits for your go unless you passed --no-hold.

spky project move                # copy the data, then wait for the go
spky project move --no-hold      # cut over by itself when the data is across
spky project move status         # the current or last move
spky project move watch          # follow it
spky project move cutover        # go ahead; the project is down for a few minutes
spky project move abort          # before the cutover: nothing changes
spky project move rollback       # after it: back to the shared host

cutover and rollback ask before they start, and say that the project goes down. abort stops a move before the cutover and leaves everything where it was. After the cutover the project is watched for a while, and rollback takes it back to the shared host in that time: the project goes down and its data travels back. Only a Pro project can move.

When a project is destroyed, the volumes of its VMs are kept for 7 days.

Checking Status

spky status

Shows the current deployment version, status, and all running VMs with their roles and versions.

The operator dashboard

Every deployment also serves a dashboard in the browser, at https://<slug>-admin.<domain>/admin. spky status prints the URL alongside the others.

It is the same information spky status and spky logs give you, live and without a terminal: scheduler and SSP health, end-to-end sync latency, backend health, workflow runs as they happen, and a live log tail. It also carries the actions, so you can restart an SSP, retry a failed workflow run, take a backup or upgrade images from the browser.

Nobody can sign in until you grant someone access with spky admin add <user>. See the admin dashboard for sign-in, the actions and their consequences, and the MCP server it serves for AI agents.

Scaling

Scale your SSP instances horizontally:

# Scale to 3 SSP instances
spky scale ssp 3

You can also set the default SSP count in your sp00ky.yml:

deployment:
  sspCount: 2

Bucket Storage

Buckets with a file: backend keep their files on a volume of their own, mounted at /buckets in your SurrealDB container. Size it in sp00ky.yml:

deployment:
  storage:
    sizeGB: 100
  • A Pro project on its own VMs: the scheduler no longer mounts this volume, so the dashboard does not list storage subfolders for such a project.
  • It is a real disk of that size. When it is full, uploads fail; nothing else on the host is affected. Raise sizeGB and redeploy (or push) to grow it in place, without restarting the database.
  • It only grows. A deploy that asks for less than the volume already has is refused with the current size. Removing the key keeps the volume as it is.
  • The smallest volume is 10 GB. Asking for less gets 10 GB, and you are billed for what you asked for.
  • Billing: Starter includes 5 GB and Pro 20 GB. Above that, storage is $0.10 per GB-month, prorated by the hour, on a separate invoice at the start of each month. spky cloud billing usage (and the billing_usage MCP tool) shows the month so far.

See File Buckets for defining a bucket on this volume.

Infrastructure Environment Variables

Backends read their environment from the encrypted vault (spky env set). The infrastructure containers Sp00ky Cloud owns — SSP, Scheduler, SurrealDB — take a fixed environment assembled by the control plane. deployment.env is the escape hatch for setting a runtime knob on them, per role and per project:

deployment:
  env:
    ssp:
      SPKY_SSP_MERGE_VIEWS: "true"
      SPKY_SSP_DB_TIMEOUT_SECS: "15"
    scheduler:
      SPKY_SOME_FLAG: "1"
Note

These are not secrets. Unlike spky env set, values are stored unencrypted in the project’s cloud config. Put credentials in the vault, not here.

Keys the control plane manages itself are refused rather than silently overridden: SPKY_DB_*, SPKY_AUTH_SECRET, SPKY_SCHEDULER_URL, SPKY_SSP_ID, SPKY_SSP_ADVERTISE_ADDR, the SSP snapshot/arena directories, SPKY_JOB_CONFIG, and S3_*. Those carry credentials, per-slot identity, or values derived from your deployed backends — overriding them breaks the deployment in ways that are hard to see (a stale SPKY_JOB_CONFIG disables job routing while health checks still pass).

Values take effect when containers are next created, so run spky deploy or spky restart after changing them. Omitting the key entirely keeps the previous setting; an explicit empty map clears it.

deployment.env, the sync: block and the top-level anonymousLiveQueries are read from sp00ky.yml on every deploy, whether spky deploy runs it or a git-linked build does. Both paths write the same values, so a push after a CLI deploy never reverts them.

One pair is set for you: with sync.transport: changefeed in sp00ky.yml, spky deploy merges SPKY_INGEST_TRANSPORT and SPKY_CHANGEFEED_RETENTION into the scheduler and ssp roles (your own keys win on a clash), so the scheduler tails the feed the schema just gained. See Sync transport. Likewise, with sync.queryAllowlist set to warn or enforce, spky deploy merges SPKY_SSP_QUERY_ALLOWLIST=<mode> into the ssp role and publishes the committed allowlist JSON as _00_query_allowlist rows keyed by the frontend’s package.json version; under enforce a missing or stale JSON fails the deploy. See the Query allowlist guide.

SPKY_SSP_DB_TIMEOUT_SECS is the knob most worth knowing here: it bounds how long a single SSP database call may take before it fails and the connection is rebuilt (default 15s, 0 disables). It exists because the SurrealDB HTTP engine has no request timeout of its own and every SSP database call shares one connection, so an unbounded stall parks every route that touches the database. See environment variables and GET /health.

Log Level

logLevel in sp00ky.yml sets RUST_LOG on the Scheduler and SSP containers. Default is info.

# Single value applies everywhere
logLevel: info

Use a { dev, cloud } map to differ per environment:

# Per-environment: chatty locally, quiet in production
logLevel:
  dev: trace
  cloud: info

The value is forwarded to tracing-subscriber, so target-specific directives work:

# Tracing-style target=level directives also work
logLevel: info,ssp=debug,scheduler=trace

Each spky deploy sends the resolved cloud value to Sp00ky Cloud, which restarts the Scheduler and SSP containers with the new RUST_LOG. Use --upgrade if you also want the newest SSP and Scheduler build at the same time (it does not move a pinned version).

Note

At trace, the Scheduler logs every replica query and every record it ingests during bootstrap. Useful for diagnosing “why isn’t this row showing up?” See Logs & Monitoring.