step one

Getting started

kute is a single binary. Install it, then run it — there is no configuration file to write first and no setup wizard to sit through.

$curl -fsSL https://kute.dev/install.sh | sh

Homebrew, Scoop and PowerShell one-liners, plus what to do when something goes wrong, are on the install page.

Try it without a cluster

kute --demo runs the whole app against a built-in fake cluster that lives in memory: real screens, real keys, real failure states, nothing to connect to and nothing to break. It is the fastest way to learn the keys, and it is what every recording on this page was made with.

$kute --demo

Connecting to your own cluster

Plain kute uses the same kubeconfig kubectl does — $KUBECONFIG if it is set, otherwise ~/.kube/config, and in-cluster credentials when it is running inside a pod. It opens on your current context, or on whichever context and namespace you were last in. You can also say so explicitly:

$kute --context prod-eks -n payments

kute reads with your own credentials and never asks for more than the API server already gives you. Anything you cannot list shows up as a permission card, not an empty table — see Troubleshooting.

Your first screen

You land on the Pods of your current namespace. Three things are worth knowing before anything else:

  • The top line is a breadcrumb: context › namespace › kind, plus the connection state and how fresh the data is.
  • The bottom line is the keybar. It only shows keys that do something on the row you are on right now, so it is worth glancing at when you change kind.
  • ? opens the full key list for wherever you are, esc always steps back exactly one level, and q quits.
browse

Browsing resources

Every kind is the same table, so the keys you learn on Pods work on Deployments, Nodes, Secrets and on custom resources you have never opened before. j/k and the arrow keys move; opens whatever the row is; esc comes back.

  • / filters the list as you type. It filters what is on screen — the data behind it keeps updating.
  • space marks the row under the cursor and moves down; * marks everything the current filter matches. Filter first, then mark, then act — that is how you do anything in bulk.
  • Unhealthy rows sort to the top on their own. You should not have to hunt for the broken one.

The whole cluster at once

a switches the list to every namespace. Namespaces with problems open expanded and sort first; healthy ones collapse to a single line so they take one row instead of forty. tab expands or collapses the group you are in, and N drops you into the selected row's namespace for good.

Custom resources

Your CRDs appear in the same list of kinds as everything else, discovered when kute connects — there is nothing to register and no layout file to write. Their columns come from the printer columns the CRD itself declares, which is what kubectl get shows you, and a Ready-style condition becomes the status dot. A kind that declares neither still lists, it just has nothing to colour.

The cluster overview

Press g and type ov for a cluster-wide summary: capacity, nodes, everything currently in trouble, and what changed recently. It is a place to jump from — every row opens the real screen for that object — rather than a dashboard to sit on.

Cluster overview — every row is a door, not a chart.

diagnose

Diagnosing pods and nodes

Press on a pod and the screen opens with the reason it is unhappy, not with its labels. A crashlooping container leads with its last termination — exit code, reason, how long ago — above everything else, so the first line you read is the one you came for.

  • Containers, their images, and live CPU/memory against their limits sit below it.
  • Numbered RELATED links — Deployment, ReplicaSet, Service, ConfigMap — go straight to the object. There is no sidebar to tab through.
  • The pod's own events are on the same screen, so "why is it Pending" is usually answered without going anywhere.

Pod detail — the termination reason is the headline, not a field you scroll to.

Nodes

on a node gives you its conditions, how much of it is allocated against what it has, and its taints — with the node's own pods sorted by memory underneath. When a node is under pressure, the pod causing it is usually the first row.

A worked example

This is the whole loop end to end: a namespace that looks fine, all-namespaces revealing the pod that isn't, detail and logs giving the cause, and the timeline tying it to a rollout ten minutes earlier.

read

Logs, events and timelines

Logs

l opens logs from anywhere a pod is selected, following by default. The toolbar picks the container, how far back to start, whether to wrap long lines and whether to show timestamps. / filters the live stream without stopping it, and severity is coloured so an ERR block is visible while scrolling past.

