Two clusters in one window, with no kubectl config use-context in between. That is what the Aptakube Kubernetes GUI sells, and pointed at a pair of live k3s clusters it turns out to be literally true: one pod list, both clusters, a Context column on the far left.
This guide installs it from the deb, the rpm and the AppImage, wires two clusters into one kubeconfig, walks the multi-cluster views worth the trial fortnight, and measures what the app actually costs in disk and RAM next to K8Studio and Freelens on the same box. Built and run in September 2026 on Ubuntu 26.04.1, Debian 13 and Fedora 44 against k3s v1.36.4+k3s1, with Aptakube 1.20.5.
Install the Aptakube Kubernetes GUI on Ubuntu and Debian
There is no apt repository. Everything ships as a GitHub release asset, so detect the tag first and feed it into the download. The tag carries no leading v, which matters if you reuse a version-detection snippet from another project.
export APTA_VER=$(curl -sL https://api.github.com/repos/aptakube/aptakube/releases/latest \
| grep '"tag_name"' | head -1 | sed 's/.*"tag_name": *"\([^"]*\)".*/\1/')
echo "$APTA_VER"
A clean version string comes back, no leading letter and no stray newline:
1.20.5
Pull the package and hand it to apt so dependency resolution happens properly instead of via dpkg -i:
curl -sfLO "https://github.com/aptakube/aptakube/releases/download/${APTA_VER}/aptakube_${APTA_VER}_amd64.deb"
sudo apt install -y ./aptakube_${APTA_VER}_amd64.deb
On a minimal Ubuntu cloud image the transaction is larger than the 17 MB package suggests, because the app links the system WebKitGTK rather than bundling a browser engine:
Summary:
Upgrading: 0, Installing: 175, Removing: 0, Not Upgrading: 3
Download size: 0 B / 124 MB
Space needed: 471 MB / 36.8 GB available
The control file asks for exactly two things, libwebkit2gtk-4.1-0 and libgtk-3-0. Everything else in that 175 is the transitive desktop stack, counted from a cloud image with no desktop on it. Every package apt lists that your session already has installed drops straight out of the transaction, so the number you see on a real workstation will be far lower.
Verify what landed before launching anything. The binary is the whole application:

