Skip to content
Raster
Esc
navigateopen⌘Jpreview
On this page

Machines

The core resource — sizes, states, the lifecycle, and the event log.

A machine is a persistent Linux computer with a desktop, a shell and a browser. It keeps its disk across a stop and a start, and loses it only when it is deleted.

Sizes

Sizes are named rather than free-form. The concrete numbers are server-owned, so they can change without a client release and so quota and billing have a fixed set to reason about.

Size vCPU Memory Disk Weight
small 2 4 GB 10 GB 0.5 std-hours
standard 4 8 GB 25 GB 1 std-hour
large 8 16 GB 50 GB 2 std-hours

The weight is what a size costs per hour of runtime, expressed in standard-hours. There is one compute price and the per-size rates are derived from it, so they cannot drift apart. Read the live numbers from GET /v1/plans rather than hardcoding them — no price or weight is hardcoded in any client, which is what lets an older SDK display a newer catalog.

Your plan caps the largest size you may create. See plans and billing.

States

creating → running ⇄ stopping → stopped → starting → running
                  ↘ error        ↘ deleting
State Meaning
creating accepted; the substrate has not reported it running yet
running booted. not a promise that the desktop answers
stopping shutting down; the disk is kept
stopped not running, disk intact, still billed for storage
starting booting from its existing disk
error a rest state, not a transient one
deleting on its way out; the disk is going

Transitions are guarded in the database — the update only applies if the row is still in the state that was read — so two concurrent lifecycle calls cannot both win. An illegal transition is a conflict naming both states.

Running is not ready

POST /v1/machines returns once the machine is running. A machine has a working shell before it has a working desktop, so running says nothing about whether the screen routes will answer.

const machine = await client.machines.create({ size: "small" });
const display = await machine.waitForDesktop();

waitForDesktop polls GET /v1/machines/{id}/display until it reports up. Until then that route answers compute_unavailable, which is a normal boot state to wait through rather than a failure to report — which is why the SDKs treat it as retryable.

Creating

await client.machines.create({
  name: "research", // lowercase, digits and dashes; generated when omitted
  size: "small",
  template: "tpl_...", // start from a template's captured disk
  region: "us-east", // a placement hint
  metadata: { owner: "ana" },
  start: true, // false is born `stopped`
});

metadata is caller-defined and echoed back unchanged: up to 64-character keys and 1024-character values.

A template pins the image; the size is still this call’s to choose. See persistence.

Stopping, starting, deleting

  • Stop keeps the disk. A stopped machine costs storage and no compute.
  • Start boots the existing disk. It is a normal boot, not a resume — nothing that was running survives.
  • Restart is a stop and a start.
  • Delete destroys the disk. Snapshots taken from it survive.

Containers

The machine ships podman with the docker CLI and compose v2. docker run, docker build, volume mounts and localhost networking all work. Container port publishing does not, and it fails without an error: the guest kernel lacks the netfilter support that port mapping needs, so the runtime cannot install a mapping, and instead runs every container in the machine’s own network namespace.

  • docker run -p 8080:80 is silently dropped. The container comes up with NetworkMode: host and an empty Ports; nothing listens on 8080, and the service is on the machine’s localhost at the container’s own port instead.
  • a compose ports: mapping on a compose-created network yields a connection that times out rather than one that is refused.

Write compose files for this machine with network_mode: host. That is how a stack is actually reachable here: a service on the machine’s network namespace is directly reachable on its own port — which is also the port to hand to published ports when you want it reachable from outside.

Forking

const copy = await machine.fork({ name: "branch-a" });

Fork copies a disk into a second machine. Without a snapshot id the current disk is captured first, which needs the machine running; with one, the fork stands on bytes that already exist. Either way the fork comes back in creating, exactly like a create, so waitForDesktop is what waits for a usable computer.

The fork owes the original nothing afterwards. Deleting the parent leaves it running.

The event log

for await (const event of machine.events()) console.log(event.type, event.created_at);

Newest first, cursor-paginated. The types are a closed set:

machine.created, machine.started, machine.ready, machine.stopped, machine.failed, machine.updated, machine.deleted, session.connected, session.control_acquired, session.control_released, session.disconnected, snapshot.started, snapshot.completed, snapshot.failed, snapshot.deleted, machine.restore_started, machine.restore_completed, machine.restore_failed, host.unhealthy.

The same events stream live over the gateway’s events channel.

What a machine never exposes

No substrate identifier, host address, environment id or guest bearer token appears in any response, and there is no route that would return one. That is a property of the API, not of the SDK wrapping it: the vocabulary is machines, screens, keyboards and terminals, all the way down.

Was this page helpful?