---
title: Machines
description: 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](/docs/reference/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.

```ts
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

```ts
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](/docs/reference/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](/docs/reference/ports) when you want
it reachable from outside.

## Forking

```ts
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

```ts
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](/docs/reference/realtime).

## 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.
