Features

In the order you'll meet them: open a cluster, look at a list, open a thing, act on it — then the plumbing that makes it work.

Every kind, two ways to find it.

The sidebar has two taxonomies of the same catalog. Workloads groups things the way every desktop client does, so nothing moves on day one. API groups mirrors kubectl api-resources, with the exact command as each link's tooltip. Toggle between them; nothing refetches.

Type in the sidebar to narrow it. Fuzzy, prefix-aware, and it matches group names too — cert-manager.io keeps that whole section. The order never changes under you.

The sidebar twice: the Workloads taxonomy, and the API-groups taxonomy filtered to "cert".
The sidebar in both taxonomies side by side, with the filter active in one of them.

Your operators' types, without a plugin.

Sternpost knows the ~35 kinds every cluster has. It cannot know that yours also serves Orders, Applications and RabbitmqClusters — so it asks on connect and adds them to the sidebar, one group per API group, in both taxonomies.

Columns come from the CRD's own additionalPrinterColumns — what the operator's author decided matters, including the ones kubectl hides behind -o wide. The detail page walks the spec and status and hangs the CRD's schema off each row as a tooltip, which is usually the only documentation those types have.

And it follows the pointers. An Order says it's controlled by its CertificateRequest; a Certificate links to its Secret and its ClusterIssuer; a Pod that Argo created says so. Only when the link would be right — a wrong link is worse than none.

The Certificates list from cert-manager, a discovered kind, with the columns its CRD declares.
A CRD-backed list (Certificates from cert-manager) with the printer columns, and a detail page with the schema tooltip open on a row.

Watches, not polling.

Kubernetes has native subscriptions, so Sternpost uses them: list once, then hold a watch and apply changes as they arrive. kubectl get -w is a thin wrapper over the same endpoint — Sternpost speaks it directly rather than shelling out.

When a watch drops — network, expired credentials — it reconnects with backoff and says so. Watches start when you first look at a kind and are dropped after five minutes idle, so the app doesn't pay for what you're not looking at.

The Pods list for the ingest namespace, every row a live watch.
A short clip: kubectl scale in a terminal beside Sternpost, the row count changing without a click.

One word on how it's doing.

Every detail page opens with a word — Running, Rolling out 2/3 updated, Complete, Failed, CrashLoopBackOff, Cordoned, Suspended — the number behind it, and a dot in the matching tone. Chosen to match what kubectl rollout status and kubectl get would say. Conditions took three rows of True (MinimumReplicasAvailable) to say "Available".

It's the one part of the page that's live on its own: it refetches its word on every change to the cluster, without re-reading the rest of the page.

Four state strips: Available, a rollout in progress, Suspended, and CrashLoopBackOff.
Four state strips cropped tight: one ok, one progressing, one warning, one down.

A Deployment's logs, not a pod's.

Open a Deployment, StatefulSet, DaemonSet, ReplicaSet or Job and get the logs of every pod behind it, merged, each line tagged with the pod it came from. Mute a pod and the lines already on screen filter too. Up to twenty pods per stream.

Streams by default, from the API server's follow endpoint. Autoscroll follows only if you were already at the bottom — it never yanks you back down while you're reading. Five thousand lines are kept.

Open in window puts the log in its own window with no sidebar and no search bar, so it can live on the other monitor.

Logs of a Deployment's three pods merged into one stream, each line tagged with its pod, one pod muted.
The multi-pod log view with three pods, one muted; the detached window beside the main one.

A shell in the container.

The second tab on a pod: a terminal over the API server's exec endpoint. No kubectl on PATH, auth from your kubeconfig, so exec plugins work here too.

Auto finds bash, then ash, then sh. Or pick one. Whichever it is, you get a prompt with the shell-integration mark iTerm2 and VS Code use, so your terminal knows where each command starts.

Multi-byte characters that straddle frames, keystrokes that must not reorder, a tab switch that must not kill the shell — handled.

