Skip to content

k9s

RewrittenChapter 24 of 3812 min read

Rewritten for this edition in October 2026 for Kubernetes 1.37. Commands checked against the official documentation and source of k9s v0.51.0.

k9s is a terminal UI for Kubernetes. You start it with k9s, it reads the same kubeconfig as kubectl, and it shows your cluster as live tables that refresh on their own: pods, deployments, services, events, anything the API server knows about, custom resources included. Every row is one keystroke away from its logs, its YAML, a shell, a port-forward or a delete. You type : plus a resource name to jump between views and ? to see every key that works where you are. If you only remember three things from this chapter, make it those two keys and esc to go back.

What k9s adds on top of kubectl#

So far in the book you've typed kubectl get, read the output, then typed another command with a name copied from it. Good for learning. Slow once you're debugging. A pod restarts, you want its previous logs, then the events on its ReplicaSet, then the Deployment that owns it. Four commands, four names to paste.

k9s keeps that loop on one screen. The table under your cursor is always current (it polls the API every two seconds by default), and the actions you'd reach for next are bound to single letters. It doesn't hide Kubernetes from you either: what you see are the real resources with their real names, so the objects you met in the Deployment chapter show up exactly as they are.

The project's latest release is v0.51.0, published on 6 June 2026. You can check for newer ones on the k9s releases page.

Before you start#

You need a cluster you're allowed to break. A local kind cluster or a K3s node is ideal, since this chapter deletes and kills things on purpose. Check that kubectl get nodes answers before going further; k9s uses the same kubeconfig and current context, so if kubectl can't reach the cluster, k9s won't either.

Three small things save you trouble later:

  • k9s draws in 256-colour mode. On Linux and macOS terminals that don't advertise it, export TERM=xterm-256color.
  • Editing a resource from k9s opens the editor named in KUBE_EDITOR (or EDITOR), so set one you know how to quit.
  • The CPU and memory columns only appear when the cluster serves the Metrics API. On a fresh kind cluster that means installing metrics-server. If it can't verify your kubelets' certificates, add the --kubelet-insecure-tls argument to its container, which the project marks as for testing only.
export TERM=xterm-256color
export KUBE_EDITOR=nano

# optional: CPU/MEM columns in k9s
kubectl apply -f https://github.com/kubernetes-sigs/metrics-server/releases/latest/download/components.yaml

Install k9s#

Pick the line for your system. All of these come from the project README; prebuilt tarballs for Linux, macOS and Windows are also attached to every release.

# macOS or Linux, Homebrew
brew install derailed/k9s/k9s

# macOS, MacPorts
sudo port install k9s

# Linux
snap install k9s --devmode        # snap
pacman -S k9s                     # Arch
zypper install k9s                # openSUSE
dnf install k9s                   # Fedora 42+
pkg install k9s                   # FreeBSD

# Ubuntu / Debian, .deb from the latest release
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

# Windows
winget install k9s
scoop install k9s
choco install k9s

# Go toolchain (builds the development version)
go install github.com/derailed/k9s@latest

# no install at all: the official image with your kubeconfig mounted
docker run --rm -it -v ~/.kube/config:/root/.kube/config derailed/k9s

Confirm it's on your path with k9s version. The Go route compiles from source, and the README warns that it puts the development version in effect, so a package manager is the better default.

Launching k9s: the flags worth knowing#

Plain k9s opens in your current context and namespace. These flags change where you land:

k9s                       # current context, current namespace
k9s -n kube-system        # start in one namespace
k9s -A                    # start in all namespaces
k9s --context my-context  # start in another kubeconfig context
k9s -c deploy             # open the deployments view first
k9s --readonly            # disable delete, kill, edit and other changes
k9s -l debug              # log at debug level
k9s info                  # print where config, logs and skins live
k9s help                  # every flag

--readonly deserves a habit. Open a production context with it and the destructive keys simply aren't there. There's also a --write flag that overrides a readOnly: true you've set in config, for the moments you do mean to change something.

Walkthrough: one Deployment, start to finish#

The fastest way to learn the keys is to use them on something you made. Create a small Deployment and a Service in front of it from a normal shell first.

Create something to look at#

kubectl create deployment web --image=nginx --replicas=2 --port=80
kubectl expose deployment web --port=80
k9s

