A lightweight, internal-use-only browser-based terminal backed by a Kubernetes pod. KubeTTY provides a persistent development environment with full PTY support, session management, and integrated AI tooling.
- Browser-based Terminal: Full interactive shell with xterm.js (supports colors, cursor control, vim, tmux, etc.)
- Single Session Per Pod: One PTY session is created and reused for the lifetime of each pod
- Session Persistence: Session metadata and logs stored in CNPG database survive pod restarts
- Single Client Enforcement: Only one browser client can connect at a time per session
- Built-in Authentication: Local user auth with JWT tokens (optional, configurable)
- AI Tools Integration: Pre-installed Claude Code, Codex, and other LLM CLIs
- Development Tooling: kubectl, helm, docker, go, node/npm, git, and more
- Logging: Built-in session logging for Claude CLI interactions and PTY transcripts
- Access to a Kubernetes cluster with
~/.kube/configconfigured - Harbor registry access at
harbor.support.tools - CNPG database cluster for session persistence
- Kubernetes secrets for API keys (see Secret Management)
# Build frontendcd web
npm install
npm run build
cd ..
# Build Docker image
docker build -t harbor.support.tools/kubetty/kubetty:latest .# Push to Harbor
docker push harbor.support.tools/kubetty/kubetty:latest# Create namespace
kubectl create namespace kubetty-dev
# Create API secrets (see SECRETS.md for details)
kubectl create secret generic kubetty-api-keys \
-n kubetty-dev \
--from-literal=github-token='YOUR_TOKEN' \
--from-literal=openai-api-key='YOUR_KEY'# ... add other secrets as needed# Deploy with Helm
helm upgrade --install kubetty-dev ./deploy/helm \
-n kubetty-dev \
-f deploy/helm/values.yaml \
--set apiSecrets.existingSecret=kubetty-api-keys# Port forward to the pod
kubectl port-forward -n kubetty-dev deployment/kubetty-dev 8080:8080
# Open browser
open http://localhost:8080KubeTTY requires several API keys and credentials to function. These are managed through Kubernetes Secrets and injected as environment variables.
GITHUB_TOKEN- GitHub personal access tokenOPENAI_API_KEY- OpenAI API keyGOOGLE_CLOUD_PROJECT- GCP project IDNEXMONYX_ACCESS_KEY- Nexmonyx access keyNEXMONYX_ACCESS_SECRET- Nexmonyx secret keyANTHROPIC_BASE_URL- (Optional) Custom Anthropic endpoint
See SECRETS.md for comprehensive secret management documentation including:
- How to create Kubernetes secrets
- Security best practices
- Rotating credentials
- Troubleshooting
.bash_profile included in the Docker image contains no secrets - all sensitive values are injected via Kubernetes Secrets.
KubeTTY provides several helpful aliases and functions:
# Run Claude Code with session logging
c
# Or manually
claude_with_log
# Access logs
ls ~/claude_logs/Each pod has a single session that is created on first connection and reused for the pod lifetime:
- Automatic Reconnection: Browser automatically reconnects after network drops
- Single Client: Only one browser can connect at a time (second connection is rejected)
- PTY Persistence: Shell remains running even when browser disconnects
- Session Logs: All PTY I/O is logged to CNPG for auditing via the logs modal
Browser UI (React + xterm.js)
|
| WebSocket
v
Go Server (KubeTTY)
/ \
/ \
v v
CNPG PTY (/bin/bash)
Session
Store
- Go Backend: PTY server with WebSocket support, session management, and authentication
- React Frontend: xterm.js-based terminal UI with login form and auto-reconnection
- CNPG Database: Session metadata, logs, user credentials, and persistence
- Docker Image: All development tools pre-installed
- DESIGN.md - Comprehensive design document and architecture
- SECRETS.md - Secret management guide
- AGENTS.md - Agent-specific documentation (if applicable)
KubeTTY/
├── server/ # Go backend
│ ├── cmd/ # Binary entry points
│ │ ├── gateway/main.go # Gateway mode (multi-project tabs)
│ │ ├── project/main.go # Project mode (single PTY)
│ │ └── kubetty-authuser/ # User management CLI
│ ├── internal/
│ │ ├── handlers/ # HTTP handlers (auth, session)
│ │ ├── gateway/ # Gateway logic
│ │ ├── sessions/ # CNPG session management
│ │ └── shared/ # Shared utilities
│ ├── migrations/ # Database migrations
│ └── go.mod
├── web/ # React frontend
│ ├── src/
│ ├── public/
│ └── package.json
├── deploy/
│ └── helm/ # Helm chart (supports both modes via KUBETTY_MODE)
│ ├── Chart.yaml
│ ├── values.yaml # Base defaults
│ ├── values.project-template.yaml # Template for new projects
│ └── templates/
├── scripts/ # Helper scripts
│ └── claude_with_log.sh
├── Dockerfile # Multi-stage build (builds both binaries)
└── .bash_profile # Shell configuration (no secrets)
The repository includes Git hooks to catch issues before they reach CI/CD:
# Enable the hooks (one-time setup)
git config core.hooksPath .githooksPre-commit hook runs automatically on git commit:
- Forbidden file detection (*.key, *.pem, secrets, credentials)
- Go formatting check (gofmt)
- Go linting (go vet)
- Merge conflict marker detection
Pre-push hook runs automatically on git push:
- Full validation for main/release/* branches
- Quick validation for feature branches
- Calls
validate-pipeline-local.shto mirror CI checks
To bypass hooks temporarily (not recommended):
git commit --no-verify
git push --no-verifyBefore pushing, you can manually run the local validation:
# Full validation (matches CI/CD)
./validate-pipeline-local.sh
# Quick validation (skip tests)
./validate-pipeline-local.sh --quick
# Full validation with Docker build and security scan
./validate-pipeline-local.sh --fullKubeTTY is configured via environment variables injected by Helm:
Session Management:
SESSION_ID- Default session UUID for this deploymentDEPLOYMENT_ID- Unique deployment identifier
Database:
CNPG_HOST- CNPG database hostCNPG_PORT- CNPG database portCNPG_DATABASE- Database nameCNPG_USER- Database username (from secret)CNPG_PASSWORD- Database password (from secret)
AI/API Keys: (from apiSecrets, see SECRETS.md)
GITHUB_TOKENOPENAI_API_KEYGOOGLE_CLOUD_PROJECTNEXMONYX_ACCESS_KEYNEXMONYX_ACCESS_SECRETANTHROPIC_BASE_URL
Claude Configuration:
CLAUDE_CODE_MAX_OUTPUT_TOKENS- Token limit for Claude CodeCLAUDE_CONFIG_DIR- Claude configuration directory
Authentication (required configuration):
AUTH_MODE- Required. Set tolocalto enable built-in login, ordisabledto run without authentication. Empty values are rejected to prevent accidental insecure deployments.AUTH_JWT_SECRET- Base64 (recommended) or raw secret used to sign JWTs. Generate viaopenssl rand -base64 48. Required whenAUTH_MODE=local.AUTH_ACCESS_TTL/AUTH_REFRESH_TTL- Token lifetimes (e.g.,15m,720h).AUTH_ISSUER- JWT issuer claim (default:kubetty).AUTH_COOKIE_DOMAIN- Optional domain for auth cookies.AUTH_COOKIE_SECURE-trueby default; set tofalseonly for HTTP/dev environments.
Security Warning: AUTH_MODE=disabled
When
AUTH_MODE=disabled, all API routes are unprotected and accessible without authentication:
- All WebSocket endpoints (
/ws) are open- All tab/project management APIs (
/api/tabs,/api/projects) are accessible- Session logs (
/session/logs) can be read by anyoneThe gateway will log a security warning at startup and add an
X-Auth-Warningheader to all responses when authentication is disabled.Only use
AUTH_MODE=disabledwhen:
- Running behind a VPN or private network with strict access controls
- Using Kubernetes NetworkPolicies to restrict access
- For local development/testing purposes
For production deployments, always use
AUTH_MODE=localwith a strongAUTH_JWT_SECRET.
These knobs are exposed through the Helm chart under the auth section (deploy/helm/values.yaml). You can point AUTH_JWT_SECRET at a Kubernetes Secret via auth.jwtSecretSecret to avoid storing raw secrets in values files.
After enabling AUTH_MODE=local, create a CNPG user with the helper:
# ensure SESSION_ID is set (the helper defaults to kubetty-authuser)export SESSION_ID=kubetty-authuser
export CNPG_HOST=...
export CNPG_DATABASE=...
export CNPG_USER=...
export CNPG_PASSWORD=...
go run ./server/cmd/kubetty-authuser create --username alice --password 's3cret'
go run ./server/cmd/kubetty-authuser list
go run ./server/cmd/kubetty-authuser update-password --username alice --password 'newpass'
go run ./server/cmd/kubetty-authuser set-active --username alice --active=falseSubsequent logins happen through the /api/auth/login endpoint (the UI shows a form automatically). You can also call /api/auth/refresh and /api/auth/logout, and every request now includes auth cookies/callbacks.
KubeTTY is designed for internal-use only:
- ✅ Access via
kubectl port-forwardor private network only - ✅ No public ingress or authentication (relies on network isolation)
- ✅ Secrets managed via Kubernetes Secrets
- ✅ NetworkPolicy restrictions recommended
- ❌ Not suitable for public internet exposure
- ❌ No multi-tenancy support
See SECRETS.md for detailed security practices.
helm upgrade --install kubetty-dev ./deploy/helm \
-n kubetty-dev \
--set deploymentId=kubetty-dev \
--set apiSecrets.existingSecret=kubetty-api-keysDeploy separate instances for each project:
# Project A
helm upgrade --install kubetty-project-a ./deploy/helm \
-n project-a \
-f deploy/helm/values.project-a.yaml \
--set apiSecrets.existingSecret=kubetty-api-keys
# Project B
helm upgrade --install kubetty-project-b ./deploy/helm \
-n project-b \
-f deploy/helm/values.project-b.yaml \
--set apiSecrets.existingSecret=kubetty-api-keysEach deployment gets its own session UUID and maintains independent shell state.
To provide a single entrypoint (e.g., https://kubetty.support.tools) that fans out to multiple project pods, enable the gateway feature:
Create a catalog file describing the downstream projects. Example
projects.yaml:projects: - id: ai-devdisplayName: "AI Platform"namespace: kubetty-aiservice: kubetty-ai-kubettyport: 8080description: "LLM tooling" - id: infranamespace: kubetty-infraservice: kubetty-infra-kubettyport: 8080
Mount the file into the gateway deployment and set
PROJECT_CATALOG_PATH=/etc/kubetty/projects.yaml(or similar) via Helm values.Deploy the gateway chart so it can reach each project Service (ClusterIP) over the cluster network. NetworkPolicies should allow only the gateway namespace to talk to those Services.
When a user opens https://kubetty.support.tools they will see a tab bar with a
+button. Each tab corresponds to one downstream project and the browser WebSocketwss://…/ws?tab=<id>is proxied through the gateway to the project pod’s/wsendpoint.
Gateway APIs:
| Method | Path | Description |
|---|---|---|
| GET | /api/projects | Lists catalog entries (id, displayName, etc.). |
| GET | /api/tabs | Lists tabs owned by the current browser client. |
| POST | /api/tabs | { "projectId": "ai-dev" } → opens a new tab. |
| DELETE | /api/tabs/{id} | Closes the tab/tunnel if owned by the client. |
| GET | /ws?tab=<id> | WebSocket endpoint for a specific tab. |
The server automatically mints an opaque kubetty_client cookie to associate tabs with a browser. Tabs persist in CNPG so reloads reconnect automatically, and the React UI polls /api/tabs every ~8 seconds to refresh status.
If PROJECT_CATALOG_PATH is unset, the app behaves as before (single PTY, no tab bar).
# Check pod status
kubectl get pods -n NAMESPACE
# View logs
kubectl logs -n NAMESPACE deployment/kubetty-DEPLOYMENT
# Check port forward
kubectl port-forward -n NAMESPACE deployment/kubetty-DEPLOYMENT 8080:8080- Verify CNPG connection settings in Helm values
- Check CNPG credentials secret exists
- Review pod logs for database connection errors
- Ensure secrets are created before deployment
- Verify
apiSecrets.existingSecretis set correctly - Check environment variables in running pod:
kubectl exec POD -- env
See SECRETS.md for secret-specific troubleshooting.
Internal use only. Not for public distribution.
For issues or questions:
- Review documentation: DESIGN.md, SECRETS.md
- Check pod logs:
kubectl logs - Contact your cluster administrator for infrastructure issues