A shell inside a container, showing the output of ls -la.
The terminal tab with ls -la output in a small alpine container.

Port forwards that survive a rollout.

A rule listens on a local port and tunnels each connection through the API server to a Pod, a Service or a workload. Three things neither the CLI nor the desktop clients built on it do:

  • The pod is chosen per connection. kubectl port-forward svc/x — and every client that wraps it — picks a pod at start and dies with it. Here a Service's selector is evaluated as each connection opens and a Ready pod is chosen. A rollout costs you only the connections that were open.
  • A refused connection is a fact about that connection. The rule stays up and shows the kubelet's own words. Only a rule that can't listen goes red.
  • Rules survive a restart. Enabled rules come back when the app does.
  • Add one from any workload's page, or from ⌘K: type forward, pick the pod, deployment or service, pick the port.
The port-forwards page with two rules, one to a Service showing which pod it currently reaches, and the header's forward counter.
The port-forwards list with a couple of rules (one to a Service showing which pod it's currently on), and the :80 counter in the header.

The number, and what it's a share of.

CPU and memory in every pod and node table, printed like kubectl top — with a line under each for its share of the limit (or request, or a node's allocatable). Green under 60%, amber to 90%, red past it. 317Mi says nothing on its own; the line says whether that's a third of the limit or all of it, and says it to peripheral vision down forty rows.

Detail pages get a Metrics section: from Prometheus, thirty minutes of CPU, memory, disk and network with the limit drawn as a line; from metrics-server, a gauge each for CPU and memory. Works with either; if neither is there, the table still renders.

Usage bars in the pod table, and a node's metrics gauges.
The pod table with usage bars; a detail page's four charts.

Restart, scale, run now. Confirmed, attributable.

Restart is kubectl rollout restart — the same annotation kubectl uses, so the two don't leave separate marks. Scale shows the change (3 → 5) and won't send the count you're already at. Run now on a CronJob creates a Job the way kubectl create job --from would, named so you can tell it apart in the list.

Every action confirms first, in one dialog that recaps cluster, kind, namespace and name. A rollout on production shouldn't be one stray click away. Every write carries fieldManager=sternpost.

Which verbs ⌘K offers is a setting. The buttons next to the thing stay.

The confirmation dialog for restarting a Deployment, recapping cluster, kind, namespace and name.
The confirmation dialog for a restart.

Columns that measure themselves.

The table looks at what's in each column and sizes it: a column of short statuses is narrow, a column of image names is wide, and nothing wraps mid-word. No horizontal scroll until there genuinely isn't room.

The Pods list across all namespaces, columns sized to their content.
One wide table, no crop.

Your kubeconfig, referenced, not copied.

Add a cluster by pointing at a kubeconfig and a context. Sternpost stores the path and the context name — credentials stay in your file. Paste YAML instead and it's written 0600 under the app's data dir and deleted with the cluster.

Give each cluster a colour. It tints the status bar so you always know which one you're in, and the contrast is worked out per colour so the text stays readable.

The connectivity dot sits inside the cluster's name, not across the bar from it.

Two clusters with their own status-bar colours, and the cluster picker open.
Two windows with different cluster colours; the cluster picker open.

Works for namespace-scoped users.

Pick several namespaces and Sternpost makes one scoped request each, in parallel — not one cluster-wide list filtered on your machine. That's what a user without cluster-wide permission can actually do, and it doesn't drag the whole cluster over the wire to show two namespaces. If some are forbidden, you get the rows you can see and a note naming the rest.

The namespace scope picker open, with checkout, payments and platform ticked.
The namespace picker with three selected, and the warning line above a table.

What it doesn't do (yet)

  • No events on detail pages yet.
  • No search inside a log stream.
  • Custom resources aren't in ⌘K and aren't watch-backed.
  • Port forwards are TCP, loopback, and end when the app quits.
  • Lists stop at 10,000 rows and say so.

What's next →

Try it on a real cluster.