CLI tool
derailed/k9s avatar
derailed/k9s

k9s: the install routes, the preflight checks, and the kubectl gap

🐶 Kubernetes CLI To Manage Your Clusters In Style!

34,720 stars2,301 forksGoApache-2.0

At a glance

What is it?
k9s puts a keyboard-driven terminal UI on top of a Kubernetes API server and wires your kubeconfig into it. The package managers, the container image and the documented client versions each make a different promise, and some of them do not agree with each other.
Who is it for?
Adopt k9s for interactive cluster triage, and pin the release tarball rather than master. Skip it for CI and for anything that needs a reviewable diff, and verify the compatibility table on k9scli.io against your own cluster version first, because the client versions in the README and the Dockerfile disagree.
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 1 day 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 1, 2026, and from our analysis. They are not legal advice.

Editorial analysis

A watch loop turns every keystroke into an API call

Running k9s opens a full screen terminal interface and keeps watching the API server for changes, so the list you are looking at updates without a refresh command. That design is the whole point and also the whole risk. Instead of composing a command, you select a resource and press a key, and the action lands on a live object immediately.

The dependency list in `go.mod` shows how much of the standard Kubernetes stack it borrows. It pins `k8s.io/client-go`, `k8s.io/cli-runtime` and `k8s.io/kubectl` at v0.37.1, and it embeds `helm.sh/helm/v3` v3.22.0, which is where the Helm release views come from. Rendering happens in the terminal through `derailed/tview` v0.8.5 on top of `derailed/tcell/v2` v2.3.1-rc.4, and `cobra` v1.10.2 supplies the command layer underneath the interface.

Two other entries explain what you see on screen. `github.com/itchyny/gojq` v0.12.19 is a JSON query engine, and it is what lets a view filter and reshape raw API responses on the client. `github.com/sahilm/fuzzy` v0.1.3 backs the incremental search you type to jump to a resource. `fsnotify` watches config files, and `github.com/adrg/xdg` v0.5.3 resolves where those files live per platform.

What this means in practice: a long-lived process holds an authenticated session against your cluster for as long as you leave the interface open. It is not a shell you exit, and there is no documented dry-run mode in the repository, so a mistake is a mistake against a running workload.

The Ubuntu one-liner fetches the newest release, and go install fetches something else

The install list is long enough that the interesting comparison is between what each route actually delivers. The Ubuntu entry is a single line that downloads, installs and cleans up in one go:

shell
wget https://github.com/derailed/k9s/releases/latest/download/k9s_linux_amd64.deb && sudo apt install ./k9s_linux_amd64.deb && rm k9s_linux_amd64.deb

The filename hardcodes `linux_amd64`. On an arm64 host that download is the wrong architecture, and the failure surfaces at install time rather than at download time. It also tracks whatever is newest, so two people running it a week apart get different binaries.

The Go route carries a warning in a comment of its own:

shell
# NOTE: The dev version will be in effect!
go install github.com/derailed/k9s@latest

`@latest` on a module path resolves to the tip of the default branch, not to a tagged release. Since the last push to this repository was on 2026-09-29, the newest published release, v0.51.0 from 2026-06-06, is already behind what that command compiles. The consequence is direct: a `go install` build is unreviewed, unpinned code running with your cluster credentials, and there is no way to ask it which commit it came from without rebuilding.

Homebrew is the tidier path on macOS and Linux, and MacPorts, Arch, openSUSE, FreeBSD, Fedora, Winget, Scoop and Chocolatey each have a one-line entry of their own. The snap entry is the one to read closely:

shell
snap install k9s --devmode

`--devmode` is not the default install mode, and the README does not say when you would need it or what it changes about confinement.

Without TERM set to 256 colours and KUBE_EDITOR set, keys quietly do nothing

Two environment variables decide whether the interface behaves, and neither is optional in practice. The first is the terminal type:

shell
export TERM=xterm-256color

The README calls this out specifically for Nix systems, where TERM is frequently not set the way the rest of the ecosystem sets it. k9s asks for 256 colour mode and draws its views with cell attributes that a smaller palette cannot express. The symptom of getting this wrong is not a crash. You get a degraded interface with missing or unreadable colouring, and the cause is not obvious from inside the program.

