Descanto Docs
Concepts

Desktops and lifecycle

Lifecycle states, generation fencing, TTL/auto-hibernate, and first-wake env/setup.

A desktop is a real, persistent Linux machine: an id, an org, a tier, an image version, and a disk that survives hibernation. Its wire shape (DesktopJson) is what every desktop-returning route responds with:

{
  "id": "...",
  "org_id": "...",
  "tier": "small",
  "state": "awake",
  "image_version": "...",
  "generation": 3,
  "current_generation_id": "...",
  "idle_timeout_secs": 900,
  "billing_mode": "hourly",
  "host_id": "..."
}

Lifecycle states

A desktop's state field is one of:

StateMeaning
provisioningJust created, not yet placed on a host.
wakingA wake operation is in flight.
awakeRunning -- compute is billed, guest ops (exec/files/stream) are available.
hibernatingA hibernate operation is in flight.
hibernatedStopped -- disk state preserved, compute not billed.
destroyedPermanently gone, along with its disk. Irreversible.

wake, hibernate, and destroy are the three mutating transitions, triggered by POST /v1/desktops/{id}/{wake,hibernate,destroy}. Each of those returns an operation handle rather than blocking until the transition finishes -- see that page for the polling model.

Guest operations (exec, files, stream) only work while a desktop is awake; calling them against a desktop in any other state is a 409 Conflict (see API overview).

Generation fencing

Every desktop carries a generation, an integer that increments across its lifecycle transitions. Mutation requests can optionally include an expected_generation in the request body:

{ "expected_generation": 3 }

If the desktop's current generation doesn't match, the operation settles failed with a "generation mismatch" error, surfaced as 409 Conflict when observed through ?wait=true (or on the settled Operation itself when polled directly). This is a compare-and-swap-style guard against stale-read races -- e.g. two callers racing to wake the same desktop, or a caller acting on a desktop it fetched a while ago and hasn't refreshed since. Omitting expected_generation skips the check entirely.

Fork

POST /v1/desktops/{id}/fork creates independent copies of a hibernated desktop -- see Concepts: Forking for the full model.

TTL and auto-hibernate

Every desktop carries idle_timeout_secs -- seconds of guest idle time, measured from wake, before the desktop is automatically hibernated:

  • Set it at creation via CreateDesktopBody.idle_timeout_secs, or change it on an existing desktop with PATCH /v1/desktops/{id}:

    curl -sS -X PATCH "$BASE/desktops/$DESKTOP_ID" \
      -H "$AUTH" -H 'Content-Type: application/json' \
      -d '{"idle_timeout_secs": 3600}'
  • 0 means never auto-hibernate. A negative value is 400.

  • Omitting idle_timeout_secs on a PATCH is a no-op -- the current desktop is returned unchanged, per normal PATCH semantics.

  • The clock resets from each wake, not from the last guest activity inside it -- "idle" here means "time since this desktop last transitioned to awake," not an activity heuristic.

Ephemeral desktops auto-destroy, not just hibernate

A desktop created as ephemeral (currently only possible via fork with "ephemeral": true) does not just hibernate at its idle timeout -- it DESTROYs itself. Its idle_timeout_secs is forced to 900 (15 minutes) at creation regardless of what its source desktop's own value was. Treat an ephemeral fork as disposable: don't park anything in it you'd mind losing.

env and setup_script

POST /v1/desktops accepts two optional first-wake bootstrap fields:

{
  "tier": "small",
  "env": { "APP_ENV": "staging", "API_TOKEN": "..." },
  "setup_script": "#!/bin/sh\napt-get update && apt-get install -y ripgrep\n"
}
FieldWritten whereCaps
env/root/.canto/env in the guest, on the desktop's first successful wake100 keys / 64 KiB total serialized size; every key must match ^[A-Za-z_][A-Za-z0-9_]*$
setup_scriptRun once in the guest under hostd's reserved process_id = "setup", on first successful wake64 KiB

Both are 400 past their cap (or, for env, on any key that doesn't match the POSIX env-name rule). Neither is echoed back by GET/POST on the desktop -- they're write-only bootstrap inputs, not persisted/readable fields on the DesktopJson wire shape. setup_script runs exactly once, on the desktop's first wake; it does not re-run on subsequent wakes or after a hibernate/wake cycle.

On this page