Menu

Examples

Copy-paste recipes for common Metalhost tasks — the same operation in CLI, Go SDK, and curl. New to the platform? Read Concepts first. Set these env vars once:

export METALHOST_ENDPOINT=https://api.metalhost.net
export METALHOST_API_KEY=mh_live_...
export METALHOST_PROJECT=projects/my-project

Discover projects and datacenters

CLI

metalhost auth whoami -o json
metalhost project list
metalhost catalog datacenter list
metalhost catalog capacity --datacenter datacenters/us-dal-1

Go

projectv1connect.NewProjectServiceClient(httpClient, baseURL).ListProjects(ctx, ...)
catalogv1connect.NewCatalogServiceClient(httpClient, baseURL).ListDatacenters(ctx, ...)

curl

curl -sS -X POST "$METALHOST_ENDPOINT/aes.project.v1.ProjectService/ListProjects" \
  -H "Authorization: Bearer $METALHOST_API_KEY" -H "Content-Type: application/json" -d '{}'

Who am I?

CLI

metalhost auth whoami -o json

Go

import (
    iamv1connect "github.com/AES-Services/metalhost-sdk/gen/go/aes/iam/v1/iamv1connect"
    iamv1 "github.com/AES-Services/metalhost-sdk/gen/go/aes/iam/v1"
)

resp, err := iamv1connect.NewIamServiceClient(httpClient, baseURL).GetCallerIdentity(
    ctx, connect.NewRequest(&iamv1.GetCallerIdentityRequest{}),
)

curl

curl -sS -X POST "$METALHOST_ENDPOINT/aes.iam.v1.IamService/GetCallerIdentity" \
  -H "Authorization: Bearer $METALHOST_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{}'

Register an SSH key

CLI

metalhost vm ssh-key create \
  --id laptop \
  --public-key "$(cat ~/.ssh/id_ed25519.pub)"

Go

keys := computev1connect.NewSSHKeysServiceClient(httpClient, baseURL)
_, err := keys.CreateSSHKey(ctx, connect.NewRequest(&computev1.CreateSSHKeyRequest{
    ProjectName: os.Getenv("METALHOST_PROJECT"),
    SshKeyId:    "laptop",
    PublicKey:   string(mustRead("~/.ssh/id_ed25519.pub")),
}))

curl

curl -sS -X POST "$METALHOST_ENDPOINT/aes.compute.v1.SSHKeysService/CreateSSHKey" \
  -H "Authorization: Bearer $METALHOST_API_KEY" \
  -H "Content-Type: application/json" \
  -d "$(jq -n --arg p "$METALHOST_PROJECT" --arg k "$(cat ~/.ssh/id_ed25519.pub)" \
    '{projectName:$p, sshKeyId:"laptop", publicKey:$k}')"

Create a VM (flags)

CLI

metalhost vm create \
  --hostname web-1 \
  --vcpus 2 --ram-gib 8 --cpu-class cascadelake \
  --image ubuntu-24-04 --disk-size-gib 80 \
  --assign-public-ipv4 \
  --ssh-key-name "$METALHOST_PROJECT/ssh-keys/laptop" \
  --wait

Go

compute := computev1connect.NewComputeServiceClient(httpClient, baseURL)
resp, err := compute.CreateVirtualMachine(ctx, connect.NewRequest(
    &computev1.CreateVirtualMachineRequest{
        Manifest: &computev1.VirtualMachineManifest{
            Metadata: &computev1.VirtualMachineMetadata{
                Name: "web-1", Project: os.Getenv("METALHOST_PROJECT"),
            },
            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{os.Getenv("METALHOST_PROJECT") + "/ssh-keys/laptop"},
                }},
            },
        },
    },
))
opName := resp.Msg.GetOperation().GetName()

curl