The second variable gates the edit commands:

shell
# Kubectl edit command will use this env var.
export KUBE_EDITOR=my_fav_editor

Both `EDITOR` and `KUBE_EDITOR` have to be set before the resource edit keys do anything. The underlying edit path is kubectl's, so the variable kubectl itself reads is the one that matters. If you have neither set, the edit command has nothing to hand the manifest to, and the failure is a key that appears unresponsive rather than an error message.

The third item in the same preflight list is a version preference: k9s prefers recent Kubernetes versions, 1.28 and above. That matters more than it sounds, because k9s is a client and old clients get refused by new API servers. If your cluster is older than that floor, the README offers no supported configuration and no note about what breaks.

Mounting kubeconfig into the container hands the container every permission in that file

The official image takes a different approach from the package managers: there is no state to install, and the cluster access is whatever you bind in. The documented run mounts your kubeconfig into the container's home directory.

shell
docker run --rm -it -v $KUBECONFIG:/root/.kube/config derailed/k9s

If `KUBECONFIG` is unset, the second form spells out the default path:

shell
docker run --rm -it -v ~/.kube/config:/root/.kube/config derailed/k9s

Read what that volume means. The container process runs as root, and the file you just mounted is the credential store for your cluster. Anything the kubeconfig grants, including cluster-admin, is granted to that container for the life of the session. Using a throwaway namespace-scoped kubeconfig instead of your admin one changes the blast radius, and the README does not mention this at any point.

The image also ships a second binary. The final Dockerfile stage sets `ARG KUBECTL_VERSION="v1.37.0"`, curls that kubectl from the release host into `/usr/local/bin/kubectl`, and installs `vim` and `curl` as part of that same step before purging the package list. The entrypoint is `/bin/k9s`, so kubectl is there for k9s to call and for you to drop into. You can pin a different version at build time, and the Makefile target `kubectl-stable-version` is what fetches the current number:

shell
KUBECTL_VERSION=$(make kubectl-stable-version 2>/dev/null)
docker build --build-arg KUBECTL_VERSION=${KUBECTL_VERSION} -t k9s-docker:0.1 .

Leave the build arg alone and you get kubectl v1.37.0 inside the image, whatever your cluster actually runs.

make imgx defaults to an arm64 image and stops on hosts without QEMU

The Makefile sets `BUILD_PLATFORMS ?= linux/amd64,linux/arm64`, so the multi-platform target builds both architectures in one pass:

shell
make imgx

That target runs `docker buildx build` for both platforms and loads the result. On an Apple Silicon Mac, Docker Desktop, which bundles QEMU emulation, handles the arm64 side. On a plain Docker Engine host, the same command needs emulation enabled first, and the README says so plainly rather than hiding it: cross-architecture builds rely on QEMU, Docker Desktop includes it out of the box, and a plain install has to follow the buildx multi-platform documentation.

The consequence for a reader is a build that appears to start and then stalls. Emulated compilation of a Go project of this size is slow, and on a host without emulation the build stops partway through rather than reporting a clean architecture error. If you only ship to one architecture, override the default rather than paying for two:

shell
make imgx BUILD_PLATFORMS=linux/amd64 IMG_NAME=your-org/k9s VERSION=v0.0.1

`IMG_NAME` defaults to `derailed/k9s`, which is the namespace you do not own. Building locally under that name and then pushing it is a mistake worth avoiding, since `make pushx` takes the same variables and will happily attempt the push.

The README's Go floor says 1.23, and go.mod says 1.26.8

The build section states that k9s is using Go v1.23.X or above, then gives the two-step route: clone the repository, then build and run.

shell
make build && ./execs/k9s

The binary lands in `execs/k9s` because the Makefile sets `OUTPUT_BIN ?= execs/${NAME}`. Check what that floor really is before you pick a toolchain. The `go` directive in `go.mod` reads `go 1.26.8`, and the Dockerfile pins its build stage to `golang:1.27.1-alpine3.24`. A Go 1.23 toolchain reading that directive does not simply build an older-compatible binary. It refuses, or it reaches for a toolchain download, so the README's stated minimum will not get you a working `make build`.

