---
title: "CLI — VMs"
description: "Metalhost CLI commands for virtual machines — create, YAML manifests, lifecycle, snapshots, and SSH keys."
url: "https://metalhost.net/docs/developers/guides/cli-vms"
---

# CLI — VMs

`metalhost vm` (aliases `vms`, `compute`) provisions and manages virtual machines — from quick flags or a declarative YAML manifest.

## Commands

| Command | What it does |
| --- | --- |
| `vm list` | List VMs in the active project. |
| `vm get NAME` | Get a VM by resource name. |
| `vm create` | Provision from flags (see below). |
| `vm apply -f FILE` | Provision from YAML/JSON manifest. |
| `vm start\|stop\|restart NAME` | Power actions. |
| `vm delete NAME` | Tear down the VM. |
| `vm resize NAME --vcpus N --ram-gib N` | Change shape (stop → patch → start). |
| `vm reimage NAME` | Wipe OS disk and reinstall. |
| `vm clone --source NAME --display-name NEW` | Disk-level copy. |
| `vm console NAME --type serial\|vnc` | Short-lived console URL. |
| `vm metrics NAME` | CPU / memory / network samples. |
| `vm autorenew NAME --enabled=true\|false` | Toggle monthly auto-renew. |
| `vm renew NAME` | Renew a monthly VM immediately. |

## `vm create` flags

