Rubick

A desktop Kubernetes client

Your cluster is lying to you.

A crashlooping pod says Running. A Service with a mistyped port draws green and publishes nothing. Rubick reads what the cluster actually does, and tells you that instead.

Reported: Running. Observed: CrashLoopBackOff.

Free. GPLv3. No account. No telemetry.

Rubick showing a workload page with live status, usage history and the traffic chain

Status: fine, apparently

four lies you have already been told this week.

LIE #1

"Running", says the pod.

The container inside has crashed fourteen times. .status.phase does not care, and most dashboards read .status.phase. Rubick derives status the way kubectl does, so a crashloop looks like a crashloop. And when you open the logs they open on the container that failed, at its previous run, not the fresh copy that has not died yet.

Reported: Running. Observed: CrashLoopBackOff · log opened on the run that failed.
We got this one wrong too

The Pods list has derived kubectl's status since 3.0.0. Three of Rubick's own readers still used .status.phase where that status was meant: the port-forward picker, the infrastructure builder and the peek panel's disabled-action sentence. A reviewer saw a crash-looping pod drawn as Running there.

Fixed in 713a2ad, with a corpus of pod shapes both the Rust and the TypeScript evaluator must now agree on. If you find the fourth reader, that is worth an issue.

This lie on its own page, to send to whoever needs it
Logs opened on the failing init container's previous run

LIE #2

"All green", says the Service.

Healthy pods, matching selector, one mistyped port name. The Service publishes nothing, and it will stay green all week. Rubick reads the endpoints the cluster actually publishes, not the ones you meant to publish.

selector matches 3/3 pods

Reported: 3 endpoints. Observed: published with no port · "htp" matches no container.
This lie on its own page, to send to whoever needs it
Connections tab grouping a workload's network paths by question

LIE #3

"No route to host", says nobody at all.

Somewhere between the Ingress and the pod, the request dies quietly. Rubick draws the whole chain on the workload's own page, and where the path stops it names the reason: a backend that does not exist, a selector matching nothing, pods running but not ready.

This lie on its own page, to send to whoever needs it

Four paths on host shop.example.test, no middleware. Slash resolves to three ready pods through Service api-ok. Slash legacy reaches Service api, whose target port name matches no container, so its addresses are published with no port. Slash checkout reaches Service checkout, whose only pod crashes on start and has never been Ready, so nothing is published. Slash api points at Service api-v2, which does not exist in this namespace; inspection stops there. Cluster configuration was inspected; request delivery was not tested.

LIE #4

"Protected", says the namespace.

Four network policies in the list, three of them enforced. The fourth has an In operator inside an ingress rule with no values under it, which the Cilium operator cannot parse, so it throws the rule away and keeps the object. The name is the same, the age is the same, and the selector at the top still reads as if it covers those pods, which are now open. The only record is a condition inside the policy's status, and no ordinary list of custom resources shows it. Rubick puts that answer first in the row and names the pods left uncovered.

Reported: 4 policies. Observed: 3 enforcing · 1 thrown away for a typo.

Valid: False · invalid label selector: matchExpressions[0].values: Required value: must be specified when `operator` is 'In' or 'NotIn'

kubectl get cnp prints a VALID column, so the CLI is not fooled.

This lie on its own page, to send to whoever needs it
Endpoints with the policies that select them, and the ones nothing covers

Reproduce them

Three of them, one throwaway cluster.

kind or k3d is enough for these three, and no ingress controller is needed: the lies live in the objects, not in the traffic. You get a pod whose phase is Running while its only container crashes on every start, with the ConfigMap, Secret and volume its page will list, a Service whose three pods are Ready and which publishes no port next to a twin Service on the same pods that publishes fine, and one host with four paths: one healthy, one behind the crashlooping pod, one behind the mistyped port, one pointing at a Service that does not exist.

$kubectl apply -f https://rubick.tech/lies.yaml
$kubectl -n rubick-lies get pod,svc,endpointslice,ingress
$kubectl delete -f https://rubick.tech/lies.yaml

# kubectl reads the same objects Rubick does, so it is not fooled either. The dashboards that are fooled read .status.phase, or the selector, or the rule, and stop there.

Connections

One page, everything it touches.

Open a pod and its neighbourhood is already there: the Ingress and Service in front of it, the ConfigMap it reads, the Secret it mounts, the volume it claims, the node it landed on, and by name the kinds nobody asked about. Every neighbour is a page of its own, one click away, with its own neighbours. This is the checkout pod from the fixture, as read on a k3d cluster. Click through it.

