k9s
.mdVerified against k9s v0.51.0, CLI flags verified via `k9s --help`/`k9s version` run locally; in-app · official docs
What it is and where it fits 🎯#
k9s is a full-screen terminal UI for browsing and operating a Kubernetes cluster — the tool you reach for
once kubectx/kubens have you pointed at the right place and you need to actually look around: list
pods, tail logs, describe a resource, jump into a shell, edit a manifest, or kill a stuck pod, all without
retyping kubectl get/describe/logs/exec dozens of times with slightly different arguments each time. It's
not a replacement for kubectl in scripts or CI — it's an interactive human tool, meant to be launched into
a terminal pane and left running while you work. Where it differs from stern: stern is a single-purpose,
non-interactive log multiplexer you'd wire into a pipeline or leave streaming in a pane; k9s is a general
resource browser that happens to include a log view among many other resource-specific actions.
The three-layer navigation model#
This is the mental model that makes k9s fast: every screen is one keystroke from a resource-appropriate
action, and esc always backs out one level rather than closing the whole app. Once this loop is muscle
memory, most day-to-day debugging never touches kubectl directly again.
Installation#
brew install derailed/k9s/k9s # macOS/Linux Homebrew (dedicated tap)
sudo apt-get install k9s # Debian/Ubuntu, if present in your repos
# or download the release binary directly (verified release: v0.51.0):
curl -sL -o k9s.tar.gz \
https://github.com/derailed/k9s/releases/download/v0.51.0/k9s_Linux_amd64.tar.gz
tar -xzf k9s.tar.gz k9s && sudo mv k9s /usr/local/bin/
k9s versionReal captured k9s version output:
Version: v0.51.0
Commit: 558caafe7ba067467de46b320cc22ef11fef9c34
Date: 2026-06-06T14:04:25Z
Launch flags#
Real k9s --help output (v0.51.0, trimmed to the flags a day-to-day user actually reaches for):
Flags:
-A, --all-namespaces Launch K9s in all namespaces
--context string The name of the kubeconfig context to use
-c, --command string Overrides the default resource to load when the application launches
--headless Turn K9s header off
--kubeconfig string Path to the kubeconfig file to use for CLI requests
-n, --namespace string If present, the namespace scope for this CLI request
--readonly Sets readOnly mode by overriding readOnly configuration setting
-r, --refresh float32 Specify the default refresh rate as a float (sec) (default 2)
--write Sets write mode by overriding the readOnly configuration setting
k9s # launch scoped to the current kubeconfig context/namespace
k9s --context prod-cluster --namespace checkout # launch directly scoped, skipping the manual :ctx / :ns step
k9s -A # launch in all-namespaces mode from the start
k9s -c deploy # open straight into the Deployments view, skipping the pod table
k9s --readonly # disable every mutating action (delete, edit, scale) for this session
k9s -r 5 # slow the refresh rate to every 5s — kinder to a heavily-loaded API serverTip
k9s --context <name> --namespace <name> composes naturally with kubectx/kubens. Rather than
switching context/namespace first and then launching k9s, launching directly with both flags is one
command and doesn't leave a permanent kubeconfig change behind — useful for a quick look at a cluster you
don't otherwise want to become your shell's default.
Important
--readonly is the right default for a shared/production cluster you're only there to observe. It
disables every destructive keybinding (ctrl-d delete, e edit, shift scale) at the application level,
not just as a UI hint — genuinely useful for handing a teammate temporary read access to poke around a
production namespace during an incident without risking an accidental delete from muscle memory trained on
a dev cluster.
Command mode — the : prefix#
Press : to enter command mode, type a resource name (or its short alias), and hit enter to jump straight to
that resource's table view — this is the primary way to move around k9s, faster than arrow-key menu
navigation once memorized. Per the official docs, command mode supports filters and cross-context/namespace
jumps inline:
| Command | Jumps to |
|---|---|
:pod (or :po) | Pod table view |
:svc | Service table view |
:deploy (or :dp) | Deployment table view |
:ns | Namespace table view — also how you switch the active namespace without leaving k9s |
:ctx | Context table view — switch cluster context without leaving k9s |
:node | Node table view |
:pvc | PersistentVolumeClaim table view |
:secret | Secret table view |
:cm | ConfigMap table view |
:events | Cluster events, newest first — often the fastest first stop when triaging |
:xray deploy | XRay view — a resource and everything it owns/depends on, as a tree |
:pulses (or :pu) | Pulses view — a live health dashboard across the cluster |
:screendump (or :sd) | List of saved screen dumps |
Filters compose directly onto a resource command:
:pod ns-x # pods, scoped to namespace ns-x
:pod /fred # pods, filtered by name containing "fred"
:pod app=fred,env=dev # pods, filtered by label selector
:pod @ctx1 # pods, in a different context, without switching your active one
Keybindings — general navigation#
| Key | Action |
|---|---|
? | Show the full help/keybinding list for the current view |
: | Enter command mode |
/ | Filter the current list (supports regex) |
esc | Go back one level / clear the active filter |
ctrl-a | Show all available resource aliases |
ctrl-e | Toggle the header on/off |
ctrl-r | Force-refresh the current view |
q / ctrl-c | Quit k9s (or back out one level, context-dependent) |
Keybindings — acting on a selected resource (pod-focused)#
| Key | Action |
|---|---|
l | Stream live logs for the selected pod |
p | Show previous container logs (post-crash, the --previous equivalent) |
s | Open an interactive shell inside the selected pod's container |
d | Describe — the kubectl describe equivalent, rendered in-app |
y | Show the resource's full YAML |
e | Edit — opens the resource in $EDITOR, shows a diff, applies on save |
shift-f | Start a port-forward to the selected pod |
ctrl-d | Delete the selected resource |
Warning
ctrl-d deletes immediately after a single confirmation prompt — there's no separate "dry run" mode in
the TUI. In a table filtered to multiple selected resources (multi-select with space), it deletes all
of them in one action. Muscle-memory ctrl-d from a dev cluster session is the single most common way an
engineer accidentally deletes something in prod through k9s — this is exactly the case --readonly
exists to prevent structurally rather than relying on care alone.
Hotkeys — user-defined shortcuts (hotkeys.yaml)#
Beyond the built-in keybindings above, k9s supports fully custom shortcuts for resources/filters you jump to
often, defined in $XDG_DATA_HOME/k9s/hotkeys.yaml (typically ~/.local/share/k9s/hotkeys.yaml on Linux)
and hot-reloaded automatically — no restart needed after editing it:
# ~/.local/share/k9s/hotkeys.yaml
hotKeys:
shift-0:
shortCut: Shift-0
description: View pods labeled app=kindnet
command: pods app=kindnet
shift-1:
shortCut: Shift-1
description: Jump straight to Deployments
command: dp
shift-2:
shortCut: Shift-2
description: XRay view of Deployments
command: xray deployDefined hotkeys show up in the ? help screen automatically, so a team can standardize a shared
hotkeys.yaml for the specific resources their on-call rotation checks most.
Tip
A shared, checked-in hotkeys.yaml for your team's most-visited resources beats everyone memorizing
raw : commands independently. If the platform team always checks Deployments, HPAs, and a specific
CRD first during triage, ship a hotkeys.yaml in the team's onboarding repo rather than relying on tribal
knowledge of the right : incantations.
Aliases — custom short names for resources#
Separately from hotkeys, aliases.yaml (same $XDG_DATA_HOME/k9s/ directory) lets a team define its own
short names usable in command mode, the same way po is already a built-in alias for pod:
# ~/.local/share/k9s/aliases.yaml
alias:
cronjobs: cj
mycrd: mycrd.example.com/v1Config — config.yaml#
k9s's own behavior (refresh rate, default view, readOnly mode, logging level) lives in
$XDG_CONFIG_HOME/k9s/config.yaml (typically ~/.config/k9s/config.yaml):
# ~/.config/k9s/config.yaml
k9s:
refreshRate: 2
maxConnRetry: 5
readOnly: false
noExitOnCtrlC: false
ui:
skin: dracula
enableMouse: false
logger:
tail: 200
buffer: 5000Real-world scenario: triaging a CrashLoopBackOff during an incident#
An SRE gets paged for pods stuck in CrashLoopBackOff in checkout. The full k9s loop for this, from launch
to root cause, is genuinely faster than the equivalent sequence of separate kubectl invocations:
k9s --context prod-cluster --namespace checkout
:pod # confirm which pods are actually crash-looping, sorted by restart count
d # describe the worst offender — check Events for the immediate signal (OOMKilled? failed probe?)
p # previous-container logs — the crash's actual stack trace, not the fresh restart's empty log
y # full YAML — check resource limits against what the describe view suggested
Tip
Sort the pod table by restart count before describing anything (shift-r in most k9s builds, or
click the column header) — describing the pod with the most restarts, not just the first one
alphabetically, saves a wasted round trip when only one specific pod is actually the anomaly.
Real-world scenario: safely handing a teammate temporary production access#
A platform engineer needs a teammate on another team to check something in a production namespace during an
incident, without granting a permanent kubectl credential or risking an accidental mutation:
k9s --context prod-cluster --namespace checkout --readonlyCaution
--readonly disables mutating keybindings at the k9s application level — it is not an RBAC control.
It stops accidental ctrl-d/e/scale from inside k9s itself; it does nothing to prevent the same user
from running a mutating kubectl command directly in another terminal with the same credentials. Treat it
as a footgun guard for the interactive session, not a substitute for actually scoping the underlying
kubeconfig/RBAC role to read-only if that's the real security boundary needed.
Common pitfalls#
- Assuming
l(logs) shows the crash, when the pod already restarted — the fresh container's logs are often empty or just starting up;p(previous logs) is what actually holds the crash's last output. - Forgetting
--readonlyis per-launch, not a saved preference, unless it's also set inconfig.yaml'sreadOnly: true— a habit of always launching with the flag against production contexts is safer than relying on remembering to set it. - Not realizing
:pod @ctx1exists and manually switching context via:ctxjust to check one other cluster's pods, then switching back — the inline@contextfilter avoids the round trip entirely. - Running k9s inside a CI job or a non-interactive script — it's a TUI that expects a real terminal;
there is no headless/scriptable output mode suitable for pipeline automation. Reach for
kubectldirectly, orsternfor log-specific automation, in anything non-interactive.
Exit codes#
k9s is an interactive application, not a scriptable CLI — exit status reflects whether the TUI launched and
exited cleanly (0) versus a startup failure (bad kubeconfig, unreachable API, invalid flag combination),
not the outcome of anything done inside the session.
When to reach for something else#
For a non-interactive, scriptable log tail across many pods/containers at once (a pipeline debugging job, a
kubectl-adjacent one-shot log capture), reach for stern instead — k9s's log view is built for a human
watching it live, not for piping/redirecting output. For pure context/namespace switching without launching
a full TUI, kubectx/kubens remain the faster single-purpose tools; k9s's own :ctx/:ns command-mode views
exist mainly so you don't have to leave k9s once you're already inside it.