> ## Documentation Index
> Fetch the complete documentation index at: https://docs.ns.rocks/llms.txt
> Use this file to discover all available pages before exploring further.

# Machines

> Create, inspect, configure, connect to, and clean up machines.

Machines are the live runtime unit in Nullspace. They are created from a
template and expose command, filesystem, desktop, PTY, lifecycle, and port URL
helpers.

## Create

```python theme={"theme":{"light":"one-light","dark":"one-dark-pro"}}
from nullspace import Machine

with Machine.create(
    template="base",
    vcpus=2,
    memory_mb=512,
    timeout=300,
    on_timeout="pause",
    auto_resume=True,
    envs={"APP_ENV": "dev"},
    metadata={"user_id": "user_123"},
    cwd="/workspace",
    internet_access=True,
    network={"mask_request_host": "localhost:${PORT}"},
) as machine:
    print(machine.id)
```

Use `on_timeout="pause", auto_resume=True` when preview URL traffic should wake
a paused machine before forwarding the original request. `auto_resume=True` is
only valid with pause/hibernate timeout behavior.

Use a template warm pool for ready capacity before a burst:

```python theme={"theme":{"light":"one-light","dark":"one-dark-pro"}}
machine = Machine.create(
    template="team/fastapi-app:stable",
    warm_pool="twp_fastapi_small",
    warm_pool_mode="prefer",
    warm_pool_wait_ms=1500,
)
print(machine.get_info().warm_pool_checkout)
```

`warm_pool_mode="require"` fails with `warm_pool_unavailable` when no matching
ready instance is available. Snapshot restore, resume, fork, hibernate, and
pause do not use template warm-pool checkout.

## Egress controls

Restrict a machine's outbound network access with a typed egress policy. You can
set it at create time and change it on a running machine without recreate.

```python theme={"theme":{"light":"one-light","dark":"one-dark-pro"}}
from nullspace import Machine, NetworkEgressPolicy, EgressRateLimit

# Block all outbound traffic at create time (fail-closed).
with Machine.create(
    template="base",
    network={"egress": NetworkEgressPolicy.deny_all().to_dict()},
) as machine:
    # Later, broaden to an allowlist with a best-effort rate limit.
    machine.update_egress(
        NetworkEgressPolicy.custom(
            allow_cidrs=["1.1.1.1/32"],
            rate_limit=EgressRateLimit(kbit_per_sec=1000, burst=64),
        )
    )
```

Modes are `allow_all`, `deny_all`, and `custom`. `custom` carries IPv4
`allow_cidrs`/`deny_cidrs` and an optional outbound `rate_limit` (set one of
`kbit_per_sec` or `pps`, with an optional `burst`). The policy is IPv4-only and
does not do domain/DNS filtering. Tightening (e.g. moving to `deny_all`) fails
closed; broadening only succeeds once the new rules apply. The policy survives
hibernate/resume. The rate limit is best-effort shaping, not a hard quota.

The legacy `internet_access`, `network.deny_out`, and `network.allow_out` fields
still work but cannot express a rate limit; `internet_access=False` is
fail-closed and cannot be re-opened by a stray allow rule.

## Inspect and connect

```python theme={"theme":{"light":"one-light","dark":"one-dark-pro"}}
machines = Machine.list(limit=20)
for item in machines.items:
    print(item.id, item.status, item.template)

while machines.has_next:
    for item in machines.next_items():
        print(item.id)

machine = Machine.connect("mch_...")
print(machine.get_info().status)
```

`Machine.connect(id)` is an explicit reconnect operation. If `id` currently
points at a paused snapshot, `connect()` resumes it and returns the running
machine execution. `Machine.get_info_by_id(id)` is read-only and does not wake a
paused machine.

Pass `fields=["id", "status"]` to `Machine.list()` or
`Machine.get_info_by_id()` for compact raw dictionaries instead of full model
objects. This is useful for dashboards and polling loops that only need a few
attributes.

## Preview URLs and timeout

```python theme={"theme":{"light":"one-light","dark":"one-dark-pro"}}
machine.set_timeout(120, timeout_action="hibernate")
host_info = machine.get_host_info(8080)
print(host_info.url, host_info.websocket_url)
print(machine.get_url(8080))
preview = machine.create_signed_preview_url(8080, expires_in_seconds=900)
readiness = machine.wait_for_preview(8080, timeout_secs=30)
print(readiness.ready, preview.grant.id)
print(machine.get_metrics())
```

For machines created with `on_timeout="pause", auto_resume=True`, HTTP and
websocket traffic to those preview URLs wakes the paused machine, waits for the
bounded resume window, and then forwards to the resumed execution. If
auto-resume is disabled or the wake does not complete in time, the preview URL
returns `503 Service Unavailable` with `Retry-After: 5`.

Use `machine.create_preview_proxy_target(8080)` when your application proxy
should forward to Nullspace with `x-nullspace-preview-proxy-token` instead of
putting signed edge tokens in browser-visible URLs.
Use `redact_preview_url(...)` or `redact_preview_token(...)` before printing
preview credentials to logs or terminal output.

## Lifecycle events

Use `machine.stream_events()` to tail lifecycle events related to one machine
without polling:

```python theme={"theme":{"light":"one-light","dark":"one-dark-pro"}}
from nullspace import Machine

machine = Machine.connect("mch_123")
try:
    for event in machine.stream_events():
        print(event.operation, event.status, event.snapshot_id)
finally:
    machine.close()
```

```python theme={"theme":{"light":"one-light","dark":"one-dark-pro"}}
async for event in machine.stream_events(live_only=True):
    print(event.id, event.operation)
```

The iterator uses `GET /v1/machines/{id}/lifecycle/ws`, reconnects
automatically, and resumes with `after_event_id` from the last yielded event.
Use `live_only=True` to skip the initial replay while still keeping reconnects
gap-resistant after the first event.

Shared volume data is external durable storage. Resume and fork remount shared
volume attachments, but do not make VM memory or mutable rootfs state portable
across incompatible runtime hosts.

## One-shot runs

```python theme={"theme":{"light":"one-light","dark":"one-dark-pro"}}
result = Machine.run_once(
    "python3",
    ["-c", "print('hello')"],
    template="base",
    timeout=60,
)
print(result.stdout)
```

Use `run_once` for simple tasks where the SDK should create a machine, run the
code, and clean up without exposing a long-lived handle.

## Async API

```python theme={"theme":{"light":"one-light","dark":"one-dark-pro"}}
from nullspace import AsyncMachine

async with await AsyncMachine.create(template="base", timeout=300) as machine:
    result = await machine.commands.run("echo async", shell=True)
    print(result.stdout)

    async for event in machine.stream_events(live_only=True):
        print(event.operation, event.status)
        break
```

Async helpers follow the same pagination and `fields` behavior as the
synchronous SDK.

Concepts: [Create](../../concepts/create), [Destroy](../../concepts/destroy),
[Fork](../../concepts/fork). API reference:
[createMachine](../../api-reference), [getMachine](../../api-reference),
[getHost](../../api-reference), [setTimeout](../../api-reference).
