Open-source project
vmware-tanzu/vm-operator avatar
vmware-tanzu/vm-operator

vm-operator: a controller where thirteen submodules are pinned to the null version

Self-service manage your virtual infrastructure...

134 stars83 forksGoNOASSERTION

At a glance

What is it?
A Go controller that gives Kubernetes a declarative API for virtual machines. The README is six words long and sends you to a documentation site, so what the repository actually tells you is in the module file and the container build: a monorepo whose submodules are never versioned separately, and a scratch image that cannot be fully static because FIPS compliance requires it.
Who is it for?
vm-operator fits a team already running Kubernetes that wants virtual machines reconciled by the same controller pattern as its workloads, since the project is a controller with generated API docs and webhooks rather than a script collection. Leave it if you are evaluating operators in general, since the search results for this name are dominated by unrelated projects and you will need the documentation site to tell them apart.
Can I use it commercially?
Check first. The repository uses a licence we do not classify automatically, so read its LICENSE file before any commercial use.
Is it still maintained?
Yes. The repository received new commits within the last day.
What is it written in?
Mainly Go, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 1, 2026, and from our analysis. They are not legal advice.

Editorial analysis

The README is six words and the rest is at a docs site

The entire stated purpose of the project fits in one sentence: it enables management of virtual machines with a Kubernetes-style, declarative API.

Everything else in the README is community plumbing. Two Slack channels, one on Kubernetes Slack and one on CNFC Slack, both under the same channel name. A developer mailing list for announcements. Community meetings on a schedule published as a public calendar, joined over Google Meet, with notes and discussion topics kept in a shared document. Two files in the root explain how the project is governed and who currently maintains it.

That is a deliberate choice rather than laziness, and the evidence is in the repository structure. There is a docs directory with a MkDocs configuration, a generated API reference directory, a directory for the custom resource definition reference docs, a separate Dockerfile for building the documentation, and a Read the Docs configuration. The README links to a hosted documentation site and stops there.

The audience this addresses is stated as people who are working with virtual machines in Kubernetes, interested in the roadmap, or have ideas about the project. That is a project inviting design conversation rather than one courting first-time users, and the practical consequence is that you will not learn what a custom resource looks like from the repository root.

Thirteen modules are replaced in-tree and pinned to a placeholder

The module file is the most informative document in the repository, and one detail stands out.

Thirteen module paths are replaced with local directories: the API module, and twelve others covering application platform integration, bring your own key, capabilities, infrastructure, networking, storage policy and quota, topology, the vSphere API surface, the vSphere CSI driver, vSphere policy, a backup API, and test labels. Every one of them lives under an external or package subdirectory in the same tree.

Every replaced module is then required at the same placeholder version, and it is the same placeholder in all thirteen cases. It is the null version, the one Go uses to mean this module is not a real published release.

So the API and its dependencies are developed in-tree and are not independently versioned. That has an obvious upside, since a change to the API and a change to its consumer land in the same commit and cannot drift. The cost is that you cannot pin a specific version of the API module and get a stable set of types, because the version string carries no information. Anyone vendoring this needs to pin a commit of the whole repository, not a set of module versions.

The module also declares a Go version in the 1.26 line, which sets the floor for anything building from source.

A scratch image that has to stay dynamically linked for FIPS

The container build contains a tension that is worth reading twice, because the comments explain it plainly.

The base is a Photon image at version 5, pulled from a mirror registry, and the runtime stage is scratch. The intent is a minimal root filesystem assembled bottom-up rather than inherited.

The complication is stated in the file. The manager is built with CGO enabled and with Go's boring crypto experiment turned on, for FIPS compliance, and that combination means the binary is dynamically linked against the C library rather than static. A statically linked Go binary would be the natural fit for a scratch image, since it would need nothing, and this one cannot be.

So the build assembles a root filesystem out of four packages: the base filesystem, the certificate authority bundle, the C library, and the C library headers. It verifies each one is present and fails the build if any is missing, then copies the files into the install root.

The mechanism for the copy is the part that is genuinely neat. It queries the installed package database for each package's file list and pipes the result through a copy-into-directory tool. The comment notes that this needs no extra package installed and no network access, which matters because the alternative, installing a tool to do the copy, would mean adding a dependency to an image whose whole purpose is having none.

Three Dockerfiles cover the three build targets: the controller manager, the documentation, and the end-to-end tests.

GOTOOLCHAIN is pinned to local, for a firewall reason

The Makefile has a default worth stealing, and the comment says why.

The Go toolchain selection is defaulted to whatever is on the system rather than whatever the module files specify. The stated reason is a build environment problem: when the build happens behind a firewall, or when checksum database verification is turned off so the toolchain cannot be verified, an attempted toolchain download fails.

That is a specific and recognisable failure. A module file declaring a Go version newer than what is installed is a reasonable thing for a project to do, and the default behaviour of downloading the missing toolchain is a reasonable thing for Go to do, and the combination breaks CI in a network that cannot reach the download host. Overriding it means the build fails fast with a clear toolchain error instead of a network timeout.

The rest of the Makefile is conventional but careful. The shell is forced to bash because the syntax used is bash-specific. The default goal is help rather than a build. Goals that do not need Go are enumerated explicitly, since the end-to-end image targets are Docker-only and all Go compilation happens inside the container, which means the missing-Go check has to exclude them. Without Go on the path, the Makefile stops with a specific error rather than a confusing one later.

Operating system and architecture default to the host, so a developer build produces a binary for the machine they are sitting at rather than for a fixed target.

Editorial conclusion

vm-operator fits a team already running Kubernetes that wants virtual machines reconciled by the same controller pattern as its workloads, since the project is a controller with generated API docs and webhooks rather than a script collection. Leave it if you are evaluating operators in general, since the search results for this name are dominated by unrelated projects and you will need the documentation site to tell them apart. Verify first which module versions you are building, because thirteen submodules are replaced in-tree and pinned to a placeholder version that carries no release information at all, and confirm the build environment can produce a dynamically linked binary with the boring crypto experiment enabled.

Frequently asked questions

What does the vm-operator project do?

It enables management of virtual machines with a Kubernetes-style, declarative API, written in Go. It is a controller with its own API module, generated API reference documentation, webhooks, and a Makefile-driven build.

Where is the vm-operator documentation?

The README links to a hosted documentation site, and the repository carries a docs directory with a MkDocs configuration, a generated API reference directory, a custom resource definition reference directory, a separate documentation Dockerfile, and a Read the Docs configuration.

How are the vm-operator submodules versioned?

They are not versioned separately. Thirteen module paths, including the API module and twelve under external and package directories, are replaced with local directories and all required at the same null placeholder version, so you pin a repository commit rather than a set of module versions.

Why does the vm-operator container image not use a fully static binary?

The manager is built with CGO enabled and the boring crypto experiment on for FIPS compliance, which makes it dynamically linked against the C library. The build therefore assembles a root filesystem from the base filesystem, the certificate bundle, the C library and its headers before the scratch stage.

How do I take part in the vm-operator project?

Community meetings run on a published calendar and are joined over Google Meet, with notes in a shared document. There are Slack channels on both Kubernetes Slack and CNCF Slack, a developer mailing list for announcements, and root files covering governance and the current maintainers.

Official sources

  1. Official README
  2. Project repository