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

# SmolVM Python class reference

> SmolVM class reference: the Python API for creating microVM sandboxes, running commands over the selected control channel, and managing the full sandbox lifecycle.

## Overview

The `SmolVM` class provides a user-friendly interface for creating and managing microVMs. It supports automatic configuration, context manager usage, and command execution over the selected control channel.

## Constructor

```python theme={null} theme={null}
SmolVM(
    config: VMConfig | None = None,
    *,
    vm_id: str | None = None,
    image: str | None = None,
    data_dir: Path | None = None,
    socket_dir: Path | None = None,
    backend: str | None = None,
    os: GuestOS | str | None = None,
    memory: int | None = None,
    disk_size: int | None = None,
    ssh_user: str = "root",
    ssh_key_path: str | None = None,
    ssh_password: str | None = None,
    comm_channel: Literal["ssh", "vsock"] | None = None,
    callbacks: list[Callback] | None = None,
)
```

<ParamField path="config" type="VMConfig | None" default="None">
  VM configuration. Mutually exclusive with `vm_id`. If omitted (and `vm_id` is omitted), SmolVM auto-creates a default configuration that can run commands over vsock or SSH.
</ParamField>

<ParamField path="vm_id" type="str | None" default="None">
  ID of an existing VM to reconnect to. Mutually exclusive with `config`.
</ParamField>

<ParamField path="image" type="str | None" default="None">
  Disk image to boot. Accepts:

  * A published S3 image URI (`s3://bucket/images/alpine/`) — the manifest defines the OS, so `os=` must be omitted.
  * A local `qcow2` file path (`/var/lib/vms/win11.qcow2`, `~/win11/disk.qcow2`) or `file://` URI — requires `os=` since the file alone doesn't self-identify.

  Mutually exclusive with `config` and `vm_id`. Local images are only supported for `os="windows"` in this release; see [Windows guests](/smolvm/guides/windows-guests).
</ParamField>

<ParamField path="os" type="GuestOS | str | None" default="None">
  Guest operating system. Valid values: `"alpine"`, `"ubuntu"`, `"windows"`.

  * For auto-config mode (no `image=`), selects the Linux distribution.
  * For local images, **required** — tells SmolVM which OS is inside the file. Must be `"windows"` for the local-image path in this release.
  * For S3 images, must be omitted — the image manifest already records the OS.
</ParamField>

<ParamField path="data_dir" type="Path | None" default="None">
  Override the default data directory for VM state storage.
</ParamField>

<ParamField path="socket_dir" type="Path | None" default="None">
  Override the default socket directory.
</ParamField>

<ParamField path="backend" type="str | None" default="None">
  Runtime backend override. Options: `"firecracker"`, `"qemu"`, or `"auto"`.
</ParamField>

<ParamField path="memory" type="int | None" default="None">
  Guest memory in MiB for auto-config mode (when both `config` and `vm_id` are omitted). Default is 512 MiB.
</ParamField>

<ParamField path="disk_size" type="int | None" default="None">
  Root filesystem size in MiB for auto-config mode. Default is 512 MiB. Minimum is 64 MiB.
</ParamField>

<ParamField path="ssh_user" type="str" default="root">
  SSH user for command execution via the `run()` method. For Windows guests, pass the local Windows account name (for example `"Administrator"`) — `root` does not exist on Windows OpenSSH.
</ParamField>

<ParamField path="ssh_key_path" type="str | None" default="None">
  SSH private key path. If omitted, SmolVM first tries default SSH auth, then falls back to `~/.smolvm/keys/id_ed25519`.
</ParamField>

<ParamField path="ssh_password" type="str | None" default="None">
  SSH password. Use this when the guest only accepts password authentication — common for Windows qcow2s that ship with OpenSSH Server but no preinstalled key. When set, SmolVM skips key-based auth so paramiko does not silently prefer a key over your password.
</ParamField>

<ParamField path="comm_channel" type="Literal[&#x22;ssh&#x22;, &#x22;vsock&#x22;] | None" default="None">
  Host-to-guest control channel for `run()`, file transfer, and env var operations. Leave unset for auto-selection (vsock when the SmolVM guest agent answers on a Linux QEMU host, otherwise SSH). Pass `"vsock"` to require vsock — SmolVM raises if the agent does not answer instead of falling back. Pass `"ssh"` to force the SSH path. See [control channel](/smolvm/concepts/control-channel) for details.
