Skip to content

Repository files navigation

browser-k8s

Run a real Kubernetes control plane entirely inside a browser tab — with no server behind it.

This is not a web terminal connected to a remote Kubernetes cluster. There is no backend API, no remote node, and nothing your kubectl commands talk to over the network. The only thing served is static files (GitHub Pages on the live demo). The whole cluster runs client-side in an emulated x86 VM in your tab — reload the page and it's gone.

Nginx Demo

▶ Try it live: https://imaustink.github.io/browser-k8s/

browser-k8s is a training tool: a self-contained, zero-setup sandbox for learning Kubernetes and practicing kubectl. A user types kubectl into an in-page terminal, and those commands run inside an x86 Linux VM emulated by v86, talking to a real kube-apiserver that runs in the tab — not a remote server — backed by SQLite (via kine) instead of etcd. A real kubelet + containerd run inside the VM, so pods actually execute containers on a genuine node. Nothing is provisioned server-side and no request ever leaves the browser, so learners get a throwaway cluster they can break and reset freely.

There is no server-side infrastructure of any kind. Hosting the app is just static file serving — the live demo is plain GitHub Pages, and locally it's a tiny static server that only adds the COOP/COEP headers v86 needs. The whole cluster — control plane and a real container-running node — boots unattended inside a single browser tab. See plan.md for the original design and docs/how-it-works.md for the architecture.

Repository layout

Path What it is
index.html, src/ The web frontend: v86 emulator + xterm.js terminal
server/ Zero-dep static server that sends the required COOP/COEP headers
scripts/ setup-assets.mjs copies v86 runtime files into public/v86/
build/ Pipeline that builds the Alpine + Kubernetes guest image
public/images/ Where guest disk images / snapshots are served from

Quick start (frontend)

npm install            # also copies v86 assets into public/v86/
cp .env.example .env.local   # then point VITE_IMAGE_URL at a guest image
npm run dev            # http://localhost:5173

npm run dev and npm start both serve with the cross-origin isolation headers v86 needs for SharedArrayBuffer:

Cross-Origin-Opener-Policy: same-origin
Cross-Origin-Embedder-Policy: require-corp

You need a guest image in public/images/. To verify the emulator + terminal plumbing, drop in any small v86-compatible Linux ISO as linux.iso (see public/images/README.md). For the actual Kubernetes node image, run the build pipeline.

Production

npm run build          # typecheck + bundle into dist/
npm start              # serves dist/ on :8080 with COOP/COEP headers

Building the Kubernetes node image

The full pipeline (binaries → Alpine rootfs → bootable image) lives in build/:

cd build && ./build-image.sh

Then capture a boot snapshot via the toolbar's Save snapshot button so the page loads in seconds instead of cold-booting. Details and the two boot modes (9p filesystem vs. raw disk) are documented in build/README.md.

Toolbar

  • Save snapshot — capture the running VM state as preboot.bin (used for fast boot; serve it and set VITE_INITIAL_STATE=/preboot.bin).
  • Restore — load a previously saved snapshot.
  • Reset — reboot the VM.

Quick test

Once the node is Ready, run web-demo in the VM's terminal for a one-command smoke test. It applies a baked-in nginx Deployment (the web-deployment.yaml manifest at /etc/kubernetes/demo/), waits for the Pod, and prints where it landed:

web-demo

Equivalent to applying docker.io/browser-k8s/nginx:386 (imagePullPolicy: Never, so it runs fully offline), then kubectl get pods -l app=web -o wide. Hit the Pod with wget -qO- http://<pod-ip>/.

What works / what doesn't

Works: kubectl apply/get/describe/delete/explain, namespaces, RBAC, ConfigMaps/Secrets/Services (stored), Deployments/ReplicaSets/Jobs (real Pods run on the node), scale, rollout, and kubectl logs/exec against the real kubelet.

Doesn't: kubectl port-forward. Resource limits are stored and visible; CPU CFS quota is disabled (the emulated kernel lacks CONFIG_CFS_BANDWIDTH).

Limitations

The main constraint is memory. The whole cluster runs in the RAM v86 allocates to the emulated machine, which lives inside a single browser tab. The control plane, kubelet, containerd, and every Pod all share that budget, so only a handful of small workloads fit before the VM runs out of memory. This makes browser-k8s great for learning and experimentation, but not for running large or memory-hungry workloads.

It also only supports 32-bit 386 container images. The emulated CPU is i686, so every binary and workload must be built for GOARCH=386/i386; standard amd64 images won't run on the node.

There is also no container registry. Nothing in the tab can reach the network, so the node cannot pull images. Every image is built ahead of time, packaged into the VM, and imported into containerd at boot. Pods must therefore set imagePullPolicy: Never and reference an image that was baked in — anything else fails with ErrImageNeverPull.

Adding an image to the VM

Images are built as linux/386 docker-archive tarballs, copied into the guest image, and imported into containerd on boot. To add a new one:

  1. Build and save the tarball. Add a step to build/v86-image/build-images.sh that builds (or pulls) your image for linux/386 and runs docker save <ref> -o images/<name>-386.tar. Keep it small — it competes for the VM's limited RAM and disk.

  2. Copy it into the rootfs. Add a COPY images/<name>-386.tar /var/lib/browser-k8s-images/<name>-386.tar line to build/v86-image/Dockerfile, and include <name>-386.tar in the tarball loop in build/v86-image/build-v86-image.sh. The boot script imports every *.tar under /var/lib/browser-k8s-images/ automatically.

  3. Normalise the ref (if needed). If containerd keeps a short image name, add a ctr -n k8s.io images tag <short> docker.io/<ref> 2>/dev/null || true line next to the others in build/guest-init.sh so Pods can reference the fully-qualified docker.io/... name.

  4. Rebuild the guest image: cd build/v86-image && ./build-v86-image.sh, then reload the browser tab.

  5. Use it in a Pod with imagePullPolicy: Never:

    image: docker.io/<ref>
    imagePullPolicy: Never

About

Real Kubernetes in the browser

Resources

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages