---
title: "Go SDK — VMs"
description: "Create and manage virtual machines with the Metalhost Go SDK — manifests, lifecycle, snapshots, SSH keys, and quoting."
url: "https://metalhost.net/docs/developers/guides/sdk-vms"
---

# Go SDK — VMs

Virtual machines use `ComputeServiceClient` and `SSHKeysServiceClient`. Create returns an [operation](https://metalhost.net/docs/developers/concepts.md#operations) — poll until `SUCCEEDED`. Configure clients per [Go SDK → Configure](https://metalhost.net/docs/developers/sdk.md#configure).

## VM manifest

`CreateVirtualMachine` takes a `VirtualMachineManifest` — the same schema the CLI flags and YAML manifests map to.

### metadata

| Field | Required | Description |
| --- | --- | --- |
| `name` | No | Hostname (DNS label, ≤63 chars). |
| `project` | Yes* | `projects/{slug}` |
| `labels` / `annotations` | No | Metadata maps. |

### spec.region

Required. e.g. `datacenters/us-dal-1`.

### spec.compute

| Field | Description |
| --- | --- |
| `cpuClass` | `cascadelake`, `milan`, etc. |
| `vcpus` | 1–64 |
| `ramGib` | RAM in GiB |
| `gpu.model` / `gpu.count` | Whole-GPU passthrough, e.g. `rtx4090` |

### spec.boot

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

| Field | Description |
| --- | --- |
| `image` | Catalog id, e.g. `ubuntu-24-04` |
| `diskGib` | Boot disk size with image/imageUrl |
| `diskName` | Existing disk as boot volume |

### spec.network

| Field | Default | Description |
| --- | --- | --- |
| `publicIpv4` | `false` | Opt-in public IPv4 (~$2/mo). IPv6 always on. |
| `network` | auto | Tenant network; empty → default in DC. |

### spec.users[]

| Field | Description |
| --- | --- |
| `name` | Login user (not `root`) |
| `sshKeys` | Registered key resource names |
| `sshPubkey` | Inline public key line |
| `password` | Console login password |

If `cloudInit` is set on `spec`, it replaces generated user config.

### spec.billing

`BILLING_MODE_HOURLY` (default) or `BILLING_MODE_MONTHLY_1` through `BILLING_MODE_MONTHLY_12`. Prepaid terms debit wallet at create.

## Create a VM

```
resp, err := compute.CreateVirtualMachine(ctx, connect.NewRequest(
    &computev1.CreateVirtualMachineRequest{
        Manifest: &computev1.VirtualMachineManifest{
            ApiVersion: "compute.metalhost.io/v1",
            Kind:       "VirtualMachine",
            Metadata: &computev1.VirtualMachineMetadata{
                Name: "web-1", Project: "projects/my-app",
            },
            Spec: &computev1.VirtualMachineSpec{
                Region: "datacenters/us-dal-1",
                Compute: &computev1.VMComputeSpec{
                    CpuClass: "cascadelake", Vcpus: 2, RamGib: 8,
                },
                Boot:    &computev1.VMBootSpec{Image: "ubuntu-24-04", DiskGib: 80},
                Network: &computev1.VMNetworkSpec{PublicIpv4: true},
                Users: []*computev1.UserSpec{{
                    Name: "ubuntu",
                    SshKeys: []string{"projects/my-app/ssh-keys/laptop"},
                }},
            },
        },
    },
))
opName := resp.Msg.GetOperation().GetName()
```

Poll `opName` with `OperationsService/GetOperation`. On success, `metadata.virtual_machine_name` is the full VM name.

## Quote before create

```
catalog := catalogv1connect.NewCatalogServiceClient(httpClient, base)
resp, err := catalog.QuoteVirtualMachine(ctx, connect.NewRequest(
    &catalogv1.QuoteVirtualMachineRequest{
        Vcpus: 2, RamGib: 8, CpuClass: "cascadelake", BootDiskGib: 80,
        PublicIpv4Count: 1,
    },
))
```

## SSH keys

Separate client — register before create:

```
keys := computev1connect.NewSSHKeysServiceClient(httpClient, base)
_, err := keys.CreateSSHKey(ctx, connect.NewRequest(&computev1.CreateSSHKeyRequest{
    ProjectName: "projects/my-app",
    SshKeyId:    "laptop",
    DisplayName: "Laptop",
    PublicKey:   "ssh-ed25519 AAAA... user@host",
}))
```

## List and get

```
list, _ := compute.ListVirtualMachines(ctx, connect.NewRequest(
    &computev1.ListVirtualMachinesRequest{ProjectName: project, PageSize: 100},
))
one, _ := compute.GetVirtualMachine(ctx, connect.NewRequest(
    &computev1.GetVirtualMachineRequest{Name: vm},
))
```

## Lifecycle

Delete, resize, and clone also return operations:

```
vm := "projects/my-app/virtual-machines/web-1"
compute.StopVirtualMachine(ctx, connect.NewRequest(&computev1.StopVirtualMachineRequest{Name: vm}))
compute.StartVirtualMachine(ctx, connect.NewRequest(&computev1.StartVirtualMachineRequest{Name: vm}))
compute.RestartVirtualMachine(ctx, connect.NewRequest(&computev1.RestartVirtualMachineRequest{Name: vm}))
compute.ResizeVirtualMachine(ctx, connect.NewRequest(&computev1.ResizeVirtualMachineRequest{
    Name: vm, Vcpus: 4, RamGib: 16, CpuClass: "cascadelake",
}))
compute.DeleteVirtualMachine(ctx, connect.NewRequest(&computev1.DeleteVirtualMachineRequest{Name: vm}))
```

## Snapshots and clone

```
compute.SnapshotVirtualMachine(ctx, connect.NewRequest(&computev1.SnapshotVirtualMachineRequest{
    VmName:      vm,
    DisplayName: "pre-upgrade",
}))
compute.CloneVirtualMachine(ctx, connect.NewRequest(&computev1.CloneVirtualMachineRequest{
    SourceVmName:      vm,
    TargetDisplayName: "web-1-copy",
}))
```

## Console

`OpenConsole` returns a short-lived URL for in-browser serial/VNC access — same RPC the dashboard console uses.

> **wallet balance**
>
> `FailedPrecondition` on create usually means insufficient wallet balance. Check [Go SDK — Wallet](https://metalhost.net/docs/developers/guides/sdk-wallet.md).

## What's next

- [Go SDK — Network & firewall](https://metalhost.net/docs/developers/guides/sdk-network.md)
- [Go SDK — Storage](https://metalhost.net/docs/developers/guides/sdk-storage.md)
- [Examples → create VM](https://metalhost.net/docs/developers/examples.md#vm-create-flags)
