# r8s Define Kubernetes infrastructure as TypeScript components. Render to plain YAML. GitOps-friendly. ## Quick start ```bash npx r8s init my-app cd my-app npm install npm run render-k8s # renders k8s/r8s.tsx → k8s/manifest.yaml ``` The scaffolded `k8s/r8s.tsx` default-exports a JSX element. Edit it, re-render, commit the YAML. That's the whole loop. ## The mental model 1. **Components are TypeScript functions** that return Kubernetes resources (or trees of components). 2. **`render()`** walks the tree and returns `{ resources, operators }`. 3. **The CLI** bundles your `.tsx` with esbuild and calls `render()` — you never run TypeScript in-cluster. 4. **Git is the source of truth.** Commit the rendered YAML. FluxCD or ArgoCD applies it. Never hand-edit the YAML. ## Install ```bash npm install @r8s/core @r8s/recipes npm install -D @r8s/cli ``` Or use `npx r8s` without installing. ## CLI reference ```bash r8s init [name] # Scaffold a project (templates: basic, fullstack) r8s render # Render k8s/r8s.tsx → stdout r8s render --entry infra/app.tsx # Render a specific file r8s render --out k8s/manifest.yaml # Write to file r8s render --include-operators # Include operator manifests in output r8s operators --out operators.yaml # Only operator manifests # Discovery (no docs browsing needed): r8s list # All components + operators r8s info App # Props table + example for one component r8s preview App # Render a component with dummy props, see YAML r8s explain App # What resources + operators a component creates r8s validate infra/app.tsx # Type-check + reference-check rendered output r8s context # One-shot LLM context blob (everything above) # Community recipes (npm is the registry): r8s search database # Search npm for r8s recipes (keywords: ["r8s"]) r8s add @acme/r8s-redis # Install a community recipe from npm r8s --help ``` ## Deploy a single app (the 90% case) ```tsx // k8s/r8s.tsx import { App } from '@r8s/recipes' export default ( ) ``` ```bash npx r8s render --out k8s/manifest.yaml ``` This produces a Deployment, Service, and Ingress (or Gateway+HTTPRoute) — all wired together. Commit `k8s/manifest.yaml` and point FluxCD/ArgoCD at it. ## Add a database ```tsx import { App, Database } from '@r8s/recipes' export default ( <> ) ``` `Database` creates a CloudNativePG `Cluster` + credentials secret. Operators (CNPG, cert-manager, etc.) are declared automatically — render with `--include-operators` to get their manifests. ## Write your own reusable components This is the real power of r8s. Components are just functions — compose and reuse them across repos. ```tsx // infra/components.tsx import { jsx, Fragment } from '@r8s/core' interface ApiProps { name: string image: string host: string replicas?: number } // A reusable "service" component for your org. export function Service(props: ApiProps) { const { name, image, host, replicas = 2 } = props return ( <> ) } ``` ```tsx // k8s/r8s.tsx import { Service } from './infra/components' export default ( ) ``` Lowercase elements (``, ``) are raw Kubernetes resources — any kind works. PascalCase (``, ``) are recipe components from `@r8s/recipes`. ## TypeScript makes it testable Because components are functions, you can test them: ```tsx // infra/components.test.tsx import { describe, it, expect } from 'vitest' import { render } from '@r8s/core' import { Service } from './components' describe('Service', () => { it('creates a Deployment with correct replicas', () => { const { resources } = render() const deploy = resources.find(r => r.kind === 'Deployment') expect(deploy?.spec?.replicas).toBe(3) }) it('wires Service selector to Deployment labels', () => { const { resources } = render() const svc = resources.find(r => r.kind === 'Service') const deploy = resources.find(r => r.kind === 'Deployment') expect(svc?.spec?.selector?.app).toBe(deploy?.spec?.selector?.matchLabels?.app) }) it('creates an Ingress pointing at the Service', () => { const { resources } = render() const ingress = resources.find(r => r.kind === 'Ingress') expect(ingress?.spec?.rules?.[0]?.host).toBe('api.example.com') expect(ingress?.spec?.rules?.[0]?.http?.paths?.[0]?.backend?.service?.name).toBe('api') }) }) ``` ```bash npx vitest ``` Run `tsc --noEmit` to type-check your infrastructure before rendering — wrong field names and invalid values are compile errors, not `kubectl apply` failures. ## Platform (when you need routing, TLS, DNS, secrets) Wrap children in `` to set up cross-cutting concerns: ```tsx import { Platform, App } from '@r8s/recipes' export default ( ) ``` Platform provides contexts (namespace, routing, DNS, secrets) that all children inherit. It materializes the Namespace resource. Endpoints get TLS certificates (cert-manager) and DNS annotations (external-dns) automatically. ## Packages | Package | What | |---|---| | `@r8s/core` | JSX runtime, `render()`, contexts | | `@r8s/recipes` | `Platform`, `App`, `Database`, `Endpoint`, `Monitoring` + providers | | `@r8s/recipes/auth` | `Realms`, `Realm`, `Clients`, `Client`, `EntraID`, `Google` (Keycloak auth) | | `@r8s/crds` | Typed CRD components (CloudNativePG, cert-manager, Gateway API, Redis, Loki, Keycloak, external-dns) | | `@r8s/cli` | `r8s init`, `r8s render` | | `@r8s/grafana` | `Grafana` component | | `@r8s/superset` | `Superset` component | | `@r8s/rustfs` | `RustFS` S3-compatible storage | | `@r8s/wireguard` | WireGuard VPN | ## Common pitfalls - **Default export required.** The entry file must `export default` a JSX element (or a function returning one). - **Auth sub-components** live in `@r8s/recipes/auth`, not `@r8s/recipes`. - **Secrets**: `Database`/`Auth`/`Grafana`/`RustFS` create their credential Secrets by default, or accept `existingSecret` when you manage them externally. - **`${env:FOO}`** strings in output are literal — they're expanded by the target system (e.g. Keycloak), not by r8s. - **Operators** are declared in render output but not installed automatically. Use `--include-operators` to get their manifests, or install them separately. ## Where to go next - **CLI help**: `r8s --help` - **Discover components**: `r8s list`, `r8s info `, `r8s preview ` - **Type-check**: `npx tsc --noEmit` - **Validate**: `r8s validate infra/app.tsx` - **Test**: `npx vitest` - **Docs**: https://r8s.berget.ai - **Recipes reference**: https://r8s.berget.ai/recipes - **Source & examples**: https://github.com/berget-ai/r8s (see `examples/` for web-shop, saas-platform, monitoring-stack) ## Community recipes r8s uses npm as its package registry — no separate chart repo needed. Any npm package with `r8s` in its `keywords` is discoverable via `r8s search`. ### Publish your own recipe ```json // package.json { "name": "@yourorg/r8s-redis", "version": "1.0.0", "keywords": ["r8s"], "main": "./dist/index.js", "types": "./dist/index.d.ts", "peerDependencies": { "@r8s/core": "^0.1.0" } } ``` ```bash npm publish --access public ``` ### Find and install community recipes ```bash r8s search redis # searches npm for packages with keyword "r8s" r8s add @yourorg/r8s-redis # npm install + ready to import ``` ```tsx import { RedisCache } from '@yourorg/r8s-redis' export default ```