k9s opens on the pods view. Two web-… pods should be there, with a STATUS column that turns Running once the image is pulled. If one sits in ImagePullBackOff or CrashLoopBackOff, that's your cue for the CrashLoopBackOff chapter; k9s is the quickest way to watch those fixes take effect.

Press : and type deploy, then Enter. The command bar takes singular, plural or short names, so :deployment, :deployments and :dp all land in the same place. Now try a few variations you'll use daily:

  1. :pod default opens pods in the default namespace only.
  2. With :pod /web the pods view opens already filtered to names matching web.
  3. :svc, then Enter on the web row: k9s shows the pods behind the Service.
  4. Inside any table, / filters what's shown. It accepts a regex, /! inverts it, and /-l app=web filters by label.

Two navigation keys tie this together. esc backs out of a view, filter or prompt. And - jumps to the previous command, much like cd - in a shell. Press ? at any point to see what's bound in the current view, and ctrl-a for every resource alias k9s knows on this cluster.

Read logs and open a shell#

Go back to pods, put the cursor on a web pod and press l. You get the container's log stream. The number keys pick a time window (0 tails, 1 shows from the start, 2 to 6 mean the last 1m, 5m, 15m, 30m and 1h). w wraps long lines, t adds timestamps, s toggles autoscroll and ctrl-s saves the log to a file. When a container has restarted, p in the pods view shows the previous instance's logs, which is where the crash message usually is.

Press esc, then s on the same pod. k9s opens a shell inside the container; type exit to come back. d shows kubectl describe output, y shows the live YAML, and e opens it in your editor.

Scale, restart, delete#

Switch to :dp and select web. A few keys here change the cluster, so read the prompt each one shows:

  • s asks for a replica count. Set 3 and watch a third pod appear when you jump back to :pod.
  • r runs a rolling restart, the same as kubectl rollout restart.
  • Want to see the ReplicaSets this Deployment created? That's z.
  • ctrl-d deletes after a confirmation (TAB to the OK button, then Enter).
  • ctrl-k kills with no confirmation at all, the equivalent of kubectl delete --now.

Try ctrl-k on one of the pods. It vanishes, and a replacement shows up seconds later because the ReplicaSet notices it's one short. Watching self-healing happen live is a good reason to keep k9s open for the rest of the book.

For a tree of how everything connects, run :xray dp default. It draws each Deployment as a tree, with the objects it owns nested underneath. :pulses is the wide-angle version: a one-screen dashboard of the workloads running on the cluster you're connected to.

Port-forward and benchmark#

From the pods view, select a web pod and press shift-f. A dialog comes up for the container port and the local port; set the local one to 8080. Point a browser at http://localhost:8080 and nginx answers, through a tunnel k9s holds open for as long as the session lasts.

Type :pf. Every active forward is a row in this view. Select yours and press b to run a benchmark against it (press b again to stop a run in progress). Results land in the benchmarks view, :be, and Enter on a result shows the request stats. Enter on a row in the port-forward view also takes you straight to its benchmarks.

Under the hood k9s uses the hey load generator. Without any config a run sends 200 GET requests to / with a concurrency of 1. To change that per container or per Service, edit the context's benchmarks.yaml, which lives under the k9s data directory at clusters/<cluster>/<context>/benchmarks.yaml. The k9s benchmark docs show the full format; here's a container entry for this walkthrough:

benchmarks:
  defaults:
    concurrency: 1
    requests: 200
  containers:
    # namespace/pod-name:container-name
    default/POD_NAME:CONTAINER_NAME:
      concurrency: 4
      requests: 1000
      http:
        path: /
        method: GET

Fill in the pod name from the pods view and the container name from Enter on that pod. Services can be benchmarked the same way with b in the :svc view. That's meant for NodePort and LoadBalancer Services, and it needs a services: entry giving the node or load balancer address to hit.

k9s commands reference#

Commands start with : and finish with Enter. Any resource name, plural or short name works, including your CRDs.

CommandWhat it does
:pod, :po, :podsPods view (works for any resource name or alias)
:pod kube-systemThe resource, in one namespace
:pod /webThe resource, pre-filtered by name
:pod app=web,env=devThe resource, filtered by labels
:pod @my-contextThe resource in another context (switches your k9s context)
:ctxList contexts and switch
:ctx my-contextSwitch straight to a context
:nsList namespaces; u on a row switches to it
:dp, :svc, :sts, :ds, :jo, :secCommon short names; dp, jo and sec come from the starter alias file k9s generates
:xray RESOURCE [NAMESPACE]Tree view of po, svc, dp, rs, sts or ds and what they own
:pulses, :puCluster summary dashboard
:pfActive port-forwards
:beBenchmark results
:sd, :screendumpSaved screen dumps
:popeye, :popPopeye cluster sanitizer report
:q, :quitExit k9s