PodcheckoutCrashLoopBackOff

rubick-lies · 1 container · 3 restarts and counting

Traffic chain · on the Overview, drawn from the entry point down

Podcheckoutnever Ready

A Gateway listener and an HTTPRoute draw the same chain from the listener down. The fixture uses an Ingress so it needs no CRDs.

Needs to run · if one of these is missing the pod does not start

Runs on

Not looked at · named, so a group that is absent is never read as a group that is empty

the kinds this page did not readlisted by name, with the reason, instead of left as a gap

Every object the fixture creates, and the one it only mentions, each drawn in the groups its own Connections tab uses. What the tab does not show is not drawn here either.

Before you act

It warns before it obeys.

Scale, Restart, Delete and Edit YAML tell you who will undo your change and how fast. An autoscaler, in seconds. Argo CD or Flux, in minutes. Then it does what you asked anyway, because a hand edit during an incident is legitimate and you are an adult.

you: replicas 1, then owner: HorizontalPodAutoscaler, then undone in seconds. Named before the button does anything. The button still works.
Scaling a deployment an autoscaler owns, with the warning naming who will undo it

The third answer

Nothing there, or nobody looked?

A list that came back empty, a list the cluster refused to give, and a list nobody asked for draw the same green zero on most dashboards. Rubick keeps the three apart, because two of them are not answers.

Looked, and it was empty

most dashboards
0 Gateways
Rubick
0 Gateways

The list was read and came back empty. This is the only one of the three that means what the zero says.

Asked, and was refused

most dashboards
0 Gateways
Rubick
You do not have permission to list these

The cluster answered 403. Whose decision it was is the one fact that makes it actionable, so that is what the row says.

Never asked

most dashboards
0 Gateways
Rubick
Not looked at

Named, so a group that is absent is never read as a group that is empty. A gap on a page is a claim; this is not.

Try it yourself

Would you call this Running?

Start with the reported phase. Read the container states. Pick the status you expect inspection to leave standing.

Pod status fragment

1 of 6

reported phaseRunning

containerStatuses[0]

state
waiting
reason
CrashLoopBackOff
exitCode
not reported
signal
not reported
ready
false

After inspection

CrashLoopBackOff

containerStatuses[0].state.waiting.reason is CrashLoopBackOff; the first container verdict replaces the phase.

Six shapes the Rust and TypeScript evaluators must agree on, not proof of universal agreement with kubectl.

Also in the box

The boring parts, done properly.

Logs

Virtualised, multi-container, filtered server-side, repeats collapsed. They open where the answer is.

Shell

A real terminal tab per pod. The session survives you looking elsewhere.

Gateway API

Gateways, all five route kinds, classes, policies. A route that is not serving says which of the eight links between listener and pod broke.

Secrets

Binary values shown as binary. Private keys never revealed. Boring on purpose.

Custom resources

Every CRD in the cluster, with YAML editing and validation. Yours included.

Helm

Releases, revisions, rollback, uninstall. No opinions about how you got here.

Extensions

It knows what you installed.

Detected integrations need nothing from you. Their CRDs are in the cluster or they are not. Configured ones need an address, and Rubick never goes looking for one.

Detected integrations come from CRDs and node labels already in the cluster. Configured ones come from an address you typed in. Rubick never goes looking for an address on its own.
Traefik / ingress-nginx / Istio
hosts, rules and middleware read as routing, annotations turned into sentences with the raw key beside each
cert-manager
expiry wherever TLS is named, and the issuance chain when renewal fails
Argo CD / Flux
every object says whether it is delivered, from which revision, and whether your edit will survive
Prometheus
real history, disk fullness, network traffic
Loki
logs that outlive the pod that wrote them
GKE / EKS / AKS
node pools, machine types, spot status, read from labels with no cloud account

Adding one costs a folder and a line. See CONTRIBUTING.

Where it runs

Any cluster your kubeconfig can reach.

Your kubeconfig names the cluster and the credentials. Rubick reads it and signs in the way kubectl does, OIDC and exec plugins included. Settings, Diagnostics names the directories it searches for a plugin and which ones resolve, so a plugin your terminal finds and the app does not is a named problem, not a blank screen. Kubernetes 1.21+ is required.

GKE

How it signs in

gke-gcloud-auth-plugin

A GKE exec kubeconfig names this plugin to get a token. Rubick reads that configuration.

What Rubick reads there