Restarts are drawn as a line across the stream, so the crash and the process coming back up read top to bottom in one place — no stitching together kubectl logs --previous by hand.

Events

e opens events: for the whole namespace from a list, or for one object from its detail screen. Repeats collapse into a single row with a count rather than scrolling past you forty times, warnings sort first, and on an event goes to the object it is about.

Timeline

t merges events, container restarts and rollout revisions onto one clock, newest first — namespace-wide from a list, or for a single object from its detail screen. Rollouts act as the anchors: every row tells you how long before or after the nearest change it happened, which is usually the question you are actually asking.

act

Changing things

Before anything runs, kute prints the exact kubectl command it is about to execute on a will run line. You can read it, copy it, and learn from it — and if it isn't what you meant, you have not done anything yet.

will run kubectl scale deploy/nebula-qa --replicas=6

Workloads

  • + / scale a Deployment or StatefulSet from an inline prompt pre-filled with one more or one fewer than you have. If an HPA owns the replica count, kute says so instead of letting you set a number that will be overwritten.
  • i sets the image, offering the tags from that workload's own rollout history first — usually you are going back to one of them.
  • R edits requests and limits with the live usage bar next to the box you are typing in.
  • ctrl-r restarts a Deployment's rollout; on the revision rail, R rolls one back.

Config and secrets

on a ConfigMap or a Secret opens its data as a key/value table. Short values are edited in place with ; a long one opens in a buffer you can edit properly and apply with ctrl-o. a adds a key, ctrl-d removes one, and on a ConfigMap ctrl-r applies your change and restarts every workload that consumes it, which is the step that is easy to forget.

Secret values are masked everywhere by default: in the table, in the will-run line, and in the result message. ctrl-x toggles the mask on the value you are typing. Nothing decoded is written to disk or to a log, and leaving the screen re-masks everything.

A Secret in the YAML view: masked by default, one key revealed in place. Any Secret behaves this way, including ones an operator created for you.

Labels, YAML and the shell

  • m edits labels and annotations on any object, showing you which Services select on the label you are about to change before you change it. Keys a controller owns stay read-only.
  • y shows the object's YAML, syntax-highlighted, with the noise folded away. E opens it in your $EDITOR — kute steps aside and gives the terminal to kubectl edit, then puts you back exactly where you were.
  • x execs into a pod the same way — a real shell, handed the terminal, with the container picker only appearing when there is more than one to choose.
  • f starts a port-forward without suspending anything: a chip appears in the header and stays live while you keep browsing. s on a node opens a root shell on the node itself.

Helm releases

Helm releases are just another kind in the list — you can browse them, read their values with v, and open the revision history with h, all without the helm binary installed. Rolling back with R is the one thing that shells out to real helm.

One Deployment, one flow: scale, set image, resources, a label add and remove, and a port-forward — each showing its command first.

routing

Ingress and Gateway routing

An Ingress or an HTTPRoute only means something joined to other objects — a Service, a TLS Secret, a parent Gateway. Press on one and kute resolves those joins for you: one row per host and path, the backend it actually lands on, and how many endpoints are ready behind it right now.

  • A route whose backend Service does not exist is red on the row, spelled out — not something you find out from a 503 later.
  • Canary weights get their own column, so a 90/10 split is a number you read rather than one you compute.
  • TLS columns show the real expiry date from the referenced Secret, not just that a Secret was named.
  • p jumps from a route to the Gateway that accepted it — useful when the platform team owns one and you own the other. y copies the URL, Y the YAML, and tab moves to the TLS strip where opens the Secret.

Ingress — one row per host+path → backend, resolved live

HTTPRoute — rules, weights, and the parent Gateway join

gitops

Flux and GitOps

On a Flux cluster the generic custom-resource view misleads you, because it asks the wrong question. Ready=False on a reconciler mid-sync means it is working; a suspended one reports whatever it last said before someone paused it. Read literally, a perfectly healthy GitOps cluster renders as a column of red. kute treats Flux's five API groups as the documented, versioned status vocabulary they are — recognised by API group, never by kind name, so Flux's HelmRelease and Helm 3's own releases stay two different things.

