Everything you need to drive kute.
From your first screen to a production delete: how to find things, read them, change them safely, and get unstuck. Every recording below is captured straight from kute --demo, so you can follow along without a cluster.
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.
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.
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 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,escalways steps back exactly one level, andqquits.
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.spacemarks 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.
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, init 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 wrap their complete messages 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.
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.
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.
kubectl scale deploy/nebula-qa --replicas=6Workloads
+/−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.isets the image, offering the tags from that workload's own rollout history first — usually you are going back to one of them.Redits requests and limits with the live usage bar next to the box you are typing in.ctrl-rrestarts a Deployment's rollout; on the revision rail,Rrolls 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
medits 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.yshows the object's YAML, syntax-highlighted, with the noise folded away.Eopens it in your$EDITOR— kute steps aside and gives the terminal tokubectl edit, then puts you back exactly where you were.xexecs into a pod — a real shell, handed the terminal, with the container picker only appearing when there is more than one to choose. On a container with no shell, or a pod that will not stay running, the same key openskubectl debuginstead — see below.fstarts a port-forward without suspending anything: a chip appears in the header and stays live while you keep browsing.
Debugging without a shell
x is one key for "get me a shell near this object" — what runs behind it depends on what you pressed it on:
- A distroless or otherwise shell-less container still shows up in the exec picker — its row just reads
no shellinstead of listing one — and↵on it opens a debug panel that attaches an ephemeral debug container alongside it, sharing its process namespace. If no container in the pod has a shell, kute skips the picker and opens the panel directly. - A pod that will not stay up — crash-looping, or stuck waiting — cannot be exec'd or attached to either, so
xopens the panel straight into copy mode: a throwaway copy of the same pod spec with its entrypoint replaced by a shell, which is what actually stops the crash loop. The original keeps running untouched, and on exit kute offers to delete the copy for you. xon a node opens the same panel for a privileged, chrooted shell on the node itself —kubectl debug node/…, with the image and profile visible and editable before anything runs.
Every path through the panel prints the exact command on a will run line first, the fields it is built from — image, target container, profile — are edited right on their row, and in a production context ↵ takes an inline y/N before it launches. kute suspends, kubectl debug owns the terminal, and exiting puts you back exactly where you were.
kubectl debug -it nebula-gateway-2b81x -n nebula-stage --image=nicolaka/netshoot --target=gateway --profile=generalThe fork lives on one key: a shell-less container routes x to kubectl debug instead of failing.
An ephemeral debug container is permanent state on someone's pod, so kute never hides it: once one is attached, pod detail grows an EPHEMERAL group under CONTAINERS, tagged ⚑, with ↵ to re-attach if the connection drops. The same tag follows the pod's row in the list for as long as the container exists. A copy-mode pod gets the same honesty in reverse — it is a real pod that counts against quota, so kute tags it in the table and reminds you it is still running when you leave the panel.
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.
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.
pjumps from a route to the Gateway that accepted it — useful when the platform team owns one and you own the other.ycopies the URL,Ythe YAML, andtabmoves 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
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.
Argo CD gets a shallower treatment, on purpose: it already has a good dashboard, so kute doesn't rebuild it. Applications get their own curated list — recognised by API group, same as Flux, so Argo's Application never collides with anything else on screen — with sync and health as two honest, independent columns: Synced and Degraded is git being right while the workload is sick, OutOfSync and Healthy is drift with nothing actually broken, and they read differently because they are different incidents. A degraded row prints the sickest managed resource's own message underneath it, verbatim. r refreshes — asks argocd-application-controller to re-diff now, the same thing it would do on its own poll interval — and S syncs, both plain API calls with a will-run line, never the argocd binary. u copies the one concession to the dashboard: the deep link into the Argo CD UI itself, for sync waves and diffs kute doesn't try to imitate.
In sync, and still failing — the distinction that decides whether you go looking at git or at the workload.
Production safeguards
How much kute asks of you scales with how hard the thing is to undo, and it asks for more in production.
y/N in the keybar, under the command that is about to run — no modal in the wayIn 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.
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:
A missing or unreadable config file is never an error — kute starts anyway, with nothing marked production.
Command-line flags
--context | Context to launch against. Defaults to the one you used last, then to the kubeconfig's current context. |
-n, --namespace | Namespace to launch in. Defaults to the one you used last, then to the context's own. |
--kubeconfig | Path to a kubeconfig. Defaults to $KUBECONFIG, then ~/.kube/config. |
--demo | Run against the built-in fake cluster instead of a real one. |
--theme | dark or light, overriding both auto-detection and the config file. |
--log-file | Write the error and client log stream to a file — attach it to a bug report. |
--keycast | Show the keys you press in the corner. For recording a screencast. |
--version | Print 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.
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 |
| n | namespace — switch namespace |
| c | context — switch cluster |
| a | all namespaces |
| / | filter the current list |
| e | events |
| t | timeline |
| y | yaml |
| E | edit in $EDITOR |
| m | labels/annotations |
| space | mark the row and move down |
| * | mark all rows the filter matches |
| U | what's new in the latest release |
| ? | help for this screen |
| esc | back one level |
| q | quit |
In a list
| j k ↑ ↓ | move the cursor |
| ↵ | open the selected row |
| tab | expand/collapse the namespace group |
| N | jump into namespace of the selected row |
| ctrl-d | delete — the marked set, or the row you are on |
Pods
| l | logs — follow, filter, jump across restarts |
| x | exec — a shell in the container; on a shell-less container or a pod that won't stay up, opens the debug panel instead |
| f | forward — start a port-forward |
| pgup | page up through pod detail |
| pgdown | page down through pod detail |
| ctrl-k | force delete (grace period 0) |
Logs
| space | pause / follow the live stream |
| w | next warning (wraps to the top) |
| e | next error (wraps to the top) |
| W | wrap long log lines on or off |
| t | timestamps on or off |
| s | since — cycle the history window |
| tab | cycle container |
| Y | copy view |
Deployments, StatefulSets, DaemonSets, CronJobs
| + − | scale (Deployments and StatefulSets) |
| i | set image (CronJobs update future Jobs; running Jobs are unaffected) |
| R | resources — requests and limits |
| ctrl-r | restart — roll the Deployment |
| R on the revision rail | rollback to an earlier revision |
Jobs and CronJobs
| R | rerun (Jobs — stages a create-vs-replace choice) / run now (CronJobs — triggers a fresh Job from the schedule) |
| replace | the destructive alternative inside a Job's staged rerun — delete then recreate under the same name, tiered like delete |
| s | suspend / resume |
| S | edit schedule (CronJobs) |
Nodes
| C | cordon / uncordon |
| D | drain |
| x | debug — opens a panel for a root shell on the node, with a preview of the command before it runs |
Debug panel (kubectl debug — pods and nodes)
| ↵ | start — launches the will-run command; inline y/N first in a production context |
| m | mode — attach ephemeral / copy pod (pods only; a pod that won't stay up opens straight into copy mode) |
| i | image — edit in place, with recent images remembered per context |
| t | target/container — cycle the container to attach to or copy |
| p | profile — general / sysadmin / netadmin |
| e | entrypoint (copy mode) — replaces the command, which is what stops the crash loop |
| s | share processes (copy mode) — toggle sharing the original's process namespace |
| ctrl-d | clean up — delete the copy or node-debug pod kute left behind, offered on exit |
| esc | cancel, or keep the leftover pod instead of cleaning it up |
ConfigMap and Secret data
| a | add key |
| ↵ | edit the value under the cursor |
| e | open a long value in a buffer editor |
| ctrl-o | apply the buffer |
| ctrl-d | remove key |
| ctrl-x | show or re-mask the value you are typing |
| ctrl-r | apply + restart consumers (ConfigMaps) |
Helm releases
| v | values |
| h | history — the revision rail |
| R | rollback |
Flux CD
| g flux | the tree — sources with what they drive |
| ↵ | inventory — what this reconciler manages, and why it is failing |
| r | reconcile — with source, on a reconciler row in the tree |
| s | suspend / resume |
| o | source — the repository it syncs from |
Argo CD Applications
| r | refresh — re-diff live state against git now |
| S | sync — apply what git already says to run |
| u | dashboard url — copy the deep link into the Argo CD UI |
cert-manager Certificates
| ↵ | chain — the Certificate → CertificateRequest → Order → Challenge chain, deepest failure on top |
| r | renew — force cert-manager to reissue |
Routing tables
| ↵ | open the backend Service |
| p | parent gateway |
| y | copy url |
| Y | copy yaml |
| tab | tls secret — move to the strip below the table |
| ↵ on the strip | open secret |
Port-forwards
| x | stop this forward |
| r | restart this forward |
| X | stop all forwards |
| y | copy url |
When something is wrong
| r | retry — reconnect, or re-probe contexts |
| w | who-can — who is allowed to do this |
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. Nothing installed in the cluster.