WebDisk
Public cloud

CloudMonkey (cmk) in practice: Apache CloudStack from the terminal

Published:

WebDisk Blog · category: Public cloud · reading time: ~17 minutes

In short:- cmk (CloudMonkey) is the official command-line client for Apache CloudStack — the platform WebDisk Cloud runs on. The panel and cmk call the same API with the same permissions; the terminal adds repeatability and a written record, while the panel wins at the machine console and when you are still finding your way around.- Setting it up means one profile and a pair of API keys from the panel; you can run your first useful command (list virtualmachines) within minutes, and the output as JSON or CSV goes straight into a script or a spreadsheet.- To be fair: cmk is not infrastructure as code — it knows nothing about a desired state and will not roll a change back. Describing an environment in a repository is a job for Terraform or Ansible; cmk is for queries, one-off operations and quick automation. >Not a terminal person? Skip the command blocks — the description of the platform, the comparison of management methods and the section on the tool's limits all read fine without them.

It is Friday, 17:40. Twenty-four test machines still have to be shut down for the weekend — in the panel that means the same sequence twenty-four times over: find it, open the details, open the menu, "Stop", confirm. On Monday the same thing in reverse, except that two machines get overlooked and run all weekend. Nobody made a mistake — a tool meant for looking and deciding was used for assembly-line work.

This article shows the other route: managing the cloud from the terminal — from the shape of the CloudStack API, through cmk from installation to scripts and traps, to the honest limits of the tool and a survey of the ecosystem. You can run the examples on any CloudStack, including the WebDisk public cloud — the range of commands available to you depends on your account's role and on the services your operator offers.

What Apache CloudStack is, and what a customer sees of that architecture

CloudStack is an IaaS orchestrator, not a hypervisor. It virtualises nothing itself — it drives hypervisors (the software that runs virtual machines on a physical server): KVM, VMware vSphere, XenServer/XCP-ng. It adds the layer hypervisors lack: multi-tenancy, networking, storage, usage accounting and a single API. Apache 2.0 licensed and a full Apache Foundation project since March 2013 — no copyleft, no single-vendor "open core".

Internally the platform is hierarchical: region → zone → pod → cluster → host. Of that ladder a customer sees one rung — the zone, normally a single data centre. Pods, clusters and hosts are visible only to the operator's administrator, and that is deliberate: you do not choose the server your machine lands on.

Primary storage holds machine volumes — both running and stopped ones — and sits close to the hosts. Secondary storage is a zone-wide resource: templates, ISO images, snapshots. That is why a volume snapshot takes a while (the data travels between the two storage tiers), and why the first boot of a machine from a freshly registered template is slower than every boot after it.

Three kinds of system virtual machine that a customer meets day to day run in the background, and nobody orders them. The SSVM serves secondary storage. The Console Proxy sits between your browser and the machine's console — it is what makes "Console" work in the panel. The Virtual Router provides the network services: DHCP, DNS, source NAT, static NAT, port forwarding, load balancing, firewall and ACL rules, VPN, and the metadata endpoint for cloud-init. Architecturally that matters: NAT, port forwarding and load balancing are not separate products — you drive them through the same API as the machines. How they are billed is a question for your operator's price list.

One last thing that surprises people in the CLI: resources are owned by the account, not by the user. Users are ways of logging in; quotas, billing and ownership sit one floor above. Projects are a separate container, with their own limits and usage, exposed in the API through the projectid parameter. If you work in a project context in the panel but do not pass projectid in the terminal, you will see a different set of machines. WebDisk Cloud runs on CloudStack and KVM — we covered moving over from vSphere in the article on migration from VMware to WebDisk Cloud.

Panel, API, CLI and IaC — four shapes of the same access

Everything you do with CloudStack passes through one endpoint: the /client/api path on the management server. A request is an ordinary HTTP GET or POST with a command parameter; the response is XML by default, or JSON once you add response=json. Authentication is an HMAC-SHA1 signature computed with your secret key over the parameter string, sorted and lower-cased. Every other tool in this article is a wrapper around that.

