# Metacontroller: writing Kubernetes controllers as webhooks instead of Go binaries

> Metacontroller is a Kubernetes add-on that replaces the full Go controller with a webhook: you declare which resources to watch and return desired state as JSON from a service in any language, while the framework handles watches, caching, work queues, retries and level-triggered reconciliation. It suits platform teams who want a controller in Python or Jsonnet, and it is the wrong tool when you need the performance of an in-process controller.

**metacontroller/metacontroller** — Writing kubernetes controllers can be simple

- Repository: https://github.com/metacontroller/metacontroller
- Website: https://metacontroller.github.io/metacontroller
- Stars: 1,010 · Forks: 106
- Language: Go
- License: Apache-2.0
- Published: 2026-10-19 · Updated: 2026-10-19 · Language: en
- Canonical page: https://hysenlabs.com/projects/metacontroller-metacontroller

## A controller is reduced to a function from observed state to desired state

Metacontroller is an add-on for Kubernetes that makes it easy to write and deploy custom controllers in the form of simple scripts. The README describes the mechanism precisely: instead of writing and maintaining a full Go controller with client libraries, informers and boilerplate, you describe what resources to watch declaratively and provide a webhook, in any language, that receives the current state as JSON and returns the desired state as JSON. Everything else falls out of that: watches, caching, work queues, retries and level-triggered reconciliation are handled by the framework. A controller becomes a pure function, which is a much smaller thing to test and to move between languages than a controller binary.

## The win is the language, and the loss is the process model

The first reason in the README is that you can write controllers in any language, because all you need is a webhook that speaks JSON, so Python, JavaScript, Jsonnet, Go or anything else works. The second is no boilerplate: no schema or IDL, no generated code, no client library dependencies. The third is production-ready behaviour for free, with label selectors, adopt and orphan semantics, garbage collection, watches, caching, work queues, optimistic concurrency, and retries with backoff all built in. The fourth is building reusable abstractions, composing existing Kubernetes APIs into higher-level operators or reimplementing APIs like StatefulSet as Metacontroller hooks. The cost of the model is the one the README does not lead with, which is that the webhook is a network service invoked by the framework, so a controller that must run at high frequency pays a round trip per reconciliation.

## Installation is a kubectl apply of a kustomize directory

The quick install is one command that applies the production manifests.

```sh
kubectl apply -k https://github.com/metacontroller/metacontroller/manifests/production
```

The getting started section points to the install guide for prerequisites, for a Helm alternative, and for notes on migrating from the original GKE project, and the quick install is described as being via kubectl apply -k or Helm. Because the manifests live in a URL rather than a chart registry, a cluster can be bootstrapped without cloning the repository, which is convenient for a GitOps setup that pins a commit rather than a chart version. The repository carries a deploy/ directory with a Helm chart, a manifests/ directory with the kustomize configuration, and a pkg/ directory for the Go implementation, plus a Dockerfile and three variants of it for debug and distroless builds.

## The examples directory is a catalogue of controller patterns

The examples/ directory is where the value of the framework is easiest to see, because each directory is a working controller you can adapt. The names describe the patterns a platform team actually needs: bluegreen for staged rollouts, catset for a set of related resources, clusteredparent for grouping children under a parent, daemonjob for per-node work, globalconfigmap for cluster-wide configuration, indexedjob and namespace-list-v2 for the list-based resources, secretpropagation and configmappropagation for copying values into workloads, service-per-pod and service-per-pod-v2 for per-pod Services, target-label-selector for selecting pods, update-strategy for rolling updates, leader-election, webhook-auth for authenticating to a webhook, nodejs showing the webhook written in Node.js, pprof-profiling and prometheus-monitoring for operating it, and a kind-load-images.sh script plus test.sh for exercising them in a local kind cluster.

## The container image is distroless-minded and pinned by digest

The Dockerfile shows the shipping posture. It starts from alpine:3.24.2 pinned by digest, copies the built binary for the target platform into /usr/bin/metacontroller, installs ca-certificates, and then creates a nonroot group and user with the uid 65532 and runs the process as that user, with the command being the metacontroller binary itself. The comment in the file says the uid and naming convention match those used by the distroless base images, which is a small detail that avoids a class of permission mismatch when the image runs in a cluster with a restrictive policy. The repository also ships Dockerfile.debug and Dockerfile.distroless variants, a DOCKERHUB-README.md for the published image, and a .goreleaser.yaml for the build matrix.

