# 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
```