---
title: "CLI — Getting started"
description: "Install and configure the metalhost CLI — init, profiles, global flags, operations, and topic guides."
url: "https://metalhost.net/docs/developers/cli"
---

# CLI — Getting started

The `metalhost` CLI talks to the same HTTP API the dashboard uses. Every command in this reference works against `api.metalhost.net` with an API key — no port-forwarding, no cluster access.

> **new here?**
>
> Read [Concepts](https://metalhost.net/docs/developers/concepts.md), then follow the [CLI quickstart](https://metalhost.net/docs/developers/quickstart.md) (init → SSH key → VM → SSH in). Declarative VMs: [VM YAML manifest](https://metalhost.net/docs/developers/guides/cli-vms.md#yaml-manifest).

## Topic guides

Per-resource command reference with examples:

- [VMs](https://metalhost.net/docs/developers/guides/cli-vms.md) — create flags, YAML manifests, lifecycle, snapshots
- [Bare metal](https://metalhost.net/docs/developers/guides/cli-bare-metal.md) — browse, lease, power, ISO library
- [Storage](https://metalhost.net/docs/developers/guides/cli-storage.md) — disks and file shares
- [Network & firewall](https://metalhost.net/docs/developers/guides/cli-network.md)
- [Wallet & billing](https://metalhost.net/docs/developers/guides/cli-wallet.md)
- [IAM, projects & orgs](https://metalhost.net/docs/developers/guides/cli-iam.md)
- [Webhooks](https://metalhost.net/docs/developers/guides/cli-webhooks.md)
- [Support](https://metalhost.net/docs/developers/guides/cli-support.md)
- [Catalog, quotas & audit](https://metalhost.net/docs/developers/guides/cli-catalog.md)

## How the CLI works

The CLI is a thin wrapper around the public API (`https://api.metalhost.net`). It stores your API key and defaults in a **profile** file, sends Connect-RPC requests with `Authorization: Bearer`, and formats JSON responses as tables or YAML.

- **Scope** — `--project`, `--org`, `--region` flags (or profile defaults) determine which resources commands target. Bare slugs like `web-1` expand to `projects/my-app/virtual-machines/web-1`.
- **Operations** — VM create/delete/resize return `operations/…`. Use `--wait` or `metalhost ops wait` to block until done.
- **Output** — `-o json` for scripting, `-o yaml` for manifests, `-q` for names only.
- **Manifests** — `metalhost vm apply -f vm.yaml` for declarative VMs. See [CLI → VMs (YAML)](https://metalhost.net/docs/developers/guides/cli-vms.md#yaml-manifest).

## Install

Current release: [v1.1.2](https://github.com/AES-Services/metalhost-cli/releases/tag/v1.1.2). Includes [single-disk backups and automatic schedules](https://metalhost.net/docs/developers/guides/cli-storage.md), whole-VM capture and restore, and [monitoring, alerts, and GitHub Actions](https://metalhost.net/docs/developers/observability.md).

On macOS or Linux, run this. It fetches the latest CLI from GitHub, checks the checksum when GitHub published one, and copies the `metalhost` binary onto your machine:

```
curl -fsSL https://metalhost.net/install-cli.sh | bash
```

When it works, you’ll see an install path and a version line. That version line is the installer calling the binary by its full path — it does **not** mean you can type `metalhost` yet.

```
→ Installing metalhost v1.1.2 (darwin/arm64)
→ Checksum verified
→ Installed /Users/you/.local/bin/metalhost
→ metalhost 1.1.2 (…)
```

### Where it lands

The script writes to a directory it can write without sudo:

- `/usr/local/bin` — if that folder is writable. This is already on PATH for most systems, so `metalhost version` usually works in the same terminal.
- `~/.local/bin` — fallback when `/usr/local/bin` is not writable. This is the usual case on macOS. That folder is **not** on PATH by default, so the next command is `command not found` until you add it.

Read the `→ Installed …` line if you’re unsure which one you got.

### If you see `command not found`

The binary is installed. Your current terminal just doesn’t search that folder. `curl | bash` runs in a subprocess, so it cannot change PATH in the shell you’re typing in. Add the install directory for this session, then check:

```
export PATH="$HOME/.local/bin:$PATH"
metalhost version
```

You should see something like `metalhost 1.1.2 (…)`. To keep that in new terminal windows, append the same `export` to your shell startup file and reload it:

```
# macOS default shell (zsh)
echo 'export PATH="$HOME/.local/bin:$PATH"' >> ~/.zshrc
source ~/.zshrc

# Linux bash
echo 'export PATH="$HOME/.local/bin:$PATH"' >> ~/.bashrc
source ~/.bashrc
```

> **still not found?**
>
> Confirm the file exists (`ls ~/.local/bin/metalhost`) and that the `export` uses the same directory as the installer’s `Installed` line. Then open a new terminal and try `metalhost version` again.

### Pin a version or pick a folder

Put `VERSION` or `INSTALL_DIR` on the `bash` side of the pipe (the right-hand side). Prefixing them on `curl` has no effect:

```
curl -fsSL https://metalhost.net/install-cli.sh | VERSION=v1.1.2 bash
curl -fsSL https://metalhost.net/install-cli.sh | INSTALL_DIR=~/.local/bin bash
```

### Windows, zip files, or Go

Download a pre-built binary from [GitHub Releases](https://github.com/AES-Services/metalhost-cli/releases) (includes Windows). Or, if you have Go installed: `go install github.com/AES-Services/metalhost-cli/cmd/metalhost@latest` (installs to your Go bin directory, usually `$(go env GOPATH)/bin`).

## First-run setup: `metalhost init`

Interactive wizard that walks you through authentication and writes a profile to `~/.config/metalhost/config.yaml`. After it finishes, every other CLI command uses the saved profile — no flags needed.

```
metalhost init
```

> **press Enter through the first prompts**
>
> The wizard asks for a **profile name** (a local label in `~/.config/metalhost/config.yaml`, not your Metalhost account) and then an **API endpoint**. Press Enter on both to accept the defaults (`default` and `https://api.metalhost.net`). That is when the paste-an-API-key / log-in / sign-up options appear.
>
> Do not paste an `aes_…` API key at the endpoint prompt — that value is only for the later API-key step.

The wizard asks how you want to authenticate:

1. **Paste an existing API key** — mint one in the dashboard under *Developers → API keys*.
2. **Log in with email + password** — exchanges credentials for a session token.
3. **Sign up** — creates a new account + organization; sends a verification email.
4. **OIDC / browser SSO** — delegates to `metalhost auth login --oidc <provider>`.

Then it lists your projects (prompting to create one if you have none) and picks a default datacenter.

## Global flags

These persistent flags work on every command.

| Flag | What it does |
| --- | --- |
| `--config PATH` | Path to the CLI config file. |
| `--profile NAME` | Switch profile for this command (default: active profile). |
| `--endpoint URL` | Override the API endpoint. |
| `-o, --format FMT` | `table` (default), `json`, or `yaml`. |
| `-q, --quiet` | Print only resource names — handy for piping into scripts. |
| `--project NAME` | Project scope for this command (overrides the profile default). |
| `--org NAME` | Organization scope (overrides the profile default). |
| `--region NAME` | Region / datacenter scope (overrides the profile default). |
| `--wait` | For commands that return an operation, block until it finishes and print the final operation. |
| `--wait-timeout DUR` | Max time to wait with `--wait` (default `10m`; `0` = no limit). |

> **bare slugs work everywhere**
>
> Most commands accept either a fully-qualified resource name (`projects/main/virtual-machines/web-1`) or a bare slug (`web-1`) that's expanded against your active scope.

## Unified verbs get · describe · delete · apply

Alongside the per-service command trees, the CLI exposes kubectl-style verbs that work uniformly across resource kinds (`vm`, `ssh-key`, `disk`, `file-share`, `network`, `baremetal`, `webhook`, `project`).

| Command | What it does |
| --- | --- |
| `get KIND` | List resources of a kind in the active scope. |
| `get KIND NAME` | Get one resource by name. |
| `describe KIND NAME` | Full detail for a single resource. |
| `delete KIND NAME [--yes]` | Delete a resource (prompts unless `--yes`). |
| `apply -f FILE` | Create/update from a declarative YAML/JSON spec — pairs with `get … -o yaml`. |

```
# list, then fetch one
metalhost get vm
metalhost get disk --all -o json
metalhost describe vm web-1

# round-trip a resource through a file
metalhost get vm web-1 -o yaml > vm.yaml
metalhost apply -f vm.yaml
```

## profile

Manage CLI profiles. Each profile holds an API endpoint, API key, default project, organization, and default region.

| Command | What it does |
| --- | --- |
| `profile list` | List configured profiles. |
| `profile create NAME` | Create a new profile. |
| `profile use NAME` | Switch the active profile. |
| `profile set NAME --key=value` | Update fields on a profile. |
| `profile delete NAME` | Remove a profile. |

## auth

| Command | What it does |
| --- | --- |
| `auth whoami` | Show the caller identity + default project + accessible orgs. |
| `auth login --email ADDR` | Email/password login (prompts for the password). |
| `auth login --oidc PROVIDER` | Browser SSO via an OIDC provider (e.g. `google`, `github`). |
| `auth login --api-key` | Store the key from `METALHOST_API_KEY` in the active profile. |

## project

| Command | What it does |
| --- | --- |
| `project list` | List projects you can access (auto-scoped to your orgs). |
| `project get NAME` | Get a project. |
| `project create NAME --org ORG --display-name "..."` | Create a project. |
| `project update NAME ...` | Update display name / labels / annotations. |
| `project delete NAME` | Delete a project (must be empty). |

## iam

API keys, members, sessions, MFA, invites. Common commands are in [CLI → IAM](https://metalhost.net/docs/developers/guides/cli-iam.md). Full command list:

| Command | What it does |
| --- | --- |
| `iam keys list\|create\|revoke\|rotate` | API key lifecycle. |
| `iam members list\|invite\|update-role\|remove` | Org membership. |
| `iam sessions list\|revoke\|logout` | Session management. |
| `iam mfa enroll\|verify\|revoke` | TOTP MFA. |
| `iam invites list\|accept\|revoke\|mine` | Pending invites. |
| `iam password change\|forgot\|reset` | Password flows. |
| `iam import-github-ssh-keys` | Bulk-import GitHub keys. |

## quota

`metalhost quota` — show current quota usage + limits for the active project's organization. Details: [CLI → Catalog & quotas](https://metalhost.net/docs/developers/guides/cli-catalog.md).

## audit

`metalhost audit search` (alias `audit list`) — search audit events. See [CLI → Catalog & quotas](https://metalhost.net/docs/developers/guides/cli-catalog.md#audit).

## catalog

Datacenters, capacity, and pricing quotes — [CLI → Catalog & quotas](https://metalhost.net/docs/developers/guides/cli-catalog.md).

## health

`metalhost health` — check API health and the served build version.

## ops aka `operations`

Most write RPCs return an `operations/<uuid>`. Track progress here (or pass `--wait` to the original command).

| Command | What it does |
| --- | --- |
| `ops list` | List recent operations. |
| `ops get NAME` | Get one operation. |
| `ops wait NAME` | Block until the operation finishes (success or error). |

## support aka `sup`

Org-scoped tickets — full reference: [CLI → Support](https://metalhost.net/docs/developers/guides/cli-support.md).

## Output formats

Every command supports `-o table` (default), `-o json`, or `-o yaml`. JSON is the safest pipe target — combine with `jq` for shell scripts:

```
# every VM's hostname + public IPv4
metalhost vm list -o json \
  | jq -r '.items[] | [.hostname, .public_ipv4] | @tsv'

# create and wait for the operation in one shot
metalhost vm create ... --wait

# just the resource names, for scripting
metalhost get vm -q
```

> **see also**
>
> Same flows over HTTP: [API reference](https://metalhost.net/docs/developers/api.md). Step-by-step dashboard walkthroughs: [Guides](https://metalhost.net/docs.md).
