Kooper is a small Go library for Kubernetes controllers that leaves the conventions to you
Kooper is a simple Go library to create Kubernetes operators and controllers.
At a glance
- What is it?
- Kooper is a Go library for writing Kubernetes controllers and operators built around two concepts, a Retriever that lists and watches resources and a Handler that receives them, and it deliberately does not include CRD generation, admission webhooks or RBAC manifests. It suits a Go developer who wants controller plumbing without a framework, and it asks you to assemble the surrounding pieces yourself.
- Who is it for?
- Kooper fits a Go team that writes controllers against resources it already owns and wants the event plumbing without adopting a framework's folder layout, webhook machinery or generated clients. It does not fit a team that wants one tool to scaffold, generate and ship a full operator, because that is precisely what Kooper leaves out.
- Can I use it commercially?
- Yes. Apache-2.0 is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 39 days ago.
- 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 22, 2026, and from our analysis. They are not legal advice.
Editorial analysis
Two concepts, and the library refuses to grow more
Kooper is a Go library to create simple and flexible Kubernetes controllers and operators, in a fast, decoupled and easy way, and the README is direct about its positioning: it is a small alternative to big frameworks like Kubebuilder or operator-framework.
The core concepts are minimal by design. A `Retriever` plus a `Handler` is a controller, and an operator is also a controller. That is the whole model, and the library does not distinguish between the two terms at the library level, with the difference between a controller and an operator being only what resources get retrieved.
The features list adds a small number of things on top: metrics, extensible with Prometheus already implemented, readiness for core Kubernetes resources such as pods, ingress and deployments as well as CRDs, and an optional leader election system for controllers.
The most important property is the first line of the simplicity section, which says Kooper embraces simplicity over optimization and favours small APIs. Three consequences follow, and they are the ones to weigh before you commit.
Each controller is independent, and you share nothing unless you say so explicitly, for example by giving two controllers the same handler. Each controller gets its own resource and event cache internally, which the README describes as fewer bugs and corner cases but less optimisation. And the handler receives the Kubernetes resource with the responsibility of how that object is used left to you.
That last one is a genuine design decision rather than an omission. Kooper will not filter, batch or deduplicate events for you.
The retriever and handler wiring, end to end
The README's simplest example prints pod events, and it is worth reading in full because it shows the entire lifecycle of a controller in one function.
// Create our retriever so the controller knows how to get/listen for pod events.
retr := controller.MustRetrieverFromListerWatcher(&cache.ListWatch{
ListWithContextFunc: func(ctx context.Context, options metav1.ListOptions) (runtime.Object, error) {
return k8scli.CoreV1().Pods("").List(ctx, options)
},
WatchFuncWithContext: func(ctx context.Context, options metav1.ListOptions) (watch.Interface, error) {
return k8scli.CoreV1().Pods("").Watch(ctx, options)
},
})
// Our domain logic that will print all pod events.
hand := controller.HandlerFunc(func(_ context.Context, obj runtime.Object) error {
pod := obj.(*corev1.Pod)
logger.Infof("Pod event: %s/%s", pod.Namespace, pod.Name)
return nil
})
// Create the controller with custom configuration.
cfg := &controller.Config{
Name: "example-controller",
Handler: hand,
Retriever: retr,
Logger: logger,
}
ctrl, err := controller.New(cfg)
if err != nil {
return fmt.Errorf("could not create controller: %w", err)
}
// Start our controller.
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
ctrl.Run(ctx)Four steps: build a retriever from a client-go ListWatch, write a handler, assemble a Config, and Run it with a context. The handler is a plain function of a context and a runtime.Object, so testing your domain logic does not require a cluster, and the type assertion to *corev1.Pod is explicit rather than hidden.
The context is threaded properly, and the library takes a standard library logger interface rather than a logging framework of its own, which is one of the decisions that keeps it composable.
What v2 changed, and why the Delete event is gone
The v2 section is the most practically important part of the README for anyone with an existing deployment, and the reasons are given rather than left implicit.
The import path changed to a major version suffix.
import "github.com/spotahome/kooper/v2"The reason for using v2 rather than v1 is stated plainly: it changes the library as a whole, there is no backwards compatibility, v0 is stable and used in production, although you will eventually want to update to v2 because v0 will not be updated.
Four changes matter in practice. Before there were concepts like operator and controller, and now there is only controller, which is at library level while you can continue creating controllers and operators. CRD management used to be inside the library and now should be managed outside it, and the reason given is that controllers and CRDs have different lifecycles, with kube-code-generator offered as a way to produce the registration manifests. Prometheus metrics were refactored to be more reliable, so dashboards and alerts will need changing. And the Delete event was removed because it was not reliable, with a pointer to the Garbage-collection section of the docs.
That last change is the one to think hardest about. A delete event that fires unreliably is worse than none, because a controller written against it silently drifts. If your reconciliation logic depends on knowing when an object disappeared, you need a different mechanism, and the README does not pretend otherwise.
The comparison with Kubebuilder, in the author's own terms
The README has a section on alternatives, and it is unusually blunt about the difference in approach.
Kooper embraces the Go philosophy of having small simple components and libraries and using them as you wish in combination with others, instead of trying to solve every use case and imposing everything by sacrificing flexibility and adding complexity. The comparison offered is that Kooper is more like Go HTTP router libraries, while Kubebuilder and operator-framework are like Ruby on Rails or Django style frameworks.
The list of what Kubebuilder brings is specific: admission webhooks, folder and package structure conventions, CRD clients generation, RBAC manifest generation, and metrics, logging and HTTP and Kubernetes API clients that are not pluggable or flexible.
Kooper's answer is that it solves most of the core controller and operator problems as a small library and lets the other problems be solved by libraries specialised on them. You use whatever you want to create your CRD clients, and you might not have CRDs at all. You set up admission webhooks outside your controller with a library such as Kubewebhook. You create RBAC manifests as you wish and evolve them as you develop. You set your preferred logging system, with a logrus implementation provided, and implement your preferred metrics backend, with a Prometheus implementation provided. And you use your own Kubernetes clients.
So the trade is explicit: Kooper gives you the event plumbing and leaves generation, webhooks and manifests to you, which is less code to fight but more assembly for you to do.
Multi-resource controllers, leader election and the examples
The repository's examples/ directory is where the design choices become concrete, and the folder names map onto the feature list.
There is examples/config-custom-controller/, examples/controller-concurrency-handling/, examples/leader-election-controller/, examples/metrics-controller/, examples/multi-resource-controller/ and examples/pod-terminator-operator/.
The multi-resource example is the one to read if you are wondering how the independent-controller model scales. The README's answer is that multiresource controllers are made with independent controllers on the same app, so a controller that needs to watch three resource types runs three controllers, and coordination between them is your problem to solve rather than a framework feature.
The pod-terminator example is the closest thing here to a complete operator, and it is worth reading to see what an operator looks like once you assemble the pieces Kooper deliberately leaves out.
Leader election is optional and comes with its own example, which matters for anyone running more than one replica of a controller.
Around the library itself the repository has the usual Go project furniture: a controller/ directory for the core, log/ and metrics/ packages, a test/ directory, hack/ for the scripts the Makefile calls, a docker/ directory, a .golangci.yml for linting and a .mockery.yml for mock generation. The Makefile runs unit tests, integration tests and a kind-based integration test script, with a note that integration tests require a Kubernetes cluster configured and running on your kubeconfig.
Versions, dependencies and what the licence permits
Kooper at this moment uses v1.36 as its base, and the README points to the integration test in CI for the supported versions, which is the more reliable source since the base version changes with each Kubernetes release.
The go.mod tells you the rest. The module path is github.com/spotahome/kooper/v2, the Go directive is 1.26.0, and the direct dependencies are deliberately few: prometheus/client_golang, sirupsen/logrus, stretchr/testify, and the Kubernetes trio of k8s.io/api, k8s.io/apimachinery and k8s.io/client-go, all at 0.36.4.
That dependency list is itself an argument for the library. There is no controller-runtime, no apimachinery code generator, no framework layering to keep up with. The whole Kubernetes surface is the client library you already use, which means your types are the Kubernetes types.
The licence is Apache 2, with the LICENSE file at the root, and the README shows an Apache 2 badge alongside a Go Report Card badge and a CI badge on the master branch. There is also a CHANGELOG.md, which the v2 section points to for the full list of changes.
The release history shows a steady minor-version cadence: v2.10.0 on 2025-12-22, v2.9.0 on 2025-05-04 and v2.8.0 on 2025-03-22, with the last push to the repository on 2026-09-14 and the default branch being master. The project is written and maintained by Spotahome, whose product page on Kooper is the linked homepage.
Where Kooper is the wrong choice
Four cases argue against it.
The first is a team that wants scaffolding. Kooper does not generate CRD clients, RBAC manifests or webhook handlers, and it does not impose a folder layout. If your goal is to run a scaffolding tool and get a complete operator with generated clients and manifests, Kubebuilder is the tool for that, and Kooper's philosophy is explicitly the opposite.
The second is a controller that needs reliable delete events. The Delete event was removed in v2 because it was not reliable. If your logic depends on reacting to an object disappearing rather than to its state, you will have to design around it, and the README does not offer a replacement mechanism.
The third is throughput. A separate internal cache per controller, no event filtering or batching by default, and a handler that receives the raw object every time, add up to a design that favours correctness and clarity over efficiency. For a controller watching thousands of objects on a busy cluster, that trade may be the wrong one, and the README is candid that it is a trade.
The alternative in the same direction is controller-runtime, the library Kubebuilder itself is built on. The difference in approach is that controller-runtime gives you a shared manager, a cache you configure once, work queues with concurrency and rate limiting, and admission webhook plumbing, whereas Kooper gives you one small, predictable loop per controller and leaves the rest to you. For a single-purpose controller that owns one resource type, Kooper's version is easier to reason about and to test. For a multi-resource controller with real throughput requirements and a team that wants the ecosystem's shared patterns, controller-runtime is the better bet.
Editorial conclusion
Kooper fits a Go team that writes controllers against resources it already owns and wants the event plumbing without adopting a framework's folder layout, webhook machinery or generated clients. It does not fit a team that wants one tool to scaffold, generate and ship a full operator, because that is precisely what Kooper leaves out. Import it as `github.com/spotahome/kooper/v2`, follow the v2 changes before migrating since the controller and operator concepts were merged and the Delete event was removed, and check the integration test in CI for the Kubernetes versions actually supported rather than assuming the base v1.36 is the only one.
Frequently asked questions
What is Kooper?
Kooper is a Go library for creating simple and flexible Kubernetes controllers and operators. It is described as a small alternative to big frameworks like Kubebuilder or operator-framework, built around two concepts, a Retriever that lists and watches resources and a Handler that receives them.
What changed between Kooper v1 and v2?
The operator and controller concepts were merged into just controller, CRD management moved outside the library because controllers and CRDs have different lifecycles, the Prometheus metrics were refactored so dashboards and alerts need changing, and the Delete event was removed because it was not reliable. The import path is now github.com/spotahome/kooper/v2.
Which Kubernetes versions does Kooper support?
Kooper uses v1.36 as its base at the moment, and the README points to the integration test in CI as the place to check the supported versions. The go.mod pins k8s.io/api, k8s.io/apimachinery and k8s.io/client-go at 0.36.4.
How is Kooper different from Kubebuilder?
Kooper is compared to Go HTTP router libraries while Kubebuilder and operator-framework are compared to Rails or Django style frameworks. Kooper does not include admission webhooks, folder conventions, CRD client generation or RBAC manifest generation, and leaves those to specialised libraries.
Does Kooper support leader election and metrics?
Yes, both are listed as features, with leader election optional for controllers and metrics extensible with a Prometheus implementation already provided. The examples directory includes leader-election-controller and metrics-controller examples alongside multi-resource and pod-terminator examples.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/spotahome-kooper)Was this useful?