</ParamField>

<ParamField path="callbacks" type="list[Callback] | None" default="None">
  Callbacks that fire around each `run()` call. Use them to inspect, log, or block commands before they reach the guest. See [Run callbacks and safety hooks](/smolvm/features/callbacks) for the full guide and the [Callback reference](/smolvm/api/callbacks).
</ParamField>

### Raises

* `ValueError`: If both `config` and `vm_id` are provided, or if `memory`/`disk_size` are set outside auto-config mode.

## Class Methods

### from\_id

```python theme={null} theme={null}
@classmethod
SmolVM.from_id(
    vm_id: str,
    *,
    data_dir: Path | None = None,
    socket_dir: Path | None = None,
    backend: str | None = None,
    ssh_user: str = "root",
    ssh_key_path: str | None = None,
    ssh_password: str | None = None,
    comm_channel: Literal["ssh", "vsock"] | None = None,
) -> SmolVM
```

Reconnect to an existing VM by ID. Pass `ssh_password=` for Windows guests that use password-based SSH. Pass `comm_channel=` to override the host-to-guest control channel — see [control channel](/smolvm/concepts/control-channel).

<ParamField path="vm_id" type="str" required>
  VM identifier to reconnect to.
</ParamField>

<ResponseField name="return" type="SmolVM">
  A SmolVM instance bound to the existing VM.
</ResponseField>

#### Raises

* `VMNotFoundError`: If no VM with this ID exists.

### from\_image

```python theme={null} theme={null}
@classmethod
SmolVM.from_image(
    image: BootImage,
    *,
    vm_id: str | None = None,
    name_prefix: str = "sbx",
    data_dir: Path | None = None,
    socket_dir: Path | None = None,
    backend: str | None = None,
    arch: str | None = None,
    vcpus: int = 1,
    memory_mb: int = 512,
    guest_os: GuestOS | str = GuestOS.ALPINE,
    network: Literal["tap", "slirp"] | None = None,
    port_forwards: list[PortForwardConfig | dict] | None = None,
    vsock: VsockConfig | dict | None = None,
    comm_channel: Literal["ssh", "vsock"] | None = None,
    disk_mode: Literal["isolated", "shared"] = "isolated",
    disk_size_mb: int | None = None,
    grow_filesystem: bool = False,
    ssh_user: str = "root",
    ssh_key_path: str | None = None,
    ssh_password: str | None = None,
    internet_settings: InternetSettings | dict | None = None,
    mounts: list[str] | None = None,
    writable_mounts: bool = False,
    callbacks: list[Callback] | None = None,
) -> SmolVM
```

