Install

# Debian / Ubuntu (via mise / cargo / direct binary — the apt package lags)
K9S_VER=0.40.10
curl -L -o /tmp/k9s.tgz \
    "https://github.com/derailed/k9s/releases/download/v${K9S_VER}/k9s_Linux_amd64.tar.gz"
tar -xzf /tmp/k9s.tgz -C /tmp k9s
sudo install /tmp/k9s /usr/local/bin

# macOS
brew install derailed/k9s/k9s

# Arch
sudo pacman -S k9s

# Or via mise
mise use -g k9s@latest

Verify:

k9s version

The model

k9s uses the current kubeconfig ($KUBECONFIG or ~/.kube/config). Launch with k9s — the home view shows pods in the current namespace. The bottom of the screen lists relevant keybindings; ? opens the full help.

The command bar (top, prefixed with :) lets you jump to any resource type:

:pods             list pods (also Ctrl-A in many contexts)
:deployments
:svc              services
:cm               configmaps
:secrets
:ing              ingresses
:no               nodes
:ns               namespaces
:pv               persistent volumes
:pvc              persistent volume claims
:job
:cronjob
:crd              custom resource definitions
:events           cluster events stream
:contexts         switch kubeconfig context

For any resource type that exists in the cluster — including CRDs — the same keybindings apply.

The keystrokes worth knowing

# Navigation
↑/↓           move cursor
PgUp/PgDn     page through long lists
/             filter the current list (regex)
Esc           clear filter / go back
0             show all namespaces
1..9          jump to namespace 1..9

# On the selected resource
enter         drill in (e.g. pod → containers; deployment → pods)
y             view YAML
e             edit YAML in $EDITOR (kubectl edit semantics)
d             describe (kubectl describe)
l             logs (then s for follow / wrap / timestamps options)
s             shell into the container
f             port-forward (prompts for local port)
x             scale (deployments / statefulsets / replicasets)
Ctrl-d        delete (with confirmation)
Ctrl-k        kill (force-delete pod)
Ctrl-r        refresh

# Cluster-wide
:pulses       overview dashboard with CPU/memory/pods/nodes graphs
:xray pod     "X-ray" view: tree of pod → container → mounted volumes/configmaps
:popeye       run Popeye (cluster sanitizer; flags misconfigurations)

Filtering and search

/ filters the current view by regex on the displayed columns. Two useful patterns:

# Filter pods to those in "Failed" phase
/Failed

# Filter by label
/!app=ignore-me        # negate
/-l app=web            # explicit label selector

# Filter across all namespaces
0           # toggle "show all namespaces"
/payments   # then filter to anything containing "payments"

Aliases that pay off

k9s ships with built-in aliases; custom ones go in ~/.config/k9s/aliases.yaml:

aliases:
  vs: v1/services
  vd: v1/secrets
  hr: helm.cattle.io/v1/helmreleases
  app: argoproj.io/v1alpha1/applications

Then :app in the command bar shows all Argo CD Applications.

Plugins

~/.config/k9s/plugins.yaml defines per-context-menu actions. Example: a "tail the log to /tmp" plugin:

plugins:
  tail-to-file:
    shortCut: Shift-T
    description: "Tail log to /tmp"
    scopes: [pods, containers]
    command: bash
    background: false
    args:
      - -c
      - "kubectl logs --tail=-1 -f -n $NAMESPACE $POD -c $CONTAINER > /tmp/${POD}.log"

Now Shift-T on any pod tails to a file. Plugins can call any shell command and access $POD, $NAMESPACE, $CONTAINER, $CONTEXT, $CLUSTER, etc.

Configuration

~/.config/k9s/config.yaml:

k9s:
  liveViewAutoRefresh: true
  refreshRate: 2
  maxConnRetry: 5
  enableMouse: true
  headless: false
  logoless: false
  crumbsless: false
  readOnly: false
  noExitOnCtrlC: false
  ui:
    enableMouse: true
    headless: false
    logoless: false
    skin: dracula            # theme; pick from ~/.config/k9s/skins/

  thresholds:
    cpu:    { critical: 90, warn: 70 }
    memory: { critical: 90, warn: 70 }

  shellPod:
    image: busybox:latest
    namespace: default
    limits: { cpu: 100m, memory: 100Mi }

The "shell pod" is the throwaway pod k9s creates when you press :shell to debug network connectivity from inside the cluster. Customize the image (a debian + curl + dig + netcat image is a much more useful base than plain busybox).

Read-only mode for safer demos

k9s --readonly

Disables edit / delete / scale operations. Useful for production demos or when sharing a screen.

Multi-cluster workflow

# Switch context from inside k9s
:ctx                # context picker

# Or change kubeconfig per-launch
KUBECONFIG=~/.kube/prod-config k9s
KUBECONFIG=~/.kube/dev-config  k9s

The status bar shows the active context and namespace; it's hard to delete the wrong production resource by accident when "PROD" is in red at the top.

Pulses and Xray: the views worth exploring

  • :pulses — cluster overview: nodes, pods, CPU / memory / network / disk graphs, alerts. Useful "is anything obviously wrong" view.
  • :xray pod — per-pod tree showing containers + every ConfigMap/Secret/PVC mounted into them. Catches "is this pod actually getting the latest config" questions in one screen.
  • :popeye — cluster sanitizer pass. Flags resource-limit-less containers, missing readiness probes, services with no endpoints, deprecated APIs, etc. Worth running periodically.

Where k9s loses to kubectl

  • Anything scripted — pipelines, CI, automation. kubectl is the right tool there.
  • Cross-cluster operations — kubectl --context A get pods over many contexts is easier than navigating each in k9s.
  • Detailed history / audit — for an audit trail of what you did, kubectl + shell history beats k9s, which leaves no trail of its key presses.

For everyday "look at the cluster, fix one thing, move on," k9s is the fastest workflow available. Pair with Argo CD (see that tutorial) for the longer-term declarative state and k9s for the immediate interactive operations.