Guide for developers contributing to Radar or building custom versions.
- Go 1.26+
- Node.js 24+
- npm
- kubectl with cluster access
git clone https://github.com/skyhook-io/radar.git
cd radar
# Install dependencies
make deps
# Start development (two terminals)
# Terminal 1: Frontend with hot reload (port 9273)
make watch-frontend
# Terminal 2: Backend with hot reload (port 9280)
make watch-backendOpen http://localhost:9273 — the Vite dev server proxies /api requests to the Go backend.
make deps # Install all dependencies (Go + npm)
make build # Build everything (frontend + embedded binary)
make frontend # Build frontend only
make backend # Build backend only
make test # Run Go tests
make tsc # TypeScript type check
make lint # Run linter
make clean # Clean build artifacts
make docker # Build Docker image
make desktop-dev # Desktop app dev mode with hot reload (Wails)
make desktop # Build desktop binary
make desktop-package-darwin # Package macOS .app bundleradar/
├── cmd/
│ ├── explorer/ # CLI entry point (main.go)
│ └── desktop/ # Desktop app entry point (Wails v2)
├── internal/
│ ├── app/ # Application lifecycle management
│ ├── config/ # Persistent configuration (~/.radar/config.json)
│ ├── helm/ # Helm SDK client and handlers
│ ├── images/ # Container image inspection
│ ├── k8s/ # Kubernetes client, informers, caching
│ ├── mcp/ # MCP (Model Context Protocol) server
│ ├── opencost/ # OpenCost integration (cost analysis)
│ ├── prometheus/ # Prometheus client and discovery
│ ├── server/ # HTTP server, REST API, SSE, WebSocket
│ ├── settings/ # User preferences (~/.radar/settings.json)
│ ├── static/ # Embedded frontend (built from web/)
│ ├── traffic/ # Traffic visualization (Hubble, Caretta, Istio)
│ └── ... # errorlog, updater, version
├── pkg/
│ ├── ai/context/ # AI context minification for LLM-friendly output
│ ├── k8score/ # Shared K8s caching layer (informers, listers)
│ ├── timeline/ # Timeline event storage (memory/SQLite)
│ └── topology/ # Graph construction and relationships
├── packages/
│ └── k8s-ui/ # Shared UI package (@skyhook-io/k8s-ui)
├── web/ # React frontend
│ ├── src/
│ │ ├── api/ # API client, React Query hooks, SSE
│ │ ├── components/ # React components (topology, resources, helm, etc.)
│ │ ├── contexts/ # React contexts (capabilities, namespace, dock)
│ │ ├── types.ts # TypeScript type definitions
│ │ └── utils/ # Topology layout and helpers
│ └── package.json
├── deploy/ # Helm chart, Dockerfile
├── docs/ # User documentation
└── scripts/ # Release scripts
┌─────────────────────────────────────────────────────────────────┐
│ Go Backend │
│ │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────────┐ │
│ │ chi │ │ Informers │ │ SSE │ │
│ │ Router │───►│ (cached) │───►│ Broadcaster │ │
│ └─────────────┘ └─────────────┘ └─────────────────┘ │
│ │ │ │ │
│ ▼ ▼ ▼ │
│ REST API K8s Watches Real-time push │
│ WebSocket (exec) Resource cache to browser │
└─────────────────────────────────────────────────────────────────┘
Key patterns:
- SharedInformers — Watch-based caching, no polling. Resource changes arrive in milliseconds.
- SSE Broadcaster — Central hub for pushing real-time updates to all connected browsers.
- Topology Builder — Constructs a directed graph from cached resources on demand. Two modes: resources (hierarchy) and traffic (network flow).
- Capabilities & RBAC — SelfSubjectAccessReview checks at startup detect per-resource permissions. Informers are only created for accessible resource types. For namespace-scoped users (RoleBinding instead of ClusterRoleBinding), checks fall back from cluster-wide to namespace-scoped, and informers are scoped to the permitted namespace. The frontend hides features the user cannot access.
┌─────────────────────────────────────────────────────────────────┐
│ React Frontend │
│ │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────────┐ │
│ │ React │ │ TanStack │ │ @xyflow/react │ │
│ │ Router │───►│ Query │───►│ + ELK.js │ │
│ └─────────────┘ └─────────────┘ └─────────────────┘ │
│ │ │ │
│ ▼ ▼ │
│ API + SSE hooks Graph visualization │
└─────────────────────────────────────────────────────────────────┘
Key patterns:
- useEventSource — SSE connection with automatic reconnection
- React Query — Server state management with caching and background refetching
- CapabilitiesContext — Fetches RBAC capabilities from
/api/capabilitiesand hides unavailable features
Backend: Go 1.26+, client-go, chi router, gorilla/websocket, Helm SDK, Cilium/Hubble, go-containerregistry, SQLite, Wails v2, go:embed
Frontend: React 19, TypeScript, Vite, @xyflow/react + ELK.js, @xterm/xterm, Monaco Editor, TanStack React Query v5, Tailwind CSS v4 + shadcn/ui
See the operator-facing HTTP API reference. Route definitions in internal/server/server.go remain the source of truth when adding or changing an endpoint.
-
Add route in
internal/server/server.go:r.Get("/api/my-endpoint", s.handleMyEndpoint)
-
Implement handler:
func (s *Server) handleMyEndpoint(w http.ResponseWriter, r *http.Request) { // ... }
- Add informer in
internal/k8s/cache.go - Add to topology builder in
pkg/topology/builder.go - Add TypeScript type in
packages/k8s-ui/src/types/core.ts
- Create component in
web/src/components/ - Add route if needed in
web/src/App.tsx - Add API hooks if needed in
web/src/api/
# Go tests
make test
# TypeScript type check
make tsc
# Manual testing (two terminals)
make watch-backend # Terminal 1
make watch-frontend # Terminal 2# Interactive release (prompts for version and targets)
make release
# Dry-run goreleaser (local test, no publish)
make release-binaries-dry| Target | Command | Output |
|---|---|---|
| All | make release |
Interactive — runs scripts/release.sh |
| Dry run | make release-binaries-dry |
Local goreleaser snapshot (no publish) |
| Docker | make docker-multiarch |
Multi-arch Docker image build |
| Target | Requirements |
|---|---|
| CLI binaries | goreleaser, GITHUB_TOKEN or gh auth login |
| Docker | Docker running, GHCR auth (docker login ghcr.io) |
- Ensure tests pass:
make test - For a minor or major release (
vX.Y.0), add its What's New entry toweb/src/components/whats-new/releaseNotes.tsand merge it first. Lead with the highlight most users will care about; give highlights that open a feature apathandcta. Preview with?whats-new=vX.Y.0on a local build.scripts/check-whats-new.shenforces this:make releaserefuses to tag without the entry, and the release workflows fail before building. Patch releases need no entry. - Tag the release:
git tag v0.X.Y && git push origin v0.X.Y - Run release:
make release
The helm job in .github/workflows/release.yml rewrites the chart's version / appVersion / image-tag annotation to match the release tag and pushes the chart to skyhook-io/helm-charts. No manual chart edit is needed; in fact, hand-edited values in deploy/helm/radar/Chart.yaml will be overwritten — which is why those fields sit at the 0.0.0-dev placeholder on main. The job fails fast if radar-<version> is already tagged in helm-charts — bump the release version higher in that case.
- Go:
gofmt,golint - TypeScript: Prettier (
npm run format:writeinweb/) - Commits: Conventional commits preferred (
feat:,fix:,docs:)