curl -sS -X POST "$METALHOST_ENDPOINT/aes.compute.v1.ComputeService/CreateVirtualMachine" \
  -H "Authorization: Bearer $METALHOST_API_KEY" \
  -H "Content-Type: application/json" \
  -d @- <<'EOF'
{
  "manifest": {
    "metadata": { "name": "web-1", "project": "projects/my-project" },
    "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-project/ssh-keys/laptop"] }]
    }
  }
}
EOF

Create a GPU VM

Whole-GPU passthrough — check capacity first with metalhost catalog capacity. Full manifest fields: CLI → VMs (YAML).

CLI

metalhost vm create \
  --hostname gpu-1 \
  --vcpus 8 --ram-gib 32 --cpu-class cascadelake \
  --gpu-model rtx4090 --gpu-count 1 \
  --image ubuntu-24-04 --disk-size-gib 100 \
  --assign-public-ipv4 \
  --ssh-key-name "$METALHOST_PROJECT/ssh-keys/laptop" \
  --wait

Go

compute.CreateVirtualMachine(ctx, connect.NewRequest(
    &computev1.CreateVirtualMachineRequest{
        Manifest: &computev1.VirtualMachineManifest{
            Metadata: &computev1.VirtualMachineMetadata{Name: "gpu-1", Project: project},
            Spec: &computev1.VirtualMachineSpec{
                Region: "datacenters/us-dal-1",
                Compute: &computev1.VMComputeSpec{
                    CpuClass: "cascadelake", Vcpus: 8, RamGib: 32,
                    Gpu: &computev1.GPUSpec{Model: "rtx4090", Count: 1},
                },
                Boot: &computev1.VMBootSpec{Image: "ubuntu-24-04", DiskGib: 100},
                Network: &computev1.VMNetworkSpec{PublicIpv4: true},
            },
        },
    },
))

Create a VM (YAML manifest)

Full guide with GPU, billing, cloud-init, and multi-user examples: CLI → VMs (YAML manifest). Minimal example:

apiVersion: compute.metalhost.io/v1
kind: VirtualMachine
metadata:
  name: web-1
  project: projects/my-project
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-project/ssh-keys/laptop

CLI

metalhost vm apply -f vm.yaml --wait

Go

Unmarshal YAML into computev1.VirtualMachineManifest (e.g. with google.golang.org/protobuf/encoding/protojson after a YAML→JSON hop), then pass to CreateVirtualMachineRequest with a Manifest field.

curl

Convert the manifest to the JSON shape in the previous section and POST to CreateVirtualMachine.

Poll an operation

CLI

metalhost ops wait operations/op_abc123
# or block on the mutating command directly:
metalhost vm create ... --wait

Go

ops := opsv1connect.NewOperationsServiceClient(httpClient, baseURL)
for {
    r, _ := ops.GetOperation(ctx, connect.NewRequest(
        &opsv1.GetOperationRequest{Name: opName},
    ))
    st := r.Msg.GetOperation().GetState()
    if st == opsv1.State_STATE_SUCCEEDED || st == opsv1.State_STATE_FAILED {
        break
    }
    time.Sleep(2 * time.Second)
}

curl

curl -sS -X POST "$METALHOST_ENDPOINT/aes.ops.v1.OperationsService/GetOperation" \
  -H "Authorization: Bearer $METALHOST_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"name":"operations/op_abc123"}' | jq '.operation.state'

List and get VMs

CLI

metalhost vm list
metalhost vm get web-1 -o json | jq '.virtualMachine.publicIpv4'

Go

list, _ := compute.ListVirtualMachines(ctx, connect.NewRequest(
    &computev1.ListVirtualMachinesRequest{ProjectName: project, PageSize: 100},
))
one, _ := compute.GetVirtualMachine(ctx, connect.NewRequest(
    &computev1.GetVirtualMachineRequest{Name: "projects/my-project/virtual-machines/web-1"},
))

curl