Use flags for simple single-user VMs. For GPU, multiple users, billing modes, or cloud-init, use a [YAML manifest](#yaml-manifest) instead.

| Flag | What it does |
| --- | --- |
| `--vcpus N` | vCPU count (required). |
| `--ram-gib N` | RAM in GiB (required). |
| `--cpu-class NAME` | e.g. `cascadelake` (required). |
| `--image ID` | Catalog image, e.g. `ubuntu-24-04`. |
| `--boot-url URL` | Raw image URL (alternative to `--image`). |
| `--boot-disk-name NAME` | Existing disk as boot volume. |
| `--disk-size-gib N` | Boot disk size (required with image/boot-url). |
| `--gpu-model NAME` / `--gpu-count N` | GPU passthrough, e.g. `rtx4090`. |
| `--hostname NAME` | DNS hostname; defaults to a UUID slug. |
| `--user NAME` / `--password` / `--sudo` | Single login user (use manifest for multiple). |
| `--ssh-key PUBKEY` | Inline public key (repeatable). |
| `--ssh-key-name NAME` | Registered key resource name (repeatable). |
| `--user-data YAML` | cloud-init user-data — owns user creation. |
| `--network NAME` | Tenant network (default: project's default). |
| `--assign-public-ipv4` | Attach a public IPv4. |
| `--billing-mode MODE` | `HOURLY`, `monthly-1` … `monthly-12`. |
| `--autorenew` | Auto-renew for monthly modes. |
| `--wait` | Block until the create operation finishes. |

```
metalhost vm create \
  --vcpus 2 --ram-gib 8 --cpu-class cascadelake \
  --image ubuntu-24-04 --disk-size-gib 80 \
  --hostname my-vm \
  --ssh-key-name projects/main/ssh-keys/laptop \
  --assign-public-ipv4 \
  --wait
```

## YAML manifest

Use `metalhost vm apply -f vm.yaml` when flags aren't enough — multiple users, GPU shapes, prepaid billing, labels, or full cloud-init. Same schema as the dashboard and Go SDK.

### Apply a manifest

```
metalhost vm apply -f vm.yaml --wait

# Omit project/region in the file — fill from profile or flags:
metalhost vm apply -f vm.yaml --project projects/my-app --region datacenters/us-dal-1 --wait

# Pipe from stdin:
cat vm.yaml | metalhost vm apply -f - --wait
```

`metadata.project` and `spec.region` fall back to your active CLI profile (or `--project` / `--region`) when omitted. Returns an operation — use `--wait` to block until provisioning finishes.

### Schema

| Top-level | Required | Description |
| --- | --- | --- |
| `apiVersion` | No | `compute.metalhost.io/v1` |
| `kind` | No | `VirtualMachine` |
| `metadata` | Yes | `name`, `project`, `labels`, `annotations` |
| `spec` | Yes | Region, compute, boot, network, users, billing, cloud-init |

#### spec.compute

| Field | Description |
| --- | --- |
| `cpuClass` | `cascadelake`, `milan`, etc. |
| `vcpus` / `ramGib` | Shape — max 4 GiB RAM per vCPU on CPU VMs |
| `gpu.model` / `gpu.count` | Whole-GPU passthrough, e.g. `rtx4090` |

#### spec.boot

Exactly one of `image`, `imageUrl`, or `diskName`.

#### spec.network

`publicIpv4` defaults to `false`. IPv6 is always on.

#### spec.users[]

`name`, `sshKeys`, `sshPubkey`, `password`, `sudo`.

#### spec.billing

`mode`: `BILLING_MODE_HOURLY` (default) or `BILLING_MODE_MONTHLY_1` through `BILLING_MODE_MONTHLY_12`. `autorenew` for monthly modes.

> **cloudInit overrides users**
>
> When `spec.cloudInit` is set, it owns user creation — `users` is ignored. Use raw `#cloud-config` YAML.

### Basic CPU VM

```
apiVersion: compute.metalhost.io/v1
kind: VirtualMachine
metadata:
  name: web-1
  project: projects/my-app
spec:
  region: datacenters/us-dal-1
  compute:
    cpuClass: cascadelake
    vcpus: 2
    ramGib: 8
  boot:
    image: ubuntu-24-04
    diskGib: 80
  network:
    publicIpv4: true
  users:
    - name: ubuntu
      sshKeys:
        - projects/my-app/ssh-keys/laptop
```

### GPU VM with monthly billing

```
apiVersion: compute.metalhost.io/v1
kind: VirtualMachine
metadata:
  name: gpu-train-1
  project: projects/my-app
  labels:
    env: prod
spec:
  region: datacenters/us-dal-1
  compute:
    cpuClass: cascadelake
    vcpus: 8
    ramGib: 32
    gpu:
      model: rtx4090
      count: 1
  boot:
    image: ubuntu-24-04
    diskGib: 200
  network:
    publicIpv4: true
  users:
    - name: ubuntu
      sshKeys:
        - projects/my-app/ssh-keys/laptop
  billing:
    mode: BILLING_MODE_MONTHLY_1
    autorenew: true
```

### Multiple login users

```
apiVersion: compute.metalhost.io/v1
kind: VirtualMachine
metadata:
  name: shared-dev
  project: projects/my-app
spec:
  region: datacenters/us-dal-1
  compute:
    cpuClass: cascadelake
    vcpus: 4
    ramGib: 16
  boot:
    image: ubuntu-24-04
    diskGib: 80
  network:
    publicIpv4: false
  users:
    - name: alice
      sshKeys:
        - projects/my-app/ssh-keys/alice-laptop
      sudo: true
    - name: deploy
      password: "change-me-on-first-login"
      sudo: false
```

### Full cloud-init

```
apiVersion: compute.metalhost.io/v1
kind: VirtualMachine
metadata:
  name: custom-init
  project: projects/my-app
spec:
  region: datacenters/us-dal-1
  compute:
    cpuClass: cascadelake
    vcpus: 2
    ramGib: 8
  boot:
    image: ubuntu-24-04
    diskGib: 80
  network:
    publicIpv4: true
  cloudInit: |
    #cloud-config
    package_update: true
    packages:
      - nginx
    runcmd:
      - systemctl enable --now nginx
    users:
      - name: ubuntu
        sudo: ALL=(ALL) NOPASSWD:ALL
        ssh_authorized_keys:
          - ssh-ed25519 AAAA... you@host
```

### Round-trip from a running VM

```
metalhost vm get web-1 -o yaml > vm.yaml
metalhost vm apply -f vm.yaml --wait
```

`vm apply` also accepts JSON with the same camelCase field names as the HTTP API.

## Whole-VM backups

CLI v1.1.0 supports `vm backup` as an alias for `vm snapshot`. Capture and restore accept `--idempotency-key`; reuse the same key and inputs after an ambiguous error. Wait for READY before restoring and inspect the volume manifest. Restore creates a new VM with every captured disk, with separate billing and quotas; it does not transfer reservations or IP identities.

| Command | What it does |
| --- | --- |
| `vm snapshot create --vm NAME --display-name S` | Take a snapshot. |
| `vm snapshot list --vm NAME` | List snapshots. |
| `vm snapshot get NAME` | Get a snapshot. |
| `vm snapshot delete NAME` | Delete a snapshot. |
| `vm from-backup --snapshot NAME ...` | Provision from a snapshot. |

## SSH keys

| Command | What it does |
| --- | --- |
| `vm ssh-key list` | List registered keys. |
| `vm ssh-key create --id ID --public-key "ssh-ed25519 ..."` | Register a key. |
| `vm ssh-key delete NAME` | Unregister a key. |

## What's next

- [Go SDK — VM manifest](https://metalhost.net/docs/developers/guides/sdk-vms.md#manifest)
- [Dashboard → VM lifecycle](https://metalhost.net/docs/dashboard/guides/vms-lifecycle.md)
- [CLI → Operations](https://metalhost.net/docs/developers/cli.md#ops) — poll `--wait`
