A fast, kubectl-style command-line tool for managing Harvester HCI clusters — create and control VMs, images, volumes, networks, and hosts without leaving your terminal.
| Area | Capabilities |
|---|---|
| Virtual Machines | List, create, delete, start, stop, restart, live-migrate |
| VM Images | List (with StorageClass), upload from URL or file |
| Networks | List NADs with VLAN info |
| Volumes | List PVCs with live Longhorn usage and StorageClass; create new PVCs |
| Hosts | List nodes with real-time CPU % and memory usage from the metrics API |
| Templates | List and inspect VM templates |
| SSH Keypairs | List registered public keys |
| Shell | Direct SSH into a running VM |
| Config | Login to Rancher and auto-download the Harvester kubeconfig |
| Dry-run | Print the Kubernetes YAML for any create command without applying it — ideal for GitOps workflows |
brew install abonillabeeche/tap/harvesterHomebrew handles the macOS quarantine flag automatically, so Gatekeeper will not block the binary.
First-time setup: add the tap once with
brew tap abonillabeeche/tap, then install as above.
curl -fsSL https://raw.githubusercontent.com/abonillabeeche/harvester-cli/main/install.sh | shThe script auto-detects your OS and architecture, downloads the right binary from the latest release, and removes the macOS quarantine flag. To install to a custom location:
INSTALL_DIR=~/.local/bin sh <(curl -fsSL https://raw.githubusercontent.com/abonillabeeche/harvester-cli/main/install.sh)Download the .exe for your architecture from the latest release and place it somewhere on your %PATH%.
Requirements: Go 1.21+
git clone https://github.com/abonillabeeche/harvester-cli.git
cd harvester-cli
go build -o harvester .
sudo mv harvester /usr/local/bin/
harvester --versionharvester needs a kubeconfig that points to your Harvester cluster's Kubernetes API. There are three ways to provide it, in order of precedence:
Place the kubeconfig at ~/.harvester/config. This is the path the CLI uses automatically with no extra flags.
mkdir -p ~/.harvester
cp /path/to/your/harvester-kubeconfig ~/.harvester/configexport HARVESTER_CONFIG=/path/to/kubeconfig
harvester vm listharvester --harvester-config /path/to/kubeconfig vm listIf your Harvester cluster is imported into Rancher, you can download the kubeconfig automatically using a Rancher API token:
# Log in to your Rancher server
harvester login https://<RANCHER_URL> -t <RANCHER_API_TOKEN>
# Download the kubeconfig for the target Harvester cluster
harvester get-config <HARVESTER_CLUSTER_NAME>
# Saved to ~/.harvester/config automaticallyAll commands accept -n <namespace> (or --namespace) to target a specific namespace, mirroring kubectl behavior. The default namespace is default.
harvester vm list [-n NAMESPACE]Lists all VMs in the cluster with their state, CPU, memory, and IP address.
harvester vm create [FLAGS...] VM_NAMECreates a VM. Important: all flags must come before the VM name.
| Flag | Short | Description |
|---|---|---|
--namespace |
-n |
Target namespace (default: default) |
--cpus |
-c |
Number of vCPUs (default: 1) |
--memory |
-m |
RAM size, e.g. 4Gi (default: 1Gi) |
--disk-size |
-d |
Root disk size, e.g. 20Gi (default: 10Gi) |
--vm-image-id |
Harvester image ID to boot from | |
--ssh-keyname |
-i |
SSH keypair name (use namespace/name for cross-namespace) |
--network |
Network attachment, e.g. default/vlan1 |
|
--template |
VM template in name:version format |
|
--count |
Create N identical VMs named basename-1…basename-N |
|
--user-data-filepath |
Path to a cloud-init user-data YAML file | |
--dry-run |
Print the KubeVirt YAML without creating the VM |
Examples:
# Create a VM using a specific image and network
harvester vm create --cpus 2 --memory 4Gi --vm-image-id default/ubuntu-noble --network default/vlan1 --ssh-keyname default/mykey my-vm
# Create 3 VMs from a template
harvester vm create --template ubuntu-base:1 --count 3 test-vm
# Create a VM in a non-default namespace
harvester vm create -n dev --cpus 4 --memory 8Gi dev-vm
# Preview the manifest without applying it
harvester vm create --dry-run --vm-image-id default/ubuntu-noble --network default/vlan1 my-vmharvester vm stop VM_NAME [-n NAMESPACE]
harvester vm start VM_NAME [-n NAMESPACE]
harvester vm restart VM_NAME [-n NAMESPACE]
harvester vm delete VM_NAME [-n NAMESPACE]harvester vm migrate [--node TARGET_NODE] VM_NAME [-n NAMESPACE]Live-migrates a running VM to another host without downtime. Omit --node to let the scheduler choose the best target automatically.
# Migrate to a specific host
harvester vm migrate --namespace default --node gr6-2 my-vm
# Let the scheduler pick the target
harvester vm migrate -n default my-vmharvester image list [-n NAMESPACE]Lists VM images with their source type, StorageClass, and URL.
NAME ID SOURCE TYPE STORAGE CLASS URL
ubuntu-noble default/ubuntu-noble download tworeplicas https://cloud-images.ubuntu.com/...
harvester image create --source URL [--storage-class CLASS] [-n NAMESPACE] IMAGE_NAMEUploads a VM image from an HTTP/HTTPS URL (or local file path). Prints the image ID on success.
# Upload Ubuntu Noble using the tworeplicas storage class
harvester image create \
--source https://cloud-images.ubuntu.com/noble/current/noble-server-cloudimg-amd64.img \
--storage-class tworeplicas \
ubuntu-noble
# Image created: default/ubuntu-noble
# Preview the manifest without applying it
harvester image create --dry-run \
--source https://cloud-images.ubuntu.com/noble/current/noble-server-cloudimg-amd64.img \
ubuntu-nobleharvester network list [-n NAMESPACE]Lists NetworkAttachmentDefinitions with their CNI type and VLAN ID.
NAME NAMESPACE TYPE VLAN ID
vlan1 default bridge 1
vlan10 default bridge 10
harvester volume list [-n NAMESPACE]Lists PersistentVolumeClaims cross-referenced with Longhorn to show actual used space.
NAME NAMESPACE STATE CAPACITY USED STORAGE CLASS
my-vm-disk default Healthy 20.0 GiB 3.2 GiB tworeplicas
data-vol default Healthy 100.0 GiB 45.1 GiB harvester-longhorn
harvester volume create --storage-class CLASS --size SIZE [-n NAMESPACE] [--dry-run] VOLUME_NAMECreates a PersistentVolumeClaim backed by the specified StorageClass. Use binary suffixes for size (Gi, Mi).
| Flag | Short | Description |
|---|---|---|
--storage-class |
--sc |
StorageClass for the volume (see harvester volume list-storageclass) |
--size |
-s |
Volume size, e.g. 10Gi, 500Mi |
--namespace |
-n |
Target namespace (default: default) |
--dry-run |
Print the PVC YAML without creating it |
harvester volume create --sc tworeplicas --size 20Gi my-data-vol
# Volume created: default/my-data-volharvester volume list-storageclassLists all StorageClasses in the cluster — equivalent to kubectl get sc.
NAME PROVISIONER RECLAIM POLICY BINDING MODE ALLOW EXPANSION
harvester-longhorn driver.longhorn.io Delete Immediate true
tworeplicas driver.longhorn.io Delete Immediate true
harvester host listLists all cluster nodes with real-time CPU and memory usage pulled from the Kubernetes Metrics API. Falls back gracefully to <unknown> if the metrics server is unavailable.
NAME STATUS ROLES AGE CPU(cores) CPU% MEM USE MEM% MEM TOTAL
gr6-1 Ready etcd,control-plane,master 769d 1918m 12% 26.0 GiB 41% 62.2 GiB
gr6-2 Ready etcd 769d 1126m 10% 3.0 GiB 10% 28.3 GiB
gr6-3 Ready etcd,master,control-plane 769d 2480m 16% 26.5 GiB 42% 62.2 GiB
- CPU(cores): live usage in millicores (e.g.
1918m= 1.918 cores) - CPU%: usage relative to allocatable CPU on that node
- MEM USE: live memory usage from the metrics API
- MEM%: usage relative to allocatable memory on that node
- MEM TOTAL: total installed RAM (
node.Status.Capacity)
harvester template list [-n NAMESPACE]
# Show a specific template version
harvester template show NAME:VERSION [-n NAMESPACE]
harvester template show ubuntu-base:1harvester keypair list [-n NAMESPACE]Lists SSH public keys registered in Harvester, which can be referenced by name when creating VMs.
harvester shell [--ssh-user USER] [--ssh-key PATH] VM_NAMEOpens an interactive SSH session directly into a running VM. Requires ssh to be available on the local system.
| Flag | Default | Description |
|---|---|---|
--ssh-user |
ubuntu |
Username to connect as |
--ssh-key |
~/.ssh/id_rsa |
Path to private key |
--ssh-port |
22 |
SSH port |
harvester shell --ssh-user ubuntu --ssh-key ~/.ssh/mykey my-vmEvery create subcommand accepts a --dry-run flag. Instead of calling the Kubernetes API, the CLI prints the fully-rendered YAML manifest to stdout and exits. This is useful for:
- GitOps workflows — generate manifests locally, commit them to a Git repo, and let Fleet, Flux or ArgoCD apply them to the cluster.
- Reviewing changes before applying — inspect the exact object the CLI would create before committing to it.
- Piping into
kubectl apply— runharvester vm create --dry-run ... | kubectl apply -f -for one-shot creation using the same flags as your normal workflow.
harvester volume create --dry-run --sc tworeplicas --size 5Gi testvol1---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
creationTimestamp: null
name: testvol1
namespace: default
spec:
accessModes:
- ReadWriteMany
resources:
requests:
storage: 5Gi
storageClassName: tworeplicas
volumeMode: Block
status: {}harvester image create --dry-run \
--source https://cloud-images.ubuntu.com/noble/current/noble-server-cloudimg-amd64.img \
--storage-class tworeplicas \
ubuntu-noble---
apiVersion: harvesterhci.io/v1beta1
kind: VirtualMachineImage
metadata:
creationTimestamp: null
generateName: image-
namespace: default
spec:
displayName: ubuntu-noble
sourceType: download
storageClassParameters: {}
targetStorageClassName: tworeplicas
url: https://cloud-images.ubuntu.com/noble/current/noble-server-cloudimg-amd64.img
status:
progress: 0harvester vm create --dry-run \
--vm-image-id default/image-abc12 \
--network default/vlan1 \
--cpus 2 --memory 4Gi --disk-size 20Gi \
my-vm---
apiVersion: kubevirt.io/v1
kind: VirtualMachine
metadata:
annotations:
harvesterhci.io/volumeClaimTemplates: '[{"metadata":{"name":"my-vm-disk-0-...","annotations":{"harvesterhci.io/imageId":"default/image-abc12"}},"spec":{"accessModes":["ReadWriteMany"],"resources":{"requests":{"storage":"20Gi"}},"volumeMode":"Block","storageClassName":"longhorn-image-abc12"}}]'
networks.harvesterhci.io/ips: '[]'
labels:
harvesterhci.io/creator: harvester
name: my-vm
namespace: default
spec:
runStrategy: Always
template:
spec:
domain:
cpu:
cores: 2
sockets: 1
threads: 1
devices:
disks:
- disk:
bus: virtio
name: disk-0
- disk:
bus: virtio
name: cloudinitdisk
interfaces:
- bridge: {}
model: virtio
name: nic-1
resources:
limits:
cpu: "2"
memory: 4Gi
networks:
- multus:
networkName: default/vlan1
name: nic-1
volumes:
- name: disk-0
persistentVolumeClaim:
claimName: my-vm-disk-0-...
- cloudInitNoCloud:
networkData: "..."
userData: "#cloud-config\n..."
name: cloudinitdisk# Generate manifests for a new environment
harvester volume create --dry-run --sc tworeplicas --size 50Gi -n prod db-vol > manifests/db-vol.yaml
harvester image create --dry-run --source https://example.com/os.qcow2 prod-os >> manifests/images.yaml
harvester vm create --dry-run --vm-image-id default/prod-os \
--network default/vlan10 --cpus 4 --memory 8Gi -n prod \
db-server > manifests/db-server.yaml
# Commit and push — let your GitOps controller apply them
git add manifests/ && git commit -m "add prod db-server" && git pushDue to how Go's flag parser works with positional arguments, all flags must appear before the VM name:
# Correct
harvester vm create --cpus 2 --memory 4Gi my-vm
# Wrong — flags after the name are silently ignored
harvester vm create my-vm --cpus 2 --memory 4GiWhen your VM lives in namespace dev but the SSH key or network is in default, use the namespace/name format:
harvester vm create -n dev \
--ssh-keyname default/mykey \
--network default/vlan1 \
dev-vmThe show subcommand requires both name and version separated by a colon:
harvester template show ubuntu-base:1Add --debug before any command to enable verbose logging:
harvester --debug vm list