curl -sS -X POST "$METALHOST_ENDPOINT/aes.compute.v1.ComputeService/ListVirtualMachines" \
  -H "Authorization: Bearer $METALHOST_API_KEY" \
  -H "Content-Type: application/json" \
  -d "{\"projectName\":\"$METALHOST_PROJECT\"}"

VM lifecycle

Start, stop, resize, snapshot, and clone — see CLI → VMs.

CLI

metalhost vm stop web-1
metalhost vm start web-1
metalhost vm resize web-1 --vcpus 4 --ram-gib 16 --cpu-class cascadelake
metalhost vm snapshot create web-1 --display-name pre-upgrade
metalhost vm clone --source projects/my-project/virtual-machines/web-1 --display-name web-1-clone

Go

compute.StopVirtualMachine(ctx, connect.NewRequest(&computev1.StopVirtualMachineRequest{Name: vm}))
compute.StartVirtualMachine(ctx, connect.NewRequest(&computev1.StartVirtualMachineRequest{Name: vm}))
compute.ResizeVirtualMachine(ctx, connect.NewRequest(&computev1.ResizeVirtualMachineRequest{
    Name: vm, Vcpus: 4, RamGib: 16, CpuClass: "cascadelake",
}))
compute.CreateVmSnapshot(ctx, connect.NewRequest(&computev1.CreateVmSnapshotRequest{
    VmName: vm, DisplayName: "pre-upgrade",
}))
compute.CloneVirtualMachine(ctx, connect.NewRequest(&computev1.CloneVirtualMachineRequest{
    SourceVmName: vm, TargetDisplayName: "web-1-clone",
}))

Delete a VM

CLI

metalhost vm delete web-1 --yes --wait

Go

_, err := compute.DeleteVirtualMachine(ctx, connect.NewRequest(
    &computev1.DeleteVirtualMachineRequest{
        Name: "projects/my-project/virtual-machines/web-1",
    },
))

curl

curl -sS -X POST "$METALHOST_ENDPOINT/aes.compute.v1.ComputeService/DeleteVirtualMachine" \
  -H "Authorization: Bearer $METALHOST_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"name":"projects/my-project/virtual-machines/web-1"}'

Quote pricing

CLI

metalhost catalog pricing quote \
  --vcpus 2 --ram-gib 8 --cpu-class cascadelake --boot-disk-gib 80

Go

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

curl

curl -sS -X POST "$METALHOST_ENDPOINT/aes.catalog.v1.CatalogService/QuoteVirtualMachine" \
  -H "Authorization: Bearer $METALHOST_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"vcpus":2,"ramGib":8,"cpuClass":"cascadelake","bootDiskGib":80}'

Lease bare metal

CLI

metalhost bare-metal available list
metalhost bare-metal quote hosts/dal-r740-01 --billing-mode monthly-1
metalhost bare-metal create \
  --name my-box --host hosts/dal-r740-01 \
  --image ubuntu-24-04 --ssh-key-name "$METALHOST_PROJECT/ssh-keys/laptop" \
  --billing-mode monthly-1 --public-ipv4-prefix-len 32

Go

bm := baremetalv1connect.NewBareMetalServiceClient(httpClient, baseURL)
bm.ListAvailableBareMetal(ctx, connect.NewRequest(&baremetalv1.ListAvailableBareMetalRequest{}))
bm.CreateBareMetalInstance(ctx, connect.NewRequest(&baremetalv1.CreateBareMetalInstanceRequest{
    Name: "bare-metal-instances/my-box", ProjectName: project,
    HostName: "hosts/dal-r740-01", OsImage: "ubuntu-24-04",
    SshKeyNames: []string{project + "/ssh-keys/laptop"},
}))

curl

curl -sS -X POST "$METALHOST_ENDPOINT/aes.baremetal.v1.BareMetalService/ListAvailableBareMetal" \
  -H "Authorization: Bearer $METALHOST_API_KEY" -H "Content-Type: application/json" -d '{}'

