Introduction
And here it is: kvmcli v1.0.0-alpha
The goal of this release was not to add more features, but to fix architectural problems that appeared as soon as I started managing more than a few VMs.
Earlier versions worked, but they were basically just wrappers around the virsh command line (with Docker-Compose-inspired configuration files), which made further development very limited.
Why Switching to Go
go-libvirt is a Go client for the libvirt API, which allows direct, structured interaction with libvirt and provides better control over resource lifecycles and error handling.
Project link: https://github.com/digitalocean/go-libvirt
To be honest, the first reason was that I wanted to learn Go.
At the same time, I knew that Go is fast and has a good library for libvirt (go-libvirt), which made the decision easier ¯\_(ツ)_/¯
This project was also ambitious. I wanted to create something that can handle:
- Virtual networks
- Virtual machines
- Snapshots
- Clusters (groups of VMs with logic and metadata)
- Stores
- ...etc
Using the libvirt library for Go instead of writing just a wrapper gave me the freedom to achieve what I really wanted.
Today, I can say that this project is ready to be tagged as v1.0.0-alpha.
There are still issues that will come later as patches, but the foundation is now solid.
What go-libvirt provided:
- Direct API calls
- Better error handling
- Less implicit behavior
- Clearer lifecycle management
YAML Configuration: Why It Didn't Scale
When I first switched to Go, I kept using YAML.
The idea was to use Kubernetes-like manifests, because the model is familiar and works well for simple objects.
Example of a config in YAML:
apiVersion: kvmcli/v1
kind: VirtualMachine
metadata:
name: admin
namespace: k8s
store: homelab-store
labels:
role: dns
environment: homelab
spec:
image: rocky-9.5
cpu: 1
memory: 2048
disk:
size: 20G
network:
name: homelab
mac: "02:A3:10:00:00:02"
autostart: true
This worked fine for a few VMs. But once I started defining clusters, multiple roles, shared networks and stores, and repeated specs with small variations, the YAML files became:
- Very long
- Hard to read (indentation hell)
- Hard to refactor and maintain
- Easy to break
YAML was good for static data, but bad for composition.
Switching to HCL
The main configuration change in v1.0.0 is the move from YAML to HCL.
HCL allows:
- Direct references between resources
- Clearer structure
- Less duplication
- Better readability as configs grow
Example of a config in HCL:
vm "admin" {
image = "rocky-10.1"
namespace = "k8s"
cpu = 1
memory = 2048
disk = "20G"
store = data.store.homelab
network = network.kubernetes
ip = "10.10.10.10"
labels = {
environment = "homelab"
role = "dns"
}
}
For me, this made a big difference. The configuration stays readable even when the number of VMs increases.
SQLite Backend
kvmcli now uses SQLite to store information about resources. This includes networks, VMs, stores, and the relationships between them.
SQLite was chosen because:
- No external service is required
- It is reliable
- It fits local infrastructure tooling very well
Logging Improvements
Logging was improved to make debugging easier. Go really pushed me to be more strict and intentional about logging 😄
What's Next
Planned work after v1.0.0:
- Provisioners support (Ansible and shell)
- Cluster-level logic
- Variables (similar to Terraform and Packer — very handy)
- Snapshots (and restore)