Command names are consistent: verb + noundeployVirtualMachine, listVirtualMachines, stopVirtualMachine, createNetwork, associateIpAddress, deleteSnapshot. There are close to nine hundred commands (the API snapshot bundled with cmk 6.5.0 knows 880 of them, 370 of those asynchronous), but an ordinary account will see distinctly fewer — the list depends on your role and on the platform version. The scheme is regular enough that after a day of use you start guessing the names correctly. Two things are worth knowing from the outset: some commands are asynchronous — they return a job identifier (jobid) immediately rather than a result; and an API key inherits the permissions of the account's role, so the CLI is not a side entrance with wider privileges.

Most important of all: the graphical interface has no privileged channel into the platform. The panel — any panel — is an ordinary client of the same /client/api, and it discovers the list of features with the listApis command; the very same command drives autocompletion in cmk. The conclusion is often the opposite of what people expect: a new platform capability tends to reach the API before it gets a form of its own.

Hence the four management methods — not levels of skill, but shapes of the same access:

  • The panel — discoverability and context. Drop-down lists resolve UUIDs for you, forms enforce the order of dependencies, and the machine console is realistically available only here. It does not scale and it leaves no procedure behind.
  • The raw API — full control, zero dependencies. The signature alone is a few dozen lines of code; the cost is everything else: pagination, asynchronous jobs, visibility scope, encoding structured parameters.
  • A CLI (cmk and its relatives) — repeatability without writing a client; your command history is a free log of what actually happened.
  • IaC (Terraform, Ansible) — the desired state in a repository, changes reviewed in a pull request, the environment rebuilt from scratch.

Two platform mechanisms are worth knowing about. An operator may enable API request rate limiting — a script that polls the server in a loop can exhaust the allowance. Newer CloudStack releases have webhooks: the platform sends an HTTP notification itself on events such as a machine changing state, so an integration does not have to rely on polling.

What cmk is, and why "cloudmonkey" now means two different tools

This is the first obstacle people lose an hour on: two tools carry the same name. The older one, cloudmonkey, is written in Python and installs via pip. The newer one, cmk, is a rewritten port in Go — a single executable, compatible with CloudStack 4.9 and above.

The Python version should not be installed today: the last release on PyPI is 5.3.3.1 from 13 October 2021 — nearly five years of silence. Nobody has declared end of support, but the package description itself points to the Go version. The current cmk release is 6.5.0 from 26 August 2025 (as of August 2026); the development branch is alive, but releases come out rarely.

The rewrite in Go dropped a few things: XML output, request logging to a file and coloured output. In return you get one dependency-free file — for Windows too — and a ~/.cmk configuration directory, deliberately different from the old version's. Sending requests as POST arrived for passwords and user data in 6.4.0, and 6.5.0 generalised it to all requests and turned it on by default (postrequest). Also added: a prompt for a two-factor authentication code, uploading templates and ISOs, and inverse filtering with the exclude= parameter.

How do you set up cmk in five minutes?

The simplest route is to download the binary from the project's releases. There is also a Homebrew formula (brew install cloudmonkey; the command is called cmk), a snap package and a container image — in CI, always pin a specific tag, because latest on Docker Hub has been out of date for years.

# Linux x86-64: download into your working directory, verify it, and only then install
curl -fsSLO https://github.com/apache/cloudstack-cloudmonkey/releases/download/6.5.0/cmk.linux.x86-64
sha256sum cmk.linux.x86-64
sudo install -m 0755 cmk.linux.x86-64 /usr/local/bin/cmk
cmk version

The project publishes SHA-256 (and MD5) checksums for every file in the release notes — compare the output of sha256sum against them before you make the file executable and let it into a system directory. This is a binary that is about to receive your API keys. While you are on the releases page, check whether anything newer than 6.5.0 has appeared — the version number is baked into the download URL.

You generate the API keys yourself — you will find the "API key + secret key" pair and the API endpoint address in the WebDisk Cloud panel, under your user settings. The secret key is a password: whoever holds it can do in your cloud whatever you can — we have written before about why long-lived static keys are a problem.

Set the configuration with commands rather than a text editor — trap number one explains why.

cmk set profile moja-chmura # creates a profile with that name and switches to it
cmk set url <API-ENDPOINT-URL> # usually ends in /client/api
cmk set apikey <YOUR-API-KEY>
cmk set secretkey <YOUR-SECRET-KEY>
cmk set output json # json | table | text | column | csv | default
cmk sync # fetches the API list from the server

