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

# VM Instances

> Provision GPU/CPU virtual machines and run workloads on them

<Info>
  VMs are dedicated, long-lived machines you SSH into. Use them for training jobs that span hours, interactive development, or anything that doesn't fit a one-shot serverless run.
</Info>

<Note>
  Provisioning typically takes 1–3 minutes. The CLI polls automatically; the API exposes `GET /vms/{vm_id}/status` so you can poll yourself.
</Note>

## Quick Start with the CLI

```bash theme={null}
# Authenticate (one-time)
lyceum auth login

# See what hardware is available right now
lyceum vm availability

# Start an A100 instance
lyceum vm start \
  -h a100 \
  -k "$(cat ~/.ssh/id_ed25519.pub)"

# CLI waits for the VM to become ready and prints its IP.
# Then SSH in (the username depends on the image, see vm status output).
ssh root@<ip>
```

To launch with multiple GPUs, add `-g 2` (or `4`, `8` if the profile supports it). To return immediately without waiting, add `-a` / `--async`.

For the full set of available profiles and flags see the [VM page](/docs/instances/vms) and [Launch an Instance](/docs/instances/launch).

## ML Training Workflow

<Steps>
  <Step title="Start the VM">
    ```bash theme={null}
    lyceum vm start -h a100 -k "$(cat ~/.ssh/id_ed25519.pub)"
    ```
  </Step>

  <Step title="Connect and set up the environment">
    ```bash theme={null}
    ssh root@<ip>

    # On the VM
    nvidia-smi                          # verify GPU is visible
    git clone https://github.com/your-org/ml-project.git
    cd ml-project
    python -m venv venv && source venv/bin/activate
    pip install -r requirements.txt
    ```
  </Step>

  <Step title="Run training inside tmux">
    ```bash theme={null}
    tmux new -s training
    python train.py --epochs 100 --batch-size 32
    # Detach with Ctrl+B, D, reattach later with: tmux attach -t training
    ```
  </Step>

  <Step title="Pull results and terminate">
    ```bash theme={null}
    # From your local machine
    scp root@<ip>:~/ml-project/model.pt ./

    # Tear the VM down
    lyceum vm terminate <vm_id> -f
    ```
  </Step>
</Steps>

<Warning>
  Local disk on a VM is **wiped on termination**. Anything you want to keep should be `scp`'d off, pushed to git, or written to your [storage bucket](/docs/configuration/storage) before terminating.
</Warning>

## API Examples

The CLI is a thin wrapper around the VMs API. Use the API directly when you need to integrate provisioning into your own tooling.

### Provision and wait

<CodeGroup>
  ```python Python theme={null}
  import requests
  import time

  BASE_URL = "https://api.lyceum.technology"
  TOKEN = "your-token"

  def create_vm(public_key: str, hardware_profile: str = "a100", gpu_count: int = 1):
      """Create a new VM instance."""
      payload = {
          "user_public_key": public_key,
          "hardware_profile": hardware_profile,
          "instance_specs": {"gpu_count": gpu_count},
      }
      r = requests.post(
          f"{BASE_URL}/api/v2/external/vms/create",
          headers={"Authorization": f"Bearer {TOKEN}"},
          json=payload,
      )
      r.raise_for_status()
      return r.json()

  def wait_for_ready(vm_id: str, timeout: int = 600) -> dict:
      """Poll until the VM is ready or fails."""
      start = time.time()
      while time.time() - start < timeout:
          r = requests.get(
              f"{BASE_URL}/api/v2/external/vms/{vm_id}/status",
              headers={"Authorization": f"Bearer {TOKEN}"},
          )
          r.raise_for_status()
          status = r.json()
          if status["status"] in ("ready", "running"):
              return status
          if status["status"] in ("failed", "error"):
              raise RuntimeError(f"VM failed: {status}")
          print(f"Status: {status['status']}...")
          time.sleep(20)
      raise TimeoutError("VM provisioning timed out")

  with open("/Users/me/.ssh/id_ed25519.pub") as f:
      public_key = f.read().strip()

  vm = create_vm(public_key, hardware_profile="a100", gpu_count=1)
  print(f"Created VM: {vm['vm_id']}")

  ready = wait_for_ready(vm["vm_id"])
  print(f"VM ready! IP: {ready['ip_address']}")
  ```

  ```bash cURL theme={null}
  # Create VM
  curl -X POST https://api.lyceum.technology/api/v2/external/vms/create \
    -H "Authorization: Bearer <TOKEN>" \
    -H "Content-Type: application/json" \
    -d '{
      "user_public_key": "ssh-ed25519 AAAAC3...",
      "hardware_profile": "a100",
      "instance_specs": {"gpu_count": 1}
    }'

  # Poll status
  curl https://api.lyceum.technology/api/v2/external/vms/<vm_id>/status \
    -H "Authorization: Bearer <TOKEN>"

  # Terminate
  curl -X DELETE https://api.lyceum.technology/api/v2/external/vms/<vm_id> \
    -H "Authorization: Bearer <TOKEN>"
  ```
</CodeGroup>

### List VMs

```python theme={null}
def list_vms():
    r = requests.get(
        f"{BASE_URL}/api/v2/external/vms/list",
        headers={"Authorization": f"Bearer {TOKEN}"},
    )
    r.raise_for_status()
    return r.json().get("vms", [])

for vm in list_vms():
    name = vm.get("name") or "-"
    print(f"{vm['vm_id']:38}  {vm['status']:10}  {vm.get('hardware_profile', '-'):10}  {name}")
```

### Check availability

```python theme={null}
def check_availability():
    r = requests.get(
        f"{BASE_URL}/api/v2/external/vms/availability",
        headers={"Authorization": f"Bearer {TOKEN}"},
    )
    r.raise_for_status()
    return r.json().get("available_hardware_profiles", [])

for profile in check_availability():
    print(f"{profile['hardware_profile']}: ${profile.get('price_per_hour', 0):.2f}/GPU/hr")
```

## Common Patterns

<AccordionGroup>
  <Accordion title="Long-running jobs with tmux" icon="terminal">
    ```bash theme={null}
    tmux new -s training
    python train.py --epochs 1000
    # Detach: Ctrl+B, D
    # Reattach: tmux attach -t training
    ```
  </Accordion>

  <Accordion title="Transferring files" icon="upload">
    ```bash theme={null}
    # Upload
    scp ./data.tar.gz root@<ip>:~/

    # Download
    scp root@<ip>:~/results/* ./local-results/

    # Sync directories
    rsync -avz ./project/ root@<ip>:~/project/
    ```
  </Accordion>

  <Accordion title="Port forwarding for Jupyter / TensorBoard" icon="globe">
    ```bash theme={null}
    # Jupyter on 8888
    ssh -L 8888:localhost:8888 root@<ip>

    # Multiple ports
    ssh -L 8888:localhost:8888 -L 6006:localhost:6006 root@<ip>
    ```
  </Accordion>
</AccordionGroup>

<Warning>
  VMs bill from the moment they enter `running` until you terminate them, regardless of whether anything is executing. Run `lyceum vm list` periodically to make sure you don't have idle instances.
</Warning>
