kubetsx
v0.1.6
Published
The Declarative Kubernetes Framework. Write K8s configs with JSX. Yes, really.
Maintainers
Readme
🎯 Kubetsx
⚠️ Experimental Project - This project is under active development. Use carefully in production environments.
The Declarative Kubernetes Framework. Write K8s configs with JSX. Yes, really.
Kubetsx transforms Kubernetes configuration from fragile YAML files into type-safe, composable TypeScript components. Get full IntelliSense, compile-time validation, and the power of loops and conditionals.
Type-safe. Composable. Finally, Kubernetes configuration that makes sense.
import { render, Manifest, Deployment, Container, Port, Service } from 'kubetsx';
const App = () => (
<Manifest>
<Deployment name="api" replicas={3}>
<Container name="api" image="mycompany/api:v1">
<Port container={3000} />
</Container>
</Deployment>
<Service name="api" port={80} targetPort={3000} />
</Manifest>
);
render(<App />);Output: Valid Kubernetes YAML, ready for kubectl apply.
🚀 Features
| Feature | YAML | Kubetsx |
|---------|------|-------|
| IntelliSense | ❌ None | ✅ Full autocomplete |
| Type checking | ❌ Runtime errors | ✅ Compile-time errors |
| Loops | ❌ Copy-paste | ✅ .map(), for |
| Conditionals | ❌ External tools | ✅ Native if/ternary |
| Variables | ❌ Limited | ✅ Full JavaScript |
| Reusability | ❌ Copy-paste | ✅ Components |
| Refactoring | ❌ Find & replace | ✅ IDE rename |
📦 Installation
Step 1: Install
npm install kubetsxStep 2: Configure TypeScript
Create or update tsconfig.json:
{
"compilerOptions": {
"jsx": "react-jsx",
"jsxImportSource": "kubetsx",
"module": "ESNext",
"moduleResolution": "bundler"
}
}Step 3: Create your first config
Create k8s.tsx:
import { render, Manifest, Deployment, Container, Port, Service } from 'kubetsx';
const App = () => (
<Manifest>
<Deployment name="my-app" replicas={2}>
<Container name="app" image="nginx:latest">
<Port container={80} />
</Container>
</Deployment>
<Service name="my-app" port={80} targetPort={80} />
</Manifest>
);
render(<App />);Step 4: Generate YAML
# Output to terminal
npx kubetsx k8s.tsx
# Save to file
npx kubetsx k8s.tsx > k8s.yaml
# Apply directly to cluster
npx kubetsx k8s.tsx | kubectl apply -f -💡 Tip: Use
console.error()for debug output. The CLI validates that stdout contains only valid YAML and will error ifconsole.log()corrupts the output.
🛡️ Runtime Validation: Kubetsx validates component nesting at runtime. Invalid structures like
<Service>inside<Deployment>will throw clear errors with helpful messages showing what's allowed and where the error occurred.
Render Options
import { render, Manifest, Deployment, Container, Port } from 'kubetsx';
const App = () => (
<Manifest>
<Deployment name="api" replicas={3}>
<Container name="api" image="mycompany/api:v1" />
</Deployment>
</Manifest>
);
// Output to stdout (default)
render(<App />);
// Write to a directory with separate files per resource
render(<App />, {
output: './manifests',
splitFiles: true,
});
// Dry run - returns YAML string without outputting
const yaml = render(<App />, { dryRun: true });| Option | Type | Description |
|--------|------|-------------|
| output | string | Output directory for YAML files (default: stdout) |
| splitFiles | boolean | Split resources into separate files (e.g., api-deployment.yaml) |
| dryRun | boolean | Return YAML string without writing to stdout or files |
🎯 Quick Start
Basic Deployment
import { render, Manifest, Deployment, Container, Port, Service } from 'kubetsx';
const MyApp = () => (
<Manifest>
<Deployment name="api" replicas={3}>
<Container name="api" image="mycompany/api:v1.0.0">
<Port container={3000} />
</Container>
</Deployment>
<Service name="api" port={80} targetPort={3000} />
</Manifest>
);
render(<MyApp />);Using Loops (The Game Changer!)
const services = [
{ name: 'api', port: 3000, replicas: 5 },
{ name: 'auth', port: 4000, replicas: 3 },
{ name: 'worker', port: 5000, replicas: 2 },
];
const Microservices = () => (
<Manifest>
{services.map(svc => (
<Manifest key={svc.name}>
<Deployment name={svc.name} replicas={svc.replicas}>
<Container name={svc.name} image={`mycompany/${svc.name}:latest`}>
<Port container={svc.port} />
</Container>
</Deployment>
<Service name={svc.name} port={80} targetPort={svc.port} />
</Manifest>
))}
</Manifest>
);3 deployments + 3 services from 15 lines of code!
Environment-Specific Config
const isProduction = process.env.NODE_ENV === 'production';
const Api = () => (
<Deployment name="api" replicas={isProduction ? 5 : 1}>
<Container name="api" image={`api:${VERSION}`}>
<Resources
limitMemory={isProduction ? "2Gi" : "512Mi"}
limitCpu={isProduction ? "2" : "500m"}
/>
{isProduction && (
<Probe type="liveness" delay={30}>
<HttpProbe path="/health" port={3000} />
</Probe>
)}
</Container>
</Deployment>
);📚 Components Reference
Grouping
| Component | Description |
|-----------|-------------|
| <Manifest> | Groups multiple resources together |
| <Cluster> | Cluster-level grouping |
| <Namespace> | Namespace with automatic scoping |
Workloads
| Component | Description |
|-----------|-------------|
| <Deployment> | Deployment resource |
| <Container> | Container specification |
| <Job> | One-time job |
| <CronJob> | Scheduled job |
Container Configuration
| Component | Description |
|-----------|-------------|
| <Port> | Container port |
| <Env> | Environment variable |
| <SecretRef> | Reference secret value (use secretKey prop) |
| <ConfigMapRef> | Reference configmap value (use configKey prop) |
| <Resources> | CPU/memory limits |
| <VolumeMount> | Mount a volume |
Probes
| Component | Description |
|-----------|-------------|
| <Probe> | Liveness/readiness/startup probe |
| <HttpProbe> | HTTP GET probe |
| <TcpProbe> | TCP socket probe |
| <ExecProbe> | Command execution probe |
Networking
| Component | Description |
|-----------|-------------|
| <Service> | Service resource |
| <Ingress> | Ingress resource |
| <IngressHost> | Ingress host rule |
| <Route> | Ingress path rule |
Configuration
| Component | Description |
|-----------|-------------|
| <ConfigMap> | ConfigMap resource |
| <Secret> | Secret resource |
Storage
| Component | Description |
|-----------|-------------|
| <Volume> | Volume definition |
| <EmptyDir> | EmptyDir volume source |
| <PvcVolume> | PVC volume source |
| <ConfigMapVolume> | ConfigMap volume source |
| <SecretVolume> | Secret volume source |
| <Pvc> | PersistentVolumeClaim |
Autoscaling
| Component | Description |
|-----------|-------------|
| <Hpa> | HorizontalPodAutoscaler |
RBAC
| Component | Description |
|-----------|-------------|
| <ServiceAccount> | ServiceAccount |
| <Role> | Role |
| <ClusterRole> | ClusterRole |
| <RoleBinding> | RoleBinding |
| <ClusterRoleBinding> | ClusterRoleBinding |
🔧 Full Example
import {
render,
Manifest,
Namespace,
Deployment,
Container,
Port,
Env,
SecretRef,
Resources,
Probe,
HttpProbe,
Service,
Ingress,
Route,
ConfigMap,
Secret,
Hpa,
} from 'kubetsx';
const VERSION = 'v2.0.0';
const secrets = [
{ env: 'DATABASE_URL', secretKey: 'database-url' },
{ env: 'REDIS_URL', secretKey: 'redis-url' },
{ env: 'JWT_SECRET', secretKey: 'jwt-secret' },
];
const ProductionStack = () => (
<Namespace name="production">
<ConfigMap
name="app-config"
data={{
LOG_LEVEL: 'info',
FEATURE_FLAGS: JSON.stringify({ newUI: true }),
}}
/>
<Secret
name="app-secrets"
stringData={{
'database-url': process.env.DATABASE_URL!,
'redis-url': process.env.REDIS_URL!,
'jwt-secret': process.env.JWT_SECRET!,
}}
/>
<Deployment name="api" replicas={5}>
<Container name="api" image={`mycompany/api:${VERSION}`}>
<Port container={3000} />
{secrets.map(({ env, secretKey }) => (
<Env key={env} name={env}>
<SecretRef name="app-secrets" secretKey={secretKey} />
</Env>
))}
<Resources
requestMemory="256Mi"
requestCpu="250m"
limitMemory="512Mi"
limitCpu="500m"
/>
<Probe type="liveness" delay={30}>
<HttpProbe path="/health" port={3000} />
</Probe>
<Probe type="readiness" delay={5}>
<HttpProbe path="/ready" port={3000} />
</Probe>
</Container>
</Deployment>
<Service name="api" port={80} targetPort={3000} />
<Hpa
name="api-hpa"
target="api"
minReplicas={3}
maxReplicas={10}
targetCpuUtilization={70}
/>
<Ingress name="api-ingress" host="api.mycompany.com" ssl>
<Route path="/" service="api" port={80} />
</Ingress>
</Namespace>
);
render(<ProductionStack />, { output: './k8s/production.yaml' });🔌 Custom Resources & Plugins
Create any Kubernetes resource including CRDs. You are responsible for providing valid specs.
Custom Resources (CRDs)
import { render, Manifest, Custom } from 'kubetsx';
const App = () => (
<Manifest>
{/* Cert-Manager Certificate */}
<Custom
apiVersion="cert-manager.io/v1"
kind="Certificate"
metadata={{ name: 'my-cert', namespace: 'default' }}
spec={{
secretName: 'my-cert-tls',
issuerRef: { name: 'letsencrypt', kind: 'ClusterIssuer' },
dnsNames: ['example.com', 'www.example.com']
}}
/>
{/* ArgoCD Application */}
<Custom
apiVersion="argoproj.io/v1alpha1"
kind="Application"
metadata={{ name: 'my-app', namespace: 'argocd' }}
spec={{
project: 'default',
source: {
repoURL: 'https://github.com/org/repo',
targetRevision: 'HEAD',
path: 'k8s'
},
destination: {
server: 'https://kubernetes.default.svc',
namespace: 'production'
}
}}
/>
</Manifest>
);
render(<App />);Helm Releases (Flux CD)
import { render, Manifest, HelmRelease } from 'kubetsx';
const App = () => (
<Manifest>
<HelmRelease
name="nginx"
namespace="default"
chart={{
repository: "https://charts.bitnami.com/bitnami",
name: "nginx",
version: "15.0.0"
}}
values={{
replicaCount: 3,
service: { type: 'LoadBalancer' }
}}
/>
</Manifest>
);
render(<App />);Creating Reusable Components
import { Custom } from 'kubetsx';
import type { KubexElement } from 'kubetsx';
// Create your own typed component for a CRD
interface CertificateProps {
name: string;
namespace?: string;
secretName: string;
issuer: string;
dnsNames: string[];
}
function Certificate({ name, namespace = 'default', secretName, issuer, dnsNames }: CertificateProps): KubexElement {
return (
<Custom
apiVersion="cert-manager.io/v1"
kind="Certificate"
metadata={{ name, namespace }}
spec={{
secretName,
issuerRef: { name: issuer, kind: 'ClusterIssuer' },
dnsNames
}}
/>
);
}
// Use it like any other component
const App = () => (
<Manifest>
<Certificate
name="my-cert"
secretName="my-cert-tls"
issuer="letsencrypt"
dnsNames={['example.com']}
/>
</Manifest>
);Ready-to-Use Plugin Examples
Copy these into your project and customize as needed:
interface CertificateProps {
name: string;
namespace?: string;
secretName: string;
issuer: string;
issuerKind?: 'ClusterIssuer' | 'Issuer';
dnsNames: string[];
}
function Certificate({ name, namespace = 'default', secretName, issuer, issuerKind = 'ClusterIssuer', dnsNames }: CertificateProps) {
return (
<Custom
apiVersion="cert-manager.io/v1"
kind="Certificate"
metadata={{ name, namespace }}
spec={{ secretName, issuerRef: { name: issuer, kind: issuerKind }, dnsNames }}
/>
);
}
interface ClusterIssuerProps {
name: string;
email: string;
server?: string;
}
function ClusterIssuer({ name, email, server = 'https://acme-v02.api.letsencrypt.org/directory' }: ClusterIssuerProps) {
return (
<Custom
apiVersion="cert-manager.io/v1"
kind="ClusterIssuer"
metadata={{ name }}
spec={{
acme: {
email,
server,
privateKeySecretRef: { name: `${name}-account-key` },
solvers: [{ http01: { ingress: { class: 'nginx' } } }]
}
}}
/>
);
}interface ArgoCDAppProps {
name: string;
namespace?: string;
repoURL: string;
path: string;
destinationNamespace: string;
syncPolicy?: 'automated' | 'manual';
}
function ArgoCDApp({ name, namespace = 'argocd', repoURL, path, destinationNamespace, syncPolicy = 'automated' }: ArgoCDAppProps) {
return (
<Custom
apiVersion="argoproj.io/v1alpha1"
kind="Application"
metadata={{ name, namespace }}
spec={{
project: 'default',
source: { repoURL, path, targetRevision: 'HEAD' },
destination: { server: 'https://kubernetes.default.svc', namespace: destinationNamespace },
...(syncPolicy === 'automated' && {
syncPolicy: { automated: { prune: true, selfHeal: true }, syncOptions: ['CreateNamespace=true'] }
})
}}
/>
);
}interface VirtualServiceProps {
name: string;
namespace?: string;
hosts: string[];
gateways?: string[];
http: { match?: { uri?: { prefix?: string } }[]; route: { destination: { host: string; port?: { number: number } } }[] }[];
}
function VirtualService({ name, namespace = 'default', hosts, gateways, http }: VirtualServiceProps) {
return (
<Custom
apiVersion="networking.istio.io/v1beta1"
kind="VirtualService"
metadata={{ name, namespace }}
spec={{ hosts, ...(gateways && { gateways }), http }}
/>
);
}interface ServiceMonitorProps {
name: string;
namespace?: string;
matchLabels: Record<string, string>;
port: string;
path?: string;
interval?: string;
}
function ServiceMonitor({ name, namespace = 'default', matchLabels, port, path = '/metrics', interval = '30s' }: ServiceMonitorProps) {
return (
<Custom
apiVersion="monitoring.coreos.com/v1"
kind="ServiceMonitor"
metadata={{ name, namespace }}
spec={{ selector: { matchLabels }, endpoints: [{ port, path, interval }] }}
/>
);
}interface ExternalSecretProps {
name: string;
namespace?: string;
secretStoreName: string;
target: string;
data: { secretKey: string; remoteRef: { key: string; property?: string } }[];
}
function ExternalSecret({ name, namespace = 'default', secretStoreName, target, data }: ExternalSecretProps) {
return (
<Custom
apiVersion="external-secrets.io/v1beta1"
kind="ExternalSecret"
metadata={{ name, namespace }}
spec={{
secretStoreRef: { name: secretStoreName, kind: 'ClusterSecretStore' },
target: { name: target },
data
}}
/>
);
}interface SealedSecretProps {
name: string;
namespace?: string;
encryptedData: Record<string, string>;
}
function SealedSecret({ name, namespace = 'default', encryptedData }: SealedSecretProps) {
return (
<Custom
apiVersion="bitnami.com/v1alpha1"
kind="SealedSecret"
metadata={{ name, namespace }}
spec={{ encryptedData }}
/>
);
}Create Your Own Plugin Package
For full type safety on CRDs, you can create and publish your own plugin packages:
npm install kubetsx-cert-manager # Your package or community package
npm install kubetsx-argocd # Your package or community packageA plugin package is simply a collection of typed components that wrap <Custom>:
// kubetsx-cert-manager/src/index.tsx
import { Custom } from 'kubetsx';
export interface CertificateProps { /* ... */ }
export function Certificate(props: CertificateProps) {
return <Custom apiVersion="cert-manager.io/v1" kind="Certificate" /* ... */ />;
}Want to share your plugin? Open a PR to add it to our community plugins list.
📤 Render Options
render(<App />, {
output: './manifests/app.yaml', // Write to file
splitFiles: true, // Separate file per resource
dryRun: true, // Return YAML without writing
});🆚 Comparison with Alternatives
| Tool | Approach | Type Safety | Loops | IDE Support | |------|----------|-------------|-------|-------------| | YAML | Raw files | ❌ | ❌ | ❌ | | Helm | Templates | ❌ | 🟡 | ❌ | | Kustomize | Overlays | ❌ | ❌ | ❌ | | Pulumi | Functions | ✅ | ✅ | ✅ | | cdk8s | Constructs | ✅ | ✅ | ✅ | | Kubetsx | JSX | ✅ | ✅ | ✅ |
Kubetsx advantage: Visual hierarchy that mirrors your infrastructure structure.
🤝 Contributing
Contributions welcome! Ideas:
- [ ] StatefulSet component
- [ ] DaemonSet component
- [ ] NetworkPolicy component
- [ ] PodDisruptionBudget component
- [ ] Helm chart output
- [ ]
kubectl applyintegration
📜 License
MIT
🎭 Credits
Inspired by Tagliatelle.js — the declarative backend framework .
Made with ❤️ and plenty of carbs. 🍝