The version stamped into that binary is a third separate number. The Makefile has `VERSION ?= v0.51.0`, which matches the newest tagged release, and it is injected as `-X` linker flags alongside the short git revision and a build date. Since the last commit landed on 2026-09-29, a source build of master reports v0.51.0 while containing four months of unreleased work. For anything you intend to keep running, build from a release tag or pass `VERSION` yourself.

The same file also documents the routine targets: `test` cleans the test cache before running, `lint` installs `golangci-lint` v2.13.2 if the binary is missing, and `cover` writes a coverage profile.

k9s cannot show you the diff you are about to apply, which is the whole argument for kubectl

The honest comparison is with `kubectl`, and the difference is not speed or features. It is that kubectl is stateless and k9s is not. A kubectl command is a line of text: you can read it, put it in a script, put it in a pull request, and run it again later against a different context. Nothing persists between invocations.

k9s inverts that. The README's own description is that k9s continually watches Kubernetes for changes and offers subsequent commands to interact with the resources it has observed. You are driving a running session against whatever context is loaded, and a keypress acts on the object under the cursor. That is a much better fit for finding the one pod in eight hundred that is crash looping, and a much worse fit for anything that needs a reviewable record of what changed.

So the division of labour is concrete. Use k9s for interactive triage: scan a namespace, tail logs, watch a rollout, then move on. Use kubectl for anything reproducible, because a k9s keystroke has no scriptable equivalent in the repository. If your team is expected to run this in CI or through a change pipeline, k9s does not fit and no flag makes it fit.

One maintainer, an Apache-2.0 licence, and four months between the last release and the last commit

The licence is Apache-2.0, with the text in the `COPYING` file at the repository root. That permits commercial use and modification with attribution and notice of changes. It is a permissive licence and it is not a support contract, which matters for the reason in the next paragraph.

The README states plainly that k9s is not backed by a funded corporation, that it is a complex project demanding a lot of the maintainer's time, and that it will remain free. The project asks for sponsorship on GitHub Sponsors. Read that as a bus factor of one. There is a community Slack channel, a `change_logs/` directory at the root, and a `.goreleaser.yml` that drives releases, but the release history is thin: v0.50.17 and v0.50.18 both landed on 2026-01-11, and v0.51.0 followed on 2026-06-06. The last push was on 2026-09-29, so the working tree is carrying changes that no tag covers.

There is a compatibility table in the README, and it does not line up cleanly with the rest of the tree. The row for k9s v0.27.0 and above lists the 1.26.1 k8s client, while the Dockerfile that builds the published image defaults its bundled kubectl to v1.37.0. The preflight section separately says k9s prefers Kubernetes 1.28 and above. The README does not reconcile those three numbers, so treat the table on k9scli.io as the authority and check it against the version your API server actually runs before you install anything.

Editorial conclusion

Adopt k9s for interactive cluster triage, and pin the release tarball rather than master. Skip it for CI and for anything that needs a reviewable diff, and verify the compatibility table on k9scli.io against your own cluster version first, because the client versions in the README and the Dockerfile disagree.

Frequently asked questions

What is k9s vs kubectl?

kubectl is a stateless command line client where each invocation stands alone. k9s is a terminal UI that continually watches Kubernetes for changes and then offers commands against the resources it has observed, acting on live objects as you press keys.

What is the current version of k9s?

The newest tagged release is v0.51.0, published 2026-06-06, and the Makefile default matches it. The last push to the repository was on 2026-09-29, so master is ahead of that tag.

Can I use k9s on Windows?

Yes. The README lists Windows support with three routes: `winget install k9s`, `scoop install k9s` and `choco install k9s`. There is also a Webi installer for Windows using `curl.exe -A MS https://webinstall.dev/k9s | powershell`.

Can you provide a detailed cheat sheet for k9s?

The repository does not ship a keybinding sheet. Key bindings, the `jumps/` directory and configuration are documented on the k9scli.io site, which the README points to for installation, usage, customization and tips.

Official sources

  1. Official documentation
  2. Official README
  3. Project repository
  4. Release notes
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.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/derailed-k9s.svg)](https://hysenlabs.com/projects/derailed-k9s)