Machine

One Machine class, two backends. The same Machine API from the smolmachines SDK (Node) / smol module (Python) runs against either an embedded in-process engine (the default — no server to start) or the smolfleet cloud. The backend is selected by ConnectOptions: {} / { target: 'local' } = local, { target: 'cloud', apiKey } (or the SMOL_CLOUD_TOKEN env var) = cloud. TypeScript methods are async (return Promises); the Python API is synchronous. For the standalone hosted REST API at api.smolmachines.com, see the Cloud API reference.

A machine is a microVM instance that provides an isolated execution environment.

Creating a Machine

Machine.create(config?, conn?) creates and starts a machine in one call. A name is generated if you omit one.

import { Machine } from 'smolmachines';

// Basic creation (local embedded engine — the default)
const machine = await Machine.create();

// With configuration
const machine = await Machine.create({
  name: 'configured-machine',
  mounts: [
    { source: '/host/code', target: '/workspace' }
  ],
  resources: {
    cpus: 2,
    memoryMb: 1024
  }
});
from smol import Machine, MachineConfig, MountSpec, ResourceSpec

# Basic creation (local embedded engine — the default)
machine = Machine.create()

# With configuration
config = MachineConfig(
    name="configured-machine",
    mounts=[
        MountSpec(source="/host/code", target="/workspace")
    ],
    resources=ResourceSpec(
        cpus=2,
        memory_mb=1024
    )
)
machine = Machine.create(config)

Choosing a backend

Pass a ConnectOptions as the second argument. Local is the default; cloud is selected by target: 'cloud' or by supplying an API key (directly or via SMOL_CLOUD_TOKEN).

// Cloud — image is required on the cloud target
const machine = await Machine.create(
  { image: 'python:3.12', resources: { cpus: 2, memoryMb: 1024 } },
  { target: 'cloud', apiKey: 'smk_...' }
);
console.log(await machine.url()); // public ingress URL (cloud)
from smol import Machine, MachineConfig, ConnectOptions

# Cloud — image is required on the cloud target
machine = Machine.create(
    MachineConfig(image="python:3.12"),
    ConnectOptions(target="cloud", api_key="smk_..."),
)
print(machine.url())  # public ingress URL (cloud)

Configuration Options

MachineConfig fields:

OptionTypeDefaultDescription
namestringauto-generatedMachine name / identifier
imagestring—Base image. Required for cloud; optional for local (you typically run(image, …) per command instead)
mountsMountSpec[]noneHost directories to mount (local)
portsPortSpec[]noneHost→guest port mappings (local)
resourcesResourceSpecengine defaultCPU / memory / disk / network — see Resources
persistentbooleanfalseKeep the machine record after the process exits (local)
autoStopSeconds / auto_stop_secondsnumbernoneAuto-stop after N idle seconds (cloud)
ttlSeconds / ttl_secondsnumbernoneDelete the machine after N seconds (cloud)
forkablebooleanfalseStart as a live-RAM fork base so the machine can be cloned with fork() (cloud)

Lifecycle Methods

state()

Returns the current state as a string: "created", "running", or "stopped".

const s = await machine.state(); // "running"
s = machine.state()  # "running"

url()

Public ingress URL for the machine’s first published port (cloud). Returns null/None on the local target, before a host port is allocated, or when no port is published.

const url = await machine.url(); // "https://…" or null
url = machine.url()  # "https://…" or None

stop()

Stops the machine. The machine can be restarted.

await machine.stop();
machine.stop()

delete()

Stops the machine and deletes its storage. This is permanent.

await machine.delete();
machine.delete()

connect()

Attach to an existing machine instead of creating one — to drive a machine made elsewhere (another process, the console, the REST API). Locally this re-opens a persisted machine by name (starting it if stopped); on cloud it looks up the machine by id (mach-…).

const machine = await Machine.connect('my-machine');
machine = Machine.connect("my-machine")

Execution Methods

exec and run run against the local embedded engine by default — there is no metering or billing on the local path.

exec()

Execute a command directly in the microVM. The command is an array of argv (not a shell string).

const result = await machine.exec(['ls', '-la', '/']);

