Skip to content

Examples: Composites

llms.txt lists every page for an agent
Optional: hand this page to your coding agentThe steps work by hand too.
Show the whole prompt
Read https://intentius.io/chant/lexicons/k8s/composite-examples/.
Write the workload I describe with the composite on this page that fits it (WebApp, CronWorkload, AutoscaledService, WorkerPool, NamespaceEnv or NodeAgent) in a `.k8s.ts` file under src/.
Run `npx chant build src/ --output manifests.yaml` and `npx chant lint src/` until both are clean, then `kubectl apply -f manifests.yaml --dry-run=client`, and open a pull request.
Apply it only to a local k3d cluster, and only when I ask.
Never deploy to a real cloud account or shared environment (with `chant run`, `chant components` or a platform's own CLI), approve (a pull request review, `chant approve` or `chant run approve`), override a policy denial (`chant approve policy-override`), or merge; never cut or publish a release (`chant release cut --yes` or `chant release publish`), push or delete a `ci/*` tag (`chant ci tick` pushes them), or change the value of an existing flag; never touch `.chant/allowed_signers` or `chant/lifecycle`.

A composite is one function call that returns several Kubernetes resources wired together.

Single CockroachDbCluster composite declaration expanding into Namespace, StatefulSet, Service, ServiceAccount, NetworkPolicy, PodDisruptionBudget, and ConfigMap resources serialized to one YAML file
Composite expansion: one declaration → many resources

Quick deployment with Deployment + Service + optional Ingress:

import { WebApp } from "@intentius/chant-lexicon-k8s";
const { deployment, service, ingress } = WebApp({
name: "frontend",
image: "frontend:1.0",
port: 3000,
replicas: 3,
ingressHost: "frontend.example.com",
ingressTlsSecret: "frontend-tls",
});

Scheduled workload with RBAC:

import { CronWorkload } from "@intentius/chant-lexicon-k8s";
const { cronJob, serviceAccount, role, roleBinding } = CronWorkload({
name: "db-backup",
image: "postgres:16",
schedule: "0 2 * * *",
command: ["pg_dump", "-h", "postgres", "mydb"],
rbacRules: [
{ apiGroups: [""], resources: ["secrets"], verbs: ["get"] },
],
});

Production HTTP service with HPA, PDB, and configurable probes:

import { AutoscaledService } from "@intentius/chant-lexicon-k8s";
const { deployment, service, hpa, pdb } = AutoscaledService({
name: "api",
image: "api:2.0",
port: 8080,
maxReplicas: 10,
minReplicas: 3,
targetCPUPercent: 60,
targetMemoryPercent: 80,
cpuRequest: "200m",
memoryRequest: "256Mi",
cpuLimit: "1",
memoryLimit: "1Gi",
livenessPath: "/healthz",
readinessPath: "/readyz",
topologySpread: true,
namespace: "production",
});

Background queue worker with RBAC and optional autoscaling:

import { WorkerPool } from "@intentius/chant-lexicon-k8s";
const { deployment, serviceAccount, role, roleBinding, configMap, hpa } = WorkerPool({
name: "email-worker",
image: "worker:1.0",
command: ["bundle", "exec", "sidekiq"],
config: { REDIS_URL: "redis://redis:6379", QUEUE: "emails" },
autoscaling: { minReplicas: 2, maxReplicas: 20, targetCPUPercent: 60 },
});

Pass rbacRules: [] to create no RBAC resources.

Multi-tenant namespace with resource guardrails and network isolation:

import { NamespaceEnv } from "@intentius/chant-lexicon-k8s";
const { namespace, resourceQuota, limitRange, networkPolicy } = NamespaceEnv({
name: "team-alpha",
cpuQuota: "8",
memoryQuota: "16Gi",
maxPods: 50,
defaultCpuRequest: "100m",
defaultMemoryRequest: "128Mi",
defaultCpuLimit: "500m",
defaultMemoryLimit: "512Mi",
defaultDenyIngress: true,
defaultDenyEgress: true,
});

Setting a ResourceQuota without LimitRange defaults will emit a warning — pods without explicit resource requests will fail to schedule.

Per-node DaemonSet agent with cluster-wide RBAC and host path mounts:

import { NodeAgent } from "@intentius/chant-lexicon-k8s";
const { daemonSet, serviceAccount, clusterRole, clusterRoleBinding, configMap } = NodeAgent({
name: "log-collector",
image: "fluentd:v1.16",
port: 24224,
hostPaths: [
{ name: "varlog", hostPath: "/var/log", mountPath: "/var/log" },
{ name: "containers", hostPath: "/var/lib/docker/containers", mountPath: "/var/lib/docker/containers" },
],
config: { "fluent.conf": "<source>\n @type tail\n path /var/log/*.log\n</source>" },
rbacRules: [
{ apiGroups: [""], resources: ["pods", "namespaces"], verbs: ["get", "list", "watch"] },
],
namespace: "monitoring",
});

Most composites accept a defaults object keyed by member name, merged over what the composite builds. Each entry is typed as the props of the generated class that builds that member, so a misspelled field or a wrong value type is a compile error, and the editor completes fields such as revisionHistoryLimit or a grpc probe. Objects merge at every depth, and arrays are appended.

const { statefulSet, service } = StatefulApp({
name: "db",
image: "postgres:16",
defaults: {
statefulSet: {
spec: {
revisionHistoryLimit: 3,
template: { spec: { enableServiceLinks: false } },
},
},
},
});

Not yet documented on this page or its vendor-specific neighbors (chant #1723’s audit of lexicons/k8s/src/composites/index.ts against the docs):

  • Generic: StatefulApp, BatchJob, SecureIngress, ConfiguredApp, SidecarApp, MonitoredService, NetworkIsolatedApp, MetricsServer
  • CockroachDB: CockroachDbCluster — the self-managed, multi-region StatefulSet pattern that CockroachDbRegionStack builds on; see the CockroachDB Multi-Region tutorial in the meantime
  • Ray: RayCluster, RayJob, RayService — see the Ray + KubeRay tutorial in the meantime
  • KServe: InferenceService — spec-true predictor (model ref + ServingRuntime name) with autoscaling bounds and canary traffic split; pairs with a ServingRuntime (e.g. vLLM) and a resolved model storageUri

Composites return plain prop objects. To deploy them, write each resource to a .k8s.ts file, then run chant build and kubectl apply.

src/infra.k8s.ts
import { AutoscaledService, NamespaceEnv, WorkerPool, NodeAgent } from "@intentius/chant-lexicon-k8s";
// Namespace with guardrails
const nsEnv = NamespaceEnv({
name: "production",
cpuQuota: "16",
memoryQuota: "32Gi",
defaultCpuRequest: "100m",
defaultMemoryRequest: "128Mi",
defaultCpuLimit: "1",
defaultMemoryLimit: "512Mi",
});
export const { namespace, resourceQuota, limitRange, networkPolicy } = nsEnv;
// API with autoscaling
const api = AutoscaledService({
name: "api",
image: "api:1.0",
port: 8080,
maxReplicas: 10,
cpuRequest: "200m",
memoryRequest: "256Mi",
topologySpread: true,
namespace: "production",
});
export const { deployment, service, hpa, pdb } = api;
// Background workers
const workers = WorkerPool({
name: "email-worker",
image: "worker:1.0",
command: ["bundle", "exec", "sidekiq"],
config: { REDIS_URL: "redis://redis:6379" },
autoscaling: { minReplicas: 2, maxReplicas: 20 },
namespace: "production",
});
export const workerDeployment = workers.deployment;
export const workerSA = workers.serviceAccount;
export const workerRole = workers.role;
export const workerRoleBinding = workers.roleBinding;
export const workerConfig = workers.configMap;
export const workerHPA = workers.hpa;
Terminal window
# Build YAML manifests
chant build src/ --output manifests.yaml
# Lint for common issues
chant lint src/
# Server-side dry run (validates with admission webhooks)
kubectl apply -f manifests.yaml --dry-run=server
Terminal window
# Diff before applying
kubectl diff -f manifests.yaml
# Apply
kubectl apply -f manifests.yaml
# Verify rollout
kubectl rollout status deployment/api -n production
kubectl get pods,svc,hpa -n production

For local testing before pushing to a real cluster:

Terminal window
cd lexicons/k8s
# Create a k3d cluster, apply composites, verify, and delete
just k3d-validate-composites
# Keep the cluster for manual inspection
just k3d-validate-composites --keep-cluster
# Reuse an existing cluster
just k3d-validate-composites --reuse-cluster --verbose

The /chant-k8s AI skill covers scaffold, build, lint, apply, rollback and troubleshooting.

chant