Kustomizations, HelmReleases and sources get real columns — what revision is applied, which repository it came from, and when it last reconciled — with the failure reason printed under the row, verbatim, exactly as Flux wrote it. A reconciling object is not a failing one, and a suspended one stays at the top of the list rather than sinking in with the healthy rows: it is the object quietly drifting from git. r reconciles and s suspends or resumes, both plain API calls — the flux binary is never required, and kute never installs or bootstraps Flux, nor writes to your Git repository.

g then flux opens the tree: every repository with the Kustomizations and HelmReleases it drives nested under it, which is the join you would otherwise assemble from two kubectl lists and each object's sourceRef. A reconciler whose applied revision is behind what its source now offers is marked source ahead — a fact, not a guess: kute reads no Git remote, so it never claims to know how far behind. On a reconciler row r reconciles with source, stamping the repository first and then the reconciler, because syncing against a stale artifact only re-applies what is already there. Both commands appear in the will-run line before either runs. The tree only appears in the palette on a cluster that actually serves Flux CRDs.

on a Kustomization opens its own screen: the reconcile failure at the top with the condition message verbatim, the retry countdown, and — the part that usually costs you five minutes — the object the health check is actually waiting on, resolved for you and openable with another . Under that, whether what is applied matches what the source offers, which separates in sync but failing health checks from genuinely behind; then the inventory of everything the Kustomization manages, unhealthy first.

In the timeline (t), an applied revision is its own row — the short SHA, and the commit subject when the cluster still knows it. Flux only mentions a commit message once, in a source-controller event that expires within the hour, so kute keeps whichever ones it has seen for as long as it is running and shows the bare SHA otherwise. It never fetches your Git remote and never wants a token.

In sync, and still failing — the distinction that decides whether you go looking at git or at the workload.

safely

Production safeguards

How much kute asks of you scales with how hard the thing is to undo, and it asks for more in production.

reversible cordon runs straight away — you can uncordon with the same key
delete, restart, rollback a y/N in the keybar, under the command that is about to run — no modal in the way
prod, drain, force delete type the object's name to confirm; drain and force delete always ask this, everywhere

In a production context, the ordinary y/N confirmations become type-the-name confirmations too. A red border is used for exactly one thing in kute — a confirmation that will destroy something — so if you see red, stop and read.

Marking a context as production

kute never guesses from a name — a context called prod-eks gets no special treatment unless you say so. Open the context palette with c, put the cursor on a context and press ctrl-p to mark or unmark it. That writes the prodContexts list in your config file, which every kute session on that machine then reads.

When the cluster goes away

Losing the connection mid-incident does not blank the screen. What kute last saw stays browsable, dimmed, with an age stamp and a countdown to the next retry — stale data you can read beats an empty screen you cannot. Actions that would write are hidden until the connection is back.

settings

Configuration

kute runs with no config file at all. When you want one it lives at ~/.config/kute/config.yaml, and these are all of its keys:

prodContexts: # type-the-name confirms in these contexts
- prod-eks
- prod-gke
theme: light # dark | light — default follows your terminal
nodeShellImage: ghcr.io/you/debug:1 # when Docker Hub is unreachable
update:
check: false # stop the once-a-day release check

A missing or unreadable config file is never an error — kute starts anyway, with nothing marked production.

Command-line flags

--contextContext to launch against. Defaults to the one you used last, then to the kubeconfig's current context.
-n, --namespaceNamespace to launch in. Defaults to the one you used last, then to the context's own.
--kubeconfigPath to a kubeconfig. Defaults to $KUBECONFIG, then ~/.kube/config.
--demoRun against the built-in fake cluster instead of a real one.
--themedark or light, overriding both auto-detection and the config file.
--log-fileWrite the error and client log stream to a file — attach it to a bug report.
--keycastShow the keys you press in the corner. For recording a screencast.
--versionPrint version information and exit.