// With options
const result = await machine.exec(['long-running-task'], {
  timeout: 60, // seconds
  env: { MY_VAR: 'value' },
  workdir: '/tmp'
});
from smol import ExecOptions

result = machine.exec(["ls", "-la", "/"])

# With options
result = machine.exec(
    ["long-running-task"],
    ExecOptions(
        timeout=60,  # seconds
        env={"MY_VAR": "value"},
        workdir="/tmp",
    ),
)

run()

Pull an image (if needed) and run a command in a container of it (local).

const result = await machine.run(
  'python:3.12-alpine',
  ['python', '-c', 'print("Hello")']
);

// With options
const result = await machine.run(
  'node:22-alpine',
  ['node', 'script.js'],
  {
    timeout: 30,
    env: { NODE_ENV: 'production' }
  }
);
from smol import ExecOptions

result = machine.run(
    "python:3.12-alpine",
    ["python", "-c", "print('Hello')"]
)

# With options
result = machine.run(
    "node:22-alpine",
    ["node", "script.js"],
    ExecOptions(timeout=30, env={"NODE_ENV": "production"}),
)

execStream() / exec_stream()

Execute a command and stream its output live as it is produced (local). Yields events: { kind: "stdout" | "stderr", data }, { kind: "exit", exitCode }, { kind: "error", message }.

for await (const ev of machine.execStream(['pip', 'install', 'numpy'])) {
  if (ev.kind === 'stdout' || ev.kind === 'stderr') process.stdout.write(ev.data);
  else if (ev.kind === 'exit') console.log('exit', ev.exitCode);
}
for ev in machine.exec_stream(["pip", "install", "numpy"]):
    if ev["kind"] in ("stdout", "stderr"):
        print(ev["data"], end="")
    elif ev["kind"] == "exit":
        print("exit", ev["exit_code"])

Files

readFile() / read_file()

Read a file from the machine. Returns the raw bytes.

const buf = await machine.readFile('/tmp/out.bin'); // Buffer
data = machine.read_file("/tmp/out.bin")  # bytes

writeFile() / write_file()

Write a file into the machine. data may be a string or bytes; an optional file mode may be supplied.

await machine.writeFile('/workspace/script.py', 'print("hi")');
await machine.writeFile('/workspace/run.sh', '#!/bin/sh\necho hi\n', 0o755);
machine.write_file("/workspace/script.py", 'print("hi")')
machine.write_file("/workspace/run.sh", "#!/bin/sh\necho hi\n", 0o755)

Images

pullImage() / pull_image()

Pull an OCI image into the machine’s storage (local). Returns an ImageInfo.

const info = await machine.pullImage('python:3.12-alpine');
info = machine.pull_image("python:3.12-alpine")

listImages() / list_images()

List cached OCI images (local).

const images = await machine.listImages();
images = machine.list_images()

fork()

Fork a running, forkable machine into a new clone via copy-on-write live RAM + disks (cloud). The clone inherits the golden’s warm in-memory state; forks are fast (~tens of ms). The golden must have been created with forkable: true.

const golden = await Machine.create(
  { image: 'python:3.12', forkable: true },
  { target: 'cloud', apiKey: 'smk_...' }
);
const clone = await golden.fork('clone-1');
golden = Machine.create(
    MachineConfig(image="python:3.12", forkable=True),
    ConnectOptions(target="cloud", api_key="smk_..."),
)
clone = golden.fork("clone-1")

Best Practices

Use Context Managers (Python)

Python’s Machine is a context manager — it deletes the machine on exit:

with Machine.create(MachineConfig(name="worker")) as machine:
    machine.exec(["whoami"])
# Automatically deleted, even on error

Use try/finally (TypeScript)

There is no helper wrapper — clean up with try/finally:

const machine = await Machine.create({ name: 'worker' });
try {
  await machine.exec(['whoami']);
} finally {
  await machine.delete();
}

Reuse Machines

For multiple commands, reuse the same machine rather than creating one per command:

// Good — one machine, many commands
const machine = await Machine.create({ name: 'worker' });
try {
  for (const task of tasks) {
    await machine.exec(task.command);
  }
} finally {
  await machine.delete();
}