Verified6 commandsAI-assisted

k9s

.md

Verified 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#

Diagram

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 version

Real 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 server

Tip

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:

CommandJumps to
:pod (or :po)Pod table view
:svcService table view
:deploy (or :dp)Deployment table view
:nsNamespace table view — also how you switch the active namespace without leaving k9s
:ctxContext table view — switch cluster context without leaving k9s
:nodeNode table view
:pvcPersistentVolumeClaim table view
:secretSecret table view
:cmConfigMap table view
:eventsCluster events, newest first — often the fastest first stop when triaging
:xray deployXRay 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#

KeyAction
?Show the full help/keybinding list for the current view
:Enter command mode
/Filter the current list (supports regex)
escGo back one level / clear the active filter
ctrl-aShow all available resource aliases
ctrl-eToggle the header on/off
ctrl-rForce-refresh the current view
q / ctrl-cQuit k9s (or back out one level, context-dependent)

Keybindings — acting on a selected resource (pod-focused)#

KeyAction
lStream live logs for the selected pod
pShow previous container logs (post-crash, the --previous equivalent)
sOpen an interactive shell inside the selected pod's container
dDescribe — the kubectl describe equivalent, rendered in-app
yShow the resource's full YAML
eEdit — opens the resource in $EDITOR, shows a diff, applies on save
shift-fStart a port-forward to the selected pod
ctrl-dDelete 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 deploy

Defined 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/v1

Config — 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: 5000

Real-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 --readonly

Caution

--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 --readonly is per-launch, not a saved preference, unless it's also set in config.yaml's readOnly: true — a habit of always launching with the flag against production contexts is safer than relying on remembering to set it.
  • Not realizing :pod @ctx1 exists and manually switching context via :ctx just to check one other cluster's pods, then switching back — the inline @context filter 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 kubectl directly, or stern for 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.