Updates

kute checks for a new release about once a day and, if there is one, shows a quiet chip in the header. U opens what changed along with the exact upgrade command for the way you installed it. kute never updates itself, and update: check: false turns the check off entirely if outbound requests are a problem where you work.

reference

Keyboard reference

? shows this for wherever you happen to be, filtered to what applies. j/k work anywhere the arrow keys do, and esc always steps back exactly one level.

Anywhere

g or :goto — jump to a kind, object, namespace or context
nnamespace — switch namespace
ccontext — switch cluster
aall namespaces
/filter the current list
eevents
ttimeline
yyaml
Eedit in $EDITOR
mlabels/annotations
spacemark the row and move down
*mark all rows the filter matches
Uwhat's new in the latest release
?help for this screen
escback one level
qquit

In a list

j k move the cursor
open the selected row
tabexpand/collapse the namespace group
Njump into namespace of the selected row
ctrl-ddelete — the marked set, or the row you are on

Pods

llogs — follow, filter, jump across restarts
xexec — a shell in the container
fforward — start a port-forward
ctrl-kforce delete (grace period 0)

Deployments, StatefulSets, DaemonSets

+ scale (Deployments and StatefulSets)
iset image
Rresources — requests and limits
ctrl-rrestart — roll the Deployment
R on the revision railrollback to an earlier revision

Nodes

Ccordon / uncordon
Ddrain
snode shell — a root shell on the node

ConfigMap and Secret data

aadd key
edit the value under the cursor
eopen a long value in a buffer editor
ctrl-oapply the buffer
ctrl-dremove key
ctrl-xshow or re-mask the value you are typing
ctrl-rapply + restart consumers (ConfigMaps)

Helm releases

vvalues
hhistory — the revision rail
Rrollback

Flux CD

g fluxthe tree — sources with what they drive
inventory — what this reconciler manages, and why it is failing
rreconcile — with source, on a reconciler row in the tree
ssuspend / resume
osource — the repository it syncs from

Routing tables

open the backend Service
pparent gateway
ycopy url
Ycopy yaml
tabtls secret — move to the strip below the table
on the stripopen secret

Port-forwards

xstop this forward
rrestart this forward
Xstop all forwards
ycopy url

When something is wrong

rretry — reconnect, or re-probe contexts
wwho-can — who is allowed to do this
when it goes wrong

Troubleshooting

kute starts but cannot reach the cluster

You get a screen naming the context it tried and the error the API server or your network actually returned — not a spinner. r retries, c switches to a context that works. If there is no kubeconfig at all, the same screen tells you where kute looked.

"Forbidden" instead of a list

kute shows the server's own message on a card rather than an empty table, because "you may not list these" and "there are none" are very different answers. From that card, w opens who-can with your denial already filled in, so you can see which role would have allowed it and who has it. Browsing everything else carries on working — a permission error never blocks the app.

GKE, EKS or AKS credentials fail

Clusters that authenticate through a plugin (gke-gcloud-auth-plugin, aws, kubelogin) fail with the plugin's own error text shown to you. kute never lets those plugins take over your terminal for an interactive prompt, so if one needs you to log in, do it in a shell first — gcloud auth login, aws sso login — then press r.

A big cluster feels slow to open

kute only fetches what the screen you are on needs, and starts watching a kind the first time you look at it. Opening an unfamiliar kind on a very large cluster takes a moment the first time and is instant afterwards. Namespaces, pods and nodes are always ready at launch.

Something crashed, or looks wrong

Run kute with --log-file kute.log and reproduce it; the file has the error and client-go stream in it. If kute panics it writes a crash report and tells you where — attaching both to an issue is the fastest route to a fix.

Bugs and feature requests: github.com/kute-dev/kute/issues.

Install kute.

One binary, no cluster-side install. Or run kute --demo first and try it against a fake cluster.

$curl -fsSL https://kute.dev/install.sh | sh