# kubectl-tree: walking Kubernetes ownership graphs from the command line

> A kubectl plugin that reads ownerReferences and prints the hierarchy above and below an object, with filtering flags aimed at clusters too large to query unfiltered.

**ahmetb/kubectl-tree** — kubectl plugin to browse Kubernetes object hierarchies as a tree 🎄 (star the repo if you are using)

- Repository: https://github.com/ahmetb/kubectl-tree
- Stars: 3,443 · Forks: 138
- Language: Go
- License: Apache-2.0
- Published: 2026-10-06 · Updated: 2026-10-06 · Language: en
- Canonical page: https://hysenlabs.com/projects/ahmetb-kubectl-tree

## It plots one API field: ownerReferences

The README states the whole mechanism in its first line. This is a kubectl plugin to explore ownership relationships between Kubernetes objects through `ownersReferences` on the objects. There is no inference, no heuristics and no controller specific logic: the plugin reads the owner reference list each object carries, walks those links, and prints what it finds as a tree.

That narrowness is a feature more than a limitation, because `ownerReferences` is the one relationship the Kubernetes API guarantees. Garbage collection is built on it, so if a tree shows something, something really claimed that object. What it cannot show is a logical relationship that nobody recorded, which is the entire distinction the README draws against the `kubectl lineage` plugin: lineage understands logical relationships between some API objects without needing ownerReferences, while this plugin does not.

The README also credits the design to @nimakaviani's knative-inspect, describing this as a generalized version of it, and states plainly that the project is not an official Google project despite Ahmet Alp Balkan being a well known Google engineer.

## Installing through krew, and the default scope you get

Installation is two commands, and they only work if you already have the krew plugin manager installed for kubectl:

```sh
kubectl krew install tree
kubectl tree --help
```

The flag documentation opens with the default behaviour, and it is worth knowing before you are surprised by output. By default the plugin searches only namespaced objects in the same namespace as the object you named. Namespaced means cluster scoped resources such as nodes or persistent volumes will not appear unless you ask for them.

`--all-namespaces` widens the search to both namespaced and non namespaced objects across all namespaces, and `--namespace` sets the namespace scope for the request itself, which matters if your kubeconfig context does not default to the namespace you care about. The colour flag is the ordinary `always`, `never`, `auto` set, with auto as the default.

The demo section shows three rendered examples rather than terminal output: a Deployment hierarchy, a Knative Service hierarchy, and an Agones Fleet hierarchy. That choice of examples is informative, since Knative and Agones are both cases where a custom controller stacks several resource types above the pod.

## Resource and API group filters, and the trap in them

The flags that make this plugin usable on a real cluster are the filters. `--resources` takes a comma separated list of resource types and supports globs, and resource filters accept kind, singular, plural and short names interchangeably, so `ReplicaSets`, `replicasets`, `replicaset` and `rs` all match the same thing. `--api-groups` does the same job by API group, with globs and negation support.

Combined, they cut the amount of API traffic dramatically. A realistic invocation looks like this:

```sh
kubectl tree --api-groups '*.custom.api,*cluster.x-k8s.io' --resources '!customresource' cluster my-cluster
```

The README is unusually honest about the failure mode. If an intermediate owner resource is filtered out, traversal stops at that point and child resources below it will be missing, even if the leaf resource or API would otherwise match. It gives a concrete example: using `--resources=deployments,pods` returns nothing, because ReplicaSets are not included and pods are not directly owned by deployments.

That is correct behaviour rather than a bug, and understanding it turns the flags from a performance feature into a precision tool. A tree that shows only what you asked for is only as complete as your filter was.

## Condition types and label selectors narrow further

Two more filters refine a tree without changing which objects it walks. `--condition-types` takes a comma separated list of condition types to check, defaulting to `Ready`, and the example given is `Ready,Processed,Scheduled`. This is how you distinguish a resource that exists from a resource that has been admitted and scheduled, which matters most on custom resources where the Ready condition is the controller's own report.

`--selector` is an ordinary label query and supports equality with `=` and `==` as well as `!=`, set based operators `in` and `notin`, and existence checks. The README's examples cover equality like `-l key1=value1,key2=value2`, set based like `-l "env in (prod,staging)"`, and mixed like `-l "tier=frontend,env!=test"`, and it frames the purpose as reducing workload and data volume in large clusters.