One caveat: cmk stores both keys in plain text under ~/.cmk/, so that directory must never end up in a repository or in a container image — add it to .gitignore. Trap number six collects the rest of the consequences, key rotation included.

cmk sync is not cosmetic: it builds a local command cache — the source of autocompletion, the parameter lists and the knowledge of which commands are asynchronous. The cache is separate for each profile, so once you add a second cloud, run sync there as well. Your first test:

cmk list zones filter=id,name,networktype
cmk list virtualmachines listall=true filter=name,state,zonename

Run with no arguments, cmk starts an interactive shell: history, reverse search with Ctrl+r, Tab completion. Pressing Tab on a parameter ending in id= fires a real query at the server and offers UUIDs together with resource names — handy while learning, worth turning off (set autocomplete false) in a large environment.

How do you launch a virtual machine in one run of commands?

In CloudStack everything is identified by UUIDs, so the work starts with reconnaissance. Below is the complete path: four queries, an SSH key, the machine, a public address, an access rule.

# 1. Survey the environment — this is where the four identifiers come from
cmk list zones filter=id,name
cmk list serviceofferings filter=id,name,cpunumber,memory
cmk list templates templatefilter=executable zoneid=<ZONE-UUID> filter=id,name,ostypename
cmk list networks zoneid=<ZONE-UUID> filter=id,name,type,cidr

The templatefilter parameter is mandatory in listTemplatesexecutable means "templates I am allowed to launch a machine from".

# 2. Registering the SSH public key (@ reads the contents of the file)
cmk register sshkeypair name=laptop publickey=@/home/user/.ssh/id_ed25519.pub

# 3. Deploying the machine; userdata must be base64-encoded — cmk will not do it for you
cmk deploy virtualmachine \
  name=web-01 displayname=web-01 \
  serviceofferingid=<SO-UUID> \
  templateid=<TPL-UUID> \
  zoneid=<ZONE-UUID> \
  networkids=<NET-UUID> \
  keypair=laptop \
  userdata=$(base64 -w0 cloud-init.yaml) \
  startvm=true

Note the .pub extension: you are sending the public key; the private one never leaves your machine. And a second point: the contents of cloud-init.yaml end up in the process arguments and in your shell history, so pass passwords and tokens through a mechanism of the target system rather than through a file on the command line.

Which parameters deployVirtualMachine requires has changed between releases: from CloudStack 4.21 only serviceofferingid and zoneid are required (volumeid and snapshotid were added, so you can also recreate a machine from a volume or a snapshot), while in 4.20 and older templateid is required. On your own cloud you can check with cmk deploy virtualmachine -h — it prints the description and the field list straight from the server.

# 4. A public IP address and SSH access from the office only
cmk associate ipaddress zoneid=<ZONE-UUID> networkid=<NET-UUID>

cmk create portforwardingrule ipaddressid=<IP-UUID> protocol=TCP \
  publicport=22 privateport=22 virtualmachineid=<VM-UUID> \
  networkid=<NET-UUID> openfirewall=false

cmk create firewallrule ipaddressid=<IP-UUID> protocol=TCP \
  startport=22 endport=22 cidrlist=203.0.113.10/32

Setting openfirewall=false is a deliberate choice: you add the firewall rule separately, with an explicit list of source addresses, instead of opening the port to the whole internet. Once the machine is up, it is worth fitting it with a watchdog straight away — also a handful of commands, only this time inside the operating system.

The rest of the life cycle looks exactly as the verb + noun intuition suggests:

cmk stop virtualmachine id=<VM-UUID>
cmk start virtualmachine id=<VM-UUID>
cmk reboot virtualmachine id=<VM-UUID>
cmk scale virtualmachine id=<VM-UUID> serviceofferingid=<NEW-SO-UUID>
cmk change serviceforvirtualmachine id=<VM-UUID> serviceofferingid=<NEW-SO-UUID>
cmk create snapshot volumeid=<VOL-UUID> name=before-update quiescevm=true
cmk destroy virtualmachine id=<VM-UUID> expunge=true

scale virtualmachine applies to a running machine, change serviceforvirtualmachine to a stopped one; whether either is available depends on the hypervisor and on the operator.

The command returned a jobid. How do you wait for the operation to finish?

Most state-changing commands are asynchronous: the server hands back a job identifier and works in the background. The job status has three values: 0 — in progress, 1 — success, 2 — error.