## A Go module pinned to current Kubernetes libraries

The go.mod file shows the version surface the project tracks. The module is named metacontroller and the go directive is 1.26.4, with a comment explaining that it denotes the minimum supported language version and should not include the patch version. The Kubernetes libraries are all at v0.37.1, covering api, apiextensions-apiserver, apimachinery and client-go, and controller-runtime is at v0.25.2. Notable additions are the json-patch library, Prometheus client_golang for metrics, zap and logr for logging, zcache for caching, and sigs.k8s.io/json. The presence of an apiextensions-apiserver dependency confirms that Metacontroller installs and manages its own CRDs, which matches the Makefile, where the generate_crds target runs controller-gen over pkg/apis and writes the CRD manifest into manifests/production and copies it into the Helm chart.

## The Makefile separates unit, integration and lint gates

The build tooling in the root Makefile shows how the project is tested. The build target delegates to goreleaser with a single target and a snapshot output, and a build_debug variant sets DEBUG before it. The unit-test target runs gotestsum with the race detector and a coverage profile over the packages, excluding the integration and examples trees. The integration-test target changes into test/integration and runs with a five minute timeout and a parallelism of 1, which is the setting you want for tests that bind real API servers. The test-setup target fetches the Kubernetes binaries with a shell script and creates the temporary directory the coverage output goes into. Linting runs golangci-lint with a ten minute timeout, and the .golangci.yaml file holds the configuration. A separate Makefile variable names the API group as metacontroller/v1alpha1.

## A community project born from a GKE project, with a fast release train

The project is a continuation of work started by GKE, originally at the GoogleCloudPlatform metacontroller repository, and the README thanks the community members who made the move to a community maintained project possible. The README also carries a dedicated migration section pointing at a guide for people moving from the original GKE project. The licence is Apache License 2.0. The release history shows v4.17.6 on 2026-10-15, v4.17.5 on 2026-10-13 and v4.17.4 on 2026-10-13, so the patch line moves quickly, and the last push was on 2026-10-15. The repository is not archived, and the README points to GitHub issues and the #metacontroller channel on Kubernetes Slack for contact.

## Conclusion

Metacontroller fits a platform team that needs one or two narrow controllers and does not want to own a Go client, informer and work queue stack, because a webhook in Python or Jsonnet gets the same label selectors, adopt and orphan semantics, garbage collection, optimistic concurrency and retry with backoff that a full controller would provide. It is a poor fit for a controller on a hot path where webhook round trips matter, and a poor fit if your team already has a well-oiled Go controller pattern to copy. Verify first which Kubernetes API version your cluster runs and whether the install manifests match it, and read the migration guide if you are coming from the original GKE project.

## FAQ

### What does Metacontroller let me avoid writing?

The README says you avoid writing and maintaining a full Go controller with client libraries, informers and boilerplate. There is also no schema or IDL, no generated code and no client library dependencies, because your controller is a webhook returning JSON.

### How do I install Metacontroller?

The quick install is kubectl apply -k against the production manifests URL, and a Helm alternative is documented in the install guide along with prerequisites and notes on migrating from the original GKE project. The repository also contains a deploy/ directory holding the Helm chart.

### Which languages can a Metacontroller webhook be written in?

Any language, because the only requirement is a webhook that speaks JSON. The README names Python, JavaScript, Jsonnet and Go, and the examples directory includes a nodejs example, with a test script for running the examples in a local kind cluster.

### What controller behaviours come built in?

The README lists label selectors, adopt and orphan semantics, garbage collection, watches, caching, work queues, optimistic concurrency, and retries with backoff. It also mentions level-triggered reconciliation as part of what the framework takes care of.

## Sources

- [License: Apache-2.0](https://github.com/metacontroller/metacontroller/blob/master/LICENSE)
- [metacontroller/metacontroller on GitHub](https://github.com/metacontroller/metacontroller)
- [Project website](https://metacontroller.github.io/metacontroller)
- [README](https://github.com/metacontroller/metacontroller/blob/master/README.md)
- [Releases](https://github.com/metacontroller/metacontroller/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/metacontroller-metacontroller