Neither flag changes the ownership walk itself. They change what gets queried, and therefore how long the command takes and how much of the output you have to read, which for a cluster wide run is usually the real problem.

## Release history shows one real feature and a lot of dependency bumps

The version history is short and tells you where the project actually is. v0.5.0 on 2026-03-20 was a GoReleaser action bump from version 6 to 7. v0.6.0 on 2026-03-24 shipped `filter resources` in pull request 115, alongside a colour library bump and a Kubernetes dependency update.

So the last substantive feature landed in March 2026, and the releases before and after it were maintenance. v0.4.6 from 2025-11-15 fixed namespace and CRD handling logic, and its release note carries an unusual parenthetical, which reads as a note from whoever automated the changelog rather than a description of a user facing change.

The repository is not archived, and the last push was on 2026-08-31, roughly five months after the v0.6.0 tag. Whether the filters will be extended further is not something the README promises, and the project is at version 0.6, which conventionally signals that interfaces may still move.

Dependencies track Kubernetes closely: `apimachinery`, `cli-runtime` and `client-go` are all at 0.37.0, `cli-utils` at 0.35.0, and the Go directive is 1.26.0. Dependabot is visibly active in the commit history, which is what keeps those numbers moving.

## A small Go codebase built like a kubectl plugin should be

The tree is ten entries, and each one is doing a job. `cmd/` holds the command entry point, `go.mod` and `go.sum` pin the dependencies, `.krew.yaml` declares the plugin for the krew index, `.goreleaser.yml` configures cross platform release builds, `assets/` carries the logo and the three demo screenshots, and `.github/` holds the workflows and issue templates.

The dependency list in `go.mod` is short and chosen with care for a CLI. `cobra` and `pflag` give the flag surface, `fatih/color` handles coloured output, `gosuri/uitable` renders the table, `pkg/errors` for error wrapping, and the four `k8s.io` modules plus `sigs.k8s.io/cli-utils` do the actual cluster work. Depending on `cli-runtime` rather than `client-go` alone is what makes the plugin share kubectl's authentication and context resolution instead of reimplementing kubeconfig handling.

The licence is Apache 2.0 with the text in `LICENSE`. With 3438 stars, 137 forks and 16 open issues, this is a settled tool: the issue count is low enough that a real problem is likely to get an answer, and the fork count is high enough that teams are carrying it internally.

## Conclusion

kubectl-tree is at its best when a cluster has custom resources in play, because ownerReferences is the only relationship the Kubernetes API guarantees, and this plugin turns that field into something you can read. Two boundaries define it. It cannot infer logical relationships that were never recorded as ownership, which is exactly where kube-lineage differs, and filtering by resource type can silently truncate a tree by removing the intermediate owner that links a parent to its children. Install it with krew, run it against a Deployment first to see the familiar shape, then widen to all namespaces only after you understand how long that takes on a large cluster.

## FAQ

### How do I install the kubectl-tree plugin?

Through krew, the kubectl plugin manager. Run kubectl krew install tree, then verify it with kubectl tree --help. The repository ships a .krew.yaml file in its root, which is the manifest that registers the plugin with the krew index.

### What is the difference between kubectl-tree and kubectl lineage?

kubectl-tree only follows ownerReferences, the ownership links the Kubernetes API records on each object. The README notes that kubectl lineage is very similar but understands logical relationships between some API objects without needing ownerReferences. So lineage can connect resources a controller never claimed as its child, while tree shows only real ownership.

### Why does filtering with --resources return fewer objects than I expect?

If an intermediate owner resource is filtered out, traversal stops there and the children below it are missing even if they would otherwise match. The README's example is that using --resources=deployments,pods returns nothing, because ReplicaSets are excluded and pods are not directly owned by deployments. Include the middle resource to see the whole chain.

## Sources

- [ahmetb/kubectl-tree on GitHub](https://github.com/ahmetb/kubectl-tree)
- [Issues](https://github.com/ahmetb/kubectl-tree/issues)
- [License: Apache-2.0](https://github.com/ahmetb/kubectl-tree/blob/master/LICENSE)
- [README](https://github.com/ahmetb/kubectl-tree/blob/master/README.md)
- [Releases](https://github.com/ahmetb/kubectl-tree/releases)

---

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