Launch a VM from a custom [`BootImage`](/smolvm/guides/custom-images#describe-a-bootable-image) without writing a `VMConfig` by hand. The image supplies the rootfs, kernel, and boot arguments; `from_image` resolves the backend, architecture, and any missing base kernel, then applies the per-VM runtime knobs you pass in.

Use this when you have built a custom image (for example with `DockerRootfsBuilder`) and want a one-line launcher that still lets you tune memory, networking, port forwards, vsock, and the host-to-guest control channel.

<ParamField path="image" type="BootImage" required>
  Image to boot. Direct-kernel images may omit `kernel_path` — SmolVM downloads the matching base kernel automatically. Custom images without an SSH server can still boot, but command helpers such as `vm.run(...)` require an SSH-capable image or guest agent.
</ParamField>

<ParamField path="vm_id" type="str | None" default="None">
  Custom VM identifier. Defaults to an auto-generated name using `name_prefix`.
</ParamField>

<ParamField path="name_prefix" type="str" default="sbx">
  Prefix used when SmolVM generates a `vm_id`.
</ParamField>

<ParamField path="backend" type="str | None" default="None">
  Runtime backend (`"firecracker"`, `"qemu"`, or `"libkrun"`). Defaults to the backend recorded on the image, otherwise normal auto-selection. Firmware-boot images require `"qemu"`.

  SmolVM's public guides focus on Firecracker and QEMU because they have the broadest current support.
</ParamField>

<ParamField path="arch" type="str | None" default="None">
  Guest architecture (`"amd64"` or `"arm64"`). Defaults to the image's recorded arch, otherwise the host arch.
</ParamField>

<ParamField path="vcpus" type="int" default="1">
  Number of guest vCPUs.
</ParamField>

<ParamField path="memory_mb" type="int" default="512">
  Guest memory in MiB.
</ParamField>

<ParamField path="guest_os" type="GuestOS | str" default="GuestOS.ALPINE">
  Guest OS hint used by SmolVM's lifecycle helpers (`"alpine"`, `"ubuntu"`, or `"windows"`).
</ParamField>

<ParamField path="network" type="Literal[&#x22;tap&#x22;, &#x22;slirp&#x22;] | None" default="None">
  QEMU network mode. Use `"slirp"` to enable user-mode networking with `port_forwards`. Ignored for non-QEMU backends.
</ParamField>

<ParamField path="port_forwards" type="list[PortForwardConfig | dict] | None" default="None">
  Host-to-guest port forwards. Only valid with `backend="qemu"` and `network="slirp"`.
</ParamField>

<ParamField path="vsock" type="VsockConfig | dict | None" default="None">
  Vsock configuration for host-to-guest control or custom services.
</ParamField>

<ParamField path="comm_channel" type="Literal[&#x22;ssh&#x22;, &#x22;vsock&#x22;] | None" default="None">
  Host-to-guest control channel for `run()` and file transfers. See [control channel](/smolvm/concepts/control-channel).
</ParamField>

<ParamField path="disk_mode" type="Literal[&#x22;isolated&#x22;, &#x22;shared&#x22;]" default="isolated">
  Whether each VM gets its own per-VM disk overlay (`"isolated"`) or shares the base image (`"shared"`).
</ParamField>

<ParamField path="disk_size_mb" type="int | None" default="None">
  Target size in MiB for the per-VM disk. SmolVM only grows the disk — pick a value at least as large as the image's current size. Requires `disk_mode="isolated"` (the default); shared base images are never resized. Works for both `raw-ext4` and `qcow2` rootfs formats.
</ParamField>

<ParamField path="grow_filesystem" type="bool" default="False">
  After resizing the disk, grow the guest filesystem to fill it. Only supported for `raw-ext4` rootfs images and requires `e2fsprogs` (`e2fsck`, `resize2fs`) on the host. Leave `False` for qcow2 images and grow the filesystem from inside the guest instead.
</ParamField>

<ParamField path="ssh_user" type="str" default="root">
  SSH user for `run()`. Ignored when the image is not SSH-capable.
</ParamField>

<ParamField path="ssh_key_path" type="str | None" default="None">
  SSH private key path.
</ParamField>

<ParamField path="ssh_password" type="str | None" default="None">
  SSH password. Use this for images that ship password-only SSH.
</ParamField>

<ResponseField name="return" type="SmolVM">
  A SmolVM instance bound to the new VM. Call `start()` (or use the context manager) to boot it.
</ResponseField>

#### Raises

* `ValueError`: If the requested backend, arch, network mode, or `port_forwards` are incompatible with the image (for example, `port_forwards` outside QEMU slirp, or a firmware image on Firecracker).
* `SmolVMError`: If a disk resize is requested but cannot be honored — for example, `disk_mode="shared"`, `grow_filesystem=True` on a qcow2 image, a `disk_size_mb` smaller than the current disk, or `e2fsprogs` missing on the host.

#### Example

```python theme={null} theme={null}
from smolvm import SmolVM, DirectKernelBoot, DockerRootfsBuilder

builder = DockerRootfsBuilder(
    name="my-app",
    dockerfile="""
        FROM alpine:3.20
        RUN apk add --no-cache python3
        COPY init /init
        RUN chmod +x /init
    """,
    context={
        "init": "#!/bin/sh\nwhile true; do sleep 3600; done\n",
    },
)

image = builder.ensure(
    backend="qemu",
    arch="host",
    boot=DirectKernelBoot(root="/dev/vda", init="/init"),
)

with SmolVM.from_image(image, memory_mb=1024, vcpus=2) as vm:
    print(f"Started {vm.vm_id}")
```

### browser

```python theme={null} theme={null}
@classmethod
SmolVM.browser(
    *,
    headless: bool = True,
    session_id: str | None = None,
    backend: Literal["firecracker", "qemu", "libkrun", "auto"] = "auto",
    profile_id: str | None = None,
    persistent: bool = False,
    timeout_minutes: int = 30,
    viewport: BrowserViewport | dict[str, Any] | None = None,
    viewport_width: int = 1280,
    viewport_height: int = 720,
    record_video: bool = False,
    allow_downloads: bool = True,
    env_vars: dict[str, str] | None = None,
    workspace_mounts: list[WorkspaceMount] | None = None,
    memory_mb: int = 2048,
    disk_size_mb: int = 4096,
    data_dir: Path | None = None,
    socket_dir: Path | None = None,
    ssh_key_path: str | None = None,
    boot_timeout: float = 90.0,
    on_progress: Callable[[str], None] | None = None,
) -> DisplaySandboxProtocol
```

Start a Chromium browser inside a disposable sandbox. Use `headless=True` for a Chrome DevTools Protocol automation URL, or `headless=False` to also get a live viewer and VNC display URL. VNC means Virtual Network Computing, a standard way to view and control a remote screen.

<ResponseField name="return" type="DisplaySandboxProtocol">
  A started browser sandbox with `cdp_url`, and with `viewer_url` plus `display_url` when `headless=False`.
</ResponseField>

See [Browser and desktop sandboxes](/smolvm/api/browsersession) for the full option list and examples.

### desktop

```python theme={null} theme={null}
@classmethod
SmolVM.desktop(
    *,
    session_id: str | None = None,
    backend: Literal["firecracker", "qemu", "libkrun", "auto"] = "auto",
    profile_id: str | None = None,
    persistent: bool = False,
    timeout_minutes: int = 30,
    viewport: BrowserViewport | dict[str, Any] | None = None,
    viewport_width: int = 1280,
    viewport_height: int = 720,
    record_video: bool = False,
    allow_downloads: bool = True,
    env_vars: dict[str, str] | None = None,
    workspace_mounts: list[WorkspaceMount] | None = None,
    memory_mb: int = 2048,
    disk_size_mb: int = 4096,
    data_dir: Path | None = None,
    socket_dir: Path | None = None,
    ssh_key_path: str | None = None,
    boot_timeout: float = 90.0,
    on_progress: Callable[[str], None] | None = None,
) -> DisplaySandboxProtocol
```

Start a full desktop display inside a disposable sandbox. Use `viewer_url` to open the desktop in your browser, or `display_url` for a VNC-compatible computer-use agent.

<ResponseField name="return" type="DisplaySandboxProtocol">
  A started desktop sandbox with `viewer_url` and `display_url`.
</ResponseField>

See [Browser and desktop sandboxes](/smolvm/api/browsersession) for desktop examples.

### from\_snapshot

```python theme={null} theme={null}
@classmethod
SmolVM.from_snapshot(
    snapshot_id: str,
    *,
    data_dir: Path | None = None,
    socket_dir: Path | None = None,
    backend: str | None = None,
    resume_vm: bool = False,
    force: bool = False,
    ssh_user: str = "root",
    ssh_key_path: str | None = None,
) -> SmolVM
```

Restore a snapshot and attach a facade to the restored VM.

<ParamField path="snapshot_id" type="str" required>
  The identifier of the snapshot to restore.
</ParamField>

<ParamField path="resume_vm" type="bool" default="False">
  Resume the restored VM immediately. When False, the VM is restored in a paused state.
</ParamField>

<ParamField path="force" type="bool" default="False">
  Allow restoring a snapshot that was already restored before. By default, each snapshot can only be restored once.
</ParamField>

<ParamField path="data_dir" type="Path | None" default="None">
  Override the default data directory.
</ParamField>

<ParamField path="socket_dir" type="Path | None" default="None">
  Override the default socket directory.
</ParamField>

<ParamField path="backend" type="str | None" default="None">
  Runtime backend override.
</ParamField>

<ParamField path="ssh_user" type="str" default="root">
  SSH user for command execution.
</ParamField>

<ParamField path="ssh_key_path" type="str | None" default="None">
  SSH private key path.
</ParamField>

<ResponseField name="return" type="SmolVM">
  A SmolVM instance bound to the restored VM.
</ResponseField>

#### Raises

* `SnapshotNotFoundError`: If no snapshot with this ID exists.
* `SmolVMError`: If the snapshot was already restored (use `force=True` to override).

## Lifecycle Methods

### start

```python theme={null} theme={null}
def start(self, boot_timeout: float = 30.0) -> SmolVM
```

Start the VM. If the VM config contains `env_vars`, they are injected into the guest via SSH after boot completes.

<ParamField path="boot_timeout" type="float" default="30.0">
  Maximum seconds to wait for boot to complete.
</ParamField>

<ResponseField name="return" type="SmolVM">
  Returns `self` for method chaining.
</ResponseField>

#### Raises

* `SmolVMError`: If `env_vars` is set but the image does not support SSH (missing `init=/init` in boot args).

### stop

```python theme={null} theme={null}
def stop(self, timeout: float = 3.0) -> SmolVM
```

Stop the VM gracefully.

<ParamField path="timeout" type="float" default="3.0">
  Seconds to wait for graceful shutdown.
</ParamField>

<ResponseField name="return" type="SmolVM">
  Returns `self` for method chaining.
</ResponseField>

### delete

```python theme={null} theme={null}
def delete(self) -> None
```

Delete the VM and release all resources.

### pause

```python theme={null} theme={null}
def pause(self) -> SmolVM
```

Pause a running VM. The VM's memory and CPU state are frozen in place. Use `resume()` to continue execution.

<ResponseField name="return" type="SmolVM">
  Returns `self` for method chaining.
</ResponseField>

#### Raises

* `SmolVMError`: If the VM is not in a pausable state (must be running).

### resume

```python theme={null} theme={null}
def resume(self) -> SmolVM
```

Resume a paused VM. Execution continues from the exact point where it was paused.

<ResponseField name="return" type="SmolVM">
  Returns `self` for method chaining.
</ResponseField>

#### Raises

* `SmolVMError`: If the VM is not paused.

### snapshot

```python theme={null} theme={null}
def snapshot(
    self,
    snapshot_id: str | None = None,
    *,
    snapshot_type: SnapshotType | str = SnapshotType.FULL,
    resume_source: bool = False,
) -> SnapshotInfo
```

Create a snapshot of the VM. The VM must be running or paused. If running, SmolVM pauses it during snapshot creation unless `resume_source=True` is set.

<ParamField path="snapshot_id" type="str | None" default="None">
  Custom snapshot identifier. If omitted, SmolVM generates one automatically (for example, `snap-my-vm-1717012345`). Must contain only lowercase letters, numbers, hyphens, and underscores.
</ParamField>

<ParamField path="snapshot_type" type="SnapshotType | str" default="SnapshotType.FULL">
  What to save:

  * `"full"` — complete disk copy plus memory and CPU state.
  * `"diff"` — smaller disk artifact. QEMU diff snapshots need their backing image at restore time.
  * `"disk"` — disk-only on QEMU; restores as a cold boot.

  On Firecracker, SmolVM captures memory and VM state for every snapshot type.
</ParamField>

<ParamField path="resume_source" type="bool" default="False">
  Resume the source VM after snapshot creation. When False, the VM stays paused.
</ParamField>

<ResponseField name="return" type="SnapshotInfo">
  Metadata about the created snapshot, including file paths and timestamps.
</ResponseField>

#### Raises

* `SnapshotAlreadyExistsError`: If a snapshot with this ID already exists.
* `SmolVMError`: If the VM is not in a snapshotable state, uses a Windows guest, uses shared disk mode, has extra drives, has workspace mounts, or is a QEMU raw disk created for filesystem growth.

## Command Execution Methods

### run

```python theme={null} theme={null}
def run(
    self,
    command: str,
    timeout: int = 30,
    shell: Literal["login", "raw"] = "login",
) -> CommandResult
```

Execute a command on the guest via SSH. Lazily creates an SSH client on first call and reuses it for subsequent invocations.

<ParamField path="command" type="str" required>
  Shell command to execute on the guest.
</ParamField>

<ParamField path="timeout" type="int" default="30">
  Maximum seconds to wait for the command to complete.
</ParamField>

<ParamField path="shell" type="Literal['login', 'raw']" default="login">
  Command execution mode:

  * `"login"` (default): run via guest login shell
  * `"raw"`: execute command directly with no shell wrapping
</ParamField>

<ResponseField name="return" type="CommandResult">
  Result object containing exit code, stdout, and stderr.
</ResponseField>

#### Raises

* `SmolVMError`: If the VM is not running or has no network.
* `CommandExecutionUnavailableError`: If SSH is not available on the guest.
* `CommandBlockedError`: If an `on_pre_run` callback vetoes the command. Any other exception raised by a pre-run callback also propagates from `run()`.

### add\_callback

```python theme={null} theme={null}
def add_callback(self, callback: Callback) -> SmolVM
```

Register a [`Callback`](/smolvm/api/callbacks) on this sandbox after construction. Returns `self` so calls can be chained.

<ParamField path="callback" type="Callback" required>
  A `Callback` instance to attach. Its hooks will fire on subsequent `run()` calls.
</ParamField>

<ResponseField name="return" type="SmolVM">
  Returns `self` for method chaining.
</ResponseField>

#### Raises

* `TypeError`: If `callback` is not a `Callback` instance.

### wait\_for\_ssh

```python theme={null} theme={null}
def wait_for_ssh(self, timeout: float = 60.0) -> SmolVM
```

Wait for SSH to become available on the guest.

<ParamField path="timeout" type="float" default="60.0">
  Maximum seconds to wait.
</ParamField>

<ResponseField name="return" type="SmolVM">
  Returns `self` for method chaining.
</ResponseField>

#### Raises

* `OperationTimeoutError`: If SSH is not available within the timeout.
* `SmolVMError`: If the VM is not running.

### ssh\_commands

```python theme={null} theme={null}
def ssh_commands(
    self,
    *,
    ssh_user: str | None = None,
    key_path: str | Path | None = None,
    public_host: str | None = None,
) -> dict[str, str]
```

Get ready-to-run SSH commands for this VM.

<ParamField path="ssh_user" type="str | None" default="None">
  SSH user override. Defaults to the instance's configured ssh\_user.
</ParamField>

<ParamField path="key_path" type="str | Path | None" default="None">
  SSH key path override.
</ParamField>

<ParamField path="public_host" type="str | None" default="None">
  Public hostname override for remote access.
</ParamField>

<ResponseField name="return" type="dict[str, str]">
  Dictionary mapping command names to ready-to-run SSH command strings.
</ResponseField>

## Environment Variable Methods

### set\_env\_vars

```python theme={null} theme={null}
def set_env_vars(
    self,
    env_vars: dict[str, str],
    *,
    merge: bool = True
) -> list[str]
```

Set environment variables on a running VM. Variables are persisted in `/etc/profile.d/smolvm_env.sh` and affect new SSH sessions/login shells.

<ParamField path="env_vars" type="dict[str, str]" required>
  Key/value pairs to set.
</ParamField>

<ParamField path="merge" type="bool" default="True">
  If True, merge with existing variables. If False, replace all variables.
</ParamField>

<ResponseField name="return" type="list[str]">
  Sorted list of variable names present after the update.
</ResponseField>

### unset\_env\_vars

```python theme={null} theme={null}
def unset_env_vars(self, keys: list[str]) -> dict[str, str]
```

Remove environment variables from a running VM.

<ParamField path="keys" type="list[str]" required>
  Variable names to remove.
</ParamField>

<ResponseField name="return" type="dict[str, str]">
  Mapping of removed keys to their previous values.
</ResponseField>

### list\_env\_vars

```python theme={null} theme={null}
def list_env_vars(self) -> dict[str, str]
```

Return SmolVM-managed environment variables for a running VM.

<ResponseField name="return" type="dict[str, str]">
  Dictionary of environment variable names to values.
</ResponseField>

## Port Forwarding Methods

### expose\_local

```python theme={null} theme={null}
def expose_local(
    self,
    guest_port: int,
    host_port: int | None = None
) -> int
```

Expose a guest TCP port on localhost only. Forwards `127.0.0.1:<host_port>` on the host to `<guest_ip>:<guest_port>` inside the VM.

<ParamField path="guest_port" type="int" required>
  Guest TCP port to expose (1-65535).
</ParamField>

<ParamField path="host_port" type="int | None" default="None">
  Host localhost port. If omitted, an available port is automatically chosen.
</ParamField>

<ResponseField name="return" type="int">
  The host localhost port to connect to.
</ResponseField>

#### Raises

* `SmolVMError`: If the VM is not running or has no network.
* `ValueError`: If port numbers are out of valid range (1-65535).

### unexpose\_local

```python theme={null} theme={null}
def unexpose_local(self, host_port: int, guest_port: int) -> SmolVM
```

Remove a previously configured localhost-only port forward.

<ParamField path="host_port" type="int" required>
  Host localhost port (1-65535).
</ParamField>

<ParamField path="guest_port" type="int" required>
  Guest TCP port (1-65535).
</ParamField>

<ResponseField name="return" type="SmolVM">
  Returns `self` for method chaining.
</ResponseField>

## Properties

### vm\_id

```python theme={null} theme={null}
@property
def vm_id(self) -> str
```

The VM identifier.

### info

```python theme={null} theme={null}
@property
def info(self) -> VMInfo
```

Current VM runtime information (cached). Call `refresh()` to update from the state store.

### status

```python theme={null} theme={null}
@property
def status(self) -> VMState
```

Current VM lifecycle state (cached). Values: `CREATED`, `RUNNING`, `PAUSED`, `STOPPED`, `ERROR`.

### data\_dir

```python theme={null} theme={null}
@property
def data_dir(self) -> Path
```

Directory backing the VM state database and logs.

## Utility Methods

### get\_ip

```python theme={null} theme={null}
def get_ip(self) -> str
```

Return the guest IP address.

<ResponseField name="return" type="str">
  The guest VM's IP address.
</ResponseField>

#### Raises

* `SmolVMError`: If the VM has no network configuration.

### refresh

```python theme={null} theme={null}
def refresh(self) -> SmolVM
```

Refresh cached VM info from the state store.

<ResponseField name="return" type="SmolVM">
  Returns `self` for method chaining.
</ResponseField>

### can\_run\_commands

```python theme={null} theme={null}
def can_run_commands(self) -> bool
```

Whether this VM config supports command execution via SSH. Command execution requires SmolVM's SSH init flow, enabled by booting with `init=/init`.

<ResponseField name="return" type="bool">
  True if SSH command execution is supported, False otherwise.
</ResponseField>

### close

```python theme={null} theme={null}
def close(self) -> None
```

Release underlying SDK resources for this facade instance.

## Async methods

SmolVM provides async versions of lifecycle and command methods for use in async applications.

### async\_start

```python theme={null} theme={null}
async def async_start(self, boot_timeout: float = 30.0) -> SmolVM
```

Async version of `start()`. Starts the VM without blocking the event loop.

### async\_stop

```python theme={null} theme={null}
async def async_stop(self, timeout: float = 3.0) -> SmolVM
```

Async version of `stop()`.

### async\_run

```python theme={null} theme={null}
async def async_run(
    self,
    command: str,
    timeout: int = 30,
    shell: Literal["login", "raw"] = "login",
) -> CommandResult
```

Async version of `run()`. Executes a command on the guest without blocking.

### async\_wait\_for\_ssh

```python theme={null} theme={null}
async def async_wait_for_ssh(self, timeout: float = 60.0) -> SmolVM
```

Async version of `wait_for_ssh()`.

### async\_create\_many

```python theme={null} theme={null}
@classmethod
async def async_create_many(
    cls,
    count: int,
    *,
    memory: int | None = None,
    disk_size: int | None = None,
    backend: str | None = None,
) -> list[SmolVM]
```

Create multiple sandboxes concurrently. Each sandbox is auto-configured and started.

<ParamField path="count" type="int" required>
  Number of sandboxes to create.
</ParamField>

<ParamField path="memory" type="int | None" default="None">
  Memory for each sandbox in MiB.
</ParamField>

<ParamField path="disk_size" type="int | None" default="None">
  Disk size for each sandbox in MiB.
</ParamField>

<ResponseField name="return" type="list[SmolVM]">
  List of started SmolVM instances.
</ResponseField>

**Example:**

```python theme={null}
import asyncio
from smolvm import SmolVM

async def main():
    vms = await SmolVM.async_create_many(3)
    for vm in vms:
        result = await vm.async_run("hostname")
        print(result.stdout.strip())
    for vm in vms:
        await vm.async_stop()
        vm.delete()

asyncio.run(main())
```

## Async context manager

SmolVM also works as an async context manager:

```python theme={null}
import asyncio
from smolvm import SmolVM

async def main():
    async with SmolVM() as vm:
        result = await vm.async_run("echo 'Hello from async'")
        print(result.stdout.strip())

asyncio.run(main())
```

## Context manager

SmolVM implements the context manager protocol for automatic lifecycle management:

```python theme={null} theme={null}
with SmolVM() as vm:
    # VM auto-starts on context entry
    result = vm.run("uname -r")
    print(result.stdout)
# VM auto-stops and auto-deletes on context exit
```

On context entry (`__enter__`):

* Auto-starts VMs created by this facade instance

On context exit (`__exit__`):

* Best-effort stop if the VM is running
* Auto-deletes only VMs created by this facade instance (not reconnected VMs)
* Releases all resources via `close()`

## Usage Examples

### Auto-Configuration Mode

Create an SSH-ready VM with default settings:

```python theme={null} theme={null}
from smolvm import SmolVM

with SmolVM() as vm:
    result = vm.run("echo 'Hello from SmolVM'")
    print(result.stdout.strip())
```

### Custom Configuration

Create a VM with specific resources:

```python theme={null} theme={null}
from smolvm import SmolVM

with SmolVM(memory=1024, disk_size=1024) as vm:
    result = vm.run("free -m")
    print(result.stdout)
```

### Manual Lifecycle Management

```python theme={null} theme={null}
from smolvm import SmolVM, VMConfig
from pathlib import Path

config = VMConfig(
    vm_id="my-vm",
    vcpu_count=2,
    memory=512,
    kernel_path=Path("/path/to/kernel"),
    rootfs_path=Path("/path/to/rootfs"),
    boot_args="console=ttyS0 reboot=k panic=1 init=/init"
)

vm = SmolVM(config)
vm.start()

result = vm.run("hostname")
print(result.stdout)

vm.stop()
vm.delete()
vm.close()
```

### Windows Guest

Boot a pre-installed Windows 11 `qcow2` image, run a PowerShell command, and upload a file. Requires a Linux host with KVM, the OVMF + `swtpm` packages installed, and an image with OpenSSH Server set up. See the [Windows guests guide](/smolvm/guides/windows-guests) for prerequisites and limitations.

```python theme={null} theme={null}
from smolvm import SmolVM

with SmolVM(
    os="windows",
    image="~/win11-vm/disk-baseline.qcow2",
    ssh_user="celesto",
    ssh_password="celesto",
    memory=4096,
) as vm:
    vm.wait_for_ssh()

    result = vm.run("Write-Output 'hello from windows'")
    print(result.stdout)  # 'hello from windows\r\n'

    vm.upload_file("./hello.ps1", "C:\\Users\\celesto\\hello.ps1")
```

On Windows guests, `vm.run(...)` executes the command in PowerShell, and `vm.upload_file(...)` accepts Windows-style destination paths (`C:\\...`, `C:/...`, or `/C:/...`).

### Reconnecting to Existing VM

```python theme={null} theme={null}
from smolvm import SmolVM

# Reconnect to a VM created earlier
vm = SmolVM.from_id("my-vm")
result = vm.run("uptime")
print(result.stdout)
vm.close()
```

### Port Forwarding

```python theme={null} theme={null}
from smolvm import SmolVM

with SmolVM() as vm:
    # Install and start a web server
    vm.run("apk add python3")
    vm.run("python3 -m http.server 8000 &")
    
    # Expose guest port 8000 on localhost
    host_port = vm.expose_local(guest_port=8000)
    print(f"Server available at http://localhost:{host_port}")
    
    # Access the server from host
    # ...
    
    # Clean up port forward
    vm.unexpose_local(host_port=host_port, guest_port=8000)
```

### Pause and Resume

```python theme={null} theme={null}
from smolvm import SmolVM

vm = SmolVM()
vm.start()

# Run some setup
vm.run("echo 'setup complete'")

# Pause the VM to save resources
vm.pause()

# Resume when you need it again
vm.resume()
result = vm.run("echo 'back online'")
print(result.stdout.strip())

vm.stop()
vm.delete()
vm.close()
```

### Environment Variables

```python theme={null} theme={null}
from smolvm import SmolVM

with SmolVM() as vm:
    # Set environment variables
    vm.set_env_vars({"API_KEY": "secret", "ENV": "production"})
    
    # List current variables
    env_vars = vm.list_env_vars()
    print(env_vars)  # {'API_KEY': 'secret', 'ENV': 'production'}
    
    # Variables persist across sessions
    result = vm.run("echo $API_KEY")
    print(result.stdout.strip())  # secret
    
    # Remove variables
    removed = vm.unset_env_vars(["API_KEY"])
    print(removed)  # {'API_KEY': 'secret'}
```
