All writing
Updated 8 min readNaren

Your first ten minutes in k9s

Install it, point it at a cluster, and learn the handful of keys that replace most of the kubectl you type.

kubectl answers one question per command.

A pod is one or more containers that Kubernetes schedules and restarts as a unit, and working out why one is unhappy takes three commands: one to list what is running, one to read the pod's own account of itself, one to read what its container printed.

The last two need a name you copy out of the first. None of it is hard. It is slow, and you lose your place every time.

k9s puts that loop on one screen. It is a TUI, a terminal user interface: a full-screen, keyboard-driven program that runs inside your terminal window and holds the screen until you quit it, the way top and vim do.

Fernand Galiana writes it. The project is Apache-2.0 licensed, and the current release is v0.51.0, from June 2026.

By the end of this you will have installed k9s, pointed it at a cluster, found a pod and read its logs without typing its name once, and given yourself a hotkey that jumps straight to the view you use most.

You need a cluster you can already reach with kubectl. k9s puts nothing into that cluster; it is a single binary on your laptop. Ten minutes, if the install behaves.

Install it

One line, whichever platform you are on.

# macOS (homebrew-core - no tap needed)
brew install k9s

# Linux, Arch
sudo pacman -S k9s

# Linux, Debian/Ubuntu (.deb from the release page)
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

# Linux, Fedora 42+
sudo dnf install k9s

# Windows
winget install k9s

Every one of those resolves the current version on its own, which is why none of them names one. On an ARM Linux box, swap k9s_linux_amd64.deb for k9s_linux_arm64.deb. Then check what you got:

k9s version
k9s info

k9s version prints the version, commit and build date. k9s info prints the paths k9s resolves on your machine for its config, hotkeys, aliases, skins and logs. Those differ by operating system, and you will need the Hotkeys path when you give yourself a hotkey later on, so leave that output on screen.

Check the connection with kubectl first

k9s reads the same kubeconfig as kubectl and adds nothing of its own. The kubeconfig is the YAML file at ~/.kube/config by default, holding the cluster addresses, credentials and contexts you already use.

A context is one named bundle of which cluster, which user and which default namespace; a namespace is a partition inside a cluster that groups resources, and most k9s views show one namespace at a time.

kubectl config current-context
kubectl config get-contexts
kubectl get nodes

Run those before you launch. If kubectl get nodes fails, k9s fails identically, and a failure inside a full-screen UI is harder to read than the same one at a shell prompt.

One optional check decides whether two columns in k9s have numbers in them:

kubectl top nodes

If that prints figures, k9s will show CPU and memory per pod and per node.

If it errors, your cluster has no provider for the Metrics API, the metrics.k8s.io interface reporting live CPU and memory use. metrics-server usually provides that interface, and Kubernetes does not install metrics-server for you.

Without it those two columns stay blank and nothing else breaks.

Launch it and find a pod

k9s -n kube-system

-n scopes k9s to one namespace instead of everything your credentials can see, which keeps the first screen small. Any namespace with a few pods in it will do; kube-system is the one this walkthrough uses.

If the colours look wrong or text is unreadable, quit with :q and set the 256-colour mode k9s draws in:

export TERM=xterm-256color

Inside tmux, the terminal multiplexer, that value is tmux-256color. Relaunch with k9s -n kube-system once it is set.

Now press ?. It lists every key that does something in the view you are in, which answers most questions this post does not cover; Esc closes it.

Type :pod and press Enter. The colon opens command mode, and the resource name goes straight after it with no space between.

:pod, :pods and :po all land on the same view, because k9s accepts the singular, the plural, the short name and any alias.

A space after the name passes an argument: :pod kube-system scopes to a namespace, and :pod /kube opens the view already filtered. Ctrl-A lists every alias, built from your cluster's API plus the k9s default table.

In the pod view, / filters. Type / followed by a few letters from any pod name on the screen and press Enter, and the rest drop away.

The match is a regular expression, so a | between two fragments keeps both.

Where k9s runs

k9s read that view out of the cluster without putting anything into it. It is one binary on your laptop that talks to the Kubernetes API server with the credentials already in your kubeconfig. That server is the one service in a cluster that every client, kubectl included, reads and writes through. k9s takes the same --kubeconfig, --context, --cluster, --user and --token flags kubectl does.

Two things follow from that.

The k9s binary and your kubeconfig both sit on your laptop. k9s reads the kubeconfig and polls the cluster's API server every two seconds using your credentials, so your own permissions bound what appears. Nothing belonging to k9s runs inside the cluster, and the Metrics API provider that fills the CPU and memory columns is a separate cluster add-on that is often absent. Your laptop The cluster polls every 2s, as you often not installed kubeconfig k9s binary API server Pods and deployments Metrics API provider

k9s inherits your permissions exactly. RBAC, role-based access control, is Kubernetes' permission system: it decides what your credentials may read and change, and it bounds everything k9s can show or do.

An empty view is usually a permissions answer, not a bug.

