Core Concepts

Learn the fundamentals of r8s in 5 minutes

01

Components

r8s components are TypeScript functions that return Kubernetes resources. They follow the same pattern as React components but render infrastructure instead of DOM.

import { App } from '@r8s/recipes';

export default () => (
  <App
    name="api"
    image="myapp/api:v1.2.3"
    host="api.example.com"
  />
);

The <App> component above creates a Deployment, Service, and Endpoint — all wired together with sensible defaults.

02

Composition

Compose multiple components together using fragments. Each component renders its own resources, and the renderer flattens everything into a single list.

import { App, Database } from '@r8s/recipes';

export default () => (
  <>
    <Database name="api-db" storage="10Gi" />
    <App
      name="api"
      image="myapp/api:v1.2.3"
      host="api.example.com"
    />
  </>
);
03

Escape Hatches

Prefer raw Kubernetes? Every API resource is available as a lowercase component. Drop down to raw components at any level:

import { Database, Endpoint } from '@r8s/recipes';

export default function CustomApp() {
  return (
    <>
      <Database name="myapp-db" storage="20Gi" />

      <deployment
        metadata={{ name: 'myapp-web' }}
        spec={{
          replicas: 3,
          template: {
            spec: {
              containers: [{
                name: 'web',
                image: 'myapp/web:v1.2.3',
                ports: [{ containerPort: 3000 }],
              }],
            },
          },
        }}
      />

      <service
        metadata={{ name: 'myapp-web' }}
        spec={{
          selector: { app: 'myapp-web' },
          ports: [{ port: 80, targetPort: 3000 }],
        }}
      />

      <Endpoint
        host="myapp.example.com"
        serviceName="myapp-web"
        servicePort={80}
        tls={{ secretName: "myapp-tls", clusterIssuer: "letsencrypt" }}
      />
    </>
  );
}
04

Rendering

The render() function takes your component tree and returns two things: all Kubernetes resources and all required operators.

import { render } from '@r8s/core';
import { App, Database } from '@r8s/recipes';

const result = render(
  <>
    <Database name="app-db" storage="10Gi" />
    <App name="api" image="myapp/api:v1" host="api.example.com" />
  </>
);

// All Kubernetes resources
console.log(result.resources);
// [{ apiVersion: "apps/v1", kind: "Deployment", ... }, ...]

// All required operators
console.log(result.operators);
// [{ name: "cnpg", source: { type: "helm", ... } }, ...]
05

Context

Share configuration across components using context — namespace, labels, and operators can be provided once and consumed everywhere.

import { Namespace, Labels } from '@r8s/core/defaults';
import { Database, App } from '@r8s/recipes';

export default function Platform() {
  return (
    <Namespace.Provider value="production">
      <Labels.Provider value={{ app: 'myapp', team: 'platform' }}>
        <Database name="app-db" storage="10Gi" />
        <App name="api" image="myapp/api:v1" host="api.example.com" />
      </Labels.Provider>
    </Namespace.Provider>
  );
}
06

Providers

Providers configure cluster-level concerns: secrets, DNS, and routing. Use strings for simple cases, components for advanced configuration.

import { SecretProvider, DnsProvider, EndpointProvider, App } from '@r8s/recipes';

export default () => (
  <SecretProvider provider="openbao">
    <DnsProvider provider="external-dns">
      <EndpointProvider provider="nginx">
        <App name="api" image="myapp:v1" host="api.example.com" />
      </EndpointProvider>
    </DnsProvider>
  </SecretProvider>
);

For advanced configuration, pass a component instead of a string:

import { SecretProvider, OpenBao, DnsProvider, ExternalDns, EndpointProvider, Nginx, App } from '@r8s/recipes';

export default () => (
  <SecretProvider provider={<OpenBao mount="secret" path="infra" />}>
    <DnsProvider provider={<ExternalDns server="ns1.example.com" tsig={{ path: 'dns/tsig', key: 'secret' }} />}>
      <EndpointProvider provider={<Nginx className="nginx-internal" />}>
        <App name="api" image="myapp:v1" host="api.example.com" />
      </EndpointProvider>
    </DnsProvider>
  </SecretProvider>
);

Each provider declares its required operators automatically. The hierarchy is composable — wrap your entire cluster or a single namespace.

Ready to dive deeper?

Check out the recipes for production-ready templates, or read about deployment strategieswith FluxCD and GitHub Actions.