Bare-metal leases include unlimited bandwidth — no transfer metering. Full lifecycle (power, ISO, release): Go SDK — Bare metal.

Create a file share

CLI

metalhost storage file-share create \
  --size-gib 100 \
  --datacenter datacenters/us-dal-1 \
  --display-name shared-data

Go

storage.CreateFileShare(ctx, connect.NewRequest(&storagev1.CreateFileShareRequest{
    ProjectName: project, DatacenterName: "datacenters/us-dal-1",
    SizeGib: 100, DisplayName: "shared-data",
}))

Mount from any VM on the same tenant network — CLI → Storage.

Create and attach a disk

CLI

metalhost storage disk create --size-gib 100 --region datacenters/us-dal-1
metalhost storage disk attach projects/my-project/disks/data-1 web-1
metalhost storage disk resize projects/my-project/disks/data-1 --size-gib 200

Go

storage := storagev1connect.NewStorageServiceClient(httpClient, baseURL)
storage.CreateDisk(ctx, connect.NewRequest(&storagev1.CreateDiskRequest{
    Parent: project,
    Disk: &storagev1.Disk{
        DatacenterName: "datacenters/us-dal-1",
        SizeGib: 100, StorageClass: "nvme",
    },
}))
storage.AttachDisk(ctx, connect.NewRequest(&storagev1.AttachDiskRequest{
    DiskName: "projects/my-project/disks/data-1",
    VmName:   "projects/my-project/virtual-machines/web-1",
}))

curl

curl -sS -X POST "$METALHOST_ENDPOINT/aes.storage.v1.StorageService/CreateDisk" \
  -H "Authorization: Bearer $METALHOST_API_KEY" -H "Content-Type: application/json" \
  -d '{"parent":"projects/my-project","disk":{"datacenterName":"datacenters/us-dal-1","sizeGib":100,"storageClass":"nvme"}}'

Create a tenant network

Only needed for extra isolation — default network is auto-created per datacenter.

CLI

metalhost network create \
  --id staging-net \
  --region datacenters/us-dal-1 \
  --display-name "Staging"

Go

net.CreateNetwork(ctx, connect.NewRequest(&networkv1.CreateNetworkRequest{
    ProjectName: project, NetworkId: "staging-net",
    DatacenterName: "datacenters/us-dal-1", DisplayName: "Staging",
}))

Open a firewall port

CLI

metalhost network firewall create \
  --vm web-1 --datacenter datacenters/us-dal-1 \
  --port tcp:22 --source 203.0.113.10/32 \
  --display-name "ssh from office"

Go

net := networkv1connect.NewNetworkServiceClient(httpClient, baseURL)
net.CreateFirewallRule(ctx, connect.NewRequest(&networkv1.CreateFirewallRuleRequest{
    ProjectName: project, DatacenterName: "datacenters/us-dal-1",
    TargetVm: project + "/virtual-machines/web-1",
    Sources: []string{"203.0.113.10/32"},
    Ports: []*networkv1.PortMapping{{Protocol: "tcp", Port: 22}},
}))

curl

curl -sS -X POST "$METALHOST_ENDPOINT/aes.network.v1.NetworkService/CreateFirewallRule" \
  -H "Authorization: Bearer $METALHOST_API_KEY" -H "Content-Type: application/json" \
  -d '{"projectName":"projects/my-project","datacenterName":"datacenters/us-dal-1","targetVm":"projects/my-project/virtual-machines/web-1","sources":["203.0.113.10/32"],"ports":[{"protocol":"tcp","port":22}]}'

Register a webhook

CLI support is limited — use the SDK or curl. Verify deliveries with HMAC-SHA256:

Go