By default cmk handles this for you: the asyncblock setting is on, so the tool polls the server every two seconds itself and shows you only the final result — a complete machine object instead of a bare jobid. The time limit comes from the timeout setting (1800 seconds by default); once it is exceeded you get async API job query timed out.

Sometimes you want the opposite: fire the operation off and not wait.

cmk set asyncblock false
cmk deploy virtualmachine ... # returns the job identifier immediately
cmk query asyncjobresult jobid=<JOB-UUID> filter=jobstatus,jobresultcode

This is also the answer to a classic beginner's error: a script that sets up a NAT rule right after deploy virtualmachine and gets an error because the machine does not exist yet. With asyncblock=true the problem disappears; with your own HTTP client you write the polling loop yourself.

How to turn cmk into a tool for reports and scripts

Two mechanisms turn the CLI into a data source. The first is filter= — the list of fields that stay in the result — plus its opposite, exclude= (the filtering happens locally; details in trap number five). The second is the output format: globally with set output, one-off with the -o flag.

# A report for a spreadsheet: all the account's machines, selected columns, CSV format
cmk -o csv list virtualmachines listall=true \
    filter=name,state,zonename,cpunumber,memory > machines.csv

# The same thing for further processing in jq
cmk -o json list virtualmachines listall=true \
  | jq -r '.virtualmachine[] | [.name, .state, .zonename] | @tsv'

The listall=true parameter means "show everything I am allowed to see"; without it you see only your own resources. The Friday assembly line from the opening of this article looks like this:

#!/usr/bin/env bash
# Stop every running machine whose name contains "test"
set -euo pipefail # a cmk error goes to stdout, not into the JSON — hence pipefail

ids=$(cmk -o json list virtualmachines listall=true state=Running keyword=test \
      | jq -r '.virtualmachine[].id')

for id in ${ids}; do
  printf 'stopping %s ... ' "${id}"
  if cmk stop virtualmachine id="${id}" >/dev/null; then
    echo "ok"
  else
    echo "FAILED" >&2
  fi
done

Before you run the loop, look at the list of identifiers on its own: keyword matches a fragment of the name, so test will also catch latest-api or protest-db — and that makes it a script that stops other people's machines. Filtering by tag (tags) is safer than by name, and the first run is best done with echo in front of cmk stop. Two things here are deliberate: filtering on the server side (state=Running keyword=test) rather than in jq, so the server does not ship data you will not use anyway; and checking the exit code instead of an errortext field — the reason, and the caveat, are in trap number two.

Six cmk traps that are not in the official documentation

1. Do not write ~/.cmk/config by hand. This is the costliest mistake. cmk keeps its global settings in an unnamed block at the top of the file, with no header. A [core] section added in good faith is not global — it lands in the list of server profiles. The effects are not obvious: timeout then becomes zero, so every asynchronous job ends with async API job query timed out, and an empty profile name produces failed to authenticate to make API call despite perfectly good keys. The cure: set everything through cmk set ... and delete the sections you added.

2. Errors go to standard output and are not JSON. A failed call prints plain text and exits with code 1 — a script looking for an errortext field will never find one. The output of the -d flag goes there too, so cmk -d ... | jq will always fall over. There is one painful exception: when cmk rejects a command through its own validation (the Missing required parameters message), it exits with code 0. A typo in a parameter name, or a stale cache, gives you a script that reports "ok" while having done nothing. For critical operations, then, check not only the exit code but also whether the response is JSON at all.

3. set profile with a typo silently creates a new profile — with the default address localhost and the credentials admin/password — and switches to it, which produces a run of baffling connection errors. The switch profile command, which only switches to profiles that already exist, is waiting in the development branch and is not in 6.5.0.

4. Parameter validation happens client-side, from the cache. Without sync, cmk uses the built-in snapshot of the API list — in 6.5.0 that snapshot corresponds to CloudStack release 4.21, the version baked into the tool rather than the version of your cloud — and it then asks you, on the diagnostic output, to run sync. A stale cache can block a perfectly valid call with Missing required parameters, or report unknown command or API requested for a command that exists. Run cmk sync after every upgrade of your cloud.

5. filter= filters locally. It does not reduce transfer or server load, it does not handle nested paths, and given together with exclude= it cancels the latter out. There is no automatic pagination either: page and pagesize are supplied together, and it is your script that collects the following pages.