k9s shortcuts reference#

Keys depend on the view you're in, and ? always shows the current set. These are the ones the README lists for k9s v0.51.0, grouped by where they work. Lowercase letters are plain keys; shift-x means the capital.

Anywhere#

These work in every view.

KeyAction
?Help for the current view
ctrl-aAll resource aliases
:Command mode
/Filter (regex); /! inverse, /-l labels, /-f fuzzy
escBack, or leave command/filter mode
-Previous command
[ / ]Back / forward in command history
ctrl-rRefresh the view
ctrl-eToggle the header
ctrl-gToggle breadcrumbs
ctrl-cQuit (unless noExitOnCtrlC is set)

Any resource table#

Pods, deployments, secrets, CRDs: any list of resources accepts these, as long as the action makes sense for the type.

KeyAction
dDescribe
yView YAML
eEdit (not in read-only mode)
l / pLogs / previous logs, where the resource has them
ctrl-dDelete, with confirmation
ctrl-kKill, no confirmation
spaceMark a row; ctrl-space marks a range, ctrl-\ clears marks
c / nCopy resource name / namespace
wWarp to the row's namespace
shift-jJump to the owner
ctrl-wToggle wide columns
ctrl-zToggle the faults/error display
ctrl-sSave the table to a file
shift-n, shift-a, shift-s, shift-pSort by name, age, status, namespace
shift-o, shift-←, shift-→Sort by selected column, move the column

Specific views#

Some letters mean different things in different views. s is a shell on a pod and a scale prompt on a Deployment, so glance at the view title before you press it.

ViewKeyAction
Podss / aShell into / attach to the container
Pods, Services, Containersshift-f / fStart a port-forward / show port-forwards
Deployments, StatefulSetssScale
Deployments, DaemonSets, StatefulSetsrRolling restart
DeploymentszShow ReplicaSets
ReplicaSetsctrl-lRoll back to this ReplicaSet
Port-forwards, ServicesbRun / stop a benchmark
CronJobstTrigger a job now
Nodesu / rCordon or uncordon / drain
NamespacesuUse this namespace
ServiceAccounts, PVCs, Secrets, ConfigMapsuShow what uses this one
Logs0–6Tail, head, 1m, 5m, 15m, 30m, 1h
Logsw, t, s, fWrap, timestamps, autoscroll, fullscreen

Where k9s keeps its files#

k9s follows the XDG base-directory convention, so its files are spread over a config directory, a data directory and a state directory. The main config directory, per the k9s configuration docs:

OSConfig directory
Linux / Unix~/.config/k9s
macOS~/Library/Application Support/k9s
Windows%LOCALAPPDATA%\k9s

That's where config.yaml, aliases.yaml, hotkeys.yaml, plugins.yaml, views.yaml and the skins/ folder go. Per-context settings (including benchmarks.yaml) sit in the data directory under clusters/, and logs, benchmark reports and screen dumps go to the state directory. If you've exported XDG_CONFIG_HOME and friends yourself, k9s uses those, which is why some Macs end up with ~/.config/k9s. Don't guess: ask k9s.

k9s info

On a Linux machine with no XDG variables set, the output looks like this (logo trimmed, home directory shortened):

Version                     v0.51.0
Config                      ~/.config/k9s/config.yaml
Custom Views                ~/.config/k9s/views.yaml
Custom Jumps                ~/.config/k9s/jumps.yaml
Plugins                     ~/.config/k9s/plugins.yaml
Hotkeys                     ~/.config/k9s/hotkeys.yaml
Aliases                     ~/.config/k9s/aliases.yaml
Skins                       ~/.config/k9s/skins
Context Configs             ~/.local/share/k9s/clusters
Logs                        ~/.local/state/k9s/k9s.log
Benchmarks                  ~/.local/state/k9s/benchmarks
ScreenDumps                 ~/.local/state/k9s/screen-dumps

To keep everything in one folder, for a dotfiles repo say, set K9S_CONFIG_DIR. k9s then reads config, skins, plugins and per-context files from that one directory. Logs are the exception, with their own K9S_LOGS_DIR variable.