Debian 13 takes the identical command and its apt is new enough to print the same Summary: block. The bill is bigger, because the Debian cloud image starts from less:
Summary:
Upgrading: 0, Installing: 387, Removing: 0, Not Upgrading: 0
Download size: 294 MB / 311 MB
Space needed: 1143 MB / 17.7 GB available
Same package, same two declared dependencies, 212 more transitive packages. Nothing about the app changed; the base image did.
Install on Fedora
Same detection, different asset name. Note the underscore before the version and the dot before the architecture, which is not the pattern the deb uses:
export APTA_VER=$(curl -sL https://api.github.com/repos/aptakube/aptakube/releases/latest \
| grep '"tag_name"' | head -1 | sed 's/.*"tag_name": *"\([^"]*\)".*/\1/')
curl -sfLO "https://github.com/aptakube/aptakube/releases/download/${APTA_VER}/aptakube_${APTA_VER}.x86_64.rpm"
sudo dnf install -y ./aptakube_${APTA_VER}.x86_64.rpm
On a Fedora Cloud image, which ships no desktop libraries at all, the bill is much bigger than on Ubuntu:
Transaction Summary:
Installing: 280 packages
Total size of inbound packages is 262 MiB. Need to download 245 MiB.
After this operation, 962 MiB extra will be used (install 962 MiB, remove 0 B).
That gap is the base image, not the app. Fedora Cloud starts with nothing GUI-shaped, so GTK, GStreamer, PipeWire and the font stack all arrive in one go. Run the same rpm on a Workstation install and dnf skips whatever the desktop already put there.
The rpm is honest about its licence in a way its competitors are not. rpm -qip reports License: UNLICENSED, which is correct for a closed-source commercial product, and the deb declares no licence at all. Running dpkg -I over all three packages on the same box, both Electron clients carry License: MIT in their control metadata. For Freelens that is accurate, because the project is MIT-licensed open source with its full source tree published. For K8Studio, whose repository carries a readme, screenshots and a demo video but no source and no licence file, it is the electron-builder default rather than a statement about the code.
SELinux needs no attention. With enforcing mode on, a full session produced zero AVC denials under ausearch -m avc -ts recent.
Run the AppImage instead
For anything that is not Debian or RPM based, or for a throwaway try on a machine you do not want to modify, the AppImage runs from your home directory:
curl -sfLO "https://github.com/aptakube/aptakube/releases/download/${APTA_VER}/aptakube_${APTA_VER}_amd64.AppImage"
chmod +x aptakube_${APTA_VER}_amd64.AppImage
./aptakube_${APTA_VER}_amd64.AppImage
It needs FUSE and nothing else, and the FUSE it needs is already there. Most AppImage instructions tell you to install a libfuse2 shim first, which has not been the default on Debian or Ubuntu for two releases. This build ships the newer type 2 runtime, which links FUSE statically, so nothing from the distribution has to match. Confirmed both ways: readelf -d on the extracted runtime lists no shared library dependencies at all, and --appimage-mount succeeded unchanged on a stock Ubuntu cloud image and again on Debian trixie, neither of which had a libfuse2 package installed. The kernel FUSE module and /dev/fuse are all it wants. The tradeoff is size: the AppImage is 88 MB against the deb’s 17 MB, because it carries the WebKit runtime the packaged builds borrow from the distribution. Judge portability against that five-fold difference.
macOS and Windows are packaged too. The Homebrew cask tracks the same release train, so brew install --cask aptakube gets you the current build, and the release page carries a universal .dmg plus an .msi for Windows. Everything measured in this guide was run on Linux.
Connect two clusters at once
Aptakube reads ~/.kube/config and nothing else needs configuring. If your clusters live in separate files, merge them into one first, giving each context a name you will recognise in a shared table. Back the existing file up and build the merged copy somewhere else, because the shell truncates a redirect target before kubectl ever reads it:
mkdir -p ~/.kube
cp ~/.kube/config ~/.kube/config.bak 2>/dev/null
KUBECONFIG=/tmp/prod.yaml:/tmp/staging.yaml kubectl config view --flatten > /tmp/merged-kubeconfig.yaml
mv /tmp/merged-kubeconfig.yaml ~/.kube/config
chmod 600 ~/.kube/config
kubectl config get-contexts
Never redirect the output into a file that is itself listed in KUBECONFIG. That is the natural shortcut when one of the clusters already lives in ~/.kube/config, and it empties the file before the merge reads it, taking every other context and any embedded client certificate with it. The kubeconfig merge rules explain which entry wins when two files define the same name.
Both contexts should be listed with the current one starred:
CURRENT NAME CLUSTER AUTHINFO NAMESPACE
* prod-cluster prod-cluster prod-cluster
staging-cluster staging-cluster staging-cluster
Launch the app. The first screen is a context picker, and the instruction on it is the part that separates this client from the rest of the field: select one or more contexts. Tick both, press Connect.

Connection is instant and leaves no trace on either cluster. A kubectl get all,sa,clusterrole,crd -A sweep on both clusters after a full session returns zero objects matching the app’s name, and ~/.kube/config keeps its original mtime because the client only reads it. Nothing is written to /tmp either, which is worth knowing if you have used a client that leaves a copy of your kubeconfig lying around on a shared jump host. The vendor’s security notes claim read-only access to the kubeconfig and in-memory storage of everything fetched, and both held up on the test box.
What changes with two contexts live
The window title switches to a count rather than a cluster name, and so does the sidebar. Every list from that point on is a union across both clusters with the context as the first column.

Two details in that overview are easy to miss. The counters are additive across clusters, so 16 running pods and 1 ImagePullBackOff is the combined state, not one cluster’s. And the Recent Restarts panel prefixes every entry with context / namespace, so a crash loop in staging and a failed init job in prod sit in the same list without you having to work out which is which.
The namespace filter defaults to default, which is why a fresh connection often looks empty. Open it and the list is the union of namespaces from both clusters, so a namespace that exists only in staging appears next to one that exists only in prod. Switch it to All namespaces and the pod list fills up.

CPU and memory bars come from metrics-server, which k3s ships by default. No Prometheus, no extra scrape config, no cluster-side install. That is a real difference from the Lens lineage, where the cluster overview greets you with “Metrics are not available due to missing or invalid Prometheus configuration” until you point it at a Prometheus.
Nodes behave the same way. Three nodes across two clusters in one table, with kubelet version, OS image and kernel side by side, which is how you notice that staging is a release behind or running a different distribution.

One thing to be careful about: row selection also spans clusters. Select a Deployment in each cluster and the context menu offers Compare, Scale, Restart and Delete against both at once. Restarting the same workload in prod and staging with one keystroke is genuinely useful and also exactly the kind of action you do not want to fire by accident, so check the selection counter in the toolbar before pressing R.
Stream logs from every replica in one pane
Open a Deployment, then the Logs tab. It attaches to every pod behind the Deployment and merges the streams in timestamp order, colour coding each pod by the tail of its name.

The header reads Connected 3/3 and a line count, so you know whether a stream dropped. The equivalent with the CLI is kubectl logs deploy/<name> --all-pods --prefix -f, which works but gives you no per-pod colour, no scrollback search box and no way to widen the window after the fact.
The default window is five minutes, and that trips people up on a quiet service. A Deployment whose only log lines were written at startup shows “Waiting for logs… No logs found with current settings” even though the pods are healthy and kubectl logs returns those startup lines happily. Widen the range before assuming the viewer is broken.
Read Helm releases with no helm binary
This is the surprise of the whole test. The workstation has never had Helm installed. which helm returns nothing and a filesystem search finds no binary. The Helm view still lists every release in both clusters.

It is reading the release secrets out of the API directly. Open a release and the breadcrumb gives the game away: the object it loaded is sh.helm.release.v1.<name>.v1, the Secret that Helm itself uses as its storage backend. Values, rendered manifest, history and notes all come from there.
Confirm it by watching the process tree while the view loads. Aptakube runs three processes and forks nothing:
ps -eo pid,ppid,rss,comm,args | grep -i aptakube | grep -v grep
Only the main binary and the two WebKit helpers it owns, with no subprocess appearing while the Helm view renders:
36585 1 275040 aptakube aptakube
36767 36585 70408 WebKitNetworkPr /usr/lib/x86_64-linux-gnu/webkit2gtk-4.1/WebKitNetworkProcess 1 23
36794 36585 556864 WebKitWebProces /usr/lib/x86_64-linux-gnu/webkit2gtk-4.1/WebKitWebProcess 4 27
That matters on a locked-down workstation and it matters for correctness. A client that shells out to helm inherits whatever version, plugins and repository cache that binary happens to have. Reading the release Secret cannot drift. The K8Studio Kubernetes IDE takes the other approach and forks the local helm binary for its Helm view, as measured in that guide, so its releases list depends on a CLI being present and current.
Diff the same resource across clusters
Select the matching object in each cluster and pick Compare. You get a side-by-side YAML view with two toggles that make it usable: one to include metadata and status, one to collapse everything that is identical.

Leave metadata off for the question you usually care about, which is whether the spec drifted. Turn it on when you want to see resourceVersion, uid and creation timestamps, which will always differ and will always be noise unless you are chasing a specific incident.
Doing this with the CLI means two kubectl get -o yaml calls against two contexts, two temporary files and a diff. Worth knowing how, and covered in the kubectl cheat sheet, but four steps against one.
Where kubectl is still required
Browsing, editing YAML, scaling, restarting and reading Helm all work with no CLI tools on the machine. Shell access does not. Open Preferences and the Terminal page is explicit about it.

The in-app terminal is marked Coming Soon. Exec into a container and the app opens your system terminal and drives kubectl there. The Debug field on the same page sets the ephemeral container image used for kubectl debug against pods and nodes, defaulting to busybox:latest. So keep kubectl installed if you shell into containers, and treat the client as a reader and editor rather than a full replacement.
Custom resources need nothing extra. Gateway API CRDs installed on the prod cluster were picked up on their own and every kind is addressable by name from the command palette.

Price, trial, and what the trial writes to disk
There is no free tier. The trial runs fifteen days, after which you need a key. Prices below come from the vendor’s pricing page, checked the same week this was written.
| Plan | Monthly | Billed yearly | Devices |
|---|---|---|---|
| Personal | $9 per month | $79 per year ($7 per month) | 1 user, 2 devices |
| Team | $7 per seat per month | $59 per seat per year ($5 per month) | 1 device per user |
Team licences get a single key for everyone plus priority support, and SSO unlocks at twenty seats. Checkout runs through Paddle and the refund window is fourteen days. The vendor is Zandar Labs SL and publishes an ISO 27001 certificate, which is more compliance paperwork than most desktop tools in this category bother with.
The trial state is a single file. The whole local footprint of a running install is one directory holding one 238-byte artefact:
find ~/.config/com.aptakube.Aptakube -type f
head -c 40 ~/.config/com.aptakube.Aptakube/trial.bin
Despite the .bin extension it is a signed token, an HS512 JWT whose payload opens with an expiry date:
/home/jmutai/.config/com.aptakube.Aptakube/trial.bin
eyJhbGciOiJIUzUxMiJ9.eyJleHBpcnlfZGF0ZSI6IjIwMjY
No cache directory is ever created, and cluster data is held in memory only, so closing the window discards everything it read.
Footprint against K8Studio and Freelens
“Lightweight” appears in the package description, so it is worth checking. All three clients below were measured on the same pristine Ubuntu 26.04 cloud image, with package counts and disk taken from apt on an identical starting state.
| Measured on Ubuntu 26.04 | Aptakube 1.20.5 | K8Studio 4.0.2 | Freelens 1.10.3 |
|---|---|---|---|
| Engine | Tauri (system WebKitGTK) | Electron | Electron |
| Package download | 17.6 MB | 102 MB | 154 MB |
| App files on disk | 30 MiB | 318 MiB | 613 MiB |
| Distro packages pulled | 174 | 167 | 171 |
| Total disk after install | 471 MB | 647 MB | 960 MB |
| Processes when running | 3 | 6 | 8 |
| Memory, PSS | 638 MiB | 530 MiB | 670 MiB |
| Memory, RSS | 847 MiB | 893 MiB | 1039 MiB |
Read that table carefully, because it does not say what the marketing says. On disk the claim holds and holds big: the application itself is an order of magnitude smaller than either Electron client, and the total install is 176 MB lighter than K8Studio and 489 MB lighter than Freelens. The dependency tail is the same size for all three, roughly 170 packages, because every one of them needs the desktop stack.
In RAM it does not hold, at least on the honest measure. Aptakube has the lowest RSS of the three, but RSS double counts pages that Electron’s many processes share, so the shared-adjusted number is the one to read, and on that number Aptakube is not the lightest. Memory figures were taken with both clusters connected and a pod list open, summing Pss across every process from /proc/<pid>/smaps_rollup, three minutes after connect. Aptakube sat above K8Studio on shared-adjusted memory and below Freelens. Borrowing the system WebKit saves disk, not working set, and the window still ends up rendering the same web application.
One caveat runs in Aptakube’s favour: its pod list spanned every namespace in both clusters at the time of measurement, while the two Electron clients show one cluster at a time and were sitting on far less data. One runs against it: a forty-five minute session of heavy navigation pushed the same install to 950 MiB PSS, so budget for growth rather than the fresh number if you leave it open all day.
Watch connections too. With the overview live, 31 established TCP sessions were open to the two API servers, roughly one per watched resource type, dropping to a handful once a single list view had focus. Fine on a LAN, worth a thought over a VPN with a connection-tracking firewall in the path.
Rough edges
Four things came up often enough on the test boxes to be worth naming, with the exact strings you will find in a log or a search box.
Error: “Failed to run OS command `update-desktop-database`: No such file or directory (os error 2)”
Printed at every startup on both Ubuntu and Fedora minimal installs. The deep-link plugin wants update-desktop-database, which lives in desktop-file-utils, and neither the deb nor the rpm declares it as a dependency. The app runs fine; only the desktop MIME association is skipped.
Installing that package alone does not clear it. The error simply moves one step down the chain to Failed to run OS command `xdg-mime`: No such file or directory, which needs xdg-utils. Install both and the startup log comes back clean. On Debian and Ubuntu:
sudo apt install -y desktop-file-utils xdg-utils
The package names are identical on Fedora:
sudo dnf install -y desktop-file-utils xdg-utils
Neither is needed for the app to work. This is cosmetic unless you are shipping the client onto minimal images and want quiet logs.
Warning: “DRI3 error: Could not get DRI3 device”
Appears as a libEGL warning on Ubuntu and a MESA-EGL warning on Fedora when there is no accelerated X server, which is the normal case on a virtual machine or a remote session. Rendering falls back to software and the interface is fully usable. No flag is needed to work around it.
The command palette does not find objects by name
Ctrl+K searches resource kinds and navigation targets, not the objects in your cluster. Typing the name of a Deployment that is plainly visible two clicks away returns “No results found”, while typing part of a kind name brings up that list. Useful once you know the rule, confusing for the first ten minutes.
There is no version flag
There is no command line surface at all, and asking for one is worse than useless. Both --version and -V print nothing and abort on SIGABRT with a core dump, exit status 134, while --help prints nothing and exits zero. Read the version from the bottom-right corner of the context picker, or ask the package manager with dpkg-query -W aptakube or rpm -q aptakube.
Remove it
Uninstall is cleaner than most desktop packages manage. Purge the package, then let apt take back the dependency tree it brought in:
sudo apt purge -y aptakube
sudo apt autoremove -y
rm -rf ~/.config/com.aptakube.Aptakube
The purge frees the binary, then autoremove hands back the dependency tree in one sweep:
Summary:
Upgrading: 0, Installing: 0, Removing: 1, Not Upgrading: 3
Freed space: 31.5 MB
Summary:
Upgrading: 0, Installing: 0, Removing: 175, Not Upgrading: 3
Freed space: 440 MB
That is 471.5 MB recovered against the 471 MB that went in, so the transaction comes back and then some. The extra package is an orphan the base image was already carrying, which autoremove sweeps along with the rest. Package managers frequently do not manage that. The rm -rf on the config directory stays a manual step regardless, because apt never owns files under your home directory.
Fedora does not come back as cleanly. dnf remove pulls the orphans in the same pass rather than needing a second command, and the run reported “Removing: 173 packages” for 596 MiB freed, with dnf autoremove finding one straggler afterwards. Against an install that pulled 280 packages and 962 MiB, that leaves roughly a hundred packages and a third of the space in place, mostly shared GTK and font libraries dnf will not judge unused. Run both commands anyway:
sudo dnf remove -y aptakube
sudo dnf autoremove -y
rm -rf ~/.config/com.aptakube.Aptakube
The config directory under your home survives on both families, because neither package manager owns files there.
Keyboard shortcuts worth learning
Almost everything in a resource list has a single-key binding, which is where the speed actually comes from once the novelty of the multi-cluster table wears off. Press Ctrl+/ to bring up the full sheet.

These are the ones that replace a shell command outright:
| Keys | Action | What it saves |
|---|---|---|
| Ctrl+K | Command palette | Jump to any resource kind |
| Ctrl+N | Namespace selector | The -n flag |
| Ctrl+F | Filter the list | grep on a wide table |
| L | View logs | kubectl logs |
| R | Restart | kubectl rollout restart |
| S | Scale | kubectl scale |
| E | View YAML | kubectl get -o yaml |
| Ctrl+E then Ctrl+S | Edit and apply YAML | kubectl edit |
| Ctrl+A then Ctrl+C | Copy selected rows as CSV | A custom -o custom-columns |
| Shift plus arrows | Select multiple rows | Scripting a loop |
| F1 to F9 | Open a saved favourite | Re-navigating to the same view |
Star the three or four views you open every morning and the function keys become your real navigation. If a paid desktop client is not where you want to spend the budget, Headlamp and k9s in the terminal cover a lot of the same ground for nothing, and the wider field is laid out in our roundup of Kubernetes UI management tools. What none of them do is put two clusters in one table, and after a fortnight of driving it that is the feature you would actually miss.