6. Secrets sit in files in plain text. set apikey and set secretkey write the values into ~/.cmk/config, and if you type them in cmk's interactive shell, into ~/.cmk/history as well. Since 6.5.0 both files have 0600 permissions, but the contents remain readable and travel into every copy of your home directory. Keys passed with the -k/-s flags are additionally visible in your shell history and in the process list — clear such an entry, or prefix the command with a space if you have HISTCONTROL=ignorespace enabled. A leaked key is revoked by generating a new one in the panel — the old one stops working at that point; do it without hesitation on any suspicion: a new laptop, someone leaving the team, an accidental commit. If certificate verification fails, do not turn it off with the verifycert setting — fix the trust in the certificate authority instead; without verification your keys can be intercepted in flight. And a detail: cmk signs the request with an expires parameter set fifteen minutes ahead (signatureversion=3), so a clock that has drifted produces an authentication error that looks like a bad key — check the time before you generate new keys.

When the terminal is a bad idea, and what cmk will not solve

No dodging it: there are cases where the CLI is slower and riskier than clicking.

  • Exploratory work. "What have I actually got here?" is a single glance at a list with statuses in the panel; in the terminal it is three queries and some squinting.
  • A single operation, performed once. Setting up a profile and hunting down three UUIDs takes longer than five clicks.
  • The machine console. No terminal will render it — although cmk create consoleendpoint virtualmachineid=<VM-UUID> will return an address to open in a browser. A broken fstab that stops the system from booting is still something you will only fix in a console window.
  • Irreversible operations. destroy virtualmachine ... expunge=true will not ask "are you sure?". The panel will.
  • Complex constructs with dependencies. With a VPC spanning several tiers, ACL lists and a VPN tunnel, the form enforces the correct order; the CLI will happily let you build it wrong and you will find out later.
  • An environment that has to be reproducible. Here the answer is Terraform or Ansible: cmk executes commands, it does not maintain a declaration. We covered that route separately — managing CloudStack with Terraform, from configuring the provider to a Kubernetes cluster defined in code. A bash script that is "almost" infrastructure as code is the worst of all possible worlds.

There is also a limit on our side: a customer account's role does not cover the operator's administrative commands. You will not see hosts, address pools, service offerings or zone parameters through the API — that is what a ticket to us is for, and no CLI will shorten it.

And a limit of scope: cmk manages platform resources — machines, volumes, networks, addresses, templates — but not the data inside them. S3-compatible object storage is driven with tools from the S3 ecosystem (aws CLI, rclone, s5cmd), not with CloudMonkey. The inside of the operating system is a job for cloud-init and Ansible.

What else manages CloudStack? The state of the ecosystem as of August 2026

cmk is the reference client, but not the only one. The dates come from repositories and package registries, because in this ecosystem "exists" and "is maintained" are two different things:

  • cs (Python) — a library and a CLI in one, configured through ~/.cloudstack.ini, handling asynchronous jobs by itself. The default choice for Python scripts; released in June 2026. A hard dependency of the Ansible collection.
  • cloudstack-cli (Ruby) — an operator's tool with higher-level operations: a machine with forwarding rules in a single command, declarative "stacks", reports. Developed by one person, but steadily; released in April 2026.
  • apache/cloudstack-go — the official Go SDK and the foundation of the rest of the ecosystem (the Terraform provider, Kubernetes controllers), the liveliest of the libraries. Do not use the archived xanzy/go-cloudstack in new code.
  • Apache Libcloud (Python) — for code that has to be portable between CloudStack and other clouds. The abstraction does lose the platform's specifics, though: projects, domains, VPCs, offerings.
  • Java, PHP, Node.js, .NET — bluntly: no living clients. Jclouds went into the Apache attic in 2025, the rest sit on commits that are years old. What is left is a thin client of your own on top of the raw API, or calling cmk from outside.
  • Terraform and OpenTofu — the cloudstack/cloudstack provider, stable version 0.6.0 from October 2025 (the one that introduced Kubernetes cluster resources), with 0.7.0-rc1 from August 2026 bringing the move to the plugin framework. Developed under Apache. It reads CloudMonkey's configuration file, which is convenient locally — but in CI supply the keys through environment variables rather than copying a file full of secrets next to your code.
  • Ansible — the ngine_io.cloudstack collection, thoroughly refreshed after a year of silence: release 3.0.0 in October 2025 and three more in August 2026 alone. Note that in 3.0.0 the modules lost their cs_ prefix. There is also an api_request module — a back door to any API call at all, without computing a signature.
  • Kubernetes — the Cluster API Provider CloudStack for creating clusters declaratively, a cloud controller manager for load balancers and node labels, a CSI driver for persistent volumes. That last one is a community project rather than a Foundation one, unlike apache/cloudstack-go.
  • Packer and Vagrant — an honest warning: the Packer plugin sits on a 2022 release, and the Vagrant plugin has been abandoned since 2019. Build the image with anything else and register it with register template.
  • Pulumi — there is no native provider and nothing suggests there will be; the only route is a bridge to the Terraform provider.