And k9s polls; it does not stream. The refresh rate is two seconds by default, so a view spanning every namespace asks the API server for all of it, every two seconds.

Launching k9s with -n NAMESPACE narrows that to one namespace, so each poll asks for less.

Read the logs without typing a name

Move to a pod with the arrow keys and press l. k9s streams that container's logs full-screen, and Esc goes back to the list.

d runs describe on whatever row is selected. Neither key made you type the pod's name.

List, describe, logs: that is the loop from the top of this post, without the copying and pasting.

Open a shell in a running container

Move to a pod and press s. k9s tries bash first and falls back to sh, and if the image has either one you land at a prompt inside the container. exit returns you to k9s.

Learn its two limits now rather than during an incident. The first is that s cannot work on a distroless image, one stripped down to the application and its runtime, with no shell and no package manager.

There is no bash or sh inside to run. If nothing opens, that is the first thing to suspect, and another pod may have a shell.

The second limit is that s needs a running container: a pod that is restarting has nothing to attach to, which is why you debug a failing pod from its logs and its describe output instead of from a shell.

The keys worth knowing

Five keys did most of that: :, /, l, d and s, with ? and Ctrl-A for looking things up. A few more are worth learning before you need them.

:ctx and :ns switch context and namespace without leaving k9s, y shows the selected resource's YAML as the API server stores it, and Esc always goes back one view.

Shift-N and Shift-A sort by name and by age, and Ctrl-R forces a refresh. Ctrl-D deletes the selected resource and asks first: TAB, then ENTER.

Ctrl-K kills it and does not ask, which makes it the one key here that can ruin an afternoon. The key-bindings table in the k9s README has the rest.

e opens the selected resource in your editor, and unlike kubectl edit there is no vi fallback. k9s checks K9S_EDITOR, then KUBE_EDITOR, then EDITOR, and when none of them is set it flashes an error for a second and opens nothing. It reads them at launch, so quit with :q, set one, and start k9s again:

# k9s checks K9S_EDITOR, then KUBE_EDITOR, then EDITOR - and errors if none is set
export EDITOR=vim

What --readonly actually disables

k9s --context CONTEXT_NAME --readonly -c deploy

Substitute a context from kubectl config get-contexts. -c deploy picks the view to land on, which saves a :deploy on arrival. --readonly disables the modification commands: delete, kill and edit.

In the pod view it also takes away shell, attach and file transfer.

The flag disables less than the name suggests.

Logs stay available, and so does Shift-F port-forwarding: an ephemeral tunnel from a port on your laptop to a port on a pod, so you can reach an in-cluster app at localhost for as long as the tunnel lives.

And the guard sits in the k9s UI rather than in the cluster: --readonly touches neither your credentials nor your RBAC, anyone can relaunch without it, and --write exists precisely to override it. It stops a mistyped keystroke. It is not production access control, whatever somebody eventually tells an auditor.

To make a cluster read-only without remembering a flag every launch, set readOnly: true in its per-context config file. k9s keeps one of those per cluster and context, at clusters/CLUSTER/CONTEXT/config.yaml with your own names in both places.

The directory above it differs by operating system. k9s info is how you find it, the same answer as for the hotkeys file below.

The k9s config reference documents refreshRate alongside readOnly.

Give yourself a hotkey

k9s reads custom key bindings from a file you create yourself. It is called hotkeys.yaml, all lowercase, and the top-level key inside it is hotKeys, in camel case.

Getting that pair wrong is the usual reason custom hotkeys silently do nothing: k9s reports no error and never reads the file.

The directory differs by operating system too, so use the Hotkeys path k9s info printed rather than one from a blog post, including this one.

# Linux:   ~/.config/k9s/hotkeys.yaml
# macOS:   ~/Library/Application Support/k9s/hotkeys.yaml
# Windows: %LOCALAPPDATA%\k9s\hotkeys.yaml
# Run `k9s info` to see the exact path on your machine.
# Note the filename is lowercase but the top-level key is camelCase: hotKeys
hotKeys:
  shift-1:
    shortCut: Shift-1
    description: Deployments
    command: deploy
  shift-2:
    shortCut: Shift-2
    description: Kube-system pods
    command: pod kube-system

Shift-2 jumps straight to pods in kube-system, argument and all. The same command: field takes any resource name k9s can resolve, and Ctrl-A shows which ones your cluster has.

Two views to try

Each is worth ten seconds on a real cluster. Type :pulses for cluster health at a glance; :pulse, :pu and :hz reach the same view.

Then type :xray deploy to see deployments and what hangs off them, as a tree; :x deploy is the short form.

:xray needs a resource argument, so a bare :xray gives you an error rather than a view. It takes pods, services, deployments, daemonsets, statefulsets and replicasets.

Nothing you just did changed what you are allowed to do. k9s used the same kubeconfig, the same RBAC and the same API as the kubectl commands at the top of this post; it removed the typing between one question and the next.

An empty view is still your permissions, and a blank CPU column is still a missing metrics-server.

Where k9scli.io and the project README disagree (the hotkeys path, for one), the README is the current one.