Make it yours: aliases, hotkeys, plugins, skins#

All four are plain YAML files in the config directory. Hotkeys reload on save. After editing the others, restart k9s.

Aliases#

An alias maps a short command to a resource (as group/version/resource) or to a whole command line. Add yours to aliases.yaml next to the starter entries:

aliases:
  cm: v1/configmaps
  webpods: pod /web

Now :webpods opens the pods view already filtered to the walkthrough's pods.

Hotkeys#

Hotkeys bind a key combination to a command, so the views you open fifty times a day take one press. Put them in hotkeys.yaml. Saved hotkeys appear in the help screen right away, and they can't take a key that k9s already uses for something else:

hotKeys:
  shift-1:
    shortCut: Shift-1
    description: Pods
    command: pods
  shift-2:
    shortCut: Shift-2
    description: Port-forwards
    command: pf

Plugins#

A plugin runs an external command against the selected row. k9s fills in variables such as $NAME, $NAMESPACE and $CONTEXT before it runs, so this plugins.yaml entry gives the pods view a shift-e key that streams the selected pod's events (ctrl-c stops the watch):

plugins:
  pod-events:
    shortCut: Shift-E
    description: Pod events
    scopes:
      - pods
    command: kubectl
    background: false
    args:
      - events
      - --context
      - $CONTEXT
      - --namespace
      - $NAMESPACE
      - --for
      - pod/$NAME
      - --watch

The plugin docs cover the remaining fields (confirm, dangerous to hide a plugin in read-only mode, and input prompts). Before writing your own, check the repository's community plugins folder. Helm, Flux and Argo are covered.

Skins#

A skin is a colour scheme in skins/<name>.yaml under the config directory. Grab one from the skins folder in the k9s repo (dracula, nord, solarized-light and around thirty more), drop it in, and name it without the extension:

# config.yaml
k9s:
  ui:
    skin: dracula

The same skin key in a context's own config gives that cluster a different look, which is a cheap guard against running a command in the wrong place. Set K9S_SKIN=dracula in your shell to override both. If the named file isn't found, k9s keeps its stock colours.

Common mistakes#

  • Pressing ctrl-b to benchmark. Older guides, and one paragraph of the README, still say ctrl-b. In current k9s the key is a plain b, listed as Benchmark Run/Stop in the port-forward view's help.
  • Wondering where your forward went. Port-forwards die with the session. A forward you need all afternoon belongs in a separate kubectl port-forward running in its own terminal.
  • No CPU or MEM columns. Nothing's broken: the cluster doesn't serve the Metrics API yet, and installing metrics-server from the prerequisites above brings the columns back.
  • Using ctrl-k on a bare pod. Pods owned by a ReplicaSet come back. A pod you created with kubectl run doesn't.
  • Editing the wrong config.yaml. If changes don't stick, run k9s info; you're probably editing a file in a directory k9s doesn't read.

Clean up#

Quit with :q (any port-forwards close with it), then remove the walkthrough objects:

kubectl delete service web
kubectl delete deployment web

Or do it from inside k9s: :svc, select web, ctrl-d, then the same in :dp. Leave metrics-server in place if you'll keep using k9s.

Common questions#

A few things people ask once they've used k9s for a day.

Is k9s safe to point at a production cluster?#

It can do exactly what your kubeconfig user can do, no more. Start it with --readonly, or set readOnly: true in that context's config, and the delete, kill, edit and other mutating actions are disabled. Plugins marked dangerous disappear too.

Does switching namespace in k9s change kubectl's namespace?#

No. k9s keeps the active namespace and view in its own per-context config, and switching contexts inside k9s happens in memory. It writes to your kubeconfig only when you rename or delete a context from the :ctx view.

Does it work with custom resources?#

Yes. Anything the API server serves is browsable by name, so once an operator installs its CRDs, typing the resource's plural or short name after : opens them like any built-in type. You can pick the columns for those views in views.yaml.

Next in the book#

Keep k9s open in a second terminal from here on. The next tools chapters install controllers that create a lot of objects you didn't write yourself, and watching them appear in :pulses or :xray makes them far easier to follow. GitOps and Argo is a good place to try that, and the Kubernetes Service chapter reads differently once :svc and Enter show you which pods each Service picks.

Further watching#