Frequently asked questions

What is cmk, and how does it differ from the old cloudmonkey? They are two generations of the same tool. The Python cloudmonkey installed through pip has had no release since 13 October 2021, and the package description points to its successor itself. cmk is a port to Go distributed as a single binary; the current version is 6.5.0. They have separate configuration directories (~/.cmk), so they can coexist — but build anything new on cmk only.

Where do I get the API key and secret key for WebDisk Cloud? You generate them yourself in the WebDisk Cloud panel, under your user settings. That is also where you will find the API endpoint address that goes into your cmk profile. The keys carry exactly the permissions your account has.

How do I use cmk in CI without leaving keys in a file? Keep the key and the secret in your CI system's protected and masked variables, add the ~/.cmk/ directory to .gitignore, and never bake it into a container image. For automation, set up a separate account or a separate project with a minimal role — a key has no narrower permissions than the account it belongs to. Pin the cmk container image to a specific tag; latest on Docker Hub is years old.

Which parameters are mandatory in deploy virtualmachine? Since CloudStack 4.21, formally only serviceofferingid and zoneid — the template became optional, because a machine can also be recreated from a volume or a snapshot. In 4.20 and older, templateid is required. In practice you also pass networkids, name and keypair. The definitive answer for your cloud comes from cmk deploy virtualmachine -h run after cmk sync.

The command returned a jobid and nothing else. What now? It means you have asyncblock turned off. Query the job with cmk query asyncjobresult jobid=<UUID> and check the jobstatus field: 0 is in progress, 1 success, 2 error. The default set asyncblock true makes cmk wait on your behalf and show only the result; the upper bound is then set timeout, 1800 seconds by default.

Can I create a Kubernetes cluster through cmk? Yes. The cluster service is available in WebDisk Cloud — you can check with cmk list kubernetessupportedversions, and the Kubernetes page describes the offering in detail. Formally create kubernetescluster requires only name and zoneid, but in practice you also pass kubernetesversionid, serviceofferingid and size; scaling is cmk scale kubernetescluster id=<UUID> size=5. The cluster itself you manage from the inside with kubectl, of course, and declaratively from the outside through Cluster API. On other CloudStacks the availability of this service depends on the operator.

curl, cmk, Terraform or Ansible — when do I use which? curl only when you are debugging the signature layer or your language has no living client. cmk for queries, one-off operations and quick scripts. Ansible when you are configuring machines imperatively in playbooks. Terraform when the environment has to be described in a repository and reproducible from scratch. All four hit the same endpoint with the same permissions — you are choosing the shape of the work, not the level of access.

Start with a single query

The cheapest first step is not migrating your processes into scripts but a single command: download the binary, check the checksum, set up a profile, run cmk sync and list your machines into a CSV. A quarter of an hour later you will know whether the terminal suits the way you work — and the Friday assembly line turns into the loop from this article, which can be reviewed in a repository and run from a scheduler. The panel stays where it is genuinely good: at the machine console, at the first look around, and for operations you perform once.

WebDisk Cloud is run by Mazura sp. z o.o., a company under Polish law: your data stays in Europe and support answers in Polish. If you are working out the costs, Cloud PAYG configurations start at €6 net per month for Small (2 vCPU, 4 GB RAM, 50 GB NVMe and a public IP address) — as of August 2026, and we do not charge for the use of the resources you have bought, bandwidth included. Write to us if you would like to talk an automation scenario through.