k9s: Terminal UI and Read-Only Mode
k9s is an interactive terminal UI for Kubernetes; --readonly disables all mutating actions, which is the only sane way to run it near CI.
k9s shines for humans debugging a cluster. It is not a scripting tool: it needs a TTY and is interactive, so in CI contexts it is used read-only by an operator, not by the pipeline itself.
What it does
k9s renders cluster resources in a live, navigable TUI: switch resources with :pods, drill into logs, exec into containers, and watch events. --readonly removes destructive key actions (delete, edit, scale) so a session cannot mutate the cluster.
Common usage
k9s --readonly # safe view-only session
k9s -n payments # start scoped to a namespace
k9s --context staging # pick a context
k9s --command pods # open directly on the pods viewOptions
| Flag | What it does |
|---|---|
| --readonly | Disable all mutating actions |
| -n, --namespace | Start scoped to a namespace |
| --context | Use a specific kubeconfig context |
| --command | Open directly on a given view |
| --headless | Hide the header for more rows |
In CI
k9s is interactive and needs a TTY, so it does not belong inside an automated pipeline step. Treat it as a read-only operator tool: when a CI run fails, an engineer opens k9s --readonly against the same cluster to investigate, while the pipeline relies on stern and kubectl for non-interactive output. You can also enforce read-only via the config so no session can mutate.
Common errors in CI
Launching k9s without a TTY (e.g. in a non-interactive runner step) fails with open /dev/tty: no such device or address or a blank hang; do not run it unattended. Boom!! No connectivity means the kubeconfig context is unreachable or its token expired. Unable to locate K8s cluster configuration means KUBECONFIG is unset.
Using this in CI
A runner has no kubeconfig, no cached context, and no interactive auth. Every kubectl invocation in CI needs the context supplied explicitly, and most confusing CI failures here are the command running against the wrong cluster or no cluster at all.
# never rely on the ambient context on a runner
kubectl --context "$KUBE_CONTEXT" -n "$NAMESPACE" get pods
# confirm what you are actually connected to before mutating anything
kubectl config current-context
kubectl cluster-info
# fail fast instead of hanging on an unreachable API server
kubectl --request-timeout=30s get nodes