One Helm chart turns a Kubernetes cluster into a fleet of AI-native cloud dev workspaces.
KubeCoder.com Β· Docs & public demo Β· Benchmark Β· r/kubecoder
Give every developer β or every AI agent β a real computer in the cloud, and make spinning one up a two-minute, self-service action.
Each kube-coder workspace is an isolated pod with a persistent home directory and everything a developer needs, reachable from any browser behind your own GitHub login:
- VS Code in the browser (
code-server) - a persistent tmux terminal that keeps running when you close the tab
- an in-pod Chrome you can watch over VNC (for previewing web apps, or letting an agent drive a browser)
- a dashboard that ties it together β plus Chat that operates the pod for you and an AI CTO that keeps track of your projects
- pluggable coding agents β Claude Code, Codex, Gemini, Ante, OpenCode β that you can spawn, message, and fan out in parallel
Because it's just Kubernetes underneath, a single chart deploys as many of these as your cluster can hold β each with its own namespace, ingress, TLS certificate, persistent volume, and OAuth allowlist. Onboarding a new teammate is a form in the admin console; their workspace resolves and issues its own TLS on first request.
The short version: it's a self-hosted, multi-tenant, AI-first replacement for "here's a laptop, spend two days setting it up." The environment survives restarts, an in-flight agent keeps working after you disconnect, and you can drive the whole thing from your phone.
The agent doesn't run on your machine. It runs on a pod in a cluster you operate β so the repo, the .env, the build cache and the model API calls all stay inside your perimeter. Close the lid mid-build and it keeps going.
kube-coder isn't a demo β the agents inside it solve real problems, and there's a public benchmark to show it.
kubecoder-bench runs coding agents against the Aider polyglot (Exercism) suite, scored against hidden tests β a pass means the agent genuinely solved the task, not that it memorized the answer. Every task runs in its own isolated kube-coder workspace, fanned out across parallel workers.
| Backend | Python split (34 tasks) | Notes |
|---|---|---|
| Oracle (reference solutions) | 34 / 34 | validates the harness end-to-end |
| Claude Code | 27 / 34 β 79.4% | single attempt, no retries; ~5.3Γ faster via parallel workspaces |
| Ante (DeepSeek/OpenRouter) | runnable β | agent-agnostic β swap one flag |
The 79.4% is a conservative floor: public leaderboards allow multiple attempts, this run allows one. The point isn't a headline number β it's that the same infrastructure this repo ships parallelizes real agent work across isolated environments and validates it honestly.
git clone https://github.com/imran31415/kubecoder-bench && cd kubecoder-bench
./setup.sh
python3 harness/bench.py --lang python --backend claude --workers 6Three screenshots that capture the whole idea β a workspace home you drive from one composer, every agent in one queue, and a chat that operates the pod for you:
Desktop home β ask anything or start a build in one line; the Mission Control strip keeps running, waiting, and finished agents one glance away
Mission Control β every agent across builds, chats, and sub-agents in one queue: waiting on you, running, needs review, done
Hypervisor β chat with the workspace and it acts; here it captured a screenshot and rendered it right in the thread
Chat is the feature that makes a kube-coder pod feel alive β internally the Hypervisor, which is the name you'll see in the code and specs. You talk to the workspace in plain language and it does the thing:
"How many tasks are running and what's my CPU?" Β· "Spin up a build to run the tests." Β· "Remember that I deploy with
make ship." Β· "Pin port 3000 to Apps."
Under the hood it's not a screen-scraped terminal β each thread is a structured agent session. Your chosen CLI (Claude, Codex, Ante, β¦) runs headless over pipes, and a canonical event stream renders clean prose plus expandable tool cards. It's wired to three MCP servers so it can genuinely operate the pod:
- dashboard β read metrics/tasks/health, create & message builds, pin apps, manage memory
- memory β persistent facts about you and your projects
- agent-orchestrator β spawn and coordinate sub-agents
Destructive actions (kill_task, delete_memory) ask for confirmation right in the chat. It's fully mobile, works with whichever assistant you pick, and survives pod restarts (the Claude adapter --resumes its session). See docs/hypervisor-spec.md for the architecture.
Chat operates the pod. The AI CTO (/cto) works a level up: it holds a picture of your projects.
Projects are discovered automatically β no forms. Each carries its own north star, decision log, goals and memory namespace, and the CTO answers from a live brief (running builds, recent activity, open threads) rather than from whatever happens to be in context. Ask it what to work on next and it argues from the actual state of the repo.
The Feed (/feed) is the single stream of what changed and what needs you: build outcomes, decisions recorded, trigger fires, plus anything an agent judged worth surfacing. Anything in it can be handed straight to the CTO with "Discuss with CTO."
Both are on the phone app too.
If a repo already carries a .devcontainer/devcontainer.json β because it came from Codespaces, Coder, Ona or DevPod β kube-coder reads it. Forwarded ports land on the Apps page with their labels, VS Code extensions and settings reach code-server, containerEnv/remoteEnv reach the agents you dispatch, and the lifecycle commands are shown to you verbatim so you can run them with one click. Moving a project across stops being "rebuild your environment by hand" and becomes "point it at the repo."
It interprets the file inside the workspace pod rather than building a container, so features, image, build and dockerComposeFile cannot be honoured β the pod runs as UID 1000 with no privilege escalation and there is no Docker daemon. Those come back named, with the reason and a remedy, instead of being silently dropped: a workspace that looks configured and isn't is the worst outcome for something whose whole job is migration.
Nothing from a cloned repo ever runs on discovery. Consent is pinned to the file's hash, so if devcontainer.json changes between the dialog and your click the server refuses rather than running text you never read. See docs/devcontainer.md.
A build session is an interactive Claude / Codex / Ante / OpenCode tmux session inside the pod. Start one from the dashboard, the phone app, or the API; it survives restarts, and its output is mirrored to a log you can tail from anywhere.
The New build flow is deliberately minimal β pick an assistant + working directory, get a memorable name (funny-kitty-37), and land straight in a live terminal. No prompt box to fill out first; type your first prompt in the REPL, like you would locally.
Parallelism is the point. The built-in agent-orchestrator MCP lets any session spawn sub-agents β across different harnesses β to fan work out and collect results:
spawn_agent("Refactor module A", assistant="ante") β task_1
spawn_agent("Write tests for module C", assistant="claude") β task_2
wait_for_agent(task_1); wait_for_agent(task_2) β synthesize
Guardrails (max spawn depth, max concurrent agents) keep a runaway loop from fork-bombing the pod.
# POST /api/claude/tasks (oauth2 headers OR Authorization: Bearer <token>)
curl -s https://<user>.dev.example.com/oauth/api/claude/tasks \
-H 'Content-Type: application/json' \
-d '{"prompt": "review this PR", "assistant": "claude"}'| Endpoint | Purpose |
|---|---|
POST /api/claude/tasks |
Create a build (prompt optional) |
GET /api/claude/tasks |
List sessions |
GET /api/claude/tasks/{id} |
Detail |
GET /api/claude/tasks/{id}/output |
Tail the tmux pane |
GET /api/claude/tasks/{id}/stream |
SSE live stream |
POST /api/claude/tasks/{id}/message |
Send a follow-up prompt |
DELETE /api/claude/tasks/{id} |
Kill the tmux session |
Full reference: docs/claude-task-api.md. There's also a /remote-task skill to dispatch a prompt from a lighter workspace to a stronger one and stream the result back.
Every workspace exposes the same set of surfaces, all behind a single GitHub OAuth login:
| Surface | What it is | Access URL |
|---|---|---|
| Dashboard SPA | Vite + Preact app: Desktop, Hypervisor, Build, Memory, Triggers, Apps, Skills, Files, Settings | / |
| Hypervisor | Chat that reports live state and acts on the pod | / β Hypervisor |
| Terminal | ttyd-attached tmux, reachable from any browser | /oauth/terminal/ |
| VS Code | code-server rooted at /home/dev |
/oauth/vscode/?folder=/home/dev |
| In-pod browser | Chrome on a virtual X display, viewed via noVNC | /oauth/vnc-direct/vnc.html |
| Metrics + health | Live CPU / Mem / Disk + service health | /oauth/metrics, /oauth/health |
| Assistants | Claude Code, Codex, Gemini, Ante, OpenCode, LibreFang | per-session |
The dashboard at / is a single Preact app:
- Desktop β your workspace home: a centered ask-anything / start-a-build composer, a Mission Control strip of recent agents, and a dock of pinned shortcuts.
- Mission Control β every agent across builds, chats, and sub-agents on one board: waiting on you, running, and done.
- Build β live + past agent sessions on the left, a detail pane on the right with Terminal, Preview (split ttyd β noVNC), Send message (chat mirror of the tmux pane, with image paste), Info, and Subagents tabs.
- Memory β persistent, SQLite-backed memory with history + relations, mirrored over MCP.
- Triggers β webhooks + cron jobs that spawn builds on a schedule or an inbound POST.
- Apps β pin a running port (e.g.
:3000) to preview your app inside the dashboard. - Skills β browse and sync
SKILL.mdfiles across every harness in the pod. - Files β read the PVC, upload files, make directories.
- Settings β appearance, GitHub identity, subscription logins, browser/VNC controls, and real-time metrics with alerts.
A persistent top bar shows live CPU/mem/disk and one-click VS Code + New-terminal buttons. It's fully responsive β below 720px the rail collapses to a bottom nav, the detail pane becomes a swipeable sheet, and the layout re-flows for touch.
Every workspace has a SQLite-backed memory store β facts about you, your projects, and your conventions β reachable three ways:
- Dashboard β Memory (CRUD with history + relations)
- MCP server auto-spawned for every agent (read + write from inside the assistant)
- REST at
/api/memory
Agents read it on demand; optional pre-injection (KC_MEMORY_PREINJECT=1, off by default) prefixes a new build's prompt with the most relevant records. It's how the Hypervisor "remembers that I deploy with make ship" and how a fresh session already knows your setup. Deep dive: docs/persistent-memory.md.
Three ways to start a build without clicking New build:
- Completion hooks β fire a webhook when an agent finishes (status, output URL, summary).
- Webhooks β accept inbound POSTs and turn the body into a build prompt via a template.
- Crons β UNIX cron expressions that POST to a webhook on a schedule.
All three live under Triggers, and what-fired-what is tracked in the memory store.
Drive your workspace agent from WhatsApp. The gateway is opt-in and
bring-your-own-credentials β connect your own Twilio (or Meta) app from
Settings β Messaging / WhatsApp, paste the webhook URL into the provider
console, tap Link WhatsApp, and text the pairing code once. After that you
just chat: messages drive a Hypervisor turn and replies come back to your phone,
with keyword commands (new chat, stop, unlink, workspaces, @ws) and a
fail-closed signature check on every inbound. The same card is in the mobile app.
Full guide: docs/whatsapp-gateway.md.
Every session β and every orchestrator sub-agent β picks its assistant at create-time, so you can mix them freely in one workspace. Keys live in users-private/<name>/secrets/assistant.yaml (gitignored); the public defaults are empty, so it ships Claude-only out of the box, and users can add their own provider keys self-service from Settings.
Assistant, model and reasoning effort (low β max, translated to each CLI's native knob) can be set per turn, or saved as a per-project default so the CTO dispatches work with the right one without being told.
| Assistant | Backend | Configure with |
|---|---|---|
| Claude Code (default) | Anthropic API key or subscription login | claude.apiKey, or make shell USER=<name> β claude to log in once |
| Codex | OpenAI's terminal agent; ChatGPT OAuth (no API key) | codex login once in the pod |
| Google Gemini | Google's gemini CLI (default gemini-2.5-pro) |
assistant.gemini.apiKey |
| Ante | Antigma's terminal agent; defaults to DeepSeek v3.2 via OpenRouter | assistant.openrouter.apiKey (CLI pre-installed) |
| OpenCode β OpenRouter | any OpenRouter model | assistant.openrouter.apiKey + model |
| OpenCode β DeepSeek | DeepSeek native API | assistant.deepseek.apiKey |
| LibreFang | open-source agent OS; reuses set provider keys | assistant.librefang.agent |
Pair the Ante CLI β pre-installed in every workspace β with DeepSeek and you get a Claude-Code-style experience: autonomous multi-step edits, shell/file tools, and the same MCP memory + orchestrator servers Claude uses, at a fraction of the cost. Set OPENROUTER_API_KEY and Ante defaults to deepseek/deepseek-v3.2 (~$0.23 / $0.34 per 1M in/out tokens) β cheap enough to be the default background sub-agent in the orchestrator.
Beyond the responsive web dashboard, kube-coder ships a native Expo / React Native app (mobile/) to drive your workspace from your phone: list / create / message / kill agent sessions, tail their color terminal output with a control-key bar (Shift-Tab, Esc, arrows, Ctrl-C, Paste), chat with the Hypervisor, attach photos to a follow-up, search memory, and watch live metrics β all over the workspace's Bearer-token API. It points at any kube-coder host: a cloud workspace, or a local minikube one via make mobile-forward.
cd mobile && npm install
npm run ios # iOS Simulator (or: npm run android / npm run web)Enter your workspace host + API token (copy both from Settings β Mobile app) on the first screen. Store builds use EAS β no Mac needed:
make mobile-build # EAS cloud build (iOS .ipa + Android .aab)
make mobile-screenshots # regenerate App Store / Play Store screenshotsFull walkthrough: mobile/README.md.
kube-coder runs two ways β pick the one that fits:
| Local (minikube) | Cloud / multi-tenant | |
|---|---|---|
| Best for | trying it out, dev, offline | real deployments, teams |
| Needs | Docker + minikube | a cluster, registry, DNS, GitHub OAuth |
| Auth | http basic (admin/admin) |
GitHub OAuth2 (or basic) |
| TLS | none (plain HTTP, localhost) | cert-manager + Let's Encrypt |
| Guide | Option A + docs/local-development.md | Option B + docs/NEW_USER_PROVISIONING.md |
π Follow-along walkthroughs:
- Getting started on a MacBook with minikube β clean laptop β local dashboard
- Deploying on Kubernetes (multi-tenant, OAuth + TLS) β cluster β per-user workspace
Run the whole stack on a local single-node cluster β no cloud account, registry, DNS, or TLS.
Prerequisites: Docker, minikube, kubectl, helm (brew install minikube kubectl helm on macOS).
make local # start minikube, build the image, deploy, and print access infoThen reach the dashboard:
echo '127.0.0.1 kube-coder.local' | sudo tee -a /etc/hosts # one time
make local-forward # keep running in a terminal
# open http://kube-coder.local:8080/ β basic auth: admin / adminmake local wraps steps that each run on their own (local-up, local-build, local-secret, local-deploy, local-info, local-down). Everything targets the minikube context explicitly, so it never touches a remote cluster. Full guide: docs/local-development.md.
Prerequisites: Kubernetes 1.19+, Helm 3.0+, an nginx-ingress controller, wildcard DNS (*.<your-domain> β the ingress IP), a GitHub OAuth App for the controller console, a private GitHub repo as the GitOps config store, and a regcred image-pull secret.
DOMAIN=<your-domain> make doctor # preflight: check ALL of the above at once (read-only)
make deploy-base # base infra: nginx-ingress, oauth2-proxy, cert-manager
make ship-controller-config # the admin console (workspace-controller)Run make doctor first. It's a read-only preflight that verifies every prerequisite above β kubectl/helm versions, the ingress controller and its external IP, cert-manager plus a ClusterIssuer, that *.<your-domain> actually resolves to your ingress IP (the failure that otherwise only surfaces as a dead workspace later), the regcred secret, and GitOps-repo reachability β reporting all failures at once with a fix for each. Pass DOMAIN=<your-domain> to include the wildcard-DNS check (skipped otherwise).
Two things make self-service onboarding work; you set them once:
- Wildcard DNS β point
*.<your-domain>at your ingress IP. Every workspace lives at<github-login>.<your-domain>, so a new user's host resolves the moment they're created, and cert-manager + Let's Encrypt issues its TLS cert on first request. - The console's GitHub OAuth App + admin allowlist β gates the dashboard at
controller.<your-domain>; only allowlisted logins may administer workspaces. One-time config: docs/PROVISIONING.md.
Scenario. Dana (
@dana-codes) joins Monday morning and needs a full cloud workspace before standup.
- Open the console at
https://controller.<your-domain>β New workspace. - Look up the user β type
dana-codes, hit Look up. The controller confirms it's a real GitHub account and shows the host:dana-codes.<your-domain>. - Create a GitHub OAuth App β the one manual step (GitHub has no API for it). The form shows the exact Homepage + callback URLs.
β οΈ It must be an OAuth App (Client ID starts withOvβ¦), not a GitHub App (Ivβ¦), which silently 404s the login. - Paste & create β drop the Client ID + Secret into the form β Create workspace. The controller commits Dana's config to your GitOps repo and launches a provisioner Job; the page streams the rollout: Starting β Deploying β Workspace ready.
- Hand it off β send Dana
https://dana-codes.<your-domain>. She signs in with her own GitHub account (she, and only she, is on the allowlist) and lands in VS Code, a terminal, and Claude Code. Elapsed time: a couple minutes, most of it the image pull.
All per-workspace, in the same console:
- Right-size resources β Edit limits β set CPU / memory. Applying patches the live pod and commits the new limits to GitOps, so it's durable across redeploys. Live usage + estimated monthly cost sit right above the control.
- Keep them current β each workspace shows its release and an update action; users can also self-update from their own dashboard.
- Pause to save spend β Stop scales the pod to zero (PVC preserved); Start brings it back unchanged.
The same workspaces are fully operable from the Makefile β for scripting, CI, or when the console is down. Config lives in the GitOps repo; make users-sync checks it out locally so CLI and console share one source of truth:
make users-sync # pull the GitOps config store into .users/
make new-user USER=<name> # scaffold a workspace (prints the OAuth-App checklist)
make deploy USER=<name> # helm upgrade --install
make logs|shell|test USER=<name> # operate a running workspace
make stop|start USER=<name> # scale to zero / backFull CLI walkthrough: docs/NEW_USER_PROVISIONING.md.
A per-user PVC mounted at /home/dev survives pod restarts; the tmux sessions attached to it survive too, so an in-flight agent build keeps running even after you close the tab. oauth2-proxy injects X-Auth-Request-User on every /oauth/* route; each workspace's proxy is pinned to exactly one GitHub login.
Each workspace is a tenant boundary, not a folder:
- Its own namespace (
ws-<user>) with a dedicated ServiceAccount, ResourceQuota and LimitRange β scoped so a workspace can read nothing outside itself. - NetworkPolicy both ways β ingress only from
ingress-nginx; egress denied to the cloud metadata endpoint and to other tenants' pods, while normal internet (GitHub, npm, registries, model providers) stays open. - No signing keys in the agent's container β the GitHub App private key lives in its own sidecar, which hands the
idecontainer only an hourly token. - Agent-readable instruction files are scanned for hidden-text prompt injection (zero-width and Unicode-tag characters in
CLAUDE.md,.cursorrules, β¦) β the technique used by the 2026 TrapDoor supply-chain campaign. - Provisioning is opt-in and constrained β a ValidatingAdmissionPolicy pins the shape of the privileged Job, and chart refs must be immutable.
Full policy, reporting process and hardening defaults: SECURITY.md Β· docs/SUPPLY_CHAIN.md.
charts/
βββ base-infrastructure/ # ingress, oauth2-proxy, cert-manager
βββ workspace/ # per-user workspace chart
βββ server.py # API + dashboard backend (tmux, memory, metrics, hypervisor)
βββ hypervisor_session.py # structured agent-session runner + per-CLI adapters
βββ mcp_dashboard.py # dashboard MCP server (read/act on the pod)
βββ web/ # Vite + Preact SPA (the dashboard at /)
βββ src/routes/ # desktop, cto, feed, hypervisor, mission, tasks, memory, triggers, apps, files, docs, skills, walkie, settings
βββ src/store/ # signals: tasks, ui, metrics, router
βββ scripts/shoot.mjs # playwright screenshots
deployments/ # public sample per-user values.yaml + secrets
users-private/ # gitignored; controller bootstrap config
mobile/ # native Expo / React Native app (iOS + Android, EAS builds)
devlaptop/Dockerfile # the workspace image (SPA baked into /opt/dashboard-dist)
Makefile # all common commands (`make help`)
| Component | Version |
|---|---|
| Node.js | 20 LTS |
code-server (VS Code) |
v4.x |
| Claude Code CLI | 2.1.174 |
| Codex CLI | 0.144.3 |
| OpenCode CLI | 1.17.4 |
| Ante CLI | stable channel |
| LibreFang | 2026.x beta |
| ttyd | 1.7.7 |
| tmux, yarn, gh, jq, ripgrep, fzf | latest from Ubuntu |
Bump versions in devlaptop/Dockerfile and run make push to rebuild.
# Docker image
make build / make push / make clean
# Per-user lifecycle
make deploy USER=<name> # helm upgrade --install
make ship USER=<name> # build + push + roll the pod
make rollback USER=<name> # helm rollback
make logs|shell|test USER=<name> # operate a running workspace
# Dashboard SPA
make dashboard-web # type-check + vite build β web/dist
make dashboard-web-test # vitest unit tests
# Tests across the repo
make test-all-units # SPA (vitest) + server.py (unittest)
make coverage # HTML coverage reports
# Cluster status
make status # helm + pod statusmake help (or just make) lists everything with one-line descriptions.
| Component | Coverage | Framework |
|---|---|---|
| Frontend (Dashboard) | 41.6% | Vitest + @testing-library |
| Backend (Python API) | 74% | unittest + coverage.py |
| Overall | 60% | statement-weighted average |
make test-all-units # SPA + Python unit tests (run before every PR)
make coverage # HTML reports β web/coverage/ and htmlcov/All tests run automatically on GitHub Actions.
# Run the SPA locally against a built dist with auth bypassed
make dashboard-web
DASHBOARD_DIST_DIR=$(pwd)/charts/workspace/web/dist \
python3 charts/workspace/web/dev_server.py
# β http://127.0.0.1:7070Pull requests welcome β please run make test-all-units and keep coverage healthy before opening a PR. Contribution guide: CONTRIBUTING.md.
kube-coder stands on excellent open source:
- Kubernetes + Helm β orchestration & deployment
- NGINX Ingress + oauth2-proxy β routing & auth
- VS Code / code-server, tmux, ttyd, noVNC + Xvfb β the dev surfaces
- Claude Code, Codex, OpenCode, Ante β the coding agents
- Preact + Vite + Playwright β the dashboard & its screenshots
Interested in a demo, an enterprise deployment, or a custom integration? scalebaseio@gmail.com Β· KubeCoder.com Β· r/kubecoder
MIT β see LICENSE.










