# k9s Cheat Sheet

> **Tool:** k9s
> **Category:** Containers & Orchestration
> **Verified against:** k9s v0.51.0, CLI flags verified via `k9s --help`/`k9s version` run locally; in-app
> keybindings and colon-command reference cross-checked against the official docs at k9scli.io (Commands and
> Hotkeys pages), since a full-screen TUI has no `--help`-exposed equivalent, 2026-08-29
> **Official docs:** https://k9scli.io/

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

```mermaid
stateDiagram-v2
    [*] --> TableView: launch k9s
    TableView --> ResourceView: ":pod / :svc / :deploy ..."
    ResourceView --> LogsView: "l"
    ResourceView --> ShellView: "s"
    ResourceView --> DescribeView: "d"
    ResourceView --> YamlView: "y"
    ResourceView --> EditView: "e"
    LogsView --> ResourceView: "esc"
    ShellView --> ResourceView: "exit / esc"
    DescribeView --> ResourceView: "esc"
    YamlView --> ResourceView: "esc"
    EditView --> ResourceView: "esc / save"
    ResourceView --> TableView: "esc"
    TableView --> [*]: "q / ctrl-c"

    classDef info fill:#e5f0fa,stroke:#1d6fb8,color:#10161c
    classDef warn fill:#fbeee0,stroke:#b8650f,color:#10161c
    classDef crit fill:#fbe8e6,stroke:#b3261e,color:#10161c
    class TableView,ResourceView info
    class LogsView,DescribeView,YamlView warn
    class ShellView,EditView crit
```

**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

```bash
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
```

```bash
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, `s`hift 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:

```yaml
# ~/.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`:

```yaml
# ~/.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`):

```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:

```bash
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.