The context name identifies the GKE flavour. Node labels name the pool and spot or preemptible capacity.

cloud.google.com/gke-nodepool
node pool
cloud.google.com/gke-spot
spot when true
cloud.google.com/gke-preemptible
preemptible when true
cloud.google.com/gke-provisioning
spot or preemptible

EKS

How it signs in

aws eks get-token

An EKS exec kubeconfig can name this command to get a token. Rubick uses the configured command and credentials.

What Rubick reads there

The context ARN or EKS name identifies the EKS flavour. Labels name managed node groups; Karpenter labels name the pool that made its nodes.

eks.amazonaws.com/nodegroup
node group
eks.amazonaws.com/capacityType
spot, compared without case
karpenter.sh/nodepool
Karpenter pool
karpenter.sh/provisioner-name
older Karpenter pool
karpenter.sh/capacity-type
spot when spot

AKS

How it signs in

kubelogin

An AKS exec kubeconfig can name kubelogin to get a token. The credentials come from your kubeconfig setup.

What Rubick reads there

The context name identifies the AKS flavour. Labels name the agent pool and spot capacity.

kubernetes.azure.com/agentpool
agent pool
kubernetes.azure.com/priority
spot when spot
kubernetes.azure.com/scalesetpriority
older spot label

k3s

How it signs in

Client certificate or token

Rubick uses the credentials in your kubeconfig, as kubectl does.

What Rubick reads there

A k3s word in the context name reads as K3S before cloud names are considered. This integration adds no provider-specific node label keys.

k3d

How it signs in

Client certificate or token

Rubick uses the credentials in your kubeconfig, as kubectl does.

What Rubick reads there

A context starting with k3d- reads as K3D, even if its name also says eks. This integration adds no provider-specific node label keys.

kind

How it signs in

Client certificate or token

Rubick uses the credentials in your kubeconfig, as kubectl does.

What Rubick reads there

Rubick reads Kubernetes objects through the configured context. There is no kind-specific flavour or node label reader in the integration registry.

minikube

How it signs in

Client certificate or token

Rubick uses the credentials in your kubeconfig, as kubectl does.

What Rubick reads there

A context name containing minikube reads as LOCAL before cloud names are considered. This integration adds no provider-specific node label keys.

Anything else with a kubeconfig

How it signs in

Token, certificate, OIDC or exec plugin

Your kubeconfig states how to authenticate. Rubick uses those credentials to reach the Kubernetes API.

What Rubick reads there

Kubernetes objects and recognised node labels, where present and permitted. An unrecognised label does not establish a cloud, pool or spot status.

Shared node labels, with no cloud account

Machine types, zones and regions come from these node labels when present. Pool and spot labels are read across providers, including Karpenter. Reading them needs access to the Kubernetes nodes, with no separate cloud account.

node.kubernetes.io/instance-type
machine type
beta.kubernetes.io/instance-type
older machine type
topology.kubernetes.io/zone
zone
failure-domain.beta.kubernetes.io/zone
older zone
topology.kubernetes.io/region
region
failure-domain.beta.kubernetes.io/region
older region

Rubick is developed every day against a GKE cluster; AWS and Azure are the least exercised paths. If something reads wrong on yours, that is worth an issue.

Social proof

What people are saying.

Nothing. Nobody has said anything yet.

We could have written some quotes here and put smiling faces next to them. It felt off-brand for a product whose entire personality is not lying to you.

The first thing this box will ever show is a report of a status we got wrong, and what we did about it.

Scope, defended

Features we are proud to not have.

License

Steal our code. Legally.

GPL-3.0-or-later. Fork it, change it, ship the source with it. Running it at home or across your whole company is not distribution and obliges you to nothing.

license
GPL-3.0-or-later
runtime
Tauri, Rust, kube-rs. React on top. No Electron.
account
none
telemetry
none

Install

Get it.

$brew install --cask Dudude-bit/tap/rubick

# signed with a Developer ID certificate, notarised by Apple. Opens on a double-click, like software should.

# or take the .dmg straight:

Rubick-setup.exeinstaller

The installer is not signed, so SmartScreen will warn you on first launch. More info, then Run anyway.

We would rather tell you that here than let the dialog surprise you.

# Arch: rubick-kubernetes-bin is on the AUR, maintained by @Prototik from the same .deb we publish. Thanks to him for it.

Every version, with signatures, lives on GitHub Releases.

It talks to your clusters, and to GitHub to check for updates. Nothing else. There is nothing else to talk to.