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

# Windows sandboxes in Celesto

> Boot a Windows 11 microVM with Celesto: run PowerShell over SSH, upload files, and inject environment variables from Python on a Linux host.

Celesto can boot a Windows 11 sandbox alongside its Linux sandboxes. You bring a pre-installed Windows disk image (a `.qcow2` file), and Celesto handles the firmware, virtual TPM, QEMU wiring, and SSH plumbing for you.

You can boot the VM, run PowerShell commands inside it with `vm.run(...)`, and upload files to Windows-style paths with `vm.upload_file(...)` — the same API you use for Linux guests.

This guide explains when to use Windows guests, what you need before you start, and how to drive one end to end.

<Note>
  Windows guest support is rolling out in phases. Boot, PowerShell command execution, file upload, environment variable injection, and unattended image building work today. Host mounts, network controls, and snapshots are not yet supported — see [Current limitations](#current-limitations).
</Note>

## What this is

A Windows sandbox is a virtual machine that runs the real Windows 11 operating system instead of Linux. You'd reach for it when:

* You're testing software that only ships for Windows.
* You're building an agent that needs to drive a Windows desktop or Windows-only application.
* You want a disposable Windows environment that won't touch your host machine.

The Windows VM boots the same way Linux sandboxes do — with `Celesto(...)` in Python — and is isolated from your host using KVM hardware virtualization.

## Before you start

You need three things on the host:

1. **A Linux host with KVM.** Windows guests use QEMU + KVM. macOS hosts are not supported yet.
2. **A Windows 11 `qcow2` disk image with OpenSSH Server.** The easiest way to get one is the [`celesto windows build-image`](/smolvm/cli/windows) CLI command, which drives an unattended Windows install from a stock Windows ISO + the virtio-win driver ISO and produces a ready-to-use `qcow2` (OpenSSH, virtio-win drivers, and a known local admin account all pre-installed). If you already have your own Windows `qcow2`, point Celesto at that file instead — just make sure the OpenSSH Server feature is installed and running, otherwise `vm.run(...)` and `vm.upload_file(...)` will fail with a connection or auth error.
3. **OVMF (UEFI firmware) and `swtpm` (software TPM).** Windows 11 requires UEFI Secure Boot and TPM 2.0. Celesto looks for OVMF in the standard distro install paths and will tell you exactly which package to install if it's missing.

Quick install of the prerequisites:

<Tabs>
  <Tab title="Debian / Ubuntu">
    ```bash theme={null}
    sudo apt-get install qemu-system-x86 ovmf swtpm
    ```
  </Tab>

  <Tab title="Fedora">
    ```bash theme={null}
    sudo dnf install qemu-system-x86 edk2-ovmf swtpm
    ```
  </Tab>

  <Tab title="Arch">
    ```bash theme={null}
    sudo pacman -S qemu-full edk2-ovmf swtpm
    ```
  </Tab>
</Tabs>

## Build a Windows image

If you don't have a Windows `qcow2` yet, `celesto windows build-image` produces one for you in a single command. You provide a Windows ISO and the [virtio-win driver ISO](https://fedorapeople.org/groups/virt/virtio-win/direct-downloads/stable-virtio/virtio-win.iso), walk away for 15–30 minutes, and get back a ready-to-boot image:

```bash theme={null}
celesto windows build-image \
  --iso ./Win11.iso \
  --virtio-win-iso ./virtio-win.iso \
  --output ~/.smolvm/images/win11.qcow2
```

The built image has OpenSSH Server installed and running, the virtio-win drivers in place, and a local admin account (`celesto` / `celesto` by default — override with `--username` and `--password`). Boot it like any other Windows image:

```python theme={null}
from celesto import Celesto

with Celesto(
    os="windows",
    image="~/.smolvm/images/win11.qcow2",
    ssh_user="smolvm",
    ssh_password="smolvm",
) as vm:
    print(vm.run("hostname").stdout)
```

See the [`celesto windows build-image`](/smolvm/cli/windows) reference for all available flags (edition, hostname, disk size, build timeout) and for using `WindowsImageBuilder` directly from Python.

## Launch a Windows sandbox

Point `Celesto` at your Windows `qcow2` file, pass `os="windows"`, and supply the Windows account credentials that map to the OpenSSH user inside the image:

```python theme={null}
from celesto import Celesto

with Celesto(
    os="windows",
    image="~/win11-vm/disk-baseline.qcow2",
    ssh_user="celesto",
    ssh_password="celesto",
    memory=4096,
) as vm:
    print(vm.vm_id)
    # The VM is now running Windows 11 and accepting SSH.
    # Stop / delete happens automatically on context exit.
```

What happens behind the scenes:

* Celesto copies the OVMF UEFI variable store to a per-VM location so the firmware can persist boot order independently.
* It starts a per-VM `swtpm` software TPM 2.0 process.
* It launches QEMU with the right machine type (`q35`), Hyper-V enlightenments for performance, and a `virtio-scsi` root disk.
* On `stop()` or `delete()`, the `swtpm` sidecar and per-VM firmware state are cleaned up.

### Accepted image paths

`image=` accepts any of these for Windows guests:

* An absolute path: `image="/var/lib/vms/win11.qcow2"`
* A tilde-relative path: `image="~/win11-vm/disk.qcow2"`
* A `file://` URI: `image="file:///var/lib/vms/win11.qcow2"`

S3 URIs work too, but in this release only the local-path form is meaningful for Windows — there is no published Windows image yet.

### SSH user and password

Windows OpenSSH does not support the `root` user that Celesto uses for Linux guests. Pass the local Windows account you set up inside the qcow2:

* `ssh_user="..."` — the Windows account name (for example `"Administrator"`, or whatever local user you created).
* `ssh_password="..."` — that account's password.

When `ssh_password` is set, Celesto uses paramiko's password-auth path and does not try SSH keys. If you have configured key-based SSH inside the Windows image, drop `ssh_password` and pass `ssh_key_path=` instead.

### Run PowerShell commands

`vm.run(...)` works on Windows guests just like on Linux guests, but the command string is interpreted by PowerShell instead of `sh`:

```python theme={null}
from celesto import Celesto

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

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

    # Real PowerShell cmdlets work too.
    result = vm.run("(Get-Item C:\\Windows).Name")
    print(result.stdout.strip())  # 'Windows'
```

Under the hood, Celesto base64-encodes the command and runs it with `powershell.exe -NoProfile -EncodedCommand` so quotes, backticks, and special characters survive the Windows OpenSSH `cmd.exe` layer unchanged. You don't need to escape anything yourself.

### Inject environment variables

Pass secrets and configuration into a Windows sandbox the same way you do for Linux — via `env_vars=` on the constructor, or `vm.set_env_vars(...)` / `vm.unset_env_vars(...)` / `vm.list_env_vars(...)` at runtime:

```python theme={null}
from celesto import Celesto

with Celesto(
    os="windows",
    image="~/.smolvm/images/win11.qcow2",
    ssh_user="smolvm",
    ssh_password="smolvm",
    env_vars={
        "OPENAI_API_KEY": "sk-...",
        "APP_MODE": "production",
    },
) as vm:
    vm.wait_for_ssh()

    # New SSH sessions see the variables.
    print(vm.run("$env:APP_MODE").stdout.strip())  # production

    # Add / remove at runtime.
    vm.set_env_vars({"DEBUG": "1"})
    vm.unset_env_vars(["APP_MODE"])
    print(vm.list_env_vars())  # {'OPENAI_API_KEY': '...', 'DEBUG': '1'}
```

On Windows, Celesto writes the variables into `HKCU\Environment` (the standard per-user environment store) via `[Environment]::SetEnvironmentVariable(name, value, 'User')`. Celesto tracks which variables it owns through a sentinel key, so `list_env_vars` and `unset_env_vars` only ever touch values Celesto set — anything you configure inside Windows yourself is left alone.

Variables become visible to **new** processes, not the SSH session that set them. Every `vm.run()` call opens a fresh SSH session, so the very next `vm.run()` after `set_env_vars` will see the new values. See the [environment variables guide](/smolvm/guides/environment-variables) for the full API.

### Upload files into Windows paths

`vm.upload_file(...)` accepts Windows-style destination paths. All three forms work:

* Native Windows: `"C:\\Users\\celesto\\hello.ps1"`
* Forward-slash mix: `"C:/Users/celesto/hello.ps1"`
* SFTP/POSIX style: `"/C:/Users/celesto/hello.ps1"`

```python theme={null}
vm.upload_file("./hello.ps1", "C:\\Users\\celesto\\scripts\\hello.ps1")

# Then run the script you just uploaded.
result = vm.run("powershell.exe -File C:\\Users\\celesto\\scripts\\hello.ps1")
print(result.stdout)
```

Celesto creates missing parent directories on the Windows side using `New-Item -ItemType Directory -Force`, so you can upload straight into a path that doesn't exist yet.

### Memory defaults

Windows 11 defaults to **4096 MiB** of RAM in Celesto. The minimum that boots is 2 GiB, but Edge and Defender want more. Override with `memory=`:

```python theme={null}
Celesto(os="windows", image="~/win11-vm/disk.qcow2", memory=8192)
```

### Your baseline image stays read-only

When you point `Celesto` at a Windows `qcow2`, Celesto does **not** write to that file. Each sandbox gets its own thin scratch disk — a per-VM `qcow2` overlay stacked on top of your baseline — so the original image stays byte-for-byte unchanged across runs.

This gives you two things for free:

* **Run multiple Windows sandboxes from the same image in parallel.** Each `Celesto(os="windows", image=...)` call gets a fresh overlay, so concurrent processes don't fight over the disk write lock.
* **Crashes and Ctrl-C don't corrupt your baseline.** If a sandbox dies mid-run, the only thing lost is its overlay. Your golden image is untouched.

The overlay is created near-instantly with `qemu-img create -b` and lives under `data_dir/disks/{vm_id}.qcow2`. It is deleted automatically when the VM is deleted unless you set `retain_disk_on_delete=True` on the `VMConfig`.

```python theme={null}
from celesto import Celesto

# Two sandboxes, same baseline image, running side by side — safe.
with Celesto(os="windows", image="~/win11-vm/baseline.qcow2") as vm_a, \
     Celesto(os="windows", image="~/win11-vm/baseline.qcow2") as vm_b:
    vm_a.wait_for_ssh()
    vm_b.wait_for_ssh()
    # Writes inside vm_a and vm_b land in their own overlays.
    # baseline.qcow2 is read-only across both lifecycles.
```

If you specifically want writes to land in the baseline — for example, a one-shot image-baking workflow where you install software once and want it to persist — drop down to the lower-level API and construct a `VMConfig` directly with `disk_mode="shared"`. See [`VMConfig`](/smolvm/api/vmconfig) for the full options.

## How `os=` and `image=` work together

Celesto uses two slightly different rules depending on where the image comes from:

| Image source                   | Pass `os=`? | Why                                                               |
| ------------------------------ | ----------- | ----------------------------------------------------------------- |
| Local file (path or `file://`) | **Yes**     | The file alone doesn't tell Celesto which OS is inside.           |
| Published S3 image             | No          | The image manifest already records the OS — passing `os=` errors. |

For Windows today, you always use the local-file form, so always pass `os="windows"`.

## Current limitations

The first release of Windows guest support is deliberately narrow. The following raise a clear error rather than silently misbehaving:

* **Linux hosts only.** macOS support comes in a later phase.
* **No `mounts=`.** Host directory mounts (`virtio-9p`) are Linux-guest only for now.
* **No `internet_settings=`.** Domain allowlists and egress controls are not yet wired into the Windows network stack.
* **No snapshots.** `vm.snapshot()` and `Celesto.from_snapshot()` are rejected for Windows VMs — Windows guests use multiple state artifacts (qcow2 + UEFI vars + TPM state) that can't be checkpointed atomically yet.

If you call any of the unsupported features, Celesto raises `ValueError` with a plain-English message naming the feature and the workaround.

## Troubleshooting

<AccordionGroup>
  <Accordion title="OVMF not found">
    Celesto probes the four standard install paths (Debian, Fedora, Arch, Homebrew). If none match, it raises a `ValueError` naming the package to install. Re-run after installing the `ovmf` / `edk2-ovmf` package from the table above.
  </Accordion>

  <Accordion title="`swtpm` not found">
    Install the `swtpm` package from your distro. The error message includes the install hint.
  </Accordion>

  <Accordion title="Local image not found">
    `image=` must point at an existing `.qcow2` file. Tilde and relative paths are expanded against your current working directory.
  </Accordion>

  <Accordion title="Windows guests need a pre-installed disk image">
    You passed `os="windows"` without `image=`. Celesto does not build a Windows image for you yet — point at a `qcow2` you've already installed Windows into.
  </Accordion>

  <Accordion title="`vm.run()` or `vm.wait_for_ssh()` fails with an auth or connection error">
    Celesto expects the OpenSSH Server feature to be installed and running inside the Windows image, and the `ssh_user` / `ssh_password` you passed must match a real local Windows account. Install OpenSSH Server (Settings → Apps → Optional features → "OpenSSH Server"), start the `sshd` service, and confirm you can SSH in from the host before retrying.
  </Accordion>
</AccordionGroup>

## Next steps

<CardGroup cols={2}>
  <Card title="Build a Windows image" icon="hammer" href="/smolvm/cli/windows">
    Run `celesto windows build-image` to produce a Windows qcow2 unattended
  </Card>

  <Card title="Backends" icon="microchip" href="/smolvm/concepts/backends">
    Why Windows guests run on QEMU
  </Card>

  <Card title="VMConfig" icon="sliders" href="/smolvm/api/vmconfig">
    The full configuration model, including `guest_os`
  </Card>

  <Card title="VM lifecycle" icon="circle-play" href="/smolvm/guides/vm-lifecycle">
    Start, stop, and delete sandboxes
  </Card>
</CardGroup>


## Related topics

- [celesto windows](/smolvm/cli/windows.md)
- [celesto sandbox create](/smolvm/cli/create.md)
- [Environment variables in Celesto sandboxes](/smolvm/guides/environment-variables.md)
- [celesto sandbox snapshot](/smolvm/cli/snapshot.md)
- [Snapshot and restore Celesto sandboxes](/smolvm/features/snapshots.md)