hooks := webhooksv1connect.NewWebhooksServiceClient(httpClient, baseURL)
resp, _ := hooks.CreateSubscription(ctx, connect.NewRequest(
    &webhooksv1.CreateSubscriptionRequest{
        Name: "webhook-subscriptions/deploy-hook",
        ProjectName: project,
        EndpointUrl: "https://hooks.example.com/metalhost",
        EventTypes: []string{"compute.vm.*", "ops.operation.completed"},
    },
))
secret := resp.Msg.GetSecret() // store for HMAC verification

curl

curl -sS -X POST "$METALHOST_ENDPOINT/aes.webhooks.v1.WebhooksService/CreateSubscription" \
  -H "Authorization: Bearer $METALHOST_API_KEY" -H "Content-Type: application/json" \
  -d '{"name":"webhook-subscriptions/deploy-hook","projectName":"projects/my-project","endpointUrl":"https://hooks.example.com/metalhost","eventTypes":["compute.vm.*"]}'

See Go SDK — Webhooks for signature verification.

Mint an API key

CLI

metalhost iam keys create \
  --display-name ci-bot \
  --project projects/my-app \
  --project-scoped \
  -o json | jq -r '.secret'

Go

iam.CreateApiKey(ctx, connect.NewRequest(&iamv1.CreateApiKeyRequest{
    DisplayName: "ci-bot", DefaultProject: project, ProjectScoped: true,
}))
// resp.Msg.Secret — shown once; store immediately

Dashboard walkthrough: API keys.

Open a support ticket

CLI

metalhost support ticket create \
  --org organizations/my-org \
  --subject "Quota increase" \
  --body "Need 50 VMs in projects/prod for load test." \
  --category BILLING

Go

support.CreateTicket(ctx, connect.NewRequest(&supportv1.CreateTicketRequest{
    OrganizationName: org, Subject: "Quota increase",
    Body: "Need 50 VMs in projects/prod for load test.",
    Category: supportv1.TicketCategory_TICKET_CATEGORY_BILLING,
}))

Check quota usage

CLI

metalhost quota --project projects/my-app -o json

Request increases via support — CLI → Support.

Check wallet balance

CLI

metalhost wallet account list --org organizations/my-org
metalhost wallet balance organizations/my-org/wallets/default

Go

wallet := walletv1connect.NewWalletServiceClient(httpClient, baseURL)
_, err := wallet.GetWalletBalance(ctx, connect.NewRequest(
    &walletv1.GetWalletBalanceRequest{
        Name: "organizations/my-org/wallets/default",
    },
))

curl

curl -sS -X POST "$METALHOST_ENDPOINT/aes.wallet.v1.WalletService/GetWalletBalance" \
  -H "Authorization: Bearer $METALHOST_API_KEY" -H "Content-Type: application/json" \
  -d '{"name":"organizations/my-org/wallets/default"}'

Top up with crypto (Coinbase)

Returns a checkout URL — settled 1:1 in USDC. Minimum $5 (500 minor units).

CLI

metalhost wallet top-up coinbase \
  --wallet organizations/my-org/wallets/default \
  --amount-minor 5000

Card top-ups and payment methods: CLI → Wallet.

CI script pattern

A minimal bash deploy script using only the CLI and jq:

#!/usr/bin/env bash
set -euo pipefail

: "${METALHOST_API_KEY:?}"
: "${METALHOST_PROJECT:?}"

NAME="ci-$(date +%s)"

metalhost vm create \
  --hostname "$NAME" \
  --vcpus 2 --ram-gib 4 --cpu-class cascadelake \
  --image ubuntu-24-04 --disk-size-gib 40 \
  --ssh-key-name "$METALHOST_PROJECT/ssh-keys/ci" \
  --wait -o json > create.json

IP=$(jq -r '.operation.metadata.virtual_machine_name // empty' create.json)
VM=$(metalhost vm get "$NAME" -o json)
IP=$(echo "$VM" | jq -r '.virtualMachine.publicIpv4')
echo "VM $NAME at $IP"
# ... run tests over SSH ...
metalhost vm delete "$NAME" --yes --wait

What's next