GitHub - tackish/pigeoneye: PigeonEye is a fast, native Kubernetes GUI + CLI Tool

GitHub

10 min read Original article ↗

PigeonEye — Observe. Navigate. Control.

A bird's-eye view of your clusters. Faster than anything.

English | 한국어

PigeonEye is a fast, native Kubernetes GUI that you drive like a CLI. Every resource type your cluster serves — including every CRD — shows up automatically, with the same columns kubectl get prints.

A quick look

The sidebar: pinned kinds, every CRD, a tab per cluster, and split view

Every resource your cluster serves, in one sidebar. Built-in kinds are grouped the way you think about them, and every CRD follows on its own — 138 of them here, with nothing to configure. Pin the kinds you live in with p and gather them into groups of your own. Each cluster gets a tab, like a browser, and ⌘\ splits the window so two clusters sit side by side.

The pod list: search over every field, a shareable link to the view, and configurable columns

A list that keeps up. Search matches any field of every object, not just the name. Columns are yours to show, hide, reorder or add — CPU and memory against requests and limits included. share view copies a peye:// link that opens the same cluster, kind, namespace and row for whoever clicks it.

The detail panel: logs, shell, debug and port-forward, related objects, and an editable manifest

Everything about one object, without leaving the list. Logs, shell, a debug container and port-forward are one click away — and l follows the logs of a whole Deployment, one pod, or a single container. Related objects link straight through, and the manifest edits in place: dry-run shows what the server would change before you Apply.

A GUI you drive like a CLI

The screen of a GUI, the hands of a CLI. Point and click when you want to — but everything you do day to day has a key: moving around, searching, opening logs and shells, editing, scaling, deleting, switching clusters, splitting the view. The keys are the ones your fingers already know from vim, k9s and kubectl.

Key Does
: Go anywhere by typing — : deploy kube-system, : pod app=api, : pod @prod
/ Search every field of every row
j k · ↵ Move · open the detail
l · s · e Logs · shell · edit the manifest
d · r · space Delete · rollout restart · mark rows for a bulk action
tab · ⌘\ Next cluster · split into two views
? Every shortcut, on one sheet

Why it's fast

Measured on a production cluster with 23,770 pods and 171,267 events:

PigeonEye Full-object list (what informer clients sync on connect)
First pod screen ~0.35s / 350 KB 60s+ and 136 MB, still incomplete
Discovery on connect 0.18s, one request one round-trip per API group

The table renders only the rows on screen (virtual scroll), so filtering a 24k-row list stays at ~0.5ms per keystroke. Open lists then stay live over a watch, and revisiting a view resumes the watch from cache — no re-list — so only what changed since arrives.

Staying live is cheap too. A pod changing on that 24k-row list lands in ~0.2ms: the event is matched against the rows it names rather than re-indexing the whole list, and however many pods a rollout retires at once, they leave in a single pass.

Where the speed comes from: server-side printer columns (the Table API, one request per view — no full-object sync), streaming pagination, virtual scrolling, a lazily-built full-text index, watch-based incremental updates coalesced into batches, and a view cache that resumes the watch on return.

Highlights

  • Every resource, zero config — kinds come from the Discovery API, so every CRD shows up automatically with the columns kubectl get prints.
  • Columns your way — hide, drag-reorder, and add custom columns from any field or label (kubectl -o custom-columns, but click instead of type). Pin frequently-used kinds into your own groups — and organize the clusters themselves into named context groups too.
  • Search that finds things — full-text over every field, plus regex, !negation, per-column value filters, and numeric comparisons (> 500).
  • Search every open cluster at once — : pod api-server asks all of them and says which cluster each answer came from; picking one switches tab, kind and namespace and lands the cursor on the row. Costs nothing until you ask, and the clusters you have browsed answer from memory.
  • Split into two live views — ⌘\ puts two fully independent panes side by side: different clusters, kinds and namespaces, each with its own watch, search, sort and detail panel. ⌘← / ⌘→ moves focus; every shortcut acts on the focused pane.
  • Cross-cluster Issues — one ⚠ list of pods in a bad state and nodes that aren't Ready, gathered from every connected cluster and refreshed in the background. ↵ jumps straight to the resource in its own tab. The list is windowed, so it opens as fast with four thousand problems as with four — which matters, because it is longest exactly when something is on fire.
  • Rollout at a glance — Deployments, StatefulSets, DaemonSets, ReplicaSets and Argo Rollouts carry a dot while replicas are still spreading, so a half-finished rollout is visible without opening it. Open one and it says where the rollout actually is — Rolling out, Applying (the spec changed, the controller hasn't acted on it yet), Not fully available, Paused, Stuck or Synced — with the replica and generation numbers behind the verdict, refreshing itself until it settles.
  • Why a pod isn't up, in the list — the status column says Pending; the Recent event column next to it says FailedScheduling ×3 · 45s, and hovering it gives you 0/50 nodes are available: insufficient cpu. Only warnings appear there, so an empty cell means nothing is wrong. One older than five minutes is dimmed rather than dropped: the pod may be fine now, but that warning is often why it restarted.
  • Share the exact view — copy a peye:// link to the view, or to a single resource. Whoever opens it lands on the same cluster, kind, namespace and row.
  • A real log viewer — follow, previous (crashed) container, since windows, timestamps, in-view search, copy/download, and combined workload logs.
  • Shells & debugging — pod exec, a transparent privileged node shell, and ephemeral debug containers that mirror the pod's security context (pass restricted PodSecurity / Kyverno).
  • Safe edits & operating — YAML server-side apply with dry-run, create-from-template, scale, rollout restart / history / undo, drain, CronJob trigger, secret reveal, port-forward manager, live top metrics, and auth can-i.
  • Logins that can ask you things — an expired token re-logs in from a terminal inside the app (AWS SSO, gcloud, Teleport, Azure, OIDC), so a CLI that wants an OTP or a passphrase has somewhere to ask. A kubeconfig exec block holds one command and has no "run this first" hook, so each context can also carry a pre-connect command of its own — tsh login, a bastion tunnel, a VPN — run before connecting and kept on your machine rather than in a shared kubeconfig. A cluster whose credentials have genuinely expired is parked out of background polling until you sign in again, so a credential helper that opens a browser (aws-vault, oidc-login) is never re-run on a timer behind your back.
  • Color-coded multi-cluster tabs, light/dark, and in-app updates — the top bar flags a new release and upgrades in place, no brew needed.

vs. k9s & Lens

PigeonEye k9s Lens/OpenLens
Native GUI ✅ terminal Electron
Every daily action from the keyboard ✅ ✅ mouse-first
Cold-start on a 24k-pod cluster ~0.35s first screen fast slow (full sync)
All CRDs / aggregated APIs, no config ✅ ✅ ✅
Regex / negation / numeric / per-column filters ✅ regex/! basic
Logs: previous · since · search · download ✅ ✅ ✅ (Lens only)
Combined workload/Service logs ✅ ✅ partial
Ephemeral debug containers (policy-safe) ✅ — —
Node shell (nsenter) ✅ opt-in ✅
Secret decode / reveal ✅ ✅ ✅
Server dry-run before apply ✅ — —
Create from templates ✅ blank YAML ✅
Rollout history / undo ✅ ✅ ✅
CronJob trigger / suspend ✅ ✅ —
Node & pod metrics columns (metrics-server) ✅ ✅ needs Prometheus
auth can-i / permissions ✅ reverse-lookup RBAC views
Search by name across every open cluster ✅ — —
Two independent panes side by side ✅ — —
Problem resources across every cluster in one list ✅ — —
Deep link to an exact view / resource ✅ — —
Auth re-login, including CLIs that prompt (OTP) ✅ — —
Per-context pre-connect command (tunnel / tsh login) ✅ — —
Time-series metric charts — — ✅ (Prometheus)
Helm / extensions / xray tree / linters — xray/popeye/plugins Helm/extensions

Metric graphs, Helm, an owner xray tree, and cluster linters are the main things PigeonEye doesn't (yet) do; navigation between owners/children is one click via related-object jumps.

Platform support

Platform Status
macOS (Apple Silicon / Intel) ✅ Supported
Linux (x86_64) ✅ Supported
Windows (x86_64) ✅ Supported

Install

macOS — Homebrew

brew tap tackish/pigeoneye
brew trust tackish/pigeoneye   # third-party taps need a one-time trust
brew install --cask peye

Then launch it from anywhere — Spotlight, or just type peye in a terminal:

Windows — download the -setup.exe installer from Releases (an .msi is there too, for deploying by policy). The installer isn't code-signed yet, so SmartScreen will ask: More info → Run anyway.

Linux — download the .deb / .rpm / .AppImage from Releases.

Build from source (Rust stable + Node 20+):

npm install
npm run tauri build   # installers in src-tauri/target/release/bundle/
npm run tauri dev     # or run directly

Getting started

  1. Launch. PigeonEye reads your default kubeconfig chain ($KUBECONFIG, falling back to ~/.kube/config). No other setup.

  2. Connect a cluster with the “+ add context” dropdown in the top bar. Each cluster opens as a tab — open as many as you want; requests to different clusters run in parallel. Tabs and the last active cluster are restored on next launch.

  3. More kubeconfig files? Open ⚙ settings and add file paths (e.g. ~/.kube/staging-config). All files are merged into the context list, and each context remembers which file it came from.

  4. Cluster needs something first? Some clusters are only reachable after a login or a tunnel that the kubeconfig cannot express. Hit ⋯ next to a context in either picker and give it a command — it runs in a terminal before the connection, so anything it asks you, you can answer:

    tsh status >/dev/null 2>&1 || tsh login --proxy=teleport.example.com:443
    
  5. Cluster behind a bastion or a corporate proxy? PigeonEye honours a cluster's proxy-url: — SOCKS5 or HTTP CONNECT — and HTTPS_PROXY, so a cluster you can only reach through a jump host connects like any other. It reads the same line kubectl does, so the two never drift, and it is per-cluster: everything else keeps going out directly.

    - cluster:
        server: https://XXXX.gr7.ap-northeast-2.eks.amazonaws.com
        proxy-url: socks5://127.0.0.1:1080   # e.g. an `ssh -D 1080` bastion

    The proxy resolves the name, so a private endpoint that doesn't resolve on your laptop still works.

License

PolyForm Noncommercial 1.0.0 — free for personal and any noncommercial